一、创建和销毁对象
1、用静态工厂方法代替构造器
向客户端暴露类的实例的传统方法是提供一个公有的构造器。另一种更好的方式是提供一个静态工厂方法(static factory method)
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
静态工厂方法与构造器相比的优势:
- 有名称
- 不必每次都创建一个新对象
- 可以返回远类型的任何子类型对象
- 返回对象的类可以根据静态工厂方法的参数值发生变化
- 方法返回对象所属的类,在编写包含该静态工厂方法时可以不存在
静态工厂方法主要缺点是:1、类如果不含公有的或受保护的构造器就不能子类化;2、程序员很难发现文档中的静态工厂方法;
2、遇到多个构造器参数时要考虑使用构建器
- 重叠构造器(telescoping constructor)模式,如下:
public class NutritionFacts {
private final int servingSize; // required
private final int serving; // required
private final int calories; // optional
private final int fat; // optional
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings, int carlories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings, int carlories, int fat) {
this.servingSize = servingSize;
this.servings = servings;
this.carlories = carlories;
this.fat = fat;
}
}
重叠构造器的缺点是,当有许多参数时,客户端代码会很难编写,并且难以阅读
- 另一种方式就是采用JavaBean模式
@Data
public class NutritionFacts {
private int servingSize; // required
private int serving; // required
private int calories; // optional
private int fat; // optional
}
JavaBean模式的缺点是因为构造过程被分到了几个调用中,在构造中 JavaBean 可能处于不一致的状态。类无法仅仅通过判断构造器参数的有效性来保证一致性。还有一个严重的弊端是,JavaBeans 模式阻止了把类做成不可变的可能。,这就需要我们付出额外的操作来保证它的线程安全。
- Builder模式
public class NutritionFacts {
private final int servingSize; // required
private final int serving; // required
private final int calories; // optional
private final int fat; // optional
public static class Builder {
private final int servingSize; // required
private final int serving; // required
private final int calories = 0; // optional
private final int fat = 0; // optional
// 必选属性
public Builder(int servingSize, int serving) {
this.servingSize = servingSize;
this.serving = serving;
}
// 可选属性
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
serving= val;
}
public NutrionFacts build() {
return new NutrionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
serving= builder.serving;
calories = builder.calories ;
fat= builder.fat;
}
}
客户端可以使用如下方式基于类NutrionFacts实例化不可变对象:
NutritionFacts wahaha = new NutritionFacts.Builder(400, 200)
.calories (10).fat(20).build();
3、用私有构造器或者枚举类型强化Singleton属性
通常有两种方式实现,两种方式均需要保持构造器私有,放出公有的静态成员
- 第一种方式是对外放开共有静态成员
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
privatie Elvis() {}
public void otherMethod() {}
}
公有域的优势是:1、简单;2、API直接表名了这是一个Singleton类;
- 第二种方式是通过静态工厂方法放出公有成员
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
privatie Elvis() {}
public staic final Elvis getInstance() {
return INSTANCE ;
}
public void otherMethod() {}
}
静态工厂方式的优势:1、灵活;2、支持泛型;3、可以作为方法引用提供出去;
上述两种方式均需要防止通过反射机制调用私有构造器这种攻击方式。抵御方式是当构造器在被要求创建第二个实例时抛异常
另一个需要注意的是在Singleton类变为可序列化时(Serializable),仅声明 implements Serializable是不够的
- 第三种方式是通过枚举方式构造Singleton
public enum Elvis {
INSTANCE;
public void otherMehtod() {
}
}
单元素的枚举类型是实现Singleton的最佳方法
4、通过私有构造器强化不可实例化的能力
对于只包含静态方法和静态类域的工具类,添加私有构造器,避免被实例化
5、优先考虑依赖注入来引入资源
静态工具类和Singleton类不适合于需要引用底层资源的类
一个典型的依赖注入例子:
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = dictionary;
}
}
6、避免创建不必要的对象
最好能重用单个对象,而不是每次需要的时候就创建一个相同功能的新对象。重用方式既快速、又流行,如果对象不可变(immutable),始终就可以重用。
一个反例:
String s = new String("bikini");
上面实际上每次执行都创建了一个额外的新的String实例。
一个非常好的可以提升性能的方式是缓存一些构建成本很高的类的实例
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile("^*$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
另一个需要注意的是,优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱
7、消除过期引用
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object o) {
ensureCapacity();
elements[size++] = o;
}
// 从栈中弹出的对象仍然被elements引用,不会被垃圾回收,导致内存泄漏
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
修复方法:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
// 清空对象引用
elements[size] = null;
return result;
}
只要类是自己管理内存,就应该警惕内存泄漏问题
内存泄漏另一个常见来源是缓存
内存泄漏的第三个常见来源是监听器和其他回调
使用WeakHashMap来保存弱引用,确保回调立即被当做垃圾回收
8、避免使用终结方法和清除方法
如题
9、try-with-resources 优于 try-finally
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}