1 用静态工厂方法代替构造器
类可以设计一个静态工厂方法,对外提供自己的一个实例。
1.1 和公有构造器相比的优势
(1)静态工厂方法有名称。更容易使用,客户端代码更容易阅读。
(2)不必每次调用都创建一个新的对象。静态工厂方法可以根据需求,选择返回有限制个数实例或者创建一个实例。返回有限制的实例,可以使用Singleton,或者Flyweight。
(3)静态工厂方法可以返回子类型的对象,灵活性更高。
(4)在创建参数化类型实例的时候,使代码更简洁。
比如创建 HashMap
public static <K,V> HashMap<K,V> newInstance(){
return new HashMap<K,V>();
}
调用的时候:
HashMap<String,Object> map = Maps.newInstance();
1.2 缺点
(1)类如果不含有公有的或者受保护的构造器,就不能被子类化。
(2)静态工厂方法与其它的静态方法实际上没有任何区别,容易混淆。遵循标准的命名习惯,可以弥补这一劣势。
2 遇到多个构造器参数时,考虑使用构建器(Builder)
当一个类实例化有多个可选参数时,使用构造器和静态方法实现都不是很方便,需要多个不同参数的构造器或者静态工厂方法,当可选参数过多时,客户端代码会变得很难编写,而且很难维护。
可以使用Builder模式实现。
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;
//静态内部类实现的Builder
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;
this.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 new NutritionFacts(this);
}
}
//私有化的构造器,通过builder创建实例
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static void main(String[] args) {
//调用方法实例化类,很简洁,清晰
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
}
}
这是设计模式中,Builder模式的一种形式,不要直接生成对象,客户端调用builder,并设置可选参数,最后用builder生成对象。
3 用私有构造器或者枚举类型强化Singleton属性
3.1 使用私有构造器,实现Singleton
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.getInstance();
elvis.leaveTheBuilding();
}
}
3.2使用枚举类型实现Singleton
实现方式更加简洁,而且无偿提供了序列化功能,绝对杜绝多次实例化。
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
4 通过私有构造器强化不可实例化的能力
很多时候,需要编写只包含静态方法和静态域的类,比如工具类,这些类不希望被实例化。在缺少显示构造器的情况下,编译器会自动提供一个缺省构造器,所以最好把明确的用private 定义这些类的构造器。
5 避免创建不必要的对象
一般来说,最好能重用对象而不是每次都创建新对象(功能相同的)。
如果对象是不可变的,始终可以被重用。
类中的不可变属性,可以在静态域中初始化,不用每次调用时初始化。
适配器模式中适配器(adapter)中除了委托执行的对象(adaptee)之外,并没有其他状态信息。针对某一个对象的特定适配器,不需要多个适配器实例。
优先使用基本类型(int ,long。。),避免使用基本类型的封装类(Integer,Long等),当心无意识的自动装箱。
自动装箱:在Java1.5版本,一种创建多余对象的方法,允许程序猿将基本类型和装箱基本类型混用,按需自动装箱和拆箱。
维护自己的对象池来避免创建对象并不是一种好的解决方案,除非对象非常重且有意义,比如数据库连接。
6 消除过期的对象引用
Java语言有自动垃圾回收功能,但是也会存在内存泄露,而且更隐蔽。
6.1 自己管理内存导致内存泄漏
下面是一个典型的例子:
import java.util.*;
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 e) {
ensureCapacity();
elements[size++] = e;
}
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);
}
}
在pop弹出对象时,elements的弹出对象的elements[--size]的引用仍然被数组维护,对象不会被垃圾回收器回收,但是这个引用不会再被使用到,这就造成了内存溢出。
需要手动清空这些引用:elements[size] = null。
清除对象引用是一种特例,不是一种规范。
何时应该清除引用:
一旦数组元素变成了非活动部分的一部分,程序猿就应该手工清除这些元素。
一般而言:只要类是自己管理内存,就应该警惕内存泄漏问题。一旦元素被释放,该元素包含的任何对象引用应该被清空。
6.2 缓存也有可能导致内存泄漏
一旦对象放入缓存中,长时间不使用,仍然会被保留在缓存中。
解决方案:
(1)使用WeakHashMap代表缓存。
只要在缓存之外存在对某个项的键的引用,该项就有意义,就可以用WeakHashMap代表缓存。当缓存中的项过期后,它们就会自动被删除。
(2)启动一个后台线程定时清理,或者给缓存添加新条目时顺便清理没用的项。LinkedHashMap可以利用removeEldestEntry方法实现清理。更加复杂的缓存,必须使用java.lang.ref。
6.3 内存泄漏的第三个来源是监听器和回调
如果实现了一个API,客户端在这个API注册回调,却没有显示地取消注册,可能会导致内存泄漏,除非采取某些措施。确保回调立即被当做垃圾回收的最佳方法是只保存它们的弱引用(weak reference),例如,只将他们保存成WeakHashMap中的键。
7 避免使用终结方法
java语言规范不仅不保证终结方法会被及时的执行,而且根本就不保证他们会被执行。所以:不应该依赖终结方法来更新重要的持久状态。
如果累的对象中封装的资源必须要终止。需要显示的提供一个终止方法,规定客户端在每个实例不再有用的·时候,必须调用这个方法释放资源。
显示的终止方法,通常与try-finally结构结合起来使用,以确保及时终止。