Effective Java 第二版
引言
- java语言支持四种类型:
- 接口【interface】
- 类【class】
- 数组【array】
- 基本类型【primitive】
前三种类型成为引用类型,类实例和数组是对象,而基本类型的值不是对象。
- 类的成员组成:
- 域【field】
- 方法【method】
- 成员类【member class】
- 成员接口【member interface】
方法签名【signature】由它的名称和所有参数类型组成;签名不包括它的返回类型
第二章 创建和销毁对象
第1条 考虑使用静态工厂方法替代构造器
1 2 3
| public static Boolean valueOf(boolean b){ return b ? Boolean.TRUE : Boolean.FALSE; }
|
类可以通过静态工厂方法提供它的客户端,而不是通过构造器。
- 提供静态工厂方法而不是公有的构造器,这样做有几大优势
- 它们有名称
- 不必在每次调用它们的时候都创建一个新的对象
- 它们可以返回原返回类型的任何子类型的对象
- 在创建参数化类型实例的时候,它们使代码变得更加简洁
- 静态工厂方法的主要缺点
- 类如果不含有公开的或者受保护的构造器,就不能被子类化
- 它们与其他的静态方法实际上没有任何区别
逐一解释:
- 静态工厂方法与构造器不同的第一大优势在于,他们有名称
- 如果构造器本身没有明确的描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,代码更容易阅读
- 一个类只能够有一个带有制定签名的构造器
- 由于静态工厂有名称,所以描述更为清楚
- 当一个类需要多个带有相同签名的构造器时,就用静态工厂方法替代构造器,并慎重的选择名称以便突出它们之间的区别
- 静态工厂方法与构造器不同的第二大优势在于,不必在每次调用他们的时候都创建一个新的对象
- 这使得不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。
- 如果程序经常请求创建对象,并且创建对象的代价很高,则这项技术可以极大地提升性能
- 静态方法能够为重复的调用返回相同的对象,这样有助于类总能严格的控制在某个时刻哪些实例应该存在,这种类被称作实例受控的类
- 设立受控的类可以确保它是 Singleton 或者是不可实例化的
- 它还使得不可变的类可以确保不会存在两个相等的实例
- 即当且仅当
a==b
的时候才有 a.equals(b)
为 true
- 如果类保证了这一点就可以使用
==
操作符来替代 equals (Object)
方法,这样就可以提升性能
- 枚举类型保证了这一点
- 静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象
- 静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不必存在,这种领过的静态工厂方法构成了服务提供者框架的基础
- 服务提供者框架是指一个系统,多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个市县,并把它们从多个实现中解耦出来
- 服务提供者框架中有三个重要的组件
- 服务接口:这是提供者实现的
- 提供者注册API:这是系统用来注册实现
- 服务访问API:客户端用来获取服务实例,是“灵活的静态工厂”
- 服务提供者API:提供者负责创建其服务实现的实例
- 静态工厂方法与构造器不同的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁
1 2 3 4
| Map<String,List<String>> map = new HashMap<String,List<String>> () Map<String, List<String>> map = new HashMap<>();
|
1 2 3
| public static <K, V> Map<K, V> newInstance() { return new HashMap<K, V>(); }
|
- [x] 是可以使用下面这句简洁的代码代替上面这段繁琐的声明
1
| Map<String,List<String>> map = HashMap.newInstance();
|
- 静态工厂的第一个缺点在于,类如果不含有公开的或者受保护的构造器,就不能被子类化
- 要想将Collections Framework 中的任何方法的实现类子类化,这是不可能的。
- 它鼓励程序员使用复合【composition】,而不是继承
- 静态工厂的第二个缺点在于,他们与其他的静态方法没有任何区别
名称 |
说明 |
valueOf |
不严格的讲,该方法返回的实例与它的参数具有相同的值。这中方法实际上就是类型转换方法 |
of |
valueOf的一种更为简洁的替代,在EnumSet中使用并流行起来 |
getInstance |
返回的实例是通过方法的参数来描述的,但是不能够说与参数具有同样的值 |
newInstance |
像getInstance一样单newInstance能够确保能够返回的每个实例都与其所有其他实例不同 |
getType |
像getInstatnce一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法返回的对象类型 |
newType |
想newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型 |
静态工厂方法和公有构造器都各有用处,静态工厂通常更加合适,因此切忌第一反应就是提供共有的构造器,而不先考虑静态工厂
第2条 遇到多个构造器参数时要考虑用构建器
静态工厂和构造器都有个共同的局限性:它们都不能很好的扩展到大量的可选参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| * @author by benny on 2016/9/17. * @version 1.0 * @description 通过重载构造器模式 */ public class ConstrutorBuilder { private final int servingSize; private final int calories; private final int fat; private final int sodium; public ConstrutorBuilder(int servingSize, int calories) { this(servingSize, calories, 0, 0); } public ConstrutorBuilder(int servingSize, int calories, int fat) { this(servingSize, calories, fat, 0); } public ConstrutorBuilder(int servingSize, int calories, int fat, int sodium) { this.servingSize = servingSize; this.calories = calories; this.fat = fat; this.sodium = sodium; } public static void main(String[] args) { ConstrutorBuilder testBuilder = new ConstrutorBuilder(240, 8); ConstrutorBuilder testBuilder1 = new ConstrutorBuilder(240, 8, 0, 35); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| * @author by benny on 2016/9/17. * @version 1.0 * @description 使用JavaBean模式 */ public class JavaBeanBuilder { private int servingSize = -1; private int calories = 0; private int fat = 0; private int sodium = 0; public JavaBeanBuilder() { } public void setServingSize(int servingSize) { this.servingSize = servingSize; } 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 static void main(String[] args) { JavaBeanBuilder builder = new JavaBeanBuilder(); builder.setServingSize(240); builder.setCalories(35); builder.setFat(100); builder.setSodium(0); } }
|
使用构建器模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| * @author by benny on 2016/9/17. * @version 1.0 * @description 构建器模式 */ public class BuildBuilder { private final int servingSize; private final int calories; private final int fat; private final int sodium; public static class Builder { private final int servingSize; private final int calories; private int fat; private int sodium; public Builder(int servingSize, int calories) { this.servingSize = servingSize; this.calories = calories; } public Builder fat(int fat) { this.fat = fat; return this; } public Builder sodium(int sodium) { this.sodium = sodium; return this; } public BuildBuilder build() { return new BuildBuilder(this); } } public BuildBuilder(Builder builder) { this.servingSize = builder.servingSize; this.calories = builder.calories; this.fat = builder.fat; this.sodium = builder.sodium; } public static void main(String[] args) { BuildBuilder build = new BuildBuilder .Builder(240, 19) .sodium(30) .fat(100) .build(); } }
|
- 定义抽象工厂
1 2 3 4 5 6 7 8
| * @author by benny on 2016/9/17. * @version 1.0 * @description 抽象工厂 */ public interface BuilderFactory<T> { T build(); }
|
- 使用构建器模式构建实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| * @author by benny on 2016/9/17. * @version 1.0 * @description 构建器模式 */ public class BuildBuilder { private final int servingSize; private final int calories; private final int fat; private final int sodium; public static class Builder implements BuilderFactory<BuildBuilder> { private final int servingSize; private final int calories; private int fat; private int sodium; public Builder(int servingSize, int calories) { this.servingSize = servingSize; this.calories = calories; } public Builder fat(int fat) { this.fat = fat; return this; } public Builder sodium(int sodium) { this.sodium = sodium; return this; } @Override public BuildBuilder build() { return new BuildBuilder(this); } } public BuildBuilder(Builder builder) { this.servingSize = builder.servingSize; this.calories = builder.calories; this.fat = builder.fat; this.sodium = builder.sodium; } public static void main(String[] args) { BuildBuilder build = new BuildBuilder.Builder(240, 19) .sodium(30) .fat(100) .build(); } }
|
总结:
- 重载构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读。
- 构造器中参数如果很多的话,一长串类型相同的参数会导致一些微妙的错误,如果客户端不小心颠倒了其中两个参数的顺序,编译器也不会报错。
- JavaBean 模式弥补了重载构造器模式的不足,这种模式创建实例很容易,代码也很易读
- 在这种模式下,调用一个无参的构造器来创建对象,然后调用
setter()
方法来这是每个必要的参数,以及每个相关的可选参数。
- 遗憾的是,JavaBean模式自身有着很严重的缺点,因构造过程被分到几个调用中,在调用过程中,JavaBean可能处于不一致的状态,类无法仅通过构造器参数的有效性来保证一致性。
- Builder模式,技能保证凑在构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性。
- Builder模式,不直接生成想要的对象,而是利用所有必要参数的构造器(或者静态工厂),得到一个builder对象,然后让客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后调用无参的
build()
方法来生成不可变的对象,这个builder是它构建的类的静态成员类
- Builder模式也有自身的不足,为了常见对象,必须先创建它的构建器。
- Builder模式还比重载构造器模式更加冗长,因为它只有在很多参数的时候才使用(比如四个或4个以上的参数),但是记住,将来你可能需要添加参数。
- 如果一开始就使用构造器或者静态工厂,等到类需要多个参数时才添加构建器,就会无法控制,那些过时的构造器或者静态工厂十分不协调,因此,通常最好一开始就使用构建器模式
简而言之:
如果类的构造器或者静态工厂中具有多个参数,设计这种类的时候,Builder模式就是不错的选择,特别是当大多数参数都是可选的时候,与使用传统的重载构造器模式相比,使用Builder模式的客户端代码将更易于阅读和编写,构建器也比JavaBeans更加安全。
第3条 用私有构造器或者枚举类型强化 SIngleton 属性
- Singleton 指仅仅被实例化一次的类
- Singleton 通常被用来代表大写本质上唯一的系统组件,比如文件系统,因为无法给 Singleton 替换模拟实现,除非它实现一个充当起类型的接口
实现 Singleton 有两种方法,这两种方法都要把构造器保持为私有的,并导出公有的静态变量,一边允许客户端能够访问该类的唯一实例
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton{ public static final Singleton INSTANCE = new Singleton(); * 私有构造器 */ private Singleton(){ } public void doSomething(){ ... } }
|
私有构造器仅被调用一次,用来实例化共有的静态 final 域Singleton.INSTANCE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Singleton{ private static final Singleton INSTANCE = new Singleton(); * 私有构造器 */ private Singleton(){ } * 公有静态工厂方法 */ public Singleton getInstance(){ return INSTANCE; } public void doSomething(){ ... } }
|
- 对于
Singleton.getInstance()
的所有调用,都会返回一个对象引用
- 静态工厂方法的优势在于,提供了灵活性
- 在不改变其API的前提下,我们可以改变该类是否为Singleton的想法
为了利用这其中一种方法实现的 Singleton 类是可序列化的(Serializable)仅在声明中加上implements Serializable
是不够的。
为了维护并保证 Singleton,必须声明所有实例域都是瞬时的(transient),并提供一个readResole()
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Singleton{ private static final Singleton INSTANCE = new Singleton(); * 私有构造器 */ private Singleton(){ } * 公有静态工厂方法 */ public Singleton getInstance(){ return INSTANCE; } public void doSomething(){ } * 反序列化确保只有一个实例 * 必须提供该方法,以便重新指定反序列化得到的对象. */ private Object readResolve() { return INSTANCE; } }
|
- [x] 从Java 1.5起,实现Singleton还有第三种方法,只需要编写一个包含单个元素的枚举类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public enum Singleton { INSTANCE; public void doSomething() { System.out.println("hello wolrd"); } public static void main(String[] args) { Singleton singleton = Singleton.INSTANCE; singleton.doSomething(); } }
|
这种方法在功能上与公有域方法相近,但是它更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击(单例模式的反射攻击详情请参考:关于单例的那点事)的时候,虽然这种方法还没有被广泛采用,但是 单元素的枚举类型已经成为Singleton的最佳方法
第4条: 通过私有构造器强化不可实例化的能力
- 有时候你可能需要编写只包含静态方法和静态域的类
- 这样的工具类(utility class)不希望被实例化,实例化对它没有任何意义,然而在缺少显示构造器的情况下,编译器会自动提供一个公有的,无参的缺省构造器(default constructor)
企图通过将类做成抽象类来强制该类不可被实例化,这是行不通的。
- 虽然抽象类不能够被实例化,但是该抽象类可以被子类化,并且该子类可以被实例化
- 并且声明为抽象类,会误导用户以为这种类是专门为了继承而设计的
- 设置一个类不能被继承的两种方式
- 使用 final 修饰
- 将构造器用 private 修饰(构造器私有)
- [x] 由于只有当类中不包含显示的构造器时,编译器才会生成缺省的构造器,因此只要将这这个类包含有私有构造器,它就不能被实例化了
1 2 3 4
| public class ParentClass { private ParentClass() { } }
|
1 2 3 4 5 6 7
| public class SonClass extends ParentClass { private SonClass() { super(); ★★★★★ } }
|
- 这种类也有一副作用,它使得一个类不能被子类化。所有的构造器都必须显式的活隐式地调用超类(superclass)构造器,在这种情况下,子类就没有可访问的超类构造器可调用了
这种情况下是可以的:能够编译通过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class TestParentConstructer { public class Parent { private Parent() { System.out.println("我是父类"); } } public class Son extends Parent { public Son() { super(); System.out.println("我是子类"); } } public static void main(String[] args) { Son s = new TestParentConstructer().new Son(); AbstractClass a = new AbstractClass() { }; } }
|