没有合适的资源?快使用搜索试试~ 我知道了~
effective-java.pdf
需积分: 46 218 下载量 78 浏览量
2020-04-30
17:53:34
上传
评论 6
收藏 3.2MB PDF 举报
温馨提示


试读
265页
effective-java 自己用网上的翻译的html版本,生成的pdf版,仅供参考,仅供参考,仅供参考,仅供参考
资源推荐
资源详情
资源评论










1. 考虑使用静态工厂方法替代构造方法
一个类允许客户端获取其实例的传统方式,是提供一个公共构造方法。 其实,还有另一种技术应该
成为每个程序员工具箱的一部分。 一个类可以提供一个简单的、只返回该类实例的公共静态工厂方法。
下面是一个 Boolean 简单的例子( 基本类型 boolean 的包装类)。 此方法将基本类型 boolean 转换
为 Boolean 对象引用:
注意,静态工厂方法与设计模式中的工厂方法模式不同[Gamma95]。本条目中描述的静态工厂方法
在设计模式中没有直接的等价。
类可以为其客户端提供静态工厂方法,而不是公共构造方法。提供静态工厂方法来代替提供公共构
造方法这种方式,有优点也有缺点。
静态工厂方法的一个优点是,与构造方法不同,它们是有名字的。 如果构造方法的参数本身并不描
述被返回的对象,则具有精心选择名称的静态工厂更易于使用,并且生成的客户端代码更易于阅读。 例
如,返回一个可能为素数的 BigInteger 的构造方法 BigInteger(int,int,Random) 可以更好地表
示为名为 BigInteger.probablePrime 的静态工厂方法。 (这个方法是在 Java 1.4 中添加的。)
一个类只能有一个给定签名的构造方法。 程序员知道通过提供两个仅仅在参数类型的顺序不同的构
造方法,来解决这个限制。 这是一个非常糟糕的主意。 对于这样的 API ,用户将永远不会记得哪个构
造方法是哪个,最终会错误地调用。 阅读使用这些构造方法的代码的人只有在参考类文档的情况下才知
道代码的作用。
因为静态工厂方法有名字,所以它们不会受到上述限制。在类中似乎需要具有相同签名的多个构造
方法的情况下,用静态工厂方法替换构造方法,并仔细选择名称来突出它们的差异。
静态工厂方法的第二个优点是,与构造方法不同,它们不需要每次调用时都创建一个新对象。 这允
许不可变类 (详见第 17 条)使用预先构建的实例,或者在构造时缓存实例,并反复分配它们以避免创
建不必要的重复对象。 Boolean.valueof(boolean) 方法说明了这种方法:它从不创建对象。这种技术
类似于 Flyweight 模式[Gamma95]。如果经常请求等价对象,那么它可以极大地提高性能,特别是在
创建它们的代价非常昂贵的情况下。
静态工厂方法在重复调用时,返回相同实例这个特点,可以让类在任何时候都能对实例保持严格的
控制。这样做的类被称为实例控制类( instance-controlled)。有很多理由足以让我们去我们编写实例
控制类。实例控制可以保证一个类是单例 的(详见第 3 条) 或不可实例化的 (详见第 4 条)。同时,它
允许一个不可变的值类 (详见第 17 条) 保证不存在两个相等但不相同的实例,也就是说当且仅当 a
== b 时才有 a.equals(b) 。这是 Flyweight 模式的基础[Gamma95]。 Enum 类型 (详见第 34 条)
可以做到这点。
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}

