【EffectiveJava】chapter01对象创建与销毁

chapter01 对象创建与销毁

    This book consists of ninety items, each of which conveys one rule. The rules capture practices generally held to be beneficial by the best and most experienced programmers. The items are loosely grouped into eleven chapters, each covering one broad aspect of software design. The book is not intended to be read from cover to cover: each item stands on its own, more or less. The items are heavily cross-referenced so you can easily plot your own course through the book.

1 用静态工厂代替构造器

获取类的实例对象,最传统的的方式是公有构造器。
例如存在Boolean类:

public class Boolean {
    // 构造器
    public Boolean(boolean value) {
        this.value = value;
    }

    // 静态工厂方法
    public static Boolean valueOf(boolean b) {
        return (b ? Boolean.TRUE : Boolean.FALSE);
    }
}

静态工厂方法优势

  1. 静态工厂方法可以依靠恰当的方法名来明确创建的对象
  2. 静态工厂方法可以为重复调用返回相同对象而不必每次创造对象
  3. 静态工厂方法可以返回原类的子类对象
  4. 静态工厂方法返回的对象类型可变,取决于参数值
    // 可以利用静态工厂方法返回子类对象
    public class Animal {
        public static Animal createAnimal(String type) {
            return type.equals("dog") ? new Dog() : new Cat();  
        }
    }
    public class Dog extends Animal {}
    public class Cat extends Animal {}
  1. 静态工厂方法返回的对象类型可以在编写该静态方法时不存在,在后续以服务提供者接口或反射来决定返回对象类型。
    其是服务提供者框架的基础,例如JDBC框架 。

服务提供者框架

有三个重要组件: - 服务接口(`ServiceInterface`):提供者实现,如JDBC的Connection。 - 提供者注册API(`ProviderRegistrationAPI`):提供者注册实现,如JDBC的DriverManager.registerDriver()。 - 服务访问API(`ServiceAccessAPI`):服务访问实现,如JDBC的DriverManager.getConnection()。

另外还有第四个可选组件 服务提供者接口(ServiceProviderInterface):表示产生服务接口的实例的工厂对象,如JDBC的Driver。
如果没有服务提供者接口,实现会通过反射进行实例化。

静态工厂方法缺陷

  1. 不含public/private构造器的类不能被继承(未显式声明构造器的类默认有隐式无参构造器),这样的类无法被静态工厂方法实例化。
  2. 静态工厂方法不易于发现,JavaDoc工具能轻易识别到构造方法而不易识别静态工厂方法,未来一定会有改进

建议在类或接口注释注明静态工厂方法
遵守静态工厂方法命名规范:ofvalueOffromgetTypegetInstancenewInstance


2 遇到多参数时考虑使用Builder模式

静态工厂和构造器都不能很好的满足大量参数。
例如对于类“营养成分”,其中会有“净含量”等必要参数,还有超过20种可选参数“份数、卡路里、脂肪”等。
常见一种重叠构造器模式应对:

重叠构造器模式

这种模式将创造所有可能参数的构造器:第一个构造器只包含必填参数,第二个构造器有一个可选参数,第三个构造器有两个可选参数...然后选择包含所需参数的最短构造器用于实例化,这种模式将会导致的问题: 1. 所选的构造器参数列表中可能有不必要的参数但不得不为其赋值。 2. 参数列表过长时将难以控制,包括构造器数量失控。 3. 代码难写且可读性差。
public class NutritionFacts {
    private final int servingSize;  // (ml)
    private final int servings;     // per container
    private final int calories;     // per serving
    private final int fat;          // (g/serving)
    // 重叠构造器
    public NutritionFacts (int servingSize) {
        this(servingSize, 0);
    }
    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 = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
    }
}

另一种常见方式是JavaBeans模式

JavaBeans模式

通过无参构造器创建对象,往其中set需要的参数。
public class NutritionFacts {
    private final int servingSize;  // (ml)
    private final int servings;     // per container
    private final int calories;     // per serving
    private final int fat;          // (g/serving)
    // 无参构造器
    public NutritionFacts() {}
    // setter方法...
}

