Effective Java——创建和销毁对象

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

基本内容:一个类可以使用一个静态方法来返回自身的一个实例而不是使用构造方法去手动创建。

优势:

1、清晰易懂:一个类可能有多个构造方法,每个构造方法可能参数都不同,一般情况下,很难知道什么时候使用那个构造方法,如果使用静态方法,可以直接从方法名称就可以突然出该方法的作用,同时也避免了手动创建,这样得到指定对象实例就更加简单方便。

2、重用实例对象:使用构造器手动创建对象会每次创建就产生一个新的对象,如果使用静态方法来创建,就可以在内部预先构建好对象,或者将之前建造好的对象缓存起来,这样以后就不需要创建了,直接返回该对象就可以了。

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

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、类如果不含有公有的或者受包含的构造器,就不能被子类化,这个这是单例模式的缺陷。

第二条:遇到多个构造器参数时要考虑用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 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;
    }

}

这种重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且难以阅读。

第二种方法是使用JavaBean模式。就是调用一个无参构造器来创建对象,然后使用setter方法来设置每个必要的参数。

public class NutritionFacts {
    private int servingSize = -1;
    private int servings = -1;
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public NutritionFacts() {}

    public void setServingSize(int servingSize) {
        this.servingSize = servingSize;
    }

    public void setServings(int servings) {
        this.servings = servings;
    }

    public void setCalories(int calories) {
        this.calories = calories;
    }

    public void setFat(int fat) {
        this.fat = fat;
    }

    public void setSodium(int sodium) {
        this.sodium = sodium;
    }

    public void setCarbohydrate(int carbohydrate) {
        this.carbohydrate = carbohydrate;
    }
}

构造被分为几个调用之中,在构造过程中,JavaBean可能处于不一致状态,JavaBean模式阻止把类变成不可变对象,这样就变得线程不安全了。

最好的方法就是使用构造者模式了。

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 {
        private final int servingSize;
        private final int servings;

        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public void setCalories(int calories) {
            this.calories = calories;
        }

        public void setFat(int fat) {
            this.fat = fat;
        }

        public void setSodium(int sodium) {
            this.sodium = sodium;
        }

        public void setCarbohydrate(int carbohydrate) {
            this.carbohydrate = carbohydrate;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }

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

如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是不错的选择,特别是当大多数参数都是可选的时候,与传统的重叠构造器模式相比,使用Builder模式的客户端代码将更容易阅读和编写,Builder模式也比JavaBeans更加安全。

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

实现Singleton有以下几种方法:

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

    private Elvis() {}

}

由于缺少公有的或者受保护的构造器,这就保证了Elvis的唯一性,一旦Elvis被实例化就只存在一个Elvis对象。

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

    private Elvis() {}

    public static Elvis getInstance() {
        return INSTANCE;
    }
}

第三种

public enum Elvis {
    INSTANCE;
}

单元素的枚举类型已经成为实现Singleton的最佳方法。

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

如果我们编写的类只包含静态方法和静态域,不希望它被实例化来使用,在缺少显示构造器的情况下,编译器会自动提供一个公有的,无参的缺省构造器,所以我们需要让这个类包含一个私有的构造器,这样他就不能被实例话了。

public class UtilityClass {
    private UtilityClass() {
        throw new AssertionError();
    }
}

它的缺陷就是这个类不能被子类化了。

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

1、一般来说,最好能够重用对象而不是每次需要的时候就创建一个相同功能的对象。如果对象是不可变的,它就可以始终被重用。

2、对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象。

3、除了重用不可变对象之外,也可以重用那些已知不会被修改的可变对象。

4、要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。

第六条:消除过期的对象引用,避免内存泄漏

如果一个对象引用被无意识地保留起来了,那么,垃圾回收机制不仅不会处理这个对象,而且也不会处理这个对象所引用的其他对象。所以,一旦对象引用已经过期,只需清空这些引用即可。消除过期引用最好的方法就是让包含该引用的变量结束其生命周期。

一般而已,如果类是自己管理内存的,我们就应该警惕内存泄漏的问题。

内存泄漏的另一个常见的来源是缓存,因为一旦你把对象引用放到缓存中,它就很容易被遗忘掉。

内存泄漏的第三个常见来源是监听器和其他回调。如果你实现了一个API,客户端在这个API中注册回调,却没有显示的取消注册,所以可以使用弱引用。

第七条:避免使用终结方法(finalizer)

1、终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。

2、终结方法的缺点在于不能保证会被及时的执行,从一个对象变得不可达开始,到它的终结方法被执行,所花费的时间是任意长的。所以注重时间的任务不应该由终结方法来完成。

3、Java语言规范不仅不能保证终结方法会被及时的执行,而且根本不能保证它们会被执行。

4、不要被System.gc和System.runFinalization这个两个方法所诱惑,它确实增加了终结方法被执行的机会,但是它们并不能保证终结方法一定会被执行。

5、另外使用终结方法会带来严重的性能损失。

6、显示的终止方法通常与try-finally结构结合起来使用。这样就保证了及时在使用对象的时候有异常抛出,该终止方法也会执行。

终结方法的合理用途:当对象的所有者忘记调用前面所说的显示终止方法的时候,可以充当备胎的角色来使用。如果终结方法发现资源未被终止可以在日志中打出警告,这样就方便我们检查错误。

另外需要注意的是,终结方法链并不会自动执行,如果类有终结方法,并且子类覆盖了终结方法,子类的终结方法就必须手工的调用超类的终结方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值