-
静态工厂方法有名称
-
不必每次调用的时候创建一个新的对象
-
可以返回原返回类型的任何子类型对象
-
返回对象的类可以根据方法的参数值变化
-
方法返回对象所属的类,编写包含该静态工厂方法的类时可以不存在(服务提供者框架)
- 类如果不含有公有的或者受保护的构造器,就不能被子类化
- 程序员比较难发现一个类是否有静态工厂方法
-
from – 类型转换方法,只有单个参数,返回相对应的实例
Date d = Date.from(instant);
-
of – 聚合方法,有多个参数,返回该类型的一个实例,并合并
Set
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
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)
缺点:
- 有多个参数时,客户端代码很难编写,且难以阅读
- 参数顺序错误可能不会引起编译错误,但是运行即出现错误行为
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可能处于不一致状态
- 把类做成不可变的可能性不复存在,需要额外确保线程安全的影响
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 toppings;
abstract static class Builder>{
EnumSet toppings = EnumSet.noneOf(Topping.class); // empty
public 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{
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{
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);
}
}