静态工厂方法的第三个优点是,与构造方法不同,它们可以返回其返回类型的任何子类型的对象。
这为你在选择返回对象的类型时,提供了很大的灵活性。
这种灵活性的一个应用是, API 可以返回对象而不需要公开它的类。 以这种方式隐藏实现类,会使
API 非常紧凑。 这种技术适用于基于接口的框架(详见第 20 条),其中接口为静态工厂方法提供自然
返回类型。
在 Java 8 之前,接口不能有静态方法。根据约定,一个名为 Types 的接口的静态工厂方法被放入
一个不可实例化的伙伴类(companion class)(详见第 4 条) Types 类中。例如,Java 集合框架有
45 个接口的实用工具实现,提供不可修改的集合、同步集合等等。几乎所有这些实现都是通过静态工
厂方法在一个不可实例化的类 ( java.util.collections ) 中返回的。返回对象的类都是隐藏的。
Collections 框架 API 的规模要比它之前返回的 45 个单独的公共类要小得多,每个类在集合框架
中都有一个便利的实现。不仅精简了API,还减少了程序员为了使用 API而掌握的概念的数量和难度。程
序员知道返回的对象恰好有其接口指定的 API,因此不需要为实现类读阅读额外的类文档。此外,使用
这种静态工厂方法,需要客户端通过接口替代实现类,来引用返回的对象,这通常是良好的实践(详见
第 64 条)。
从 Java 8 开始,接口不能包含静态方法的限制被取消了,所以通常没有理由为接口提供一个不可实
例化的伴随类。 很多公开的静态成员应该放在这个接口本身。 但是,请注意,将这些静态方法的大部
分实现代码放在单独的包私有类中仍然是必要的。 这是因为 Java 8 要求所有接口的静态成员都是公共
的。 Java 9 允许私有静态方法,但静态字段和静态成员类仍然需要公开。
静态工厂的第四个优点是返回对象的类可以根据输入参数的不同而不同。 声明的返回类型的任何子
类都是允许的。 返回对象的类也可以随每次发布而不同。
EnumSet 类(详见第 36 条)没有公共构造方法,只有静态工厂。 在 OpenJDK 实现中,它们根据
底层枚举类型的大小返回两个子类中的一个的实例:大多数枚举类型具有 64 个或更少的元素,静态工
厂将返回一个 RegularEnumSet 实例, 底层是 long 类型;如果枚举类型具有六十五个或更多元素,
则工厂将返回一个 JumboEnumSet 实例,底层是 long 类型的数组。
这两个实现类的存在对于客户端而言是不可见的。 如果 RegularEnumSet 对于小的枚举类型不再
具有性能优势,则可以在未来版本中将其淘汰,且不会产生任何不良影响。 同样,如果可以证明添加
EnumSet 的更多的实现可以提高性能,那么在未来的版本可能就会这样做。 客户既不知道也不关心他
们从工厂返回的对象的类别; 他们只需要知道它是 EnumSet 的子类。
静态工厂的第五个优点是,在编写包含该方法的类时,返回的对象的类不需要存在。 这种灵活的静
态工厂方法构成了服务提供者框架的基础,比如 Java 数据库连接 API(JDBC)。服务提供者框架是提
供者实现服务的系统,并且系统使得实现对客户端可用,从而将客户端从实现中分离出来。
服务提供者框架中有三个基本组:服务接口,它表示实现;提供者注册 API,提供者用来注册实
现;以及服务访问 API,客户端使用该 API 获取服务的实例。服务访问 API 允许客户指定选择实现的标
准。在缺少这样的标准的情况下,API 返回一个默认实现的实例,或者允许客户通过所有可用的实现进
行遍历。服务访问 API 是灵活的静态工厂,它构成了服务提供者框架的基础。
服务提供者框架的一个可选的第四个组件是一个服务提供者接口,它描述了一个生成服务接口实例
的工厂对象。在没有服务提供者接口的情况下,必须对实现进行反射实例化(详见第 65 条)。在 JDBC
的情况下, Connection 扮演服务接口的一部分, DriverManager.registerDriver 提供程序注册
API、 DriverManager.getConnection 是服务访问 API, Driver 是服务提供者接口。

