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 - 创建和销毁对象相关推荐

  1. [Effective Java]第二章 创建和销毁对象

    第一章      前言 略... 第二章      创建和销毁对象 1.            考虑用静态工厂方法代替构造器 创建对象方法:一是最常用的公有构造器,二是静态工厂方法.下面是一个Bool ...

  2. Effective Java:创建和销毁对象

    前言: 读这本书第1条规则的时候就感觉到这是一本很好的书,可以把我们的Java功底提升一个档次,我还是比较推荐的.本博客是针对<Effective Java>这本书第2章所写的一篇读书笔记 ...

  3. Effective Java(1)-创建和销毁对象

    Effective Java(1)-创建和销毁对象 转载于:https://www.cnblogs.com/Johar/p/10556218.html

  4. 《Effective Java》学习笔记 第二章 创建和销毁对象

    第二章 创建和销毁对象 何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作. 1 考虑用静态工厂方法代替构造器 一般在某处获取一 ...

  5. Effective Java (3rd Editin) 读书笔记:1 创建和销毁对象

    1 创建和销毁对象 Item 1:考虑用静态工厂方法取代构造器 public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE); ...

  6. Effective Java读书笔记---二、创建和销毁对象

    二.创建和销毁对象 何时以及如何创建对象, 何时以及如何避免创建对象, 如何确保它们能够适时地销毁, 如何管理对象销毁之前必须进行的各种清理动作 1.用静态工厂方法代替构造器 优势: 它们有名称 不必 ...

  7. 《Effect Java》学习笔记1———创建和销毁对象

    第二章 创建和销毁对象 1.考虑用静态工厂方法代替构造器 四大优势: i. 有名称 ii. 不必在每次调用它们的时候都创建一个新的对象:   iii. 可以返回原返回类型的任何子类型的对象: JDBC ...

  8. java创建和销毁一个对象_有效的Java –创建和销毁对象

    java创建和销毁一个对象 创建和销毁对象(第2章) 这是Joshua Blochs的< 有效的Java>第2章的简短摘要.我仅包括与自己相关的项目. 静态工厂(项目1) 静态工厂与构造函 ...

  9. 有效的Java –创建和销毁对象

    创建和销毁对象(第2章) 这是Joshua Blochs的< 有效的Java>第2章的简短摘要.我仅包括与自己相关的项目. 静态工厂(项目1) 静态工厂与构造函数的一些优点: 工厂方法的名 ...

最新文章

  1. 2011面试题大汇总
  2. LeetCode_数组_简单题
  3. {'张三丰': 101, '无忌': 102, '赵敏': 102} (Python)
  4. LIVE555建立RTSP服务记录
  5. JS中变量和函数的使用
  6. 怎么添加一个程序集_门店小程序,微信小程序怎么添加店铺
  7. linux磁盘iops限制,linux – 我需要多少IOPS?我的工作量瓶颈是存储
  8. 允许telnet 通过root用户进行访问
  9. 第45届国际大学生程序设计竞赛(ICPC)银川站太原理工大学收获4枚奖牌
  10. keras库的安装及使用,以全连接层和手写数字识别MNIST为例
  11. ssd训练时提示:Cannot copy param 0 weights from 'xxxx',以及提示No module named caffe.proto,推理时设置GPU模式
  12. 你必须具有权限才能读取此对象_为啥手机APP要获取权限?这些权限不能随便同意...
  13. J2EE架构的优点和缺点有哪些
  14. python开发cms_Wagtail介绍 — 基于Django的Python CMS
  15. 如何做无线抄表既SCADA无线数据采集管理系统
  16. MapperReducer
  17. Python调用百度API进行人像动漫化
  18. 云计算和计算机应用的区别,普适计算与云计算的区别
  19. 5000字:一文看懂用户运营之增长八卦模型
  20. Unable to connect to test manager on xxxxx (The device is passcode protected)

热门文章

  1. 老K推荐2部好片,9.9分神作,刷爆B站!
  2. 第三章 分支语句 程序设计练习
  3. 始于见异,终于思迁——硬盘巨头启示录之昆腾篇
  4. layui 验证小数点唯一正则表达
  5. 实现掘金登录框中熊猫状态的改变案例
  6. XSS跨站脚本攻击剖析与防御笔记
  7. HTML基础【5】:表单标签
  8. 基于关系推理的无标记自监督学习训练
  9. linux opensuse,又一linux发行版发布:openSUSE 15基于Linux 4.12内核正式发布
  10. Java计算机毕业设计大学生健康电子档案系统源码+系统+数据库+lw文档