《Effective Java》第二章:创建和销毁对象

第1条:考虑用静态方法而不是构造器

静态方法相对于构造器的优势:

1.静态工厂方法有名字
2.静态工厂方法不必每次调用都创建一个新的对象
3.静态工厂方法能返回任意类型的子对象
4.静态工厂可以根据调用时传入的不同参数而返回不同类的对象
5.静态工厂在编写包含该方法的类时,返回对象的类不需要存在

对于 4,比如EnumSet.java中的allOf方法:

public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {
        EnumSet<E> result = noneOf(elementType);
        result.addAll();
        return result;
    }

可以根据传入的参数,返回对于的对象的类。而对于5,除了构造方法的大多数方法都能通过反射而返回本地不存在,而是在 API接口、jar包等中的lei

静态方法相对于构造方法劣势:

1.没有公有或者保护构造方法的类不能子类化
2.程序员难以找到他们

对于1,用静态方法获取对象的前提是,对象的构造方法的访问修饰符是private,而这种情况下的类无法被继承,除此之外,用final修饰的类也无法被继承


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

通过创建不同的构造方法来设置参数,当可设置的成员变量较多时(4个或4个以上),这样让代码比较难写(容易把参数弄错),同时,之后也比较难读。
通过JavaBean的getter和setter来设置参数,当可设置的成员变量较多时(4个或4个以上),这样将参数的设置放到了多个调用中,但这样在使用过程中可以随时改变参数,从而无法保持对象的一致性,线程安全维护起来也比较麻烦。
Builder模式就可以很好得解决这个问题,例子如下:

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

    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 sodium = 0;
        private int carbohydrate = 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 sodium(int val){ 
            sodium = val; return this; 
        }
        public Builder carbohydrate(int val){ 
            carbohydrate = 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;
    }
}

而其调用为:

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

Builder也经常用于返回协变类型,协变类型是指:子类方法返回的对象类型是对应超类方法返回的对象类型的子类。
举一个例子,Pizza为抽象类,NyPizza和Calzone继承自Pizza
Pizza.java

// Builder pattern for class hierarchies
public abstract class Pizza {
    public enum Topping { 
        HAM, MUSHROOM, ONION, PEPPER,SAUSAGE 
    }
    final Set<Topping> toppings;
    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Topping> toppings =
        EnumSet.noneOf(Topping.class);
        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        } 
        abstract Pizza build();
        // Subclasses must override this method to return "this"
        protected abstract T self();
    } 
    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone(); // See Item 50
    }
}

NyPizza.java

public class NyPizza extends Pizza {
    public enum Size { 
        SMALL, MEDIUM, LARGE 
    }
    private final Size size;

    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;
        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        } 
        @Override 
        public NyPizza build() {
            return new NyPizza(this);
        } 
        @Override 
        protected Builder self() { 
            return this; 
        }
    } 

Calzone.java

public class Calzone extends Pizza {
    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder<Builder> {
        private boolean sauceInside = false; // Default
        public Builder sauceInside() {
            sauceInside = true;
            return this;
        } 
        @Override 
        public Calzone build() {
            return new Calzone(this);
        } 
        @Override 
        protected Builder self() { 
            return this; 
        }
    }
    private Calzone(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }
}

两个子类的实例化:

NyPizza pizza = new NyPizza.Builder(SMALL).addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build();

另外,Builder模式比起前面两种创建对象的方法,它多创建了一个Builder对象,所以我们只在参数比较多的时候(4个或4个以上),以及知道后续参数会增多时,使用Builder模式。


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

为了保证Singleton,应当将所有的实例属性声明为瞬时的(transient),并提供一个readResolve方法(条目89)。否则,每次反序列化一个已被序列化的实例时,将产生一个新的实例


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

有些工具类的方法都用static进行修饰,我们不希望这样的类进行实例化。

// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
    private UtilityClass() {
        throw new AssertionError();
    }
... // Remainder omitted 
}

因为一般情况下构造方法都可以被调用,这种时候最好像上面的例子那样加上注释。


第5条:优先使用依赖注入而不是硬连接资源

将变量直接传入,而不是在直接在对象中根据不同的情况构造


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

反例

String s = new String("bikini"); // DON'T DO THIS!

正确的写法

String s = "bikini";

另一种会创建不必要对象的方式是自动装箱,比如下面会创建2^{31}不必要的Long实例:

// Hideously slow! Can you spot the object creation?
private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum; 
}

第7条:消除过时的对象引用

文中举的例子有:
1.栈中pop出来的对象不会被回收,要主动清理为null;
2.忘记清理的缓存,将缓存的对象设为weakreference或及时清理;
3.监听器忘记取消监听;


第8条:避免使用终结方法和清理方法


第9条:优先使用try-with-resources而不是try-finally

try-with-resources语句让我们更容易编写必须要关闭的资源的代码.
以往的try-finally,在finally中也会出现异常,这样如果try和finally同时出现异常,那么最后的只能看到finally中的。
这时,try-with-resources就不会隐藏错误,若要使用这个语句,一个资源必须实现AutoCloseable接口,而这个接口只有一个返回类型为void的close(void-returning)方法。

// try-with-resources - the the best way to close resources!
static String firstLineOfFile(String path) throws IOException { 
    try (
        BufferedReader br = new BufferedReader(new FileReader(path))
    ) { 
        return br.readLine();
    } 
}

注:本文所有的建议与例子都来自《Effective Java》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345