第2章:创建和销毁对象

第1条:考虑用静态工厂方法代替构造器

这里指的静态工厂方法与设计模式中的工厂方法并不等同,这里的静态工厂方法就是一个public static的可以返回类的实例的方法

1.1 静态工厂方法优势

1.1.1 有名字
  1. Book
package chapter1.number1;

public class Book {

}

  1. BookStatic
package chapter1.number1;

public class BookStatic {
	private static final BookStatic EFFECTIVE_JAVA = new BookStatic();
	private BookStatic(){
		
	}
	public static BookStatic getEffectiveJavaInstance(){
		return EFFECTIVE_JAVA;
	}
}

  1. FirstClient
package chapter1.number1;

public class FirstClient {
	public static void main(String[] args) {
		//1.构造器
		Book b = new Book();
		//2.静态工厂方法
		BookStatic bs = BookStatic.getEffectiveJavaInstance();
	}
}

1.1.2 不必每次调用时都创建一个新的对象
  1. 可以提升性能,BookStatic的静态工厂方法getEffectiveJavaInstance就不会每次都创建一个新的实例
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

  1. 享元(Flyweight)模式:目的是共享对象,减少对象创建,该模式就是判断如果有该对象了,就不再新建,而是使用之前缓存起来的内容,并修改其相关属性,该模式与本条做法类似
1.1.3 可以返回原返回类型的任何子类型对象
  1. 提升了返回对象的灵活性:例如API不用将类设置为public,就可以返回其对象。这使API简洁。
  2. 比如Collections类中有45个类似synchronizedCollection、checkedCollection的方法,分别提供了不可修改的集合、线程安全的集合等等,如果把这些集合都单独设成一个个类,那么API非常复杂,程序员难以掌握
  3. Java8之后,接口中已经可以包含静态方法,因此没必要再给接口一个不可实例化的伴生类,但由于接口中静态成员变量必须公有,会暴露实现细节,所以仍有必要将这些静态方法的大部分实现代码,放进一个包级私有(default)的类中
1.1.4 为方法传入不同参数,返回类型可以不同
  1. 返回对象可以每次不同
//返回对象每次不同
import java.util.EnumSet;

public class EnumSetTest {
	public static void main(String[] args) {
		// 1.allOf就是一个静态工厂方法,进到方法里看到,它会根据枚举类的不同情况,返回不同对象,枚举类的元素大于64,返回JumboEnumSet对象,<=64返回RegularEnumSet实例
		// 2.我们根本不知道调用者反回的是哪个子类,当RegularEnumSet无法再给元素少的枚举类型提供性能优势,我们可以删除它,同时对客户端没有任何影响
		EnumSet<MyEnum> es = EnumSet.allOf(MyEnum.class);
		System.out.println(es);
	}
}

enum MyEnum {
	BLACK, WHITE, RED, BLUE, GREEN, YELLOW
}
1.1.5 方法返回的对象所属的类,在编写该静态工厂方法的类时可以不存在

是服务提供者框架的基础,将客户端与具体实现解耦,JDBC就是这样的框架

1.1.5.1 服务提供者框架的组件
  1. 服务接口
  2. 服务提供者接口
  3. 服务提供者的管理类
    1. 注册服务提供者的方法
    2. 产生服务的方法
  4. 有了这些以后,已经可以正常编译,不同厂商提供不同的
    1. 服务实现类
    2. 服务提供者实现类
  5. 具体使用的服务对客户端透明,可以在服务提供者实现类中的返回服务对象的方法中查看具体的服务实现类
1.1.5.2 代码
  1. ServiceInterface:服务接口
public interface ServiceInterface {
	// 不同的服务方法
	// 进入地铁
	public boolean in();

	// 出地铁
	public boolean out();
}
  1. ServiceProviderInterface:服务提供者接口
public interface ServiceProviderInterface {
	// 提供一个可以创建服务对象的方法
	public ServiceInterface getService();
}
  1. ServiceManager:服务提供者的管理类
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ServiceManager {
	private ServiceManager() {

	}

	private static final Map<String, ServiceProviderInterface> providers = new ConcurrentHashMap<String, ServiceProviderInterface>();

	// 注册服务提供者的方法
	public static void registerProvider(String name, ServiceProviderInterface p) {
		providers.put(name, p);
	}

	// 产生服务的方法
	// 1.静态工厂方法返回的对象所属的类,在编写包含静态工厂方法的类时可以不必存在,这构成了服务提供者框架的基础
	public static ServiceInterface getServiceByName(String name) {
		// 2.服务提供者接口是可选的,即直接使用服务提供者的实现类创建对象也可以
		ServiceProviderInterface p = providers.get(name);
		if (p == null) {
			throw new IllegalArgumentException("No provider registered with name:" + name);
		}
		return p.getService();
	}
}
  1. ServiceOneImpl:服务实现类
