1 构造器 => 静态工厂方法

(1)优势

  • 静态工厂方法有名字
  • 静态工厂方法不必在每次被调用时都产生一个新的对象
  • 静态工厂方法能返回原返回类型的任意子类型的对象
  • 静态工厂方法根据调用时传入的不同参数而返回不同类的对象
  • 静态工厂方法返回对象的类不需要存在(SPI架构)

(2)限制

  • 没有公有或者保护构造方法的类不能子类化(但是可以鼓励我们使用组合模式,而不是继承模式)
  • 静态工厂方法难以发现

(3)常用静态工厂方法命名

  • from:传入单个参数,返回该类型实例
  1. Date d = Date.from(instant);
  • of:传入多个参数,返回一个包含这些参数的该类型实例
  1. Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf:from和of的替换方案
  1. BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance or getInstance:创建一个由参数(如果有的话)描述的实例
  1. StackWalker luke = StackWalker.getInstance(options);
  • create or newInstance:类似instance或getInstance, 但保证每次调用都返回新实例
  1. Object newArray = Array.newInstance(classObject, arrayLen);
  • getType:类似getInstance,但一般在工厂方法包含在不同类的情况下使用。Type是工厂方法返回的对象的类型。
  1. FileStore fs = Files.getFileStore(path);
  • newType:类似于newInstance,但一般在工厂方法包含在不同类的情况下使用。
  1. BufferedReader br = Files.newBufferedReader(path);
  • type:getType和newType简洁的替换方式
  1. List<Complaint> litany = Collections.list(legacyLitany);

2 构造器 => 构建者

(1)问题

  1. public class NutritionFacts {
  2. private final int servingSize; // (mL) required
  3. private final int servings; // (per container) required
  4. private final int calories; // (per serving) optional
  5. private final int fat; // (g/serving) optional
  6. private final int sodium; // (mg/serving) optional
  7. private final int carbohydrate; // (g/serving) optional
  8. public NutritionFacts(int servingSize, int servings) {
  9. this(servingSize, servings, 0);
  10. }
  11. public NutritionFacts(int servingSize, int servings, int calories) {
  12. this(servingSize, servings, calories, 0);
  13. }
  14. public NutritionFacts(int servingSize, int servings, int calories, int fat) {
  15. this(servingSize, servings, calories, fat, 0);
  16. }
  17. public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
  18. this(servingSize, servings, calories, fat, sodium, 0);
  19. }
  20. public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium,
  21. int carbohydrate) {
  22. this.servingSize = servingSize; this.servings = servings;
  23. this.calories = calories
  24. this.fat = fat
  25. this.sodium = sodium
  26. this.carbohydrate = carbohydrate;
  27. }
  28. }
  • 构造器方法数量迅速膨胀,因为有大量的可选项
  • 可伸缩构造器(使用可变参数)是可行,只是当有很多参数时,会让客户端代码很难编写,而且代码也很难阅读。

(2)替代方案:JavaBeans模式

  1. public class NutritionFacts {
  2. private int servingSize = -1; // Required; no default value
  3. private int servings = -1; // Required; no default value
  4. private int calories = 0;
  5. private int fat = 0;
  6. private int sodium = 0;
  7. private int carbohydrate = 0;
  8. public NutritionFacts() {}
  9. // Setters
  10. public void setServingSize(int val) {
  11. servingSize = val;
  12. }
  13. public void setServings(int val) {
  14. servings = val;
  15. }
  16. public void setCalories(int val) {
  17. calories = val;
  18. }
  19. public void setFat(int val) {
  20. fat = val;
  21. }
  22. public void setSodium(int val) {
  23. sodium = val;
  24. }
  25. public void setCarbohydrate(int val) {
  26. carbohydrate = val;
  27. }
  28. }

缺点:

  • 构造过程被分到了多个调用中,一个JavaBean在其构造过程中可能处于不一致的状态。

  • 类无法仅仅通过检查构造器参数的有效性来保证一致性。

