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) :内部只维护了两个对象TRUEFALSE
  • 判断是否为数字
// 每次都生成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);
}
}

Effective Java 读书笔记(一):创建和销毁对象的更多相关文章

  1. 【Effective Java读书笔记】创建和销毁对象(一):考虑使用静态工厂方法代替构造器

    类可以提供一个静态方法,返回类的一个静态实例,如Boolean包装类的一个获取实例的静态方法 public static Boolean valueOf(boolean b) { return (b ...

  2. Effective Java 读书笔记之一 创建和销毁对象

    一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...

  3. Effective Java 学习笔记之创建和销毁对象

    一.考虑用静态工厂方法代替构造器 1.此处的静态工厂方法是指返回指为类的对象的静态方法,而不是设计模式中的静态工厂方法. 2.静态工厂方法的优势有: a.使用不同的方法名称可显著地表明两个静态工厂方法 ...

  4. Effective Java(1)-创建和销毁对象

    Effective Java(1)-创建和销毁对象

  5. effective java 第2章-创建和销毁对象 读书笔记

    背景 去年就把这本javaer必读书--effective java中文版第二版 读完了,第一遍感觉比较肤浅,今年打算开始第二遍,顺便做一下笔记,后续会持续更新. 1.考虑用静态工厂方法替代构造器 优 ...

  6. 《Effective Java》读书笔记 - 2.创建和销毁对象

    Chapter 2 Creating and Destroying Objects item 1:Consider static factory methods instead of construc ...

  7. Effective Java 读书笔记之二 对于所有对象都通用的方法

    尽管Object是一个具体的类,但设计它主要是为了扩展.它的所有非final方法都有明确的通用约定.任何一个类在override时,必须遵守这些通用约定. 一.覆盖equals时请遵守通用的约定 1. ...

  8. Effective Java——(一)创建和销毁对象

    第一条 考虑用静态工厂方法代替构造器 什么叫静态工厂方法,就是通过在类中通过静态方法对对象初始化. 比如说 public class StaticFactory { private String na ...

  9. Effective Java(一)—— 创建和销毁对象

    在客户端(调用端)获取自身实例的方法: 公有的构造器: 类的静态工厂方法: 1. 使用静态工厂方法代替构造器 Boolean 是对基本类型 boolean 的包装类: public final cla ...

  10. Effective Java 读书笔记(二):对象通用方法

    1 重写equals方法时请遵守通用约定 (1)无需覆盖equals方法的情况 要求独一无二 不要求逻辑相等 超类已经覆盖equals方法,对其子类也适用 一个类是私有的或者是包私有(可以重写后抛出异 ...

随机推荐

  1. Perf -- Linux下的系统性能调优工具,第 1 部分 应用程序调优的使用和示例 Tracepoint 是散落在内核源代码中的一些 hook,一旦使能,它们便可以在特定的代码被运行到时被触发,这一特性可以被各种 trace/debug 工具所使用。Perf 就是该特性的用户之一。

    Perf -- Linux下的系统性能调优工具,第 1 部分 应用程序调优的使用和示例 https://www.ibm.com/developerworks/cn/linux/l-cn-perf1/i ...

  2. Windows上安装nodejs版本管理器nvm 安装成功之后重启终端失效

    nvm 安装成功之后重启终端失效(command not found) 安装nvm之后node不可用,“node”不是内部或外部命令,也不是可运行的程序或批处理文件(ng) 安装nvm: 下载nvm压 ...

  3. mongodb的开机自启动

    一.背景 Linux轻松的在rc.local中写上启动脚本,reboot~发现没有启动成功.这不科学啊,查看日志发现“permission denied” 二.解决 Linux系统下,使用自定配置文件 ...

  4. 小D课堂 - 零基础入门SpringBoot2.X到实战_第7节 SpringBoot常用Starter介绍和整合模板引擎Freemaker、thymeleaf_28..SpringBoot Starter讲解

    笔记 1.SpringBoot Starter讲解     简介:介绍什么是SpringBoot Starter和主要作用 1.官网地址:https://docs.spring.io/spring-b ...

  5. iptables保存规则(ubuntu和centos)

    1.Ubuntu 首先,保存现有的规则: iptables-save > /etc/iptables.rules 然后新建一个bash脚本,并保存到/etc/network/if-pre-up. ...

  6. PostgreSQL 登录时在命令行中输入密码

    有时候需要设置定时任务直接执行 sql 语句,但是 postgresql 默认需要人工输入密码,以下命令可以直接在命令行中直接填入密码 PGPASSWORD=pass1234 psql -U MyUs ...

  7. Ionic4.x、Cordova Android 检测应用版本号、服务器下载文件以及实现App自动升级、安装

    Android App 升级执行流程 1.获取本地版本号 2.请求服务器获取服务器版本号 3.本地版本和服务器版本不一致提示升级,弹窗提示用户是否更新 4.用户确定升级,调用文件传输方法下载 apk ...

  8. Flutter ExpansionPanel 可展开的收缩控件

    文档:https://api.flutter.dev/flutter/material/ExpansionPanel-class.html demo: import 'package:flutter/ ...

  9. Spark获取DataFrame中列的几种姿势--col,$,column,apply

    1.doc上的解释(https://spark.apache.org/docs/2.1.0/api/java/org/apache/spark/sql/Column.html)  df("c ...

  10. 解决:error: Cannot find libmysqlclient_r under /usr/local/mysql.

    libodb-mysql-2.4.0.tar.gz 解压完安装libodb-mysql时,执行完./cofigure后,出现如下错误: checking for libmysqlclient_r... ...