public class ServiceOneImpl implements ServiceInterface{

	@Override
	public boolean in() {
		System.out.println("通过一卡通进入地铁");
		return false;
	}

	@Override
	public boolean out() {
		System.out.println("通过一卡通出地铁");
		return false;
	}
}
  1. ServiceTwoImpl:服务实现类
public class ServiceTwoImpl implements ServiceInterface{

	@Override
	public boolean in() {
		System.out.println("通过现金进入地铁");
		return false;
	}

	@Override
	public boolean out() {
		System.out.println("通过现金通出地铁");
		return false;
	}
}
  1. ServiceOneProviderImpl:服务提供者实现类
public class ServiceOneProviderImpl implements ServiceProviderInterface{
    //静态代码块中对自身进行注册,并重写服务提供者接口的方法,返回具体服务
	static{
		ServiceManager.registerProvider("一卡通", new ServiceOneProviderImpl());
	}

	@Override
	public ServiceInterface getService() {
		return new ServiceOneImpl();
	}
}
  1. ServiceTwoProviderImpl:服务提供者实现类
public class ServiceTwoProviderImpl implements ServiceProviderInterface{
	static{
		ServiceManager.registerProvider("现金", new ServiceTwoProviderImpl());
	}

	@Override
	public ServiceInterface getService() {
		return new ServiceTwoImpl();
	}
}
  1. 测试类:TestService
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class TestService {
	public static void main(String[] args) throws ClassNotFoundException, SQLException {
		Class.forName("ServiceTwoProviderImpl");
		ServiceInterface swi = ServiceManager.getServiceByName("现金");
		swi.in();
		swi.out();
		//1. Driver:服务提供者接口
		//2. OracleDriver:服务提供者实现类,其内静态块中调用DriverManager.registerDriver方法对自身进行注册,并实现了Driver接口中的connect方法返回具体服务
		Class.forName("oracle.jdbc.driver.OracleDriver");
		//3. Connection:服务接口
		//4. DriverManager:服务提供者的管理类,其内registerDriver为注册服务提供者的方法、getConnection为产生服务的方法
		Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.41.128:1521:fcrhost","c50hst","c50hst");
		//5. createStatement就是服务接口中提供的其中一个方法
		Statement stmt = conn.createStatement();
	}
}
1.1.6 创建带泛型的实例时,代码更简洁
//构造器方式
Map<String,List<String>> m = new HashMap<String,List<String>>()
//静态工厂方式,JDK1.6为止,HashMap并没有提供这个静态方法
public static <K,V> HashMap<K,V> newInstance(){
    return new HashMap<K,V>();
}
Map<String,List<String>> m = HashMap.newInstance();
//jdk1.7中已经提供了菱形语法,该条优势也就不再存在,所以在第三版中删除该优势
Map<String,List<String>> m = new HashMap<>()

1.2 静态工厂方法缺点

1.2.1 静态工厂方法所创建的对象的类通常没有public和protected构造器,该类不能被继承
  1. 有public、protected构造器意味着可以随意创建对象,因此一般没有
  2. 创建子类时会先调用父类构造器,但父类没有public和protected构造器,编译时就会报错
1.2.2 无法通过名称区分是普通静态方法还是静态工厂方法,没法快速找到如何实例化该类

静态工厂方法惯用名称

  1. from:类型转换
//将Instant类型的instant对象,转换为Date类型
Date d = Date.from(instant);
  1. of:聚合
//将参数中的内容组合起来,返回该静态工厂方法所在类EnumSet的一个实例
Set<Rank> faceCards = EnumSet.of(JACK,QUEEN,KING);
  1. valueOf:类似from、of
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  1. instance/getInstance:参数可以描述创建出的对象
StackWalker luke = StackWalker.getInstance(options);
  1. create/newInstance:同getInstance,且每次创建一个新对象
//每次都返回一个新的实例
Object newArray = Array.newInstance(classObject,arrayLen);
  1. getType:静态工厂方法所在类,与该方法创建出的对象所属的类,不是一个
FileStore fs = Files.getFileStore(path);
  1. newType:同getType,且每次创建一个新的对象
BufferedReader br = Files.newBufferedReader(path);
  1. type:getType、newType的简化
List<Complaint> litany = Collections.list(legacyLitany);

第2条:遇到多个构造器参数时考虑使用构建器

2.1 使用Builder模式的原因

  1. 构造器与静态工厂方法都不能很好扩展到大量可选参数
2.2 代码
  1. NutritionFacts:重叠构造器模式
