Effective Java笔记-创建和销毁对象

创建和销毁对象

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

优势一 它们有名称

当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且慎重的选择名称以便突出他们之间的区别。

优势二 不必在每次调用它们的时候都创建一个新对象

实例受控的类指的是总能严格控制在哪个时刻哪些实例应该存在。
静态工厂方法可以实现实例受控的类。
创建实例受控的类的原因:
    1.实例受控的类可以确保它是一个Singleton或者不可实例化的
    2.他还使得不可变的类(见第15条)可以确保不会存在两个相等的实例,即当且经当a==b时才有a.equal(b)为true,这样就可以用前者来代替后者,提升性能,当然枚举类型可以直接保证单例

优势三 可以返回原返回类型的任何子类型的对象

这个优势有这样一种应用:
    假设我们时一个接口的设计者,然后有一些厂商(A,B,C)分别提供了它们对这个接口的实现,然后我们再建一个类,在这个类中有一个静态工厂方法,返回的类型是我们设计的接口。返回的实例是A,B,C三个实现的性能最优者比如A,客户可以并不知晓A,B,C的存在。当B经过修改性能超过A时,我们可以修改静态工厂方法让它返回B的实例,而客户不必为这个升级修改它们原有的代码
    一种典型应用(服务提供者框架):
    public interface Service {
        // Service-specific methods go here
    }


    public interface Provider {
        Service newService();
    }

    public class Services {
        private Services() {
        } // Prevents instantiation (Item 4)

        // Maps service names to services
        private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
        public static final String DEFAULT_PROVIDER_NAME = "<def>";

        // Provider registration API
        public static void registerDefaultProvider(Provider p) {
            registerProvider(DEFAULT_PROVIDER_NAME, p);
        }

        public static void registerProvider(String name, Provider p) {
            providers.put(name, p);
        }

        // Service access API
        public static Service newInstance() {
            return newInstance(DEFAULT_PROVIDER_NAME);
        }

        public static Service newInstance(String name) {
            Provider p = providers.get(name);
            if (p == null)
                throw new IllegalArgumentException(
                        "No provider registered with name: " + name);
            return p.newService();
        }
    }

优势四 在创建参数化类型实例时,它们使代码变得更加简洁

泛型的构造器没有类型推导,但泛型方法有类型推导,所以
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();//如果有了上面的静态工厂方法,就可以利用方法的类型推导,这样定义,他可以推导出K,V分别是StringList<String>

缺点一 类如果不含有公有的或者受保护的构造器,就不能被子类化(被继承)

对于公有的静态工厂方法所返回的非共有类,也是如此

缺点二 它们与其他静态方法实际上没有任何区别

所以在API文档中没有像构造器那样明确标示出来,不过javadoc工具总有一天会注意到静态工厂方法

第二条 遇到多个构造器参数时要用构建器

当遇到一个类中有很多参数,且其中有些是必须的,有些不是必须的,对于这样的类应该用什么样构造器或方法呢?比如用一个类表示包装是平外面显示的营养成分标签。这些标签中有几个域是必需的如每份的含量...,还有一些不是必须的如脂肪量..

重叠构造器

public class NutritionFacts {
    private final int servingSize; // (mL) required
    private final int servings; // (per container) required
    private final int calories; // optional
    private final int fat; // (g) optional
    private final int sodium; // (mg) optional
    private final int carbohydrate; // (g) optional

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat,
            int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat,
            int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
    }
}
缺点:当有许多参数时,客户端代码会很难编写

JavaBean模式

public class NutritionFacts {
    // Parameters initialized to default values (if any)
    private int servingSize = -1; // Required; no default value
    private int servings = -1; // "     " "      "
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public NutritionFacts() {
    }

    // Setters
    public void setServingSize(int val) {
        servingSize = val;
    }

    public void setServings(int val) {
        servings = val;
    }

    public void setCalories(int val) {
        calories = val;
    }

    public void setFat(int val) {
        fat = val;
    }

    public void setSodium(int val) {
        sodium = val;
    }

    public void setCarbohydrate(int val) {
        carbohydrate = val;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts();
        cocaCola.setServingSize(240);
        cocaCola.setServings(8);
        cocaCola.setCalories(100);
        cocaCola.setSodium(35);
        cocaCola.setCarbohydrate(27);
    }
}
缺点:
1.在构造过程中JavaBean可能处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。试图使用处于不一致状态的对象,将会导致失败(没看懂~)
2.JavaBean模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全

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;

    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();
    }
}
1.builder像个构造器一样,可以对其参数强加约束,build方法可以检验这些约束条件。将参数从builder拷贝到对象后,并在对象域而不是builder域(见第39条)中对它们进行检验,若违反约束条件,build方法应该抛出IllegalStateException。
2.对多个参数强加约束的另一个方法是,用多个setter方法对某个约束条件必需持有的所有参数进行检查。如果该约束条件没有得到满足。setter方法就会抛出IllegalStateException。这种做法跟上面那种相比,不用等到build的时候才发现。
3.用builder生成抽象工厂:
//A builder for objects of type T
public interface Builder<T>{
    public T build();
}

//比如,可以声明NutritionFacts.Builder类来实现Builder<NutritionFacts>
带有Builder实例的方法通常李勇有限制的通配符类型来约束构建器的类型参数,如:
Tree buildTree(Builder<? extends Node> nodeBuilder){...}

