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);
}
}

最新文章

  1. 防御XSS攻击-encode用户输入内容的重要性
  2. vue 2.0 开发实践总结之疑难篇
  3. php接口
  4. java中强制类型转换
  5. pushlet
  6. RoR简单的应用程序
  7. [.Net] 通过反射,给Enum加备注
  8. Wix 安装部署教程(十一) ---QuickWix
  9. loadrunner资源过滤器
  10. pgpgin|pgpgout|pswpin|pswpout意义与差异
  11. 【C#】【MySQL】C# 查询数据库语句@Row:=@Row+1
  12. Centos6 源代码部署MySQL5.6
  13. JSON 之FastJson解析
  14. MYSQL IFNULL使用功能
  15. VVDocumenter安装过程的一些问题
  16. iOS StoreKit
  17. Item 27: 明白什么时候选择重载,什么时候选择universal引用
  18. addEventListener解决多个window.onscroll共存的2个方法
  19. 进程部分(IPC机制及生产者消费者模型)和线程部分
  20. 【Codeforces 1132C】Painting the Fence

热门文章

  1. dubbo线程模型配置
  2. exp/imp 注释乱码问题或Oracle EXP-00091的解决方法
  3. MySQL按日期分组并统计截止当前时间的总数(实例教程)
  4. Docker Rootless Container
  5. Visual Studio 2019更新到16.1.6
  6. typescript属性类型接口
  7. Eclipse安装中文简体语言包
  8. ES6深入浅出-9 Promise-1.回调与回调地狱
  9. 转 mysql awr 报告
  10. Elasticsearch集成Hadoop最佳实践.pdf(内含目录)