package chapter1.number2;

public class NutritionFacts {
	private final int servingSize;//分量大小 必须
	private final int servings;//使用次数 必须
	private int calories;//卡路里 可选
	private int fat;//脂肪 可选
	private int sodium;//纳 可选
	private int carbonhydrate;//碳水化合物可选
	
	public NutritionFacts(int servingSize,int servings){
		this(servingSize,servingSize,0,0,0,0);
	}
	
	public NutritionFacts(int servingSize,int servings,int calories){
		this(servingSize,servings,calories,0,0,0);
	}
	
	public NutritionFacts(int servingSize,int servings,int calories,int fat){
		this(servingSize,servings,calories,fat,0,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 carbonhydrate){
		super();
		this.servingSize=servingSize;
		this.servings=servings;
		this.calories=calories;
		this.fat=fat;
		this.sodium=sodium;
		this.carbonhydrate=carbonhydrate;
		
	}
	@Override
	public String toString(){
		return "NutritionFacts [servingSize="+servingSize+", servings="+servings+",calories="+calories+",fat="+fat+",sodium="+sodium+",carbonhydrate="+carbonhydrate;
	}
}

  1. NutritionFactsBean:JavaBeans模式
package chapter1.number2;

public class NutritionFactsBean {
    //无法设置为final,因为如果设置为final,那么下面的setter方法就失效了
	private int servingSize=-1;//分量大小 必须
	private int servings=-1;//使用次数 必须
	private int calories=0;//卡路里 可选
	private int fat=0;//脂肪 可选
	private int sodium=0;//纳 可选
	private int carbonhydrate=0;//碳水化合物可选
	//重叠构造器模式
	public void setServingSize(int servingSize) {
		this.servingSize = servingSize;
	}
	public void setServings(int servings) {
		this.servings = servings;
	}
	public void setCalories(int calories) {
		this.calories = calories;
	}
	public void setFat(int fat) {
		this.fat = fat;
	}
	public void setSodium(int sodium) {
		this.sodium = sodium;
	}
	public void setCarbonhydrate(int carbonhydrate) {
		this.carbonhydrate = carbonhydrate;
	}
	
	@Override
	public String toString(){
		return "NutritionFacts [servingSize="+servingSize+", servings="+servings+",calories="+calories+",fat="+fat+",sodium="+sodium+",carbonhydrate="+carbonhydrate;
	}
}

  1. NutritionFactsBuilder:Builder模式
package chapter1.number2;

public class NutritionFactsBuilder {
    //因为想构建不可变类,所以所有属性都设置为final
	private final int servingSize;//分量大小 必须
	private final int servings;//使用次数 必须
	private final int calories;//卡路里 可选
	private final int fat;//脂肪 可选
	private final int sodium;//纳 可选
	private final int carbonhydrate;//碳水化合物可选
	
	private NutritionFactsBuilder(Builder builder){
		servingSize = builder.servingSize;
		servings = builder.servings;
		calories = builder.calories;
		fat = builder.fat;
		sodium = builder.sodium;
		carbonhydrate = builder.carbonhydrate;
		//可以在此处对参数有效性进行检查,例如几个值加起来不允许超过5w,但如果是JavaBeans模式,就没法这样检查
	}
	
	@Override
	public String toString(){
		return "NutritionFacts [servingSize="+servingSize+", servings="+servings+",calories="+calories+",fat="+fat+",sodium="+sodium+",carbonhydrate="+carbonhydrate;
	}
	//由于想方便的被外界所访问,所以设置为static
	public static class Builder{
		//1.由于该值是必填的,所以这个值一定会在构造器中被初始化,所以可以使用final
		private final int servingSize;//分量大小 必须
		private final int servings;//使用次数 必须
		private  int calories = 0;//卡路里 可选
		private  int fat = 0;//脂肪 可选
		private  int sodium = 0;//纳 可选
		private  int carbonhydrate = 0;//碳水化合物可选
		//2.构造函数中传入必填的成员
		public Builder(int servingSize,int servings){
			super();
			this.servingSize = servingSize; 
			this.servings = servings;
		}
		public Builder setCalories(int calories){
			this.calories = calories;
			//3.注意返回Builder类型变量
			return this;
		}
		public Builder setFat(int fat){
			this.fat = fat;
			return this;
		}
		public Builder setSodium(int sodium){
			this.sodium = sodium;
			return this;
		}
		public Builder setCarbonhydrate(int carbonhydrate){
			this.carbonhydrate = carbonhydrate;
			return this;
		}
		//5.提供一个返回其外部类的build方法
		public NutritionFactsBuilder build(){
			return new NutritionFactsBuilder(this);
		}
	}
}