第三条 用私有构造器或枚举类型强化Singleton属性

实现Singleton的几种方法:

通过私有构造器

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    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.INSTANCE;
        elvis.leaveTheBuilding();
    }
}
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();
    }
}
上面两种方法很类似只是前者的唯一实例是直接通过引用类成员获得,后者通过一个静态工厂方法返回。后者的优势在于提高了灵活性:你可以修改静态工厂方法,比如改成为每个调用该方法的线程返回一个实例,第二个优势与泛型(27条)有关
通过反射可以调用私有构造器,所以若想抵御这种攻击可以修改构造器,让他在创建第二个实例时抛出异常。

为了使利用这其中一种方法实现的Singleton类变成是可序列化的,仅仅在声明中加上implements Serializable是不够的。为了维护并保证Singleton,必需声明所有实例域都是瞬时的(transient),并提供一个readResolve方法(77条),否则,每次反序列化一个序列化的实例时,都会创建一个新的实例:
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() {
    }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

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

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

使用枚举类型

public enum Elvis {
    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.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

第四条 通过私有构造器强化不可实例化的能力

public class UtilityClass {
    // Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }
}

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

String s =new String("stringette");
String s = "stringette";
上面两句中:第一句是new一个对象并复制"stringette"这个String对象,第二句只是让s指向"stringette"这个String对象(在常量池中)。如果两个句子各执行1000次则前者会新建1000各String对象,后者不会新建对象,若在你的程序中这两者的效果是一样的话,就选择后者,尤其是要执行很多遍的时候

另一个例子:
/**
*slowversion
*/
public class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods omitted

    // DON'T DO THIS!
    public boolean isBabyBoomer() {//判断该person是否是一个出生在生育高峰期中的人
        // 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;
    }
}

/**
*fastversion
*/
class Person {
    private final Date birthDate;

    public Person(Date birthDate) {
        // Defensive copy - see Item 39
        this.birthDate = new Date(birthDate.getTime());
    }

    // Other fields, methods

    /**
     * The starting and ending dates of the baby boom.
     */
    private static final Date BOOM_START;
    private static final Date BOOM_END;

    static {//如果isBabyBoomer()很可能不会被调用,那可以延迟初始化(lazily initalizing 见71条)
        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;
    }
}
适配器,有时也叫作视图。适配器是指这样一个对象:他把功能委托给一个后备对象(backing object),从而而后备对象提供一个可以替代的接口。由于适配器除了后备对象外,没有其他的状态信息,所以针对某个给定对象的特定适配器而言,他不需要创建多个适配器实例。如Map接口的keySet接口返回该Map对象的Set视图,但每次调用keySet都是返回同样的Set实例。

关于自动装箱的例子:
public class Sum {
    // Hideously slow program! Can you spot the object creation?
    public static void main(String[] args) {
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        System.out.println(sum);
    }
}
上面的代码中sum被定义为了Long而不是long,将一个基本数据类型赋值给Integer,Long等装箱基本类型会引发自动装箱,即会创建对象。
要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱,尤其是这条语句会执行很多遍的时候.

第六条 消除过期的对象引用

public class EmptyStackException extends IllegalStateException {
}

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];
    }

    /**
     * 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)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
考虑上面一段程序,他有什么问题?(除了没用泛型):不严格的讲它存在这一个内存泄漏的问题,Stack由数组实现,如果我们我们的栈先push然后pop,那些pop的对象并不会被垃圾回收器回收,因为在垃圾回收器看来elements中的元素都是平等的,并不会因为它们在size之外就有特殊对待,这类内存泄漏一般称为无意识的对象保持。修复的方式很简单:
    public Object pop(){
        if(size==0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }
清空过期引用的另一个好处是,如果它们以后又被错误地引用了,程序就会报空指针异常,而不是悄悄地运行下去。
不过,清空对象引用应该是一种例外,而不是规范。(不用时时这样做,因为会把程序搞乱)。
一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题。

如果你正好要实现这样的缓存:只要缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用WeakHashMap代表缓存;当缓存中的项过期之后,它们就会自动被删除
内存泄漏的另一个来是监听器和其他回调,解决方式跟上面一样:只保存它们的弱引用,比如只将它们保存成WeakHashMap中的键。

第七条 避免使用终结方法

1.不应该依赖终结方法来更新重要的持久状态
2.使用终结方法有一个非常严重的(Severe)性能损失
3.显式的终止方法通常与try-finally结构结合起来使用,以确保及时终止
4.本地对等体是一个本地对象,普通对象通过本地方法(native method)委托给一个本地对象。因为本地对等体不是一个普通对象,所以垃圾回收器不会知道他,当他的Java对等体被回收的时候,他不会被回收。在本地对等体并不拥有关键资源的前提下,终结方法正是执行这项任务的最合适工具。当本地对等体拥有必需被及时终止的资源时,那么该类就应该具有一个显示的终止方法。终止方法可以使本地方法吗或者它也可以调用本地方法。
5.“终结方法链”并不会被自动执行,如果类有终结方法,并且子类覆盖了终结方法,子类的终结方法就必须手工(manual)调用超类的终结方法。你应该在一个try块中终结子类,并在相应的finally块中调用超类的终结方法,这样可以保证:即使子类的终结过程抛出异常,超类的终结方法也会得到执行。
6.终结方法守卫者
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值