(3)替代方案:Builder模式

  • 例1
  1. // Builder Pattern
  2. public class NutritionFacts {
  3. private final int servingSize;
  4. private final int servings;
  5. private final int calories;
  6. private final int fat;
  7. private final int sodium;
  8. private final int carbohydrate;
  9. public static class Builder {
  10. // Required parameters
  11. private final int servingSize;
  12. private final int servings;
  13. // Optional parameters - initialized to default values
  14. private int calories = 0;
  15. private int fat = 0;
  16. private int sodium = 0;
  17. private int carbohydrate = 0;
  18. public Builder(int servingSize, int servings) {
  19. this.servingSize = servingSize;
  20. this.servings = servings;
  21. }
  22. public Builder calories(int val){
  23. calories = val; return this;
  24. }
  25. public Builder fat(int val){
  26. fat = val; return this;
  27. }
  28. public Builder sodium(int val){
  29. sodium = val; return this;
  30. }
  31. public Builder carbohydrate(int val){
  32. carbohydrate = val; return this;
  33. }
  34. public NutritionFacts build() {
  35. return new NutritionFacts(this);
  36. }
  37. }
  38. private NutritionFacts(Builder builder) {
  39. servingSize = builder.servingSize;
  40. servings = builder.servings;
  41. calories = builder.calories;
  42. fat = builder.fat;
  43. sodium = builder.sodium;
  44. carbohydrate = builder.carbohydrate;
  45. }
  46. }
  47. // 使用
  48. NutritionFacts cocaCola = new NutritionFacts.Builder(2408)
  49. .calories(100)
  50. .sodium(35)
  51. .carbohydrate(27)
  52. .build();
  • 例2
  1. public abstract class Pizza {
  2. public enum Topping {
  3. HAM, MUSHROOM, ONION, PEPPER,SAUSAGE
  4. }
  5. final Set<Topping> toppings;
  6. abstract static class Builder<T extends Builder<T>> {
  7. EnumSet<Topping> toppings =
  8. EnumSet.noneOf(Topping.class);
  9. public T addTopping(Topping topping) {
  10. toppings.add(Objects.requireNonNull(topping));
  11. return self();
  12. }
  13. abstract Pizza build();
  14. protected abstract T self();
  15. }
  16. Pizza(Builder<?> builder) {
  17. toppings = builder.toppings.clone(); // See Item 50
  18. }
  19. }
  20. public class NyPizza extends Pizza {
  21. public enum Size {
  22. SMALL, MEDIUM, LARGE
  23. }
  24. private final Size size;
  25. public static class Builder extends Pizza.Builder<Builder> {
  26. private final Size size;
  27. public Builder(Size size) {
  28. this.size = Objects.requireNonNull(size);
  29. }
  30. public NyPizza build() {
  31. return new NyPizza(this);
  32. }
  33. protected Builder self() {
  34. return this;
  35. }
  36. }
  37. private NyPizza(Builder builder) {
  38. super(builder);
  39. size = builder.size;
  40. }
  41. }
  42. NyPizza pizza = new NyPizza.Builder(SMALL)
  43. .addTopping(SAUSAGE)
  44. .addTopping(ONION).build();

优势:

  • builder能拥有多个可变参数(例1)
  • builder能将传入到不同方法里的参数聚合起来然后传入单个域里(例2)

缺点:

  • 多创建一个Builder对象的内存开销

3 使用枚举强化单例模式

(1)饿汉

  1. public class Elvis {
  2. public static final Elvis INSTANCE = new Elvis();
  3. private Elvis() {}
  4. public void leaveTheBuilding() {}
  5. }
  6. public class Elvis {
  7. private static final Elvis INSTANCE = new Elvis();
  8. private Elvis() {}
  9. public void leaveTheBuilding() {}
  10. public static Elvis instance(){
  11. return INSTANCE;
  12. }
  13. }

(2)内部类

  1. public class Elvis {
  2. private Elvis() {}
  3. public void leaveTheBuilding() {}
  4. // 通过类加载机制来保证线程安全
  5. private static class Holder{
  6. private static final Elvis INSTANCE = new Elvis();
  7. }
  8. public static Elvis instance(){
  9. return Holder.INSTANCE;
  10. }
  11. }

(3)双重检验锁

  1. public class Elvis {
  2. private Elvis() {}
  3. public void leaveTheBuilding() {}
  4. private static volatile Elvis INSTANCE;
  5. public static Elvis instance(){
  6. if(INSTANCE == null){
  7. synchronized(Elvis.class){
  8. if (INSTANCE == null) {
  9. INSTANCE = new Elvis();
  10. }
  11. }
  12. }
  13. return INSTANCE;
  14. }
  15. }

(4)枚举

  1. public enum Elvis {
  2. INSTANCE;
  3. public void leaveTheBuilding() {}
  4. }