  1. SecondClient
package chapter1.number2;

public class SecondClient {
	public static void main(String[] args) {
		// 重叠构造器
		// 1.参数可读性差,不知道各个参数代表什么含义。
		// 2.编码复杂,如果6个参数都不是必须,需要6!个构造器才能满足所有情况的需求
		NutritionFacts cocaCola = new NutritionFacts(2500, 250, 1000, 0, 300, 265);
		System.out.println(cocaCola);

		// JavaBeans模式
		// 1.无法将该类设置为不可变类,因为不可变类必须final修饰成员变量,那么此时该成员变量初始化必须在代码块或构造器中初始化,提供setter方法是不好用的,而可变类线程不安全
		NutritionFactsBean cocaColaBean = new NutritionFactsBean();
		cocaColaBean.setServingSize(2500);
		cocaColaBean.setServings(250);
		cocaColaBean.setCalories(1000);
		cocaColaBean.setFat(0);
		cocaColaBean.setSodium(300);
		cocaColaBean.setCarbonhydrate(265);
		System.out.println(cocaColaBean);

		// Builder模式
		// 1.不直接生成想要的对象NutritionFactsBuilder
		// 2.先利用NutritionFactsBuilder中的必要参数创建一个builder对象(因此Builder类中对应NutritionFactsBuilder中必要的参数设置为final,这样就保证了Builder类该参数必须在构造器中初始化,即必输)
		// 3.然后客户端在builder对象上调用类似setter的方法来设置NutritionFactsBuilder的可选参数
		// 4.调用无参的build方法生成不可变对象
		
		// Builder模式优点(适用于多个参数创建对象)
		// 1.可读性强
		// 2.编码简单,无需根据参数的个数不同创建多个构造器
		// 3.安全:可以将其设置为不可变类(所以NutritionFactsBuilder中属性都是final修饰)
		// 4.一般有4个或更多参数时,使用构建器,但有时候可能是开始只有几个参数,后来会增加,因此最好可以一开始就使用构建器,否则后来再添加,过时的构造器和静态工厂会显得十分不协调
		
		// Builder模式缺点
		// 1.由于多创建了Builder对象,可能导致性能降低(几乎不会怎么影响)
		//NutritionFactsBuilder是不可变类,Builder像个构造器一样。我们可以在build或set方法中加入对参数的校验
		NutritionFactsBuilder cocaColaBuilder = new NutritionFactsBuilder.Builder(2500, 250).setCalories(1000).setFat(0)
				.setSodium(300).setCarbonhydrate(265).build();
		System.out.println(cocaColaBuilder);
	}
}

2.3 通过继承由Builder模式创建对象的类,来扩展属性
  1. Pizza:由Builder模式创建对象的类
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;

public abstract class Pizza {
	// 1. 此处是定义了一个内部的枚举类,Topping表示"配料"
	public enum Topping {
		HAM, MUSHROOM, ONION, PEER, SAUSAGE
	}

	// 2. 为了实现不可变类,该属性需用final修饰
	final Set<Topping> toppings;

	// 3. Builder模式下的构造器
	Pizza(Builder<?> builder) {
		toppings = builder.toppings.clone();
	}

	// 4. 此处是想定义一个泛型类,而这个T,希望是Builder的子类型,且要求这个T的self方法,可以返回自身T对应的类型
	// 实际上继承泛型类时,应指定T的具体类型,或不指定,T默认为Object
	// public class A extends Apple<T>(×)
	// public class A extends Apple<String>(√)
	// public class A extends Apple(√)
	abstract static class Builder<T extends Builder<T>> {
		// 5. Builder内的成员变量不用final修饰,方便为其赋值
		// 6. 之前set方法,最后return this,这里由于有泛型,还得转换,所以用self方法代替,其内就是return this
		// 创建空的EnumSet集合,其内元素类型只能为Topping
		EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);

		public T addTopping(Topping topping) {
			// requireNonNull方法:为空返回空指针,否则返回对象
			toppings.add(Objects.requireNonNull(topping));
			return self();
		}

		// 7. 返回具体的Pizza对象的实例,Pizza为抽象类,并不能实例化,因此在该方法中,无法完成build方法
		abstract Pizza build();

		protected abstract T self();
	}

}

  1. NyPizza:Pizza基础上,增加必须输入的属性size
import java.util.Objects;

//1. 该例演示了,类NyPizza继承(由Builder模式来创建对象的)Pizza类时,如果向其内添加必输的,不可变的成员变量时,如何处理
public class NyPizza extends Pizza {
	public enum Size {
		SMALL, MEDIUM, LARGE
	}

	// 2. 外层增加成员变量,定义为final,不可变
	private final Size size;