服务提供者框架模式有许多变种。 例如,服务访问 API 可以向客户端返回比提供者提供的更丰富的
服务接口。 这是桥接模式[Gamma95]。 依赖注入框架(详见第 5 条)可以被看作是强大的服务提供
者。 从 Java 6 开始,平台包含一个通用的服务提供者框架 java.util.ServiceLoader ,所以你不需
要,一般也不应该自己编写(详见第 59 条)。 JDBC 不使用 ServiceLoader ,因为前者早于后者。
只提供静态工厂方法的主要限制是,没有公共或受保护构造方法的类不能被子类化。 例如,要想将
Collections 框架中任何遍历的实现类进行子类化,是不可能的。但是这样也会因祸得福,因为它鼓
励程序员使用组合(composition)而不是继承(详见第 18 条),并且是不可变类型锁需要的(详见第
17 条)。
静态工厂方法的第二个缺点是,程序员很难找到它们。 它们不像构造方法那样在 API 文档中明确的
标注出来。因此,对于提供了静态方法而不是构造器的类来说,想要查明如何实例化一个类是十分困难
的。Javadoc 工具可能有一天会注意到静态工厂方法。与此同时,通过关注类或者接口的文档中静态方
法,并且遵守标准的命名习惯,也可以弥补这一劣势。下面是一些静态工厂方法的常用名称。以下清单
这是列出了其中的一小部分:
from —— 类型转换方法,它接受单个参数并返回此类型的相应实例,例如:Date d =
Date.from(instant);
of —— 聚合方法,接受多个参数并返回该类型的实例,并把他们合并在一起,例如:Set<Rank>
faceCards = EnumSet.of(JACK, QUEEN, KING);
valueOf —— from 和 to 更为详细的替代 方式,例如:BigInteger prime =
BigInteger.valueOf(Integer.MAX_VALUE);
instance 或 getinstance —— 返回一个由其参数 (如果有的话) 描述的实例,但不能说它具有相同的
值,例如:StackWalker luke = StackWalker.getInstance(options);
create 或 newInstance —— 与 instance 或 getInstance 类似,除此之外该方法保证每次调用返回
一个新的实例,例如:Object newArray = Array.newInstance(classObject, arrayLen);
getType —— 与 getInstance 类似,但是在工厂方法处于不同的类中的时候使用。getType 中的
Type 是工厂方法返回的对象类型,例如:FileStore fs = Files.getFileStore(path);
newType —— 与 newInstance 类似,但是在工厂方法处于不同的类中的时候使用。newType中的
Type 是工厂方法返回的对象类型,例如:BufferedReader br = Files.newBufferedReader(path);
type —— getType 和 newType 简洁的替代方式,例如:List<Complaint> litany =
Collections.list(legacyLitany);
总之,静态工厂方法和公共构造方法都有它们的用途,并且了解它们的相对优点是值得的。通常,
静态工厂更可取,因此避免在没有考虑静态工厂的情况下,直接选择使用或提供公共构造方法。
2. 当构造方法参数过多时使用 builder 模式
静态工厂和构造方法都有一个限制:它们在可选参数很多的情景下,无法很好得扩展。请考虑一个
代表包装食品上的营养成分标签的例子。这些标签有几个必需的属性——每次建议的摄入量,每罐的份
量和每份卡路里 ,以及超过 20 个可选的属性——总脂肪、饱和脂肪、反式脂肪、胆固醇、钠等等。大
多数产品只包含这些可选字段中的少数,且具有非零值(大部分字段为空)。

应该为这样的类编写什么样的构造方法或静态工厂?传统上,程序员使用了可伸缩(telescoping
constructor)构造方法模式。在这种模式中,首先提供一个只有必需参数的构造方法,接着提供增加了
一个可选参数的构造函数,然后提供增加了两个可选参数的构造函数,等等;最终,在构造函数中包含
所有必需和可选参数。以下就是它在实践中的样子。为了简便,只显示了四个可选属性:
当想要创建一个实例时,可以使用包含所有你要设置的参数的构造方法:
// Telescoping constructor pattern - does not scale well!
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // (per serving) optional
private final int fat; // (g/serving) optional
private final int sodium; // (mg/serving) optional
private final int carbohydrate; // (g/serving) 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;
}
}
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

通常情况下,这个构造方法的调用需要许多你不想设置的参数,但是你不得不为它们传递一个值。
在这种情况下,我们为 fat 属性传递了 0 。「只有」六个参数可能看起来并不那么糟糕,但随着参
数数量的增加,它很快就会失控。
简而言之,可伸缩构造方法模式是有效的,但是当有很多参数时,很难编写客户端代码,而且很难
读懂它。 读者不知道这些值是什么意思,并且必须仔细地去数参数才能找到答案。一长串相同类型的参
数可能会导致一些 bug。如果客户端不小心写反了两个这样的参数,编译器并不会报错,但是程序在运
行时会出现与预期不一致的行为 (详见第 51 条)。
当在构造方法中遇到许多可选参数时,另一种选择是 JavaBeans 模式,在这种模式中,调用一个无
参的构造方法来创建对象,然后调用 setter 方法来设置每个必需的参数和可选参数:
这种模式没有伸缩构造方法模式的缺点。有点冗长,但创建实例很容易,并且易于阅读所生成的代
码:
不幸的是,JavaBeans 模式本身有严重的缺陷。由于构造方法被分割成了多次调用,所以在构造过
程中 JavaBean 可能处于不一致的状态。 该类仅通过检查构造函数参数的有效性,而没有强制的一致性
措施。在不一致的状态下尝试使用对象可能会导致一些错误,这些错误与平常代码的 BUG 很是不同,
因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类不可变的可能性(详见第 17 条),并
且需要程序员增加工作以确保线程安全。
// JavaBeans Pattern - allows inconsistency, mandates mutability
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; // Required; no default value
private int servings = -1; // Required; no default value
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; }
}
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
剩余264页未读,继续阅读
资源评论


时光殇
- 粉丝: 0
- 资源: 1
上传资源 快速赚钱
我的内容管理 展开
我的资源 快来上传第一个资源
我的收益
登录查看自己的收益我的积分 登录查看自己的积分
我的C币 登录后查看C币余额
我的收藏
我的下载
下载帮助


安全验证
文档复制为VIP权益,开通VIP直接复制