(5)总结

  • 第2、3种具备懒加载

  • 第4种具备禁止反序列化创建对象问题

  • 第1~3种如果实现了Serializable接口的补偿措施

    1. // 方法1
    2. private static Class getClass(String classname) throws ClassNotFoundException {
    3. ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    4. if(classLoader == null)
    5. classLoader = Singleton.class.getClassLoader();
    6. return (classLoader.loadClass(classname));
    7. }
    8. }
    9. // 方法2
    10. // 重写readReslove方法,需要将所有成员变量声明为transient
    11. private Object readResolve() {
    12. return INSTANCE;
    13. }

参考:

4 私有化构造器强化不可实例化的能力

  1. public class UtilityClass {
  2. // Suppress default constructor for noninstantiability
  3. private UtilityClass() {
  4. throw new AssertionError();
  5. }
  6. // Remainder omitted
  7. }

5 优先使用依赖注入而不是硬连接资源

静态工具类和Singleton对于类行为需要被底层资源参数化的场景是不适用的。

(1)构造器注入

  1. public class SpellChecker {
  2. private final Lexicon dictionary; // 所需的底层资源
  3. public SpellChecker(Lexicon dictionary) {
  4. this.dictionary = Objects.requireNonNull(dictionary);
  5. }
  6. public boolean isValid(String word) { ... }
  7. public List<String> suggestions(String typo) { ... }
  8. }

(2)Setter注入

  1. public class SpellChecker{
  2. private Lexicon dictionary; // 所需的底层资源
  3. void setDictionary(Lexicon dictionary){
  4. this.dictionary = dictionary;
  5. }
  6. public boolean isValid(String word) { ... }
  7. public List<String> suggestions(String typo) { ... }
  8. }

(3)接口注入

  1. public interface DictionaryDependent{
  2. void setDependence(Lexicon dictionary);
  3. }
  4. public class SpellChecker implement DictionaryDependent{
  5. private Lexicon dictionary; // 所需的底层资源
  6. void setDependence(Lexicon dictionary){
  7. this.dictionary = dictionary;
  8. }
  9. public boolean isValid(String word) { ... }
  10. public List<String> suggestions(String typo) { ... }
  11. }

参考:

6 避免创建不必要的对象

  • String s = "bikini"; ====> String s = new String("bikini");
  • Boolean.valueOf(String) :内部只维护了两个对象TRUEFALSE
  • 判断是否为数字
  1. // 每次都生成Pattern对象,造成内存浪费
  2. static boolean isRomanNumeral(String s) {
  3. return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
  4. }
  5. public class RomanNumerals {
  6. private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
  7. + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
  8. static boolean isRomanNumeral(String s) {
  9. return ROMAN.matcher(s).matches();
  10. }
  11. }
  • Map.keySet():每次返回同一个实例,懒加载模式
  • 注意自动装箱问题
  • 尽量不要维护对象池,除非对象创建开销太大,如:JDBC数据库连接池

7 消除过时的对象引用

(1)内存泄漏存在情况与解决

  • 一个类自己管理它的内存(没用的对象没有置空):显式置null
  • 缓存相关:
    • 设置淘汰策略:设置超时清除
    • 自制回收线程
    • 内存占用做限制
    • 使用WeakHashMap容器
  • 监听器相关:
    • 使用WeakHashMap容器

(2)WeakHashMap的实现原理

  • Entry继承WeakReference类:下一次垃圾回收就会回收掉该对象
  • WeakHashMap内部一个ReferenceQueue:被回收的对象将放入该队列中
  • 任何对WeakHashMap的操作都会进行一次同步操作:ReferenceQueue与Entry[]的同步操作,把Entry过期对象清除,ReferenceQueue清空。
  1. // 回收操作和把对象放入引用队列由JVM处理,WeakHashMap只需要创建WeakReference时,把ReferenceQueue放入即可
  2. // 同步操作:该方法被WeakHashMap中的所有操作涉及,代表只要进行操作就会进行同步,可能你会担心性能问题,但是实际上如果queue中没有数据时,直接就返回了。
  3. private void expungeStaleEntries() {
  4. // 循环清除queue中的元素
  5. for (Object x; (x = queue.poll()) != null; ) {
  6. // 防止并发
  7. synchronized (queue) {
  8. @SuppressWarnings("unchecked")
  9. Entry<K,V> e = (Entry<K,V>) x;
  10. int i = indexFor(e.hash, table.length);
  11. Entry<K,V> prev = table[i];
  12. Entry<K,V> p = prev;
  13. while (p != null) {
  14. Entry<K,V> next = p.next;
  15. if (p == e) {
  16. if (prev == e)
  17. table[i] = next;
  18. else
  19. prev.next = next;
  20. // 协助GC操作,清除数组中的元素
  21. e.value = null; // Help GC
  22. size--;
  23. break;
  24. }
  25. prev = p;
  26. p = next;
  27. }
  28. }
  29. }
  30. }