	private NyPizza(Builder builder) {
		super(builder);
		size = builder.size;
	}

	// 3. Builder继承原Builder,这样不用为其父类中的属性,再定义赋值方法
	public static class Builder extends Pizza.Builder<Builder> {
		// 4. 由于新增成员变量必输,所以用final定义,并在创建Builder时,为其赋初值,表示该值必须输入
		private final Size size;

		public Builder(Size size) {
			this.size = Objects.requireNonNull(size);
		}

		// 5.重写父类方法,可以返回Pizza子类对象的实例,其内会调用NyPizza私有化(因为不想被直接创建,只想通过Builder的build方法创建)后的构造器
		@Override
		NyPizza build() {
			return new NyPizza(this);
		}

		// 6. 重写self()方法,返回设置了变量值后的Builder本身
		@Override
		protected Builder self() {
			return this;
		}

	}

}

  1. Calzone:Pizza基础上,增加非必须输入的属性sauceInside
//1. 该例演示了,类Calzone继承(由Builder模式来创建对象的)Pizza类时,如果向其内添加非必输的,不可变的成员变量时,如何处理
public class Calzone extends Pizza {
	// 2. 外层增加成员变量,定义为final,不可变
	private final boolean sauceInside;

	private Calzone(Builder builder) {
		super(builder);
		this.sauceInside = builder.sauceInside;
	}

	public static class Builder extends Pizza.Builder<Builder> {
		// 3. 由于新增成员变量非必输,所以不用final定义,可以定义set方法为其赋值
		private boolean sauceInside = false;

		public Builder setSauceInside() {
			sauceInside = true;
			return this;
		}

		@Override
		Pizza build() {

			return new Calzone(this);
		}

		@Override
		protected Builder self() {
			return this;
		}

	}

}

  1. BuilderTest:客户端代码

public class BuilderTest {
	public static void main(String[] args) {
		NyPizza pizza = new NyPizza.Builder(NyPizza.Size.SMALL).addTopping(Pizza.Topping.SAUSAGE)
				.addTopping(Pizza.Topping.ONION).build();
		Calzone calzone = new Calzone.Builder().addTopping(Pizza.Topping.HAM).setSauceInside().build();

	}
}

第3条:用枚举类型是Singleton的最佳实践

3.1 第一种Singleton实现
package com.tfrunning;

public class Elvis {
	public static final Elvis INSTANCE = new Elvis();
	private Elvis(){
		
	}
}

3.2 第二种Singleton实现
//唯一不同是私有化了成员变量,创建了一个静态工厂方法返回对象
package com.tfrunning;

public class Elvis {
	private static final Elvis INSTANCE = new Elvis();
	private Elvis(){
		
	}
	//工厂方法的优势
	//1. 容易被修改,例如如果某一天想改为每一个线程返回一个实例,那么修改方法即可
	//2. 可以编写泛型工厂
	//3. Supplier为java8内置的一个函数式接口,里面有一个get方法,用于返回指定的实例,Supplier一般用于表示工厂,静态工厂方法可以直接用如下代码,获取Supplier实例
	//其意思是,通过继承Supplier接口来创建对象,并实现get方法,由于get方法中,只有一行代码,return Elvis.getInstance,因此由java8新增"方法引用",简化代码如下
	//Supplier<Elvis> a = Elvis::getInstance
	public static Elvis getInstance(){
	    return INSTANCE;
	}
}

3.3 前两种实现的问题
  1. 通过反射和序列化仍然可以创建新对象
package com.tfrunning;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Elvis implements Serializable {
	public static final Elvis INSTANCE = new Elvis();

	private Elvis() {

	}

	public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException,
			InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException,
			FileNotFoundException, IOException, ClassNotFoundException {
		//1.反射可以创建新对象
		Class ev = Elvis.class;
		Constructor c = ev.getDeclaredConstructor();
		Elvis a1 = (Elvis) c.newInstance();
		System.out.println(a1 == Elvis.INSTANCE);
		//2.序列化创建新对象
		ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(new File("E:/Person.txt")));
		oo.writeObject(Elvis.INSTANCE);
		oo.close();
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:/Person.txt")));
		Elvis a2 = (Elvis) ois.readObject();
		System.out.println(a2 == Elvis.INSTANCE);
	}
}

  1. 为防止反序列化后出现新的对象,必须声明所有引用类型的成员变量都是transient(瞬态,不会被序列化),并提供一个readResolve(该方法会替代原本反序列化得出的对象)方法(具体为什么必须定义为transient,见89条)
3.4 Singleton最佳实践
//1. 对于枚举类,首先无法通过反射获取其构造器,其次每次序列化都会获取同一个对象
//2. 但如果一个Singleton必须扩展一个超类,而不是Enum时,就不能用这个方法了
package com.tfrunning;

