首页 > 代码库 > Effective Java 读书笔记(2创建和销毁对象)

Effective Java 读书笔记(2创建和销毁对象)

第一章是引言,所以这里不做笔记,总结一下书中第一章的主要内容是向我们解释了这本书所做的事情:指导Java程序员如何编写出清晰、正确、可用、健壮、灵活和可维护的程序


2.1考虑用静态工厂方法代替构造器

静态工厂方法与构造器相比有四大优势:

(1)静态工厂方法有名称,具有适当名称的静态工厂方法易于使用、易于阅读

(2)不必每次在调用它们的时候都创建一个新的对象

(3)可以返回原返回类型的任何子类型的对象

(4)在创建参数化类型实例的时候,它们使代码变得更加简洁

同时静态工厂方法也有两大缺点:

(1)类如果不含公有的或者受保护的构造器,就不能被子类化;

(2)它们与其它静态方法实际上没有任何区别

静态工厂方法的一些惯用名称如下:

valueOf------该方法返回的实例与它的参数具有相同的值,实际上是类型转换方法;

getInstance------返回的实例是通过方法的参数来描述的,对于单例模式(Singleton)来说,该方法无参数,并返回唯一的实例;

newInstance------功能同getInstance,但与getInstance不同的是,它能够确保返回的每个实例都与其它实例不同。

因此在写程序的时候我们可以优先考虑静态工厂方法,然后再考虑构造器。


2.2遇到多个构造器参数时要考虑用构建器

如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是种不错的选择。

其与传统构造器的优势是易于阅读和编写,其与JavaBeans的优势是安全(它能保证所有的可选参数都有默认值)。

书中的这个例子非常的好,我们可以从中体会到使用Builder模式的方法和好处:

public class NutritionFacts {
	
	private final int servingSize; 		//(ml)					required
	private final int servings;			//(per container)		required
	private final int colories;			//						optional
	private final int fat;				//(g)					optional
	private final int sodium;			//(mg)					optional
	private final int carbohydrate;		//(g)					optional
	
	public static class Builder{
		//required parameters
		private final int servingSize;
		private final int servings;
		
		//optional parameters ------initialized to default values
		private int colories = 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 setColories(int val){
			this.colories = val;
			return this;
		}
		
		public Builder setFat(int val){
			this.fat = val;
			return this;
		}
		
		public Builder setSodium(int val){
			this.sodium = val;
			return this;
		}
		
		public Builder setCarbohydrate(int val){
			this.carbohydrate = val;
			return this;
		}
		
		public NutritionFacts build(){
			return new NutritionFacts(this);
		}
	}
	
	private NutritionFacts(Builder builder){
		this.servingSize = builder.servingSize;
		this.servings = builder.servings;
		this.colories = builder.colories;
		this.fat = builder.fat;
		this.sodium = builder.sodium;
		this.carbohydrate = builder.carbohydrate;
	}
}
这样写了之后我们在别的地方去新建一个NutritionFacts实例,方法参数变短,并可设置任意个可选参数,且构造过程非常安全,比如写这样的语句去创建新的NutitionFacts实例:

NutritionFacts nutritionFacts = new NutritionFacts.Builder(240, 8)
				.setCarbohydrate(10).build();


2.3用私有构造器或者枚举类型强化Singleton属性

例子代码如下:

public class Elvis {
	
	private static final Elvis INSTANCE = new Elvis();
	
	private Elvis(){
		
	}
	
	public static Elvis getInstance() {
		return INSTANCE;
	}
	
	
}
私有构造器仅被调用一次,用来实例化public的静态final域INSTANCE,由于没有public和protected构造器,所以保证了Elvis的全局唯一性。


2.4通过私有构造器强化不可实例化的能力

当我们在写一个工具类的时候,我们为这个工具类定义了很多静态方法,我们通过“类名.方法名”这种方式来使用这个类的功能,所以我们不希望这个类能被实例化,但是如果我们不定义构造器,Java编译器会自动的提供一个public无参的缺省构造器给这个类,所以这个时候我们为这个工具类创建一个私有构造器,来强化其不可实例化的能力,示例代码如下:

//Noninstantiable utility class
public class UtilityClass {
	
	//Suppress default constructor for noninstantiability
	private UtilityClass(){
		throw new AssertionError();
	}

}

由于构造器是private,所以在类的外部不能访问它,AssertionError()可以避免不小心在类的内部调用构造器,它保证该类在任何情况下都不会被实例化。


2.5避免创建不必要的对象

一段示例代码如下:

public class Person {
	
	private Date birthDate;
	
	//other fields,methods,and constructor omitted
	
	public boolean isBabyBoomer(){
		
		Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
		
		gmtCal.set(1946, Calendar.JANUARY,1,0,0,0);
		Date boomStart = gmtCal.getTime();
		gmtCal.set(1965, Calendar.JANUARY,1,0,0,0);
		Date boomEnd = gmtCal.getTime();
		return birthDate.compareTo(boomStart)>=0 && birthDate.compareTo(boomEnd)<0;
		
	}

}
像boomStart和boomEnd这两个对象在程序中是不会改变的对象,但写成局部变量,每次调用isBabyBoomer()方法的时候都会去创建两个对象,所以这个时候用一个静态初始化器,避免这种低效率的情况的发生:

public class NewPerson {
	
	private Date birthDate;
	
	private static final Date boomStart;
	private static final Date boomEnd;

	static {
		Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
		gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
		boomStart = gmtCal.getTime();
		gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
		boomEnd = gmtCal.getTime();
	}
	
	public boolean isBabyBoomer(){
		return birthDate.compareTo(boomStart)>=0 && birthDate.compareTo(boomEnd)<0;
	}

}

另外在写代码的时候,如果不需要用到包装器类型,尽量使用基本类型来做,而不要使用包装器类型,这样程序的效率更高。


2.6消除过期的对象引用

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();
		}
		
		return elements[--size];
	}
	
	/**
	 * Ensure space for at least one more element
	 */
	private void ensureCapacity(){
		if(elements.length == size){
			elements = Arrays.copyOf(elements, 2 * size + 1);
		}
	}

}

栈内部维护着过期引用,有可能引起泄漏,因此这里将代码改为:

public Object pop(){
		if(size == 0){
			throw new EmptyStackException();
		}
		Object result = elements[--size];
		elements[size] = null;
		return result;
	}

清空过期引用。


2.7避免使用终结方法

终结方法(finally)通常是不可预测的,是很危险的,一般情况下是不必要的。使用终结方法会导致行为不稳定、降低性能,以及可移植性问题。除非作为安全网,或者是为了终止非关键的本地资源,否则请不要使用终结方法。