effective java 第二章 创建和销毁对象

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cheidou123/article/details/82319955

一、考虑用静态工厂代替构造器(实用程度◆)
二、遇到多个构造器参数时优先考虑用构建器(实用程度◆◆◆)
三、用私有构造器或枚举类型强化Singleton属性(实用程度◆◆◆)
四、通过私有构造器强化不可实例化的能力(实用程度◆◆◆◆◆)
五、避免创建不必要的对象(实用程度◆◆◆◆)
六、消除过期的对象引用(实用程度◆◆)
七、避免使用终结方法(实用程度◆)

一、考虑用静态工厂代替构造器(实用程度◆)
1.与构造器相比的优势
⑴它们有名称
⑵不必每次调用它们的时候都创建一个新对象
⑶它们可以返回返回类型的任何子类型对象
⑷它们使代码变的更加简洁
2.与构造器相比的劣势
⑴类如何不含有公有的或者收保护的构造器,就不能被子类化。
⑵它们与其他的静态方法实际上没有任何区别
二、遇到多个构造器参数时优先考虑用构建器(实用程度◆◆◆)
这里我们可以用lombok的模式,也可以自己手写。推荐采用lombok的方式。

package Chapter2_2;

/*** 
 * @author bincai
 * @email 1355869831@qq.com
 */
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 {
        //Required parameters
        private final int servingSize;
        private final int servings;

        //Optional parameters - initialized to default values
        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);
        }
    }

    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();
    }
}

三、用私有构造器或枚举类型强化Singleton属性(实用程度◆◆◆)
⑴首先我们先看两种常见的单例模式

public class Elvis{
        public static final Elvis INSTANCE = new Elvis();
        private Elvis(){...}
        ...
        public void singASong(){...}
    }

上面的方式客户端可以通过反射机制调用私有构造器

public class Elvis{
        private static final Elvis INSTANCE = new Elvis();
        private Elvis(){...}
        public static Elvis getInstance(){ return INSTANCE; }
        ...
        public void singASong(){...}
    }

上面的方式如果要实现序列化而不破坏单例,需要一个readResolve方法

private Object readResolve(){
        //Return the one true Elvis and let the garbage collector take care of the Elvis impersonator
        return INSTANCE;
    }

我们采用枚举的方式:

public enum Elvis(){
        INSTANCE;
        ...
        public void singASong(){...}
    }

四、通过私有构造器强化不可实例化的能力(实用程度◆◆◆◆◆)
有些工具类只有静态方法和静态成员变量,这些类不需要被实例化

public class UtilityClass{
        // Suppress default constructor for noninstantiability
        // (Add comment to clarify why the constructor is expressly provided)
        private UtilityClass(){
            throw new AssertionError();
        }
        ...
    }

五、避免创建不必要的对象(实用程度◆◆◆◆)
1.重用不可变的对象
我们应该这样:

String s = "stringette";    

而不是这样:

String s = new String("stringette");        

2.使用静态工厂方式比构造器方式更好(参见第一段)
3.一般可以也可以重用那些明知不会被改变的对象
有一个人我们检测他是否出生于1946至1964之间
我们应该这样:

public class Person {
    private final Date birthDate;
    ...
    private static final Date BOOM_START;
    private static final Date BOOM_END;

    static {
            Calendar gmtCal= Calendar.getInstance(TimeZone.getTimeZone("GMT"));
            gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
            BOOM_START = gmtCal.getTime();
            gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
            BOOM_END = gmtCal.getTime();
    }
        public boolean isBabyBoomer(){
            return birthDate.compareTo(BOOM_START) >= 0 &&birthDate.compareTo(BOOM_END)<0;
        }
    }

而不是这样,下面的方式每次都会新建一个Calender,一个TimeZone,和两个Date实例

    public class Person {
    private final Date birthDate;
    ...
        public boolean isBabyBoomer(){
            // Unnecessary allocation of expensive object.
            Calendar gmtCal= Calendar.getInstance(TimeZone.getTimeZone("GMT"));
            gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
            Date boomStart = gmtCal.getTime();
            gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
            Date boomEnd = gmtCal.getTime();
            return birthDate.compareTo(boomStart) >= 0 &&birthDate.compareTo(boomEnd)<0;
        }
    }

4维护自己的对象池一般不是好的做法,除非对象是非常重量级的,比如数据库连接池。

六、消除过期的对象引用(实用程度◆◆)
你能看出下面代码哪里内存泄漏了吗?

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 = Array.copyOf(elements, 2 * size + 1);
        }
    }

如果是一个栈先增长,然后再收缩,那么栈中弹出来的对象不会当做垃圾回收,即使栈中的程序不再引用这些对象,因为栈内部维护着对这些对对象的过期引用,所谓过期引用,是指永远也不会再被解除的引用。这里所有在elements数组的不小于size的元素都的引用是过期的。我们可以修改pop来防止内存泄漏:

public pop(){
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // Eliminate obsolete references.
        return result;
    }

总之:
⑴只要类是自己管理内存的,就该警惕内存泄漏
⑵缓存也容易导致内存泄漏,为了防止内存泄漏,可以采用weakhashmap,如果要保证缓存在没有意义时被回收,那么可以搞个后台线程进行清理,或者在给缓存添加新条目时候进行清理,linkedhashmap的removeEldestEntry可以实现后一种方案,更加复杂的缓存就要java.lang.ref了
⑶监听器和其它回调也容易导致内存泄漏
我们可以将它们保存成weakhashmap中的键。
七、避免使用终结方法(实用程度◆)
Finalizers一般是不可预测且危险的。
我们在流处理,数据库连接上也要显示关闭,那么可以采用下面的方法:

    Foo foo = new Foo(...);
    try {
        // Do what must be done with foo
        ...
    } finally {
        foo.terminate();  // Explicit termination method
    }
阅读更多

没有更多推荐了,返回首页