Effective Java 读书笔记(一):创建和销毁对象
1 构造器 => 静态工厂方法
(1)优势
- 静态工厂方法有名字
- 静态工厂方法不必在每次被调用时都产生一个新的对象
- 静态工厂方法能返回原返回类型的任意子类型的对象
- 静态工厂方法根据调用时传入的不同参数而返回不同类的对象
- 静态工厂方法返回对象的类不需要存在(SPI架构)
(2)限制
- 没有公有或者保护构造方法的类不能子类化(但是可以鼓励我们使用组合模式,而不是继承模式)
- 静态工厂方法难以发现
(3)常用静态工厂方法命名
- from:传入单个参数,返回该类型实例
Date d = Date.from(instant);
- of:传入多个参数,返回一个包含这些参数的该类型实例
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf:from和of的替换方案
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance or getInstance:创建一个由参数(如果有的话)描述的实例
StackWalker luke = StackWalker.getInstance(options);
- create or newInstance:类似instance或getInstance, 但保证每次调用都返回新实例
Object newArray = Array.newInstance(classObject, arrayLen);
- getType:类似getInstance,但一般在工厂方法包含在不同类的情况下使用。Type是工厂方法返回的对象的类型。
FileStore fs = Files.getFileStore(path);
- newType:类似于newInstance,但一般在工厂方法包含在不同类的情况下使用。
BufferedReader br = Files.newBufferedReader(path);
- type:getType和newType简洁的替换方式
List<Complaint> litany = Collections.list(legacyLitany);
2 构造器 => 构建者
(1)问题
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;
}
}
- 构造器方法数量迅速膨胀,因为有大量的可选项
- 可伸缩构造器(使用可变参数)是可行,只是当有很多参数时,会让客户端代码很难编写,而且代码也很难阅读。
(2)替代方案:JavaBeans模式
public class NutritionFacts {
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;
}
}
缺点:
-
构造过程被分到了多个调用中,一个JavaBean在其构造过程中可能处于不一致的状态。
-
类无法仅仅通过检查构造器参数的有效性来保证一致性。
(3)替代方案:Builder模式
- 例1
// 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();
- 例2
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();
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone(); // See Item 50
}
}
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);
}
public NyPizza build() {
return new NyPizza(this);
}
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE)
.addTopping(ONION).build();
优势:
- builder能拥有多个可变参数(例1)
- builder能将传入到不同方法里的参数聚合起来然后传入单个域里(例2)
缺点:
- 多创建一个Builder对象的内存开销
3 使用枚举强化单例模式
(1)饿汉
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {}
public void leaveTheBuilding() {}
}
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {}
public void leaveTheBuilding() {}
public static Elvis instance(){
return INSTANCE;
}
}
(2)内部类
public class Elvis {
private Elvis() {}
public void leaveTheBuilding() {}
// 通过类加载机制来保证线程安全
private static class Holder{
private static final Elvis INSTANCE = new Elvis();
}
public static Elvis instance(){
return Holder.INSTANCE;
}
}
(3)双重检验锁
public class Elvis {
private Elvis() {}
public void leaveTheBuilding() {}
private static volatile Elvis INSTANCE;
public static Elvis instance(){
if(INSTANCE == null){
synchronized(Elvis.class){
if (INSTANCE == null) {
INSTANCE = new Elvis();
}
}
}
return INSTANCE;
}
}
(4)枚举
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {}
}
(5)总结
-
第2、3种具备懒加载
-
第4种具备禁止反序列化创建对象问题
-
第1~3种如果实现了Serializable接口的补偿措施
// 方法1 private static Class getClass(String classname) throws ClassNotFoundException { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if(classLoader == null) classLoader = Singleton.class.getClassLoader(); return (classLoader.loadClass(classname)); } } // 方法2 // 重写readReslove方法,需要将所有成员变量声明为transient private Object readResolve() { return INSTANCE; }
参考:
4 私有化构造器强化不可实例化的能力
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
// Remainder omitted
}
5 优先使用依赖注入而不是硬连接资源
静态工具类和Singleton对于类行为需要被底层资源参数化的场景是不适用的。
(1)构造器注入
public class SpellChecker {
private final Lexicon dictionary; // 所需的底层资源
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
(2)Setter注入
public class SpellChecker{
private Lexicon dictionary; // 所需的底层资源
void setDictionary(Lexicon dictionary){
this.dictionary = dictionary;
}
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
(3)接口注入
public interface DictionaryDependent{
void setDependence(Lexicon dictionary);
}
public class SpellChecker implement DictionaryDependent{
private Lexicon dictionary; // 所需的底层资源
void setDependence(Lexicon dictionary){
this.dictionary = dictionary;
}
public boolean isValid(String word) { ... }
public List<String> suggestions(String typo) { ... }
}
参考:
6 避免创建不必要的对象
- String s = "bikini"; ====> String s = new String("bikini");
- Boolean.valueOf(String) :内部只维护了两个对象TRUE和FALSE
- 判断是否为数字
// 每次都生成Pattern对象,造成内存浪费
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})$");
}
public class RomanNumerals {
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})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
- Map.keySet():每次返回同一个实例,懒加载模式
- 注意自动装箱问题
- 尽量不要维护对象池,除非对象创建开销太大,如:JDBC数据库连接池
7 消除过时的对象引用
(1)内存泄漏存在情况与解决
- 一个类自己管理它的内存(没用的对象没有置空):显式置null
- 缓存相关:
- 设置淘汰策略:设置超时清除
- 自制回收线程
- 内存占用做限制
- 使用WeakHashMap容器
- 监听器相关:
- 使用WeakHashMap容器
(2)WeakHashMap的实现原理
- Entry继承WeakReference类:下一次垃圾回收就会回收掉该对象
- WeakHashMap内部一个ReferenceQueue:被回收的对象将放入该队列中
- 任何对WeakHashMap的操作都会进行一次同步操作:ReferenceQueue与Entry[]的同步操作,把Entry过期对象清除,ReferenceQueue清空。
// 回收操作和把对象放入引用队列由JVM处理,WeakHashMap只需要创建WeakReference时,把ReferenceQueue放入即可
// 同步操作:该方法被WeakHashMap中的所有操作涉及,代表只要进行操作就会进行同步,可能你会担心性能问题,但是实际上如果queue中没有数据时,直接就返回了。
private void expungeStaleEntries() {
// 循环清除queue中的元素
for (Object x; (x = queue.poll()) != null; ) {
// 防止并发
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// 协助GC操作,清除数组中的元素
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
8 避免使用finalize方法
-
finalize的调用时机无法把握
- finalizer线程优先级低,可能还没回收就发生OOM异常
- System.gc也无法保证一定会执行
-
导致严重的性能损失
-
类暴露于终结方法攻击:
- 在终结过程中若有未被捕获的异常抛出,则抛出的异常会被忽略,而且该对象的终结过程也会终止。
- 当构造器或者序列化中抛出异常,恶意子类的终结方法可以运行在本应夭折的只构造了部分的对象上(强行救活父类对象)。此时子类就可以调用该对象上的任意方法,但实际上该对象应该不存在才对。
- final类能免疫于此类攻击,因为没有类能对final类进行恶意继承。
- 为了防止非final类遭受终结方法攻击,我们可以写一个什么都不做而且是final的终结方法。
-
替代方案:继承AutoCloseable接口,close方法在被关闭后还被调用,就要抛出一个IllegalStateException异常。
public class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// Resource that requires cleaning. Must not refer to Room!
private static class State implements Runnable {
int numJunkPiles;
// Number of junk piles in this room
State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}
// Invoked by close method or cleaner
@Override
public void run() {
System.out.println("Cleaning room");
numJunkPiles = 0;
}
}
// The state of this room, shared with our cleanable
private final State state;
// Our cleanable. Cleans the room when it’s eligible for gc
private final Cleaner.Cleanable cleanable;
public Room(int numJunkPiles) {
state = new State(numJunkPiles);
cleanable = cleaner.register(this, state);
}
@Override
public void close() {
cleanable.clean();
}
}
9 优先使用try-with-resources而不是try-finally
- try-finally
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
- try-with-resources
static void copy(String src, String dst) throws IOException {
try (
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)
) {
byte[] buf = new byte[BUFFER_SIZE]; int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}