igaozp / notes

个人笔记 & 博客文章备份
MIT License
3 stars 2 forks source link

Effective Java 之创建和销毁对象 #16

Open igaozp opened 6 years ago

igaozp commented 6 years ago

考虑使用静态工厂方法替代构造器

一个简单的示例

public class Foo {
    private String objectId;
    //私有构造器
    private Foo(String objectId) {
        this.objectId = objectId;
    }

    //静态工厂方法
    public static Foo newInstance(String objectId) {
        return new Foo(objectId);
    }
}

这样做的优点

主要的缺点

igaozp commented 6 years ago

遇到多个构造器参数时要考虑使用构建器

设想一下一个类有许多的属性,这些属性需要使用构造方法初始化,大量的构造方法可能会造成代码的可读性下降。如果使用 JavaBeans 模式,通过 set 方法初始化相应的属性,可能会导致对象实例处于不一致的状态,无法保证线程安全。

这样就产生了一种替代方法,通过 Builder 模式 不直接生成对象,而是利用必要的参数调用构造器,得到一个 builder 对象,通过 builder 对象设置可选参数,最后调用无参的 build 的方法生成不可变对象。

示例代码:

public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // 必要参数
        private final int servingSize;
        private final int servings;

        // 可选参数
        private int calories = 0;
        private int fat = 0;
        private int carbohydrate = 0;
        private int sodium = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            thus.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public NutritionFacts build() {
            return NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

使用示例:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
igaozp commented 6 years ago

使用私有构造器或者枚举类型强化 Singleton 属性

Singleton 是指仅实例化一次的类,有多种方式实现 Singleton 类

  1. 通过将构造方法设置为私有,用静态 final 变量来表示唯一的实例:
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { ... }
}
  1. 通过设置静态工厂方法,每一次调用都返回同一个实例:
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { ... }

    public static Elvis getInstance() {
        return INSTANCE;
    }
}
  1. 编写一个包含单个元素的枚举类型,更加简洁,并且提供了序列化机制:
    public enum Elvis {
    INSTANCE;
    }
igaozp commented 6 years ago

通过私有构造器强化不可变实例化的能力

通常有一些工具类没有实例化的必要,只是使用类的静态函数或者变量,可以将类的构造器设置为 private 来避免类的实例化

public class UtilityClass {
    private UtilityClass() { ... }
}
igaozp commented 6 years ago

避免创建不必要的对象

避免创建不必要的对象,尽量重用已有的对象,避免重复创建对象产生的开支,可以使用静态工厂方法来实现对相关对象的重用。优先使用基本类型,避免因自动装箱而产生性能开支。

igaozp commented 6 years ago

消除过期的对象引用

Java 虽然不像 C++ 那样需要程序员手动管理内存,但是垃圾回收机制并不是那么智能,对于被引用的对象,就算我们已经不在使用,但是 Java 的回收机制是不会回收它们的,无意识的引用造成了 “内存泄漏”。这就需要我们手动去消除过期的对象引用,最为常用的方法将不再使用的对象设置为 null

igaozp commented 6 years ago

避免使用终结方法

C++ 的类中存在析构函数,当类的实例销毁时就会执行析构函数,Java 中也存在类似的机制那就是终结方法finalize(),但终结方法并不能保证会被及时的执行,因此导致终结方法失去了一定的实际意义。例如数据库连接的关闭、数据流的关闭等操作可以使用 try-finally 语句来代替。