1、术语
(1)Java语言支持四种类型:接口、类、数组、基本类型(primitive),前三种为引用类型,而基本类型的值不是对象。
(2)方法的签名包括它的名称和所有参数的类型,签名不包括它的返回类型。
2、静态工厂替代构造器
(1)静态工厂有名称
(2)不必再每次调用都创建一个新的对象
public static Boolean valueOf(boolean b){
return b? Boolean.TRUE : Boolean.FALSE;
}
(3)可以返回原返回类型的任何子类对象
适用于返回类型为接口/抽象类
public interface Service {
// Service-specific methods go here
}
public interface Provider {
Service newService();
}
public class Services {
private Services() { } // Prevents instantiation (Item 4)
// Maps service names to services
private static final Map<String, Provider> providers =
new ConcurrentHashMap<String, Provider>();
public static final String DEFAULT_PROVIDER_NAME = "<def>";
// Provider registration API
public static void registerDefaultProvider(Provider p) {
registerProvider(DEFAULT_PROVIDER_NAME, p);
}
public static void registerProvider(String name, Provider p){
providers.put(name, p);
}
// Service access API
public static Service newInstance() {
return newInstance(DEFAULT_PROVIDER_NAME);
}
public static Service newInstance(String name) {
Provider p = providers.get(name);
if (p == null)
throw new IllegalArgumentException(
"No provider registered with name: " + name);
return p.newService();
}
}
客户端
public class Test {
public static void main(String[] args) {
// Providers would execute these lines
Services.registerDefaultProvider(DEFAULT_PROVIDER);
Services.registerProvider("comp", COMP_PROVIDER);
Services.registerProvider("armed", ARMED_PROVIDER);
// Clients would execute these lines
Service s1 = Services.newInstance();
Service s2 = Services.newInstance("comp");
Service s3 = Services.newInstance("armed");
System.out.printf("%s, %s, %s%n", s1, s2, s3);
}
private static Provider DEFAULT_PROVIDER = new Provider() {
public Service newService() {
return new Service() {
@Override public String toString() {
return "Default service";
}
};
}
};
private static Provider COMP_PROVIDER = new Provider() {
public Service newService() {
return new Service() {
@Override public String toString() {
return "Complementary service";
}
};
}
};
private static Provider ARMED_PROVIDER = new Provider() {
public Service newService() {
return new Service() {
@Override public String toString() {
return "Armed service";
}
};
}
};
}
(4)创建参数化实例更简洁
public static <K,V> HashMap<K,V> newInstance(){
return new HashMap<K,V)();
}
(5)静态工厂惯用名称
valueOf,of,getInstance,newInstance,getType,newType
3、用Builder模式替代多参数的构造器
构造器的参数太多的话,调用方容易混淆,特别是针对相邻的同类型的参数,如果不小心将顺序颠倒,则编译时难以发现,运行时出问题。
(1) 重叠构造器版本,好处是安全性,坏处是难以阅读
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // optional
private final int fat; // (g) optional
private final int sodium; // (mg) optional
private final int carbohydrate; // (g) 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;
}
public static void main(String[] args) {
NutritionFacts cocaCola =
new NutritionFacts(240, 8, 100, 0, 35, 27);
}
}
(2)JavaBean版本,好处是可读性,坏处是自己要保证线程安全
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; // Required; no default value
private int servings = -1; // " " " "
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; }
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
}
}
(3)Builder版本,兼容二者好处
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 carbohydrate = 0;
private int sodium = 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 carbohydrate(int val)
{ carbohydrate = val; return this; }
public Builder sodium(int val)
{ sodium = 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;
}
public static void main(String[] args) {
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
}
}
newInstance方法可充当build方法的一部分,但是它总是企图去调用类的无参构造器,如果类没有无参构造器,编译时是不会暴露出来的,只能到客户端调用运行时才暴露。
(4)实践版
一般比如sql构造之类的,参数太多,可以用builder模式,普通bean的赋值,采用builder反而搞得太过负责,工业上的最佳实践,一般是采用JavaBean + Design by Contract 的模式,要求开发者根据约定传参数,service层再进行一层校验,来确保必填参数是不为null的。
4、私有构造器或枚举强化Singleton属性
(1)私有构造器能够防止通过反射调用去构造实例
(2)为确保反序列化之后还是单例,需要重写readResolve方法,return INSTANCE,或者直接使用枚举单例,它内置了反序列化单例的功能。
5、避免创建不必要的对象
(1)达到尽量重用对象的目的,比如字符串字面常量,不可变的对象。
反例
public class Person {
private final Date birthDate;
public Person(Date birthDate) {
// Defensive copy - see Item 39
this.birthDate = new Date(birthDate.getTime());
}
// Other fields, methods omitted
// DON'T DO THIS!
public boolean isBabyBoomer() {
// Unnecessary allocation of expensive object
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;
}
}
正解:使用静态初始化器,初始化起止日期
class Person {
private final Date birthDate;
public Person(Date birthDate) {
// Defensive copy - see Item 39
this.birthDate = new Date(birthDate.getTime());
}
// Other fields, methods
/**
* The starting and ending dates of the baby boom.
*/
private static final Date BOOM_START;
private static final Date BOOM_END;
static {
Calendar gmtCal =
Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}
public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0 &&
birthDate.compareTo(BOOM_END) < 0;
}
}
(2)优先使用基本类型,而不是封装类型,当心无意识的自动装箱额外产生的对象,下面的sum声明为long,即可减少2的31次方个多余的Long实例。
public class Sum {
// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
}
(3)对象创建的误解
误解:对象创建的代价非常昂贵,应该避免创建对象。
正解:小对象的创建,构造器只做很少量的显式工作,其创建和回收都是非常廉价的,因而对小对象没必要搞什么对象池之类的;只有重量级的对象,比如数据库连接池等,连接数据库代价是昂贵的,采用对象池正好。
6、消除过期引用
public class Stack {
private Object[] elements;
private int size = 0;
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, roughly
* doubling the capacity each time the array needs to grow.
*/
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;
}
(1)清空对象引用应该是一种例外,而不是一种行为规范,不必每次使用变量的时候,都紧张兮兮的,考虑要不要在使用后赋值为null
(2)内存泄露的常见来源
A、自己管理内存的容器
B、缓存
C、监听器和回调方法(注册监听,不需要的时候没有取消监听)
7、避免使用finalize方法
(1)JVM不保证finalize方法会被及时执行,而且根本不保证它们会被执行
(2)不要依赖finalize方法去关闭重要资源,比如关闭文件、关闭数据库连接,一般采用finally语句即可
(3)使用finalize可能会造成额外的性能损失