8 避免使用finalize方法

  • finalize的调用时机无法把握

    • finalizer线程优先级低,可能还没回收就发生OOM异常
    • System.gc也无法保证一定会执行
  • 导致严重的性能损失

  • 类暴露于终结方法攻击:

    • 在终结过程中若有未被捕获的异常抛出,则抛出的异常会被忽略,而且该对象的终结过程也会终止。
    • 当构造器或者序列化中抛出异常,恶意子类的终结方法可以运行在本应夭折的只构造了部分的对象上(强行救活父类对象)。此时子类就可以调用该对象上的任意方法,但实际上该对象应该不存在才对。
    • final类能免疫于此类攻击,因为没有类能对final类进行恶意继承。
    • 为了防止非final类遭受终结方法攻击,我们可以写一个什么都不做而且是final的终结方法。
  • 替代方案:继承AutoCloseable接口,close方法在被关闭后还被调用,就要抛出一个IllegalStateException异常。

  1. public class Room implements AutoCloseable {
  2. private static final Cleaner cleaner = Cleaner.create();
  3. // Resource that requires cleaning. Must not refer to Room!
  4. private static class State implements Runnable {
  5. int numJunkPiles;
  6. // Number of junk piles in this room
  7. State(int numJunkPiles) {
  8. this.numJunkPiles = numJunkPiles;
  9. }
  10. // Invoked by close method or cleaner
  11. @Override
  12. public void run() {
  13. System.out.println("Cleaning room");
  14. numJunkPiles = 0;
  15. }
  16. }
  17. // The state of this room, shared with our cleanable
  18. private final State state;
  19. // Our cleanable. Cleans the room when it’s eligible for gc
  20. private final Cleaner.Cleanable cleanable;
  21. public Room(int numJunkPiles) {
  22. state = new State(numJunkPiles);
  23. cleanable = cleaner.register(this, state);
  24. }
  25. @Override
  26. public void close() {
  27. cleanable.clean();
  28. }
  29. }

9 优先使用try-with-resources而不是try-finally

  • try-finally
  1. static void copy(String src, String dst) throws IOException {
  2. InputStream in = new FileInputStream(src);
  3. try {
  4. OutputStream out = new FileOutputStream(dst);
  5. try {
  6. byte[] buf = new byte[BUFFER_SIZE];
  7. int n;
  8. while ((n = in.read(buf)) >= 0)
  9. out.write(buf, 0, n);
  10. } finally {
  11. out.close();
  12. }
  13. } finally {
  14. in.close();
  15. }
  16. }
  • try-with-resources
  1. static void copy(String src, String dst) throws IOException {
  2. try (
  3. InputStream in = new FileInputStream(src);
  4. OutputStream out = new FileOutputStream(dst)
  5. ) {
  6. byte[] buf = new byte[BUFFER_SIZE]; int n;
  7. while ((n = in.read(buf)) >= 0)
  8. out.write(buf, 0, n);
  9. }
  10. }

