天天看点

effective java :创建和销毁对象1 用静态工厂方法代替构造器2 遇到多个构造器参数时,考虑使用构建器(Builder)3 用私有构造器或者枚举类型强化Singleton属性4 通过私有构造器强化不可实例化的能力5 避免创建不必要的对象6 消除过期的对象引用7 避免使用终结方法

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 避免使用终结方法

终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下不必要的。使用终结方法会导致行为不稳定、降低性能,以及可移植性的问题。

java语言规范不仅不保证终结方法会被及时的执行,而且根本就不保证他们会被执行。所以:不应该依赖终结方法来更新重要的持久状态。

如果累的对象中封装的资源必须要终止。需要显示的提供一个终止方法,规定客户端在每个实例不再有用的·时候,必须调用这个方法释放资源。

显示的终止方法,通常与try-finally结构结合起来使用,以确保及时终止。

继续阅读