public enum ElvisBest {
	INSTANCE;
}

第4条:通过私有化构造器强化不可实例化的能力

4.1 什么时候需要不可实例化的类
  1. 有时候,我们可能需要编写只包含静态方法和静态成员的类,这种类我们一般也叫做工具类
  2. 对这种工具类来说实例对他没有任何意义,我们不想这种类被实例化,但缺少显示构造器时,编译器会自动提供一个默认构造器
4.2 不可被实例化的方法
  1. 将类做成抽象类:
    1. 会误导用户,以为抽象类是专门为继承而设计,且子类还是可以被实例化
  2. 私有化构造器
//1. 这样做的副作用为该类不再可以被继承,因为所有构造器都必须显示或隐式调用超类构造器,但这种情况下子类没有可访问的超类构造器
public class UtilityClass {
    //2. 私有化构造器
	private UtilityClass(){
	    //3. 不必须,但可以防止不小心在类内部调用构造器来创建对象
		throw new AssertionError();
	}
}

第5条:优先考虑依赖注入来引用资源

依赖注入所解决的问题:多个类的实例,每个实例使用不同的资源

5.1 实现方式之一

当创建一个新的实例时,将该资源传入构造器

import java.util.Objects;

public class SpellChecker {
	// Lexicon词典
	private final Lexicon dictionary;

	// dictionary就是SpellChecker类的一个依赖,创建SpellChecker时,将dictionary注入其中
	public SpellChecker(Lexicon dictionary) {
		this.dictionary = Objects.requireNonNull(dictionary);
	}

	// 其他方法
	public boolean isValid(String word) {

	}

	public List<String> suggestions(String typo) {

	}
}

5.2 实现方式一的变体

将资源工厂传给构造器

//Supplier为java8内置函数式接口,内部提供一个get方法,返回某对象,Supplier<T>最适合表示工厂
//通过传入一个工厂,获得Tile对象,再通过Tile对象,获取Mosaic对象
Mosaic create (Supplier<? extends Tile> tileFactory){
}
5.3 依赖注入优缺点
  1. 提升灵活性、可重用性、可测试性
  2. 导致大项目凌乱不堪,因为它通常包含上千个依赖
  3. 可以使用依赖注入框架解决这种凌乱,Spring就是一个依赖注入框架
5.4 总结
  1. 不要使用Singleton和静态工具类来实现依赖一个或多个底层资源类,且该资源的行为会影响该类的行为
  2. 也不要用这个类来直接创建这些资源
  3. 应该将这些资源或者工厂传给构造器(或静态工厂、构建器),通过他们来创建类
5.5 IOC
  1. 控制的反转:是一种设计思想
  2. 控制:指谁来控制对象以及其依赖的创建
  3. 反转:传统应用程序是由我们自己在对象中直接获取依赖对象,也就是正向的控制;而反转则是由容器来帮忙创建对象及其依赖,并注入依赖对象

第6条:避免创建不必要的对象

  1. String
//"bikini"本身就是一个String实例,没必要再创建
//String s = new String("bikini");
String s = "bikini";
  1. 静态工厂
//valueOf不会每次创建新对象,Java9中new创建Boolean对象已经被废弃
//new Boolean("true");
Boolean.valueOf(true);
  1. 创建成本高的对象
//1. 创建成本高的对象,应进行缓存。
//String对象的matches方法用于查看是否匹配指定的正则表达式,但该方法内部每次都创建一个Pattern对象,成本较高
//static boolean isBaidu(String s) {
	//return s.matches("(.*)baidu(.*)");
//}
public class Baidus{
	private static final Pattern BAIDU = Pattern.compile("(.*)baidu(.*)");
	static boolean isBaidu(String s) {
		return BAIDU.matcher(s).matches();
	}
}
  1. 同一个对象的适配器对象
    1. 适配器中,并没有其要委托的对象(例如TurkeyAdapter类中的turkey)的信息,因此对于同一个turkey对象,不需要创建多个适配器
    2. Map对象中的keySet方法,针对同一个Map对象没必要返回多个keySet对象
  2. 基本类型与装箱基本类型
public long sum() {
	//此处使用Long定义sum变量,导致每次循环,long类型的i都会被转成Long类型,整个程序构造了2的31次方个多余的Long对象,速度非常慢
	//Long sum = 0L;
	long sum = 0L;
	for(long i = 0;i<=Integer.MAX_VALUE;i++) {
		sum += i;
	}
	return sum;
}

第7条:消除过期的对象引用

有些情况不消除过期对象的引用,可能会引发内存泄漏