Effective Java 读书笔记(一):创建和销毁对象的更多相关文章

  1. 【Effective Java读书笔记】创建和销毁对象(一):考虑使用静态工厂方法代替构造器

    类可以提供一个静态方法,返回类的一个静态实例,如Boolean包装类的一个获取实例的静态方法 public static Boolean valueOf(boolean b) { return (b ...

  2. Effective Java 读书笔记之一 创建和销毁对象

    一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...

  3. Effective Java 学习笔记之创建和销毁对象

    一.考虑用静态工厂方法代替构造器 1.此处的静态工厂方法是指返回指为类的对象的静态方法,而不是设计模式中的静态工厂方法. 2.静态工厂方法的优势有: a.使用不同的方法名称可显著地表明两个静态工厂方法 ...

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

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

  5. effective java 第2章-创建和销毁对象 读书笔记

    背景 去年就把这本javaer必读书--effective java中文版第二版 读完了,第一遍感觉比较肤浅,今年打算开始第二遍,顺便做一下笔记,后续会持续更新. 1.考虑用静态工厂方法替代构造器 优 ...

  6. 《Effective Java》读书笔记 - 2.创建和销毁对象

    Chapter 2 Creating and Destroying Objects item 1:Consider static factory methods instead of construc ...

  7. Effective Java 读书笔记之二 对于所有对象都通用的方法

    尽管Object是一个具体的类,但设计它主要是为了扩展.它的所有非final方法都有明确的通用约定.任何一个类在override时,必须遵守这些通用约定. 一.覆盖equals时请遵守通用的约定 1. ...

  8. Effective Java——(一)创建和销毁对象

    第一条 考虑用静态工厂方法代替构造器 什么叫静态工厂方法,就是通过在类中通过静态方法对对象初始化. 比如说 public class StaticFactory { private String na ...

  9. Effective Java(一)—— 创建和销毁对象

    在客户端(调用端)获取自身实例的方法: 公有的构造器: 类的静态工厂方法: 1. 使用静态工厂方法代替构造器 Boolean 是对基本类型 boolean 的包装类: public final cla ...

  10. Effective Java 读书笔记(二):对象通用方法

    1 重写equals方法时请遵守通用约定 (1)无需覆盖equals方法的情况 要求独一无二 不要求逻辑相等 超类已经覆盖equals方法,对其子类也适用 一个类是私有的或者是包私有(可以重写后抛出异 ...

随机推荐

  1. Python 设计模式之建造者模式 Builder Pattern

    #引入建造者模式 肯德基的菜单上有 薯条, 鸡腿,鸡翅,鸡米花,可乐,橙汁,火腿汉堡,至尊虾汉堡,牛肉汉堡 , 鸡肉卷等这些单品,也有很多套餐. 比如 套餐1:鸡翅,至尊虾汉堡,可乐,薯条 套餐2:鸡 ...

  2. GridLayout: GridLayout使用简介(转)

    Android 布局之GridLayout 1 GridLayout简介 GridLayout是Android4.0新提供的网格矩阵形式的布局控件. GridLayout的继承关系如下:java.la ...

  3. crontab 使用日期时间命名重定向文件

    使用月份命名 0 12 * * * sh /tmp/test.sh >> "/tmp/$(date +"\%Y-\%m").log" 2>&a ...

  4. 解决“Jquery的each里面return失效的问题”

    参考文章:http://blog.csdn.net/coffeesmile/article/details/53349860 问题描述: 集中获取页面的一些元素,然后用each循环处理这些元素,如果其 ...

  5. hyper-v启动虚拟机时提示“The application encountered an error while attempting to change the state of the machine ‘虚拟机名称'”如何处理?

    1. 找出发生这一问题的事件代号 1.1 在开始菜单中搜索程序Event Viewer并点击进入 1.2 点击路径如下: “Applications and Services Logs > Mi ...

  6. mysql中int长度的意义 int(0)

    问题: mysql的字段,unsigned int(3), 和unsinged int(6), 能存储的数值范围是否相同.如果不同,分别是多大?int(0) 能存多少位数字? 不同,int(3)最多显 ...

  7. Git 代码撤销、回滚到任意版本(当误提代码到本地或master分支时)

    转自https://www.cnblogs.com/lwh-note/p/9639835.html 两种情况(场景) 情况一      代码还只在本地,未push到运程仓库,想把代码还原到上一次com ...

  8. 【swoole】PHP+Swoole+Linux实现进程监控

    脚本代码 class Server { const PORT = 8888; public function port() { //netstat -anp 2>/dev/null| grep ...

  9. DELPHI 数据库操作类(工具类)

    DELPHI 数据库连接类 做的时候目地是可以通过类的创建和释放进行数据库的短连接,在做服务端的时候每一个请求都通过类生成一个数据连接 unit UnDm; interface uses SysUti ...

  10. shell每隔一秒钟就记录下netstat状态

    说明 木马可能类似随机发送心跳包的操作,随机sleep.对这类情况写好了一个监听shell脚本,每隔一秒钟就记录下netstat状态. 代码 #!/bin/bash #功能:用于定时执行lsof 和 ...