Chapter 1 Introduction
Some rules or principles
- the user of a component shoud know about its behavior
- code should be reused ranther than copied
- the dependencies between components shold be kept to a minimum
- errors should be detected as soon as possible after made [best time is at compile stage]
Chapter 2 Creating and Destroying Objects
Item 1: Consider static factory methods instead of constructors
静态工厂方法取代构造器。
Definiation
A class can provide a public static factory method, which is simply a static method that returns an instance of the class.
一个类可以提供一个返回其实例的静态工厂方法。
Advantages
1.Static factory methods have name: a static factory with a well-chosen name is eaiser to use and the resulting client code eaiser to read
2.Be not required to create a new object each time when they're invoked beacause of static. 静态工厂方法多次调用返回同一个对象的能力允许对实例对象创建的时间保持严格的控制,这样的类也称为实例控制(instance congtrol)。
3.Return an object of any subtype of their return type. 静态工厂方法可以返回 【返回类型】的任意子类型的对象。
4.The class of the returned object can vary from call to call as a function of the input parameters. 返回对象的类可以随着输入参数的变化而变化。
对于EnumSet
类,其没有共有构造方法,只有静态工厂。如果元素的个数小于或等于64个,静态工厂返回一个RegularEnumSet
实例,其为long
类型;若元素大于64个,则返回一个JumboEnumSet
实例,返回一个long
类型的数组。
5.The class of the returned object need not exist when the class containing the method is written. 当包含此静态工厂的方法已经被编写的时候,静态工厂方法返回对象的类可以不存在。
Limitations
1.Classes without public or protected constructors cannot be subclassed. 没有共有或者受保护的构造其的类不能够被继承。
建议多用组合,少用继承。
2.Static factory methods are hard for programmers to find. 很难找到静态工厂方法。
由于缺少像接口一样的API文档,很难找到如何实例化一个提供静态工厂方法而非构造器的类。可以通过遵守通用的命名规约来减少这个问题。
Some common names for static factory methods:
from:
类型转换方法,接受一个其他类型的实例,返回当前类型的实例。
Date d = Date.from(instance);
of
:将多个参数聚合在一起的方法valueOf
:可以替代of
和from
的更加详细的方法instance | getInstance
:返回一个由其参数描述的实例create or newInstance
:和上一个相似,但是保证返回的是两个不同的实例getType
:返回某个实例的类型newType
:和newInstance
相似,但是如果用在不同的类中,则返回的是工厂方法返回对象的类型type
:getType
和newType
更为简洁的替代
Summarization
静态工厂方法和公有构造方法都有他们自己的用处,要充分考虑他们的优劣情况。
Often static fatories are preferable, so avoid the reflex to provide public constructors without first considering static factories.
通常情况下静态工厂是更优的选择,避免在不先考虑静态工厂方法的情况下本能地提供共有构造器。
Item 2: Consider a builder when faced with many constructor parameters
遇到过多的构造器参数的时候,考虑使用builder
caseI-Nutrition Facts
食品包装上的营养成分表中,有许多营养成分。这些营养成分中大部分都是非零值,只有少部分是可选择的。如何为这样的类来编写构造方法?
Solution1: Telescoping constructor
使用可伸缩(telescoping constructor)构造方法模式,有一个完整的构造方法,其他的直接调用此构造方法。
kotlin
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;
}
}
思考: 这么写有什么缺点?
- 当某个类的参数数量变得很大的时候,构造方法的数量会膨胀
- 易读性比较差
- 扩展性较低
Solution2: JavaBean Pattern
- 只有一个无参构造器
- 在外部使用
setter()
方法为属性赋值
思考:JavaBean 模式有什么缺点?
- 将一条初始化语句拆分为数个赋值语句,处于线程不安全的环境中,会导致数据不一致的情况
- 在对象手动赋值完成之前,可以锁住对象,防止在对象初始化之前被调用
Solution3: Builder Pattern
- Instead of making the desired object directly, the client calls a constructor (or static factory) with all of the required parameters and gets a builder object.
- Then the client calls setter-like methods on the builder object to set each optional parameter of interest.
- Finally, the client calls a parameterless build method to generate the object, which is typically immutable.
Step1:客户端调用带有所需参数的构造器或者静态工厂方法 获取一个builder
对象
Step2:客户端调用builder
对象中类似于setter
的方法来设置每个可选参数
Step3:客户端调用一个无参build
方法来生成一个不可变的对象。
kotlin
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;
// 静态内部类 Builder
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;
}
}
静态内部类Builder
的setter
方法返回其本身,因此可以进行链式调用。即:
scss
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100).sodium(35).carbohydrate(27).build();
caseII: Pizza
Builder 模式非常适合继承结构。抽象的类有抽象的Builder,而具体的类有具体的Builder。
使用Builder模式的Pizza类为:
scala
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();
// subclass must override this method to return "this"
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
注意 Notice
Pizza.Builder
是一个带有递归类型参数 (Recursive type paramter)的泛型类型。与抽象的self
方法在一起可以实现子类中的链式调用而不需要强制类型转换
NyPizza
scala
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);
}
// Notice: build方法应该返回具体的子类型,而非父类
@Override
public NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
@Override
public String toString() {
return "NyPizza{" +
"size=" + size +
", toppings=" + toppings +
'}';
}
}
注意 Notice
在NyPizza
中,build()
方法返回NyPizza
类型的对象。
协变返回类型:子类中方法的返回值类型(NyPizza
) 为 父类方法返回类型(Pizza
)的子类
csharp
public class PizzaClient {
@Test
public void testPizzaStore() {
// 使用 Builder 创建一个 NyPizza 对象
NyPizza nyPizza = new NyPizza.Builder(NyPizza.Size.SMALL)
.addTopping(Pizza.Topping.PEPPER).addTopping(Pizza.Topping.HAM)
.build();
System.out.println(nyPizza);
}
}
advantages
- can have multiple varage parameters
- quite flexible,
-
- 单个
Builder
可以重复创建多个对象 Builder
的参数可以在build()
方法之前进行调整,以改变创建的对象Builder
可以在创建对象的时候,自动注入一些属性
- 单个
disadvantages
- must create a builder to create an object
- more verbose than telescoping pattern, then consider use a builder when the class has more than 4 parameters
Item 3: Enforce the singleton property with a private constructor or an enum type
使用私有构造器或者枚举类来实现单例属性
常见的实现单例方式的方法为:私有构造器 + 静态公共方法。
Approach I Public Static Factory Method
csharp
public class SingletonI {
private static final SingletonI INSTANCE = new SingletonI();
private SingletonI () { }
/**
* static factory method
* @return
*/
public static SingletonI getInstance() {
return INSTANCE;
}
}
advantages:
- 可以使用泛型工厂
- 调用方法的引用可以作为一个
supplier
,例如SingletonI::getInstance
Approach II Public Static Property
java
public class SingletonII {
public static final SingletonII INSTANCE = new SingletonII();
private SingletonII() { }
}
advantages
- 公共静态属性使用
final
修饰,引用不可变 - 实现起来比较简单
Approach III Enum
arduino
public enum SingletonIII {
INSTANCE;
}
Approach IV Static Inner-Class
csharp
public class SingletonIV {
// private constructor
private SingletonIV() { }
private static class SingletonHolder {
// define and initialize the outer instance in inner-class
private static final SingletonIV INSTANCE = new SingletonIV();
}
public SingletonIV getInstance() {
return SingletonHolder.INSTANCE;
}
}
Approach V Double Check Lock
csharp
public class SingletonV {
// private, static and volatile instance
private static volatile SingletonV INSTANCE;
// private constructor
private SingletonV() { }
// public static method to get instance
public static SingletonV getInstance() {
// check whether the instance is null
if (INSTANCE == null) {
// lock the Class by synchronize
synchronized (SingletonV.class) {
if (INSTANCE == null) {
INSTANCE = new SingletonV();
}
}
}
return INSTANCE;
}
}
常见破坏单例的方式为:
- 使用反射的方式,破坏私有构造器,来创建更多的对象。如果想要避免通过反射的攻击,可以修改构造器,在构造方法被第二次访问的时候,自动抛出异常。
- 序列化与反序列化 。为了防止单例类序列化和反序列化的过程中破坏单例,可以在单例类中添加
readResolve()
方法
Item 4: Enforce noninstantiablity with a private constructor
使用私有构造器来执行非实例化。
在开发的时候,经常会编写到其属性和方法全部为static
的类,例如一些工具类等。对这些类进行实例化是没有意义的,有没有好的办法类阻止其被实例化?
Approach I Abstract class
最先想到的肯定是抽象类,抽象类不能被实例化。但是这样做有几个致命问题:
- it can be subclassed and the subclass can be initialized
- it misleads the user into thinking of inheritance of abstract class
Approach II Private Constructor
通过私有构造器来实现类的非实例化。
csharp
public class UtilityClass {
// private constructor
private UtilityClass () {
throw new AssertionError("This class is non-instantiable");
}
}
但是,子类的实例化需要调用父类的构造器,父类私有构造器会阻止子类的实例化。
Item 5: Prefer dependency injection to hardwiring resources
倾向依赖注入而非硬链接
Existing problems
Static utility classes and singletons are inappropriate for classes whose behavior is parameterized by an underlying resource.
静态工具类和单例类并不适用于那些参数化底层依赖的类。
Case-SpellChecker
在创建实例的时候,将底层依赖通过构造器传入,这就是依赖注入的一种形式。
arduino
public class SpellChecker {
private final String dictionary;
public SpellChecker(String dictionary) {
this.dictionary = dictionary;
}
public boolean isValid(String word) {
return dictionary.contains(word)
}
}
advantages
- it works with an arbitrary number of resources
- it preserves immutability(不变性)
- it could apply to constructors, static factories and bulders
依赖注入还有一种形式,就是将一个工厂通过构造器传入。
disadvantages
- clutter up large projects. 对于成千上万个依赖的项目,使用依赖注入很可能会导致项目的混乱,但是可以使用一些依赖注入的框架来消除混乱。
summary
- do not use a singleton or static utility class to implement a class which depends on underlying resources
- 不要直接在类中创建底层依赖,而应该通过构造方法获取这些底层依赖
Item 6: Avoid create unnecessary object
避免创建不需要的对象
If there is a object which is immutable, reuse it instead of creating a new one.
javascript
String s = new String("[Effective Java]"); // DON'T DO THIS
some suggestions
- 如果一个不可变的类提供了构造器和静态工厂方法,建议使用静态工厂方法获取对象实例来避免创建出不需要的实例。
- 如果一个对象的创建代价比较大,但是又想复用该对象,可以将其缓存起来
case-RomanNumeral
假设要判断一个字符串是否为罗马数字,可以使用正则表达式进行匹配。
typescript
public static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
上面的代码中每次调用的时候,都会在内部创建一个Pattern
对象,其创建代价比较大,但是只使用到一次就被GC回收了。思考一下,如何进行复用呢?
在外层显示地使用Pattern
编译正则表达式。
arduino
private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
public static boolean isRomanNum(String s) {
return ROMAN.matcher(s).matches();
}
- 性能提升
- 增加了可读性
Autoboxing
自动装箱是java的一个语法糖,其模糊了基本数据类型和包装类的区别,但是包装类和基本数据类型在性能上有较大的差别。
在实际的开发中,要倾向于基本数据类型,而不是包装类,因为潜在的自动拆装箱会创建大量的无用对象,造成严重的性能问题。
Item 7: Eliminate obsolete object references
消除废弃的对象引用。
即使JVM存在垃圾回收机制,也一定要注意内存管理问题。
- 对于某些一定不会用到的对象,一定要及时使用
null
值,释放掉其引用,让垃圾回收器回收该废弃对象。如果废弃对象仍然被强引用所指,则会造成严重的内存泄漏问题。 - 赋
null
值的另一个好处就是,当外部代码错误指向废弃的对象,那么系统会抛出空指针异常,而非错误地运行下去。
Senarios for memory leaky
- 一般来讲,当一个类自己去管理内存的时候,就应该警惕内存内存泄漏问题
- 缓存。 存放在缓存中的对象很容易被忘记,当没有引用指向对象的时候,对象仍然存在于缓存中。使用
WeakHashMap
,某个entry
会在其key
没有被外部对象引用的时候自动清理 - 监听器和其他回调。 实现API之后,客户端注册了回调但是却没有显示地取消注册这些回调,这些回调就会无法被回收而积累起来。可以将其使用弱引用存储,例如
WeakHashMap
的key
值
Item 8: Aovid finalizaers and cleaners
避免使用Finalizaer和Cleaner机制。
Finalizer是不可预测的、危险的并且通常并不是必须要使用的。Cleaner的危险性不如Finalizer,但是依旧是不可预测的,其运行缓慢,并且是非必要的。
Shortcomings
- They might not be executed promptly. 并不是立马就执行的,从对象不可达到对象被清理之间的时间不可控。
- 禁止依赖finalizer或者cleaner去更新一个持久的状态。例如锁的释放,你永远不知道这些方法何时被执行或者其永远不可能被执行。
- Finalizer在运行是抛出的异常被忽略,导致其他的对象被破坏
- finalizer会打开你的类,并进行finalizer机制攻击
Finalizer机制攻击的原理:
如果构造器或者序列化方法中抛出异常,恶意子类的finalizer机制可以运行在部分构造对象上,Finalizer机制可以使用静态属性记录该对象的引用,以防止其被垃圾回收。有了缺陷对象的引用,就可以调用该对象的任何方法。
防范Finalizer机制攻击的方法:
- 使用
final
来修饰类或者使用final
来修饰finalize()
方法 - 让类实现
AutoClosable
接口
Advantages
- 作为安全网,对一些资源的释放起到兜底作用,有好过没有。
- 本地同伴类(native peer)是一个普通对象托管的本地对象,假设这个本地对等类持有了非关键资源,并且对性能要求不高,使用finalizer机制或者cleaner释放该资源是可行的
Item 9: Prefer try-with-resources to try-finally
优先使用try with resources
语句块而非try finally
try-finally
- 多层资源需要嵌套的时候,代码会变得很复杂
try
语句块和finally
语句块可能会因为相同的原因抛出异常,但是finally
语句块抛出的异常
try-with-resources
- 具有更好的可读性,更加精简
- 避免了因处理多个异常而产生的多层嵌套,污染代码的情况
Summary
使用try-with-resource
替代try-finally
代码块的好处:
- 生成的代码更加简洁
- 生成的异常更加有用
- 更加容易编写必须关闭的资源代码,不会出错