7.1 类自己管理内存
  1. 清空引用应是例外,不是规范
  2. 最好让包含引用的变量结束其声明周期
  3. 如果在最紧凑的作用域定义变量,自然而然会满足上面一点
import java.util.Arrays;
import java.util.EmptyStackException;

public class Stack {
	private Object[] elements;
	private int size;
	private static final int DEFAULT_INITIAL_CAPACITY = 16;
	public Stack() {
		elements = new Object[DEFAULT_INITIAL_CAPACITY];
	}
	public void push(Object e) {
		ensureCapacity();
		elements[size++] = e;
	}
	public Object pop() {
		if(size ==0 ) {
			throw new EmptyStackException();
		}
		//出栈时,只是取之前的一个元素,但没有把size位置的元素清空,这样下来,即使将所有元素都pop后,引用还是没有释放,内存还占用
		//但根据业务逻辑,size以上部分根本没用(过期),但也得不到释放
		return elements[--size];
		//可以改为如下形式,清除过期引用,不止释放内存,又可以防止过期引用被误用,因为误用时会报空指针
//		Object result = elements[--size];
//		elements[size] = null;
//		return result;
	}
	private void ensureCapacity() {
		if(elements.length == size ) {
			//发现之前的数组无法满足栈的长度,需要扩展数组,每次扩展成2倍数+1
			//Stack类可以自己扩大堆的内存,这叫类是自己管理内存
			elements = Arrays.copyOf(elements , 2*size+1);
		}
	}
	
}

7.2 缓存
  1. 可以用WeakHashMap代表缓存,key没有强引用后,就会被回收
  2. 有些缓存项可能随时间推移,越来越没有价值
    1. 可以通过起一个后台进程,进行清理
    2. 也可以增加新的缓存时清理:LinkedHashMap.removeEldestEntry,发现集合中元素超过一定数量时,清空最早的
7.3 监听器和其他回调

第8条:避免使用终结方法和清除方法

  1. 终结方法:Object的finalize方法。java9已取消
  2. 清除方法:Cleaner.Cleanable的clean方法
  3. 二者都是在对象被回收前被自动调用
8.1 终结方法和清除方法缺点
  1. 不保证会被及时执行甚至不保证被执行:注重时间任务不应该在此处完成,例如关闭文件
    1. System.gc、System.runFinalization:只是增加被执行机会
    2. System.runFinalizersOnExit、Runtime.runFinalizersOnExit:有致命缺点,已经废除
  2. 可移植性差:不同JVM中垃圾回收算法不同,终结/清除方法的执行时间不同,导致某些JVM程序无法正常运行
  3. 终结方法线程优先级低,清除方法稍好,但也有问题
  4. 如果终结方法有异常,该异常导致对象处于破坏状态,如果另一个线程使用该对象,发生不确定行为。正常情况,异常会使线程终止,并打印栈,在终结方法中,不会打印。清除方法无此问题
  5. 性能低下
  6. 存在安全隐患:构造器抛出异常后,如果有finalize方法, 那么该对象有可能还是会被创建
//Vulnerable被攻击类
//1. 如果该类由final修饰,则无法被继承,也就无法被攻击
class Vulnerable {
	Integer value = 0;

	Vulnerable(int value) {
		if (value <= 0) {
			throw new IllegalArgumentException("Vulnerable value must be positive");
		}
		this.value = value;
	}

	@Override
	public String toString() {
		return (value.toString());
	}
	public void test() {
		System.out.println("方法被创建");
	}
	//2. 如果final修饰终结方法,那么该方法无法被继承,同样也无法被攻击
//	public final void finalize() {
//	}
}
//AttackVulnerable攻击类
class AttackVulnerable extends Vulnerable {
	static Vulnerable vulnerable;

	public AttackVulnerable(int value) {
		super(value);
	}
	//正常情况下,构造器抛出的异常,足以防止对象继续存在,但有了终结方法,就做不到了,可以看到对象还是被创建出来,并赋给变量vulnerable
	public void finalize() {
		vulnerable = this;
	}

	public static void main(String[] args) {
		try {
			new AttackVulnerable(-1);
		} catch (Exception e) {
			System.out.println(e);
		}
		System.gc();
		System.runFinalization();
		if (vulnerable != null) {
			//该对象被错误的创建,可以调用该对象上的方法
			vulnerable.test();
		}
	}
}
8.2 需要终止的资源正确处理方式
  1. 实现AutoCloseable
    1. 客户端在实例不再需要时,调用其close方法
    2. 利用try-with-resources确保终止
  2. close方法必须在一个私有域中记录下该对象已不再有效,其他方法调用时,检查该域,并抛出IllegalStateException
