Effective java 总结1 - 创建和销毁对象
Effective java 总结 - 创建和销毁对象
第1条 用静态工厂方法代替构造器
优势
静态工厂方法有名称
不必每次调用的时候创建一个新的对象
可以返回原返回类型的任何子类型对象
返回对象的类可以根据方法的参数值变化
方法返回对象所属的类,编写包含该静态工厂方法的类时可以不存在(服务提供者框架)
缺点
- 类如果不含有公有的或者受保护的构造器,就不能被子类化
- 程序员比较难发现一个类是否有静态工厂方法
案例
from – 类型转换方法,只有单个参数,返回相对应的实例
Date d = Date.from(instant);
of – 聚合方法,有多个参数,返回该类型的一个实例,并合并
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
valueOf – 更繁琐的一种替代方法
BigInteger prime = BigInteger.valueof(Integer.MAX_VALUE);
instance - getInstance – 返回的实例通过方法的参数来描述
StackWalker luke = StackWalker.getInstance(options);
create - newInstance – 确保每次调用都返回一个新的实例
Object newArray = Array.newInstance(classObject, arraylen);
getType – 类似getInstance,是在工厂方法处于不同类中的时候使用,type表示工厂方法返回的对象类型
FileStore fs = Files.getFileStore(path);
newType – 类似newInstance,是在工厂方法处于不同类中的时候使用,type表示工厂方法返回的对象类型
BufferReader br = Files.newBufferReader(path);
type – getType、newType 的简版
List<Complaint> litany = Collections.list(legacyLitany);
第2条 遇到多个构造器参数时要考虑使用构建器
静态工厂和构造器的局限性:不能很好地扩展到大量的可选参数
解决方案一 : 重叠构造器模式
public class MyChars{private final int a;private final int b;private final int c;MyChars(int a){this(a, 0, 0)}MyChars(int a, int b){this(a, b, 0)}MyChars(int a, int b, int c){this.a = a;this.b = b;this.c = c;}
}MyChars ch = new MyChars(1, 3, 5)
缺点:
- 有多个参数时,客户端代码很难编写,且难以阅读
- 参数顺序错误可能不会引起编译错误,但是运行即出现错误行为
解决方案二: JavaBean模式
public class MyChars{private int a = -1;private int b = -1;private int c = 0;MyChars(){}public void setA(int val){a = val}public void setB(int val){b = val}public void setC(int val){c = val}
}MyChars ch = new MyChars();
ch.setA(1);
ch.setB(2);
ch.setC(3);
缺点:
- 构造过程分散,javaBean可能处于不一致状态
- 把类做成不可变的可能性不复存在,需要额外确保线程安全的影响
解决方案三: 建造者模式(Builder)
public class MyChars{private final int a;private final int b;private final int c;public static class Builder{private final int a;private final int b;private int c = 0;public Builder(int a, int b){this.a = a;this.b = b;}public Builder c(int val){c = val;return this;}public MyChars build(){return new MyChars(this);}}MyChars(Builder builder){a = builder.a;b = builder.b;c = builder.c;}
}MyChars ch = new MyChars.Builder(1, 3).c(5).build();
优点:
模拟了具名的可选参数
客户端代码很容易编写, 易于阅读
Builder模式也适用类层次结构
Builder模式十分灵活,可以利用单个builder构建多个对象
缺点:
- 为了创建对象,必须先创建构建器
- 构建器比重叠构造器还要冗长,只有在很多参数的时候使用才合适
public abstract class Pizza{public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE};public Set<Topping> toppings;abstract static class Builder<T extends Builder<T>>{EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class); // emptypublic T addTopping(Topping topping){toppings.add(Object.requireNonNull(topping));return self();}protected abstract self(); abstract Pizza build();}
}public class NyPizza extends Pizza{public enum Size {SMALL, MIDDLE, LARGE};private final Size size;static class Builder extends Pizza.Builder<Builder>{private final Size size;public Builder(Size size){this.size = size;}@Override protected self(){return this;}@Override public build(){return Nypizza(this);}}private NyPizza(Builder builder){super(builder);size = builder.size;}
}public class Calzone extends Pizza{private final boolean sauceInside;static class Builder extends Pizza.Builder<Builder>{private boolean sauceInside;public Builder sauceInside(){this.sauceInside = True;return this;}@Override protected self(){return this;}@Override public build(){return Calzone(this);}}private Calzone(Build build){super(build);sauceInside = build.sauceInside;}
}NyPizza nyp = new NyPizza.Builder(SMALL).addTopping(HAM).addTopping(MUSHROOM).build();
Calzone cz = new Calzone.Builder().sauceInside().addTopping(PEPPER).build();
第3条 用私有构造器或者枚举类型强化Singleton属性
实现Singleton的三种方法,前两个都需要保持构造器私有
公有静态成员是final域
- 公有的静态域为final,所以该域总是包含相同的对象引用
- 简单
public class Elvis{public static final Elvis INSTANCE = new Elvis();private Elvis(){}public void leaveTheBuilding(){}
}
公有的成员是静态工厂方法
- 灵活性:不改变其API的情况下,改变该类是否为Singleton的想法
- 可以编写一个泛型Singleton工厂
- 可以通过方法引用作为提供者, Elvis::insatnce 就是一个Supplier
public class Elvis{private static final Elvis INSTANCE = new Elvis();private Elvis(){}public Elvis getInstance(){return INSTANCE;}public void leaveTheBuilding(){}
}
为了将两种方法实现的Singleton类变成可以序列化的,需要:
- 声明中加上 implements Serializable
- 声明所有域都是瞬时的(transient)并提供一个 readResolve方法(保证Singleton)
private Object readResolve(){return INSTANCE;
}
声明包含单个元素的枚举类型
实现singleton的最佳方法
- 功能上与公有域方法相似,更加简洁
- 无偿提供了序列化机制,绝对防止多次实例化 (复杂序列化or反射攻击)
- 如果Singleton类必须扩展一个超类,而不是扩展enum时,则不适用
public enum Elvis{INSTANCE;public void leaveTheBuilding(){}
}
第4条 通过私有构造器强化不可实例化的能力
通过将类做成抽象类来强制该类不可以被实例化是不行的(子类可以被实例化)
不提供构造器系统会默认提供一个公有的无参的缺省构造器
让类包含一个私有构造器,就不能实例化了:
public class UtilityClass{private UtilityClass(){throw new AssertionError();}
}
第5条 优先考虑使用依赖注入来引用资源
依赖注入:创建一个新的实例时,将资源传到构造器中
public class SpellChecker{private final Lexicon dictionary;public SpellChecker(Lexcion dictionary){ // 依赖注入this.dictionary = dictionary;}public boolean isValid(String word){...}
}
一种有用的变体:将资源工厂传入构造器,工厂是可以被重复调用来创建类型实例的一个对象,接口 Supplier 最适合用于表示工厂
Mosaic create(Supplier<? extends Tile> tileFactory){...}
总结:
不使用Singleton和静态工具类来实现一个或多个底层资源的类,且该资源的行为会影响到类的行为
使用依赖注入引用资源,提升灵活性、可重用性和可测试性
第6条 避免创建不必要的对象
1、对象不可变,重用单个对象
// 错误写法
String s = new String("adas"); // 将创建不必要的String实例
// 正确写法
String s = "adas"; // 同一台虚拟机中运行的代码,该对象会被重用
2、创建成本较高的对象,建议缓存下来重用
static boolean isRomamNumeral(String s){return s.matchs("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[VX]|V?I{0,3})$")
}
问题:matchs 内部为正则表达式创建了一个Pattern实例,只用一次就回收了,重用成本高
解决:将正则表达式编译成一个有限状态机
private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[VX]|V?I{0,3})$");static boolean isRomamNumeral(String s){return ROMAN.matcher(s).matchs();
}
3、适配器情形
一个对象把功能委托给后备对象,并提供一个可以替代的接口,针对某个给定的对象的特定适配器而言,不需要创建多个适配器实例,eg:
Map接口的keySet方法,对于一个给定的Map,每次调用keySet返回的都是同样的Set实例
4、自动装箱可能创建多余对象
private static long sum(){Long sum = 0;for (long i = 0; i < Integer.MAX_VALUE; i++){sum += i;}return sum;
}
由于sum被定义为Long,程序创建了大概2的31次方个Long实例,因此:
优先使用基本类型而不是装箱类型,避免无意识的自动装箱
注意
维护自己的对象池来避免重复创建对象不是一个好的做法
现代的JVM对小对象的创建回收性能优于维护对象池
数据库连接池这种重型对象,维护对象池是有意义的
第7条 消除过期的对象引用
1、内存泄漏来源1
public class Stack{private int size;private Object[] elements;private static final int DEFAULT_INITIAL_CAPACITY;public Stack(){elements = new Object[DEFAULT_INITIAL_CAPACITY];}public void push(Object e){ensureCapacity();elements[size++] = e;}public Object pop(){if(0 == size) throw new EmptyStackError();return elements[--size]; // 内存泄漏}private void ensureCapacity(){if(elements.length == size){elements = Arrays.copyOf(elements, 2 * size + 1)}}
}
pop方法调用后,栈维持着这些对象的过期引用(永远不会再被解除的引用),垃圾回收机制不仅不会处理这个对象,而且不会处理被这个对象所引用的其他对象。
解决:
只要类是自己管理内存,就应该警惕内存泄漏问题
public Object pop(){if(0 == size) throw new EmptyStackError();Object e = elements[--size];elements[size] = NULL; // 清除过期引用return e;
}
2、内存泄漏来源2
缓存,将对象引用放入缓存中,容易被遗忘
解决:
- 缓存之外存在对某个项的键的引用,可以用WeakHashMap代表缓存
- 使用后台线程清除缓存(ScheduledThreadPoolExecutor)
- 添加新条目的时候顺便清理,LinkedHashMap类利用removeEldestEntry实现
- 更加复杂的缓存,使用 java.lang.ref
3、内存泄漏来源3
监听器和其他回调,确保被当做垃圾回收的最佳方法是只保存他们的弱引用,只保存成WeakHashMap中的键
可以借助 Heap 剖析工具发现内存泄漏
第8条 避免使用终结方法和清除方法
终结方法&&清除方法(java 9)
缺点:
- 不可预测,危险,导致行为不稳定,性能降低,可移植问题
- 不能保证会被及时执行,且不保证会被执行
- 不同的JVM中,垃圾回收算法执行方法的时间点都不相同
- 终结方法有一个严重的安全问题:为终结方法攻击打开了类的大门
结论:
- 清除和终结方法都是不可预测的,运行缓慢,一般情况下没有必要使用
- 注重时间的任务不应该由这两方法来完成
- 永远不依赖终结和清除方法来更新重要的持久状态
- System.gc 和 System.runFinalizersOnExit增加了方法被执行的机会,但仍旧不保证一定被执行
用途:
- 充当 “安全网”,在客户端无法正常结束的情况下,迟一点释放资源比永远不释放好
- 终止非关键的本地资源(清除方法)
解决:
让类实现 AutoCoseable, 利用try-with-resources确保终止。
实现细节:客户端在每个实例不再需要的时候调用close()方法,close()方法必须在一个私有域中记录下“该对象不再有效”。如果方法在对象被停止后调用,则会强制检查这个域,并抛出IllegalStateException异常。
清除方法eg:
public class Room implements AutoCloseable{private static final Cleaner cleaner = Cleaner.create();// 静态的嵌套类, 未引用Room实例, 阻止了Room实例的垃圾回收private static class State implements Runable{int numJunkPiles; // 包含一个指向本地对等体的指针State(int numJunkPiles){this.numJunkPiles = numJunkPiles;}@Override public void run(){System.out.println("cleaning room");numJunkPiles = 0;}}private final State state;private final Cleaner.Cleanable cleanable;public Room(int numJunkPiles){state = new State(numJunkPiles);cleanable = cleaner.register(this, state);}@Override public void close(){cleanable.clean();}
}
public class Adult{public static void main(String[] args){try(Room myRoom = new Room(4)){System.out.println("Peace out")}}
}
// Peace out
// cleaning room
public class Adult{public static void main(String[] args){Room myRoom = new Room(4);System.out.println("Peace out")}
}
// Peace out
加上 System.gc() 后可能打印 cleaning room
第9条 try-with-resources 优于 try-finally
必须通过close()关闭的资源: InputStream、OutputStream、java.sql.Connection …
一般情况
static String firstLineOfFile(String path) throws IOException{BufferedReader br = new BufferedReader(new FileReader(Path));try {return br.readLine();}finally{br.close();}
}
问题: 如果 readLine 和 close 都发生了异常,则第二个异常会覆盖第一个异常,难以查找问题
解决:
static String firstLineOfFile(String path) {try (BufferedReader br = new BufferedReader(new FileReader(Path))) {return br.readLine();}catch (IOException e){return "catch exception";}
}
复杂情况
static void copy(String src, String dst) throws IOException{InputStream in = new InputStream(src);try{OutputStream out = new FileOutputStream(dst);try{int n;byte[] buf = new bype[BUFFER_SIZE];while( (n = in.read(buf)) >= 0 )out.write(buf, 0, n);}finally{out.close();} }finally{in.close();}
}
改进:
static void copy(String src, String dst) throws IOException{try(InputStream in = new InputStream(src);OutputStream out = new FileOutputStream(dst)){int n;byte[] buf = new bype[BUFFER_SIZE];while( (n = in.read(buf)) >= 0 )out.write(buf, 0, n);}
}
Effective java 总结1 - 创建和销毁对象相关推荐
- [Effective Java]第二章 创建和销毁对象
第一章 前言 略... 第二章 创建和销毁对象 1. 考虑用静态工厂方法代替构造器 创建对象方法:一是最常用的公有构造器,二是静态工厂方法.下面是一个Bool ...
- Effective Java:创建和销毁对象
前言: 读这本书第1条规则的时候就感觉到这是一本很好的书,可以把我们的Java功底提升一个档次,我还是比较推荐的.本博客是针对<Effective Java>这本书第2章所写的一篇读书笔记 ...
- Effective Java(1)-创建和销毁对象
Effective Java(1)-创建和销毁对象 转载于:https://www.cnblogs.com/Johar/p/10556218.html
- 《Effective Java》学习笔记 第二章 创建和销毁对象
第二章 创建和销毁对象 何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作. 1 考虑用静态工厂方法代替构造器 一般在某处获取一 ...
- Effective Java (3rd Editin) 读书笔记:1 创建和销毁对象
1 创建和销毁对象 Item 1:考虑用静态工厂方法取代构造器 public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE); ...
- Effective Java读书笔记---二、创建和销毁对象
二.创建和销毁对象 何时以及如何创建对象, 何时以及如何避免创建对象, 如何确保它们能够适时地销毁, 如何管理对象销毁之前必须进行的各种清理动作 1.用静态工厂方法代替构造器 优势: 它们有名称 不必 ...
- 《Effect Java》学习笔记1———创建和销毁对象
第二章 创建和销毁对象 1.考虑用静态工厂方法代替构造器 四大优势: i. 有名称 ii. 不必在每次调用它们的时候都创建一个新的对象: iii. 可以返回原返回类型的任何子类型的对象: JDBC ...
- java创建和销毁一个对象_有效的Java –创建和销毁对象
java创建和销毁一个对象 创建和销毁对象(第2章) 这是Joshua Blochs的< 有效的Java>第2章的简短摘要.我仅包括与自己相关的项目. 静态工厂(项目1) 静态工厂与构造函 ...
- 有效的Java –创建和销毁对象
创建和销毁对象(第2章) 这是Joshua Blochs的< 有效的Java>第2章的简短摘要.我仅包括与自己相关的项目. 静态工厂(项目1) 静态工厂与构造函数的一些优点: 工厂方法的名 ...
最新文章
- 2011面试题大汇总
- LeetCode_数组_简单题
- {'张三丰': 101, '无忌': 102, '赵敏': 102} (Python)
- LIVE555建立RTSP服务记录
- JS中变量和函数的使用
- 怎么添加一个程序集_门店小程序,微信小程序怎么添加店铺
- linux磁盘iops限制,linux – 我需要多少IOPS?我的工作量瓶颈是存储
- 允许telnet 通过root用户进行访问
- 第45届国际大学生程序设计竞赛(ICPC)银川站太原理工大学收获4枚奖牌
- keras库的安装及使用,以全连接层和手写数字识别MNIST为例
- ssd训练时提示:Cannot copy param 0 weights from 'xxxx',以及提示No module named caffe.proto,推理时设置GPU模式
- 你必须具有权限才能读取此对象_为啥手机APP要获取权限?这些权限不能随便同意...
- J2EE架构的优点和缺点有哪些
- python开发cms_Wagtail介绍 — 基于Django的Python CMS
- 如何做无线抄表既SCADA无线数据采集管理系统
- MapperReducer
- Python调用百度API进行人像动漫化
- 云计算和计算机应用的区别,普适计算与云计算的区别
- 5000字:一文看懂用户运营之增长八卦模型
- Unable to connect to test manager on xxxxx (The device is passcode protected)