这一模式使得创建实例非常简单且代码易读,但存在严重问题:

  1. 构造对象属性被分到了几个set方法中,可能使构造过程中JavaBean不一致,使用不一致状态的对象导致的异常将难以定位原因。
  2. JavaBean模式的类几乎不能设计为final,那么要确保线程安全就需要额外的努力例如锁,那将带来更多麻烦。

Builder模式

Builder(建造者)模式不直接生成对象,客户端调用构造器或 静态工厂得到一个`builer`对象,然后通过类似setter的方法为builder对象设置需要的属性,最后调用build方法生成通常是不可变的对象。
// Builder建造者模式示例如下,其核心思路是通过类内部Builder对象来避免直接创建类对象
public class NutritionFacts {
    private final int servingSize;  // (ml) 
    private final int servings;     // per container
    private final int fat;          // (g/serving)
    // 1.Builder通常是其静态成员类
    public static class Builder {
        private final int servingSize;  // (ml) 必要参数
        // 可选参数,赋以默认值
        private final int servings = 0;     // per container
        private final int fat      = 0;     // (g/serving)
        // 2.静态内部类公有构造器
        public Builder(int servingSize) {
            this.servingSize = servingSize;
        }
        // 3.类似setter的设值方法
        public Builder servings(int val) {
            servings = val; 
            return this;
        }
        public Builder fat(int val) {
            fat = val;
            return this;
        }
        // 4.生成对象build方法
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
    //5.私有构造器
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        fat = builder.fat;
    }
}  

// 创建对象时可以链式调用:NutritionFacts nf = new NutritionFacts.Builder(240).fat(10).build(); // 可仅选需要的属性

Builder构建器模式也适用于类的创建,抽象类内嵌抽象builder,实现类可选继承builder:

public abstract class Pizza {
    abstract static class Builder<T extends Builder<T>> {
        abstract Pizza build();
    }
    Pizza(Builder<?> builder) {}
}
// 实现类定义
public class NyPizza extends Pizza {
    public static class Builder extends Pizza.Builder<Builder> {
        @Override
        public NyPizza build() {} // build()方法应返回正确的子类
    }
    private NyPizza(Builder builder) {
        super(builder);
    }
}  
// 获取NyPizza对象:NyPizza nyPizza = new NyPizza.Builder().build();

Builder模式的优点

  1. 可读性好,扩展性高,比JavaBean安全;
  2. 支持多个可变参数,参数可通过修改builder对象调整;
  3. 单个builder可创建多个对象,可自动填充某些参数如序列号;

Builder模式存在的缺陷

  1. 为了创建对象需要先创建其builder,在极其注意性能的场景会有影响;
  2. 会比重叠构造器模式设计更为冗长;

以下情况建议使用Builder模式:

  1. 类参数列表在未来可能涉及变更;
  2. 类参数数量过多(大于6个);

3 使用私有构造器枚举类强化Singleton属性

Singleton指仅能被实例化一次的类,常用于代表一个无状态对象,如函数/唯一的系统组件。

有两种常用的实现Singleton的方式,都私有构造方法,然后导出公有静态成员,以使客户端能访问该类唯一实例:

public class Elvis {
    // 第一种方式,公有静态成员final域,一旦Elvis类实例化只能有一个Elvis.INSTANCE
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
}

公有域方法的主要优势在于可以清晰知道此类是singleton,总是包含相同对象引用,且其非常简单。

public class Elvis {
    // 第二种方式,公有静态工厂方法,通过getInstance()方法永远只会获取同一实例
    public static Elvis getInstance() { return INSTANCE; }
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() {}
}

公有静态工厂方法的优势有:

  1. 灵活,不改变API前提下我们可以改变该类是否为singleton的想法,比如修改getInstance()方法,使其返回new Elvis()使每次调用都返回新的唯一实例;
  2. 如果有需要可以编写一个泛型Singleton工厂;
  3. 可以直接通过方法引用作为提供者,例如:Supplier<Elvis> supplier = Elvis::getInstance;

除非满足以上任一需要,否则更优先考虑公有域方法

为了将上述singleton类设计为可序列化的,仅仅添加implements Serializable不够,否则每次反序列化一个序列化的实例都会创建一个新的实例。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值