effective Java读书笔记:创建和销毁对象

第1条:考虑用静态工厂方法代替构造器

静态工厂方法与构造器不同,有以下几个优势:
1. 静态方法有名称。

构造器的参数本身没有确切地描述返回的对象,静态工厂方法可以,比如:newInstance()
2. 不必在每次调用他们的时候都创建一个新对象。
说白了,可以在静态工厂方法里面控制实例是否可重用,可以缓存对象等。Boolean.valueOf(boolean)就是一个很好的例子。
3. 可以返回类型的任何子类型的对象。
这是工厂方法的功能范畴了,返回不同子类的实例
4. 在创建参数化类型实例的时候,它们使代码变得更加简洁。

Map<String,List<String> m = new HashMap<String,List<String>();

如上,实例化需要接连两次提供类型参数,随着参数越多会越来越复杂,假如有静态方法:

public static <K,V> HashMap<K,V> newInstance(){
    return new HashMap<K,V>();
}
//实例化会变得简洁:
Map<String,List<String> m = HashMap.newInstance();

当然,静态工厂方法也有缺点:
1. 类如果不含有公有的或者受保护的构造器,就不能被子类化。
2. 它们与其他的静态方法实际没有任何区别。调用方不知道是否有工厂方法来构造,一般有一些通用的方法命名规则,比如valueOf,getInstance,newInstance,getType,newType


第2条:遇到多个构造器参数时要考虑用构造器

静态工厂和构造器有一个共同的局限性:不能很好地扩展大量的可选参数。通常做法是把可选参数放到后面,内部调用必选参数构造器。这种方式可行,但是当有很多参数时,客户端代码回很难编写,代码难以阅读。这种情况有哪些替代方法呢?

1. JavaBeans模式

调用无参构造函数,通过setter方法设置必选参数和可选参数。但这种模式有个最大的问题:在构造过程中JavaBean可能处于不一致的状态。

2. 更好的替代方法:Builder模式

这种方法既能保证构造模式的安全性,又能保证像JavaBeans模式那么好的可读性。看代码:

// Builder Pattern
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;

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.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);
        }
    }
}

使用方式:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();

首先,利用必选参数调用构造器得到一个builder对象,然后调用类似setter方法设置可选参数,最后调用参数的build方法来生成不可变的对象。大名鼎鼎的protobuf中的message对象就是利用这种模式创建的。


第3条:用私有构造器或者枚举类型强化singleton属性

singleton值仅仅被实例化一次的类。

方法一:把构造器设为private,并导出公有的静态成员
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis(){}
}
方法二:共有的成员是个静态工厂方法
public class Elvis {
    //改为private
    private static final Elvis INSTANCE = new Elvis();
    private Elvis(){}
    //通过func返回实例
    public static Elvis getInstance(){return INSTANCE;}
}
3. 枚举类型
public enum Elvis {
    INSTANCE;
}

问题:不是很理解java 的枚举类


第5条:避免创建不必要的对象

重要程度:5颗星
一般来说,最好能重用的对象而不是每次需要的时候就创建一个功能相同的新对象。举个反例:

String s=new String(“Hello World”);

该语句每次被执行都会创建一个新的String实例,但是这些创建对象的动作全都是不必要的。改进版:

String s = “Hello World”;

对于提供了静态工厂方法和构造器的不可变类,通常使用静态工厂方法而不是构造器,以避免不必要的对象。如Boolean.valueOf(String)几乎总是优于构造器Boolean(String)
另外一点:要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。


第6条:消除过期的对象引用

看一个例子:

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];//size~elements.length间的元素为过期元素
       }

       /**
        * Ensure space for at least one more element, roughly
        * doubling the capacity each time the array needs to grow.
        */
       private void ensureCapacity() {
              if (elements.length == size){
                     Object[] oldElements = elements;
                     elements = new Object[2 * elements.length +1];
                     System.arraycopy(oldElements, 0, elements, 0, size);
              }
       }
}

这个程序会导致内存泄漏,原因是如果一个栈先增长然后再收缩,那么,从栈中弹出来的对象将不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,他们也不会被回收。
修复方法:解除引用

public Object pop() {
       if (size == 0)
              throw new EmptyStackException();
       Object result =elements[--size];
       elements[size] = null;//消除过期引用,只要外界不再引用即可回收
       return result;
}

那么,何时应该清空引用呢?Stack类自己管理内存。存储池包含了elements数组的元素。垃圾回收器并不知道stack呢写元素是分配的,哪些是自由的。因此,只要类自己管理内存,就应该警惕内存泄漏问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值