8.3 终结方法和清除方法好处
  1. 当资源所有者忘记调用它的close方法,终结方法和清除方法可以充当"安全网",即晚释放总比不释放好,编写这样的安全网,要认真考虑清楚,这种保护是否值得
//FileInputStream类的安全网方法
protected void finalize() throws IOException {
	if ((fd != null) &&  (fd != FileDescriptor.in)) {
    	close();
	}
}
//清除方法实现安全网
import java.lang.ref.Cleaner;

public class Room implements AutoCloseable{
	private static final Cleaner cleaner = Cleaner.create();
	//State必须是静态的嵌套类,因为如果非静态,那么该类包含了对其外围实例的引用,会阻止其被回收。同样也不建议lambda,因为他们很容易捕捉到堆外围对象的引用
	private static class State implements Runnable {
		int numJunkPiles;
		State(int numJunkPiles){
			this.numJunkPiles = numJunkPiles;
		}
		public void run() {
			System.out.println("Cleaning room");
			numJunkPiles = 0;
		}
	}
	private final State state;
	private final Cleaner.Cleanable cleanable;
	public Room(int numJunkPiles) {
		state = new State(numJunkPiles);
		//Cleaner需要通过参数"被清理的对象this",与"清理方法state",注册成一个Cleaner.Cleanable对象
		//"清理方法state"只会在注册的"被清理的对象this"处于虚引用,或显示调用clean方法时,只运行一次
		//如果我们创建了Room对象,但忘记人为调用Room对象的close方法,那么垃圾回收之前,会调用cleanable中的清理任务state的run方法,实现了安全网的功能
		//Room的清除方法state只作为安全网,如果客户端所有Room实例都在try-with-resource块中,永远不会"自动"清除。cleanable.clean()代码叫手动清除
		cleanable = cleaner.register(this,state);
	}
	@Override
	public void close() throws Exception {
		
		cleanable.clean();
	}

}

//客户端1:会先打印Goodbye,再打印清理方法中的Cleaning room
public class Adult {
	public static void main(String[] args) throws Exception {
		try(Room myRoom = new Room(7)){
			System.out.println("Goodbye");
		}
	}
}

//客户端2:我的机器只打印Peace out,因为清除方法不保证被执行。
public class Teenager {
	public static void main(String[] args) {
		new Room(99);
		//如果加上System.gc(),也只是有可能打印Cleaning room,而不是一定
		//System.gc();
		System.out.println("Peace out");
	}
}

  1. 帮助关闭本地对等体对象本地对等体是本地对象,普通对象通过native方法委托给本地对象。由于本地对象不是一个普通对象,垃圾回收器不会知道它,也不会回收它,如果本地对等体没有关键资源,性能也可以接受,可以使用清楚/终结方法处理它,如果有关键资源,性能无法接受,该类就应该有close方法

第9条:try-with-resources优先于try-finally

9.1 try-finally的问题
  1. 示例
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TryFinnalyTest {
	static String firstLineOfFile(String path) throws IOException {
		BufferedReader br = new BufferedReader(new FileReader(path));
		try{
			//1. 当底层物理设备异常,该方法会抛出异常,但finally中的close方法也会抛异常,后一个异常掩盖了前一个,异常栈中没有打印前一个异常信息,不利于诊断错误位置
			br.readLine();
		}finally {
			br.close();
		}
		return path;
	}
	//2. 多个资源关闭时,代码过于冗长
	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[10];
				int n ;
				while((n=in.read(buf))>=0)
					out.write(buf,0,n);
			}finally {
				out.close();
			}
		}finally {
			in.close();
		}
	}
}

9.2 try-with-resources
  1. 必须被关闭的资源实现AutoCloseable接口
  2. 示例
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TryWithResourcesTest {
	static String firstLineOfFile(String path) throws IOException {
		try (BufferedReader br = new BufferedReader(new FileReader(path))) {
			// 1. 当readLine和close都报错时,close异常被禁止,你只能看到readLine的错误栈
			br.readLine();
		} catch (Throwable e) {
			// 2. 可以调用Throwable的getSuppressed方法,获取被禁止的错误栈
			// 3. 可以直接用一个catch捕获try中的readLine和原finnaly块中close异常
			e.getSuppressed();
		}
		return path;
	}

	// 4. 多个资源使用时,仍然很整洁
	static void copy(String src, String dst) throws IOException {
		try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) {
			byte[] buf = new byte[10];
			int n;
			while ((n = in.read(buf)) >= 0)
				out.write(buf, 0, n);
		}
	}
}

发布了32 篇原创文章 · 获赞 0 · 访问量 939

猜你喜欢

转载自blog.csdn.net/hanzong110/article/details/102658164