1. 用静态工厂方法代替构造器说明
在方法内部添加一个静态方法,用于获取一个对象,代替构造器的功能;
比如,在boolean
包装Boolean类中,就有valueOf
方法可以代替构造方法获得一个Boolean对象;
public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE);}
优势
静态方法有名字,可以指定一个功能作为方法名;
实现对象重用,优化程序运行;
在对象使用结束后,可以将对象缓存起来,若下次调用可以再次使用;
相对对象重用,创建一个新的对象损耗可能会更大;
在情况允许时,尽量多地使用对象重用,减少创建对象造成额外损耗;
如Boolean类:Boolean类加载结束后,默认会创建两个Boolean对象,分别表示true和false,在使用静态工厂创建对象时,直接将代表true或false的对象返回,以节约内存使用和程序效率。
public final class Boolean implements java.io.Serializable, Comparable{//默认创建两个Boolean对象,用于表示TRUE和FALSE public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false);// 包装了boolean类,这里存值 private final boolean value;// 构造方法新创建了一个Boolean对象 public Boolean(boolean value) { this.value = value; }//使用valueOf方法,直接返回Boolean类加载时创建的两个静态对象,无需再次创建对象。 public static Boolean valueOf(boolean b) {return (b ? TRUE : FALSE);} public static Boolean valueOf(String s) { return parseBoolean(s) ? TRUE : FALSE; }}
依据不同的参数,可以返回任何子类的对象,也可以返回不同的对象;
- 应用:
- 静态方法可以返回对象,而无需将对象的类设为公有的;
- 静态方法可以通过接口返回不同的对象。
EnumSet
没有构造器,只能通过静态工厂创建对象,在OpenJDK实现中,EnmuSet的实现有两种类型:RegalarEumSet
和JumboEnumSet
;当枚举元素数量等于小于64时,静态工厂方法返回RegalarEumSet对象;当枚举元素数量大于64时,静态工厂方法返回JumboEnumSet对象。(对于调用者,无需知道背后的实现原理,直接使用就好;对于EnumSet开发者,此做法用于代码优化。)
- 应用:
方法返回的对象所属的类,在编写静态工厂方法的类时可以不存在;
- 如mysql和JDBC;
缺点
类如果不包含public的构造器,则不能被继承;
静态工厂方法需要程序员主动去寻找,而非构造方法可以直接使用;
- 示例:
class Apple { private String type; private String status;//构造方法名与类名相同,可以直接使用 public Apple(String type, String status) { this.type = type; this.status = status; } //静态工厂方法需要在API中寻找,没有构造方法方便 public static Apple getNewApple(){return new Apple("redApple","fresh");}}
一些惯例
from,从别的类型进行转换,只有一个参数;
of,将多个参数合并;
//将多个参数合并到一起public Set of(String ...colors){ Set apples = new HashSet(); for (String s : colors) { apples.add(new Apple("Red")); } return apples;}
- valueOf,也是类型转换;
- createInstance或getInstance,通过参数获取一个对象,参数可以与成员变量不同;
- createInstance或netInstance,保证每次返回一个新创建的实例;
- getInstance一般用在单例模式。
- getType(这里可以是getApple),与getInstance一致;
- newType,与netInstance类似;
- type,getType和newType的简化版。
2. 遇到多个构造器参数,可以考虑使用构建器(Builder)说明
若一个类有多个参数,且对象使用构建器进行创建;
- 有些参数有些时候不需要输入,但构造器中必须填入一个值;
- JavaBeans模式,即一堆setter方法,这样可以解决上面的问题,但JavaBeans模式有严重的缺点,在构造过程中JavaBean可能处于不一致状态,即线程不安全。
这个时候,就可以考虑使用建造者Builder模式
public class 建造者模式 { public static void main(String[] args) { Cat cat = new Cat.Builder("小黑") .age(12).color("White").build(); System.out.println(cat); }}class Cat{ private String name; private int age; private String color; private String owner; public static class Builder{ //必要参数 private String name; //可选参数 private int age; private String color; private String owner; public Builder(String name) {this.name = name;} public Builder age(int val){age=val;return this;} public Builder color(String val){color=val;return this;} public Builder owner(String val){owner=val;return this;} public Cat build(){return new Cat(this);} } public Cat(Builder builder) { owner = builder.owner; color = builder.color; age = builder.age; name = builder.name; }// toString }
Builder模拟了具有名字的可选参数,这样的客户端易于编写,易于阅读;
示例
代码
//这里先创建一个抽象类FriedRice//然后分别创建两个类继承FriedRice,分别为FriedRiceWithHam和FriedRiceWithEgg//fried rice 炒饭 可以添加 老干妈LaoGanMa、辣条LaTiao、再加一个鸡蛋Egg等// ham 火腿 egg鸡蛋//FriedRiceWithHam 火腿炒饭,可以有:大、中、小 三种 LARGE MEDIUM SMALL//FriedRiceWithEgg 蛋炒饭,spicy辣度 可以选择:little微辣 general中辣 very特辣//具体开发中不要使用中文,也不要使用拼音//先整一个抽象类FriedRiceabstract class FriedRice{ //额外要加的东西 public enum Ingredient{老干妈,辣条,Egg}//实际开发不要使用中文 private Set ingredientSet; abstract static class Builder<T extends Builder>{ EnumSet ingredients = EnumSet.noneOf(Ingredient.class);//默认没有配料 public T addIngredient(Ingredient val){ingredients.add(val);return self();}//添加配料 public abstract FriedRice build(); protected abstract T self(); } FriedRice(Builder builder){ ingredientSet = builder.ingredients.clone(); }}//创建一个FriedRiceWithHam火腿炒饭@ToString(callSuper = true)//是Lombok插件的注解,可以自动生成toString方法,文章主要讲解内容不包含这部分,忽略就好class FriedRiceWithHam extends FriedRice{ public enum Size{SMALL,MEDIUM,LARGE} private Size size;//大小 public static class Builder extends FriedRice.Builder{ private Size size; public Builder(Size size){this.size = size;} @Override public FriedRice build() {return new FriedRiceWithHam(this);} @Override protected Builder self() {return this;} } FriedRiceWithHam(Builder builder) { super(builder); this.size = builder.size; }}//创建一个FriedRiceWithEgg鸡蛋炒饭@ToString(callSuper = true)//是Lombok插件的注解,可以自动生成toString方法,文章主要讲解内容不包含这部分,忽略就好class FriedRiceWithEgg extends FriedRice{ public enum Spicy{LITTLE,GENERAL,VERY} private Spicy spicy; public static class Builder extends FriedRice.Builder{ private Spicy spicy; public Builder(Spicy spicy){this.spicy = spicy;} @Override public FriedRice build() {return new FriedRiceWithEgg(this);} @Override protected Builder self() {return this;} } FriedRiceWithEgg(Builder builder) { super(builder); spicy = builder.spicy; }}
使用
public class Builder模式也适用于类层次结构 { public static void main(String[] args) { //创建一个鸡蛋炒饭,中辣,添加老干妈 FriedRice friedRiceWithEgg = new FriedRiceWithEgg.Builder(FriedRiceWithEgg.Spicy.GENERAL) .addIngredient(FriedRice.Ingredient.老干妈).build(); //创建一个火腿炒饭,大份,添加鸡蛋 FriedRice friedRiceWithHam = new FriedRiceWithHam.Builder(FriedRiceWithHam.Size.LARGE) .addIngredient(FriedRice.Ingredient.Egg).build(); }}
3. 用私有构造器或枚举类型强化Singleton属性
Singleton,即单例模式;对于一个类,只会被实例化一次,后续通过静态方法获取对象也只能获取到这一个对象,不会再次创建新的对象。
创建一个Singleton,有两种方式私有构造器
将构造器私有化,然后通过getInstance方法创建并获取对象。
发展默认情况下,可以通过以下方式实现单例模式。
//Chopsticks n.筷子//这里假定筷子只能有一根//这里创建一个单例对象class Chopstick{ private static final Chopstick INSTANCE = new Chopstick();//类加载后,自动创建一个Chopstick对象, private Chopstick(){}//构造器私有化,禁止二次创建 public static Chopstick getInstance(){return INSTANCE;}//获取实例}
但是,这个单例是可以通过反射进行破坏;
public static void main(String[] args) throws Exception { Chopstick instance = Chopstick.getInstance();//第一个实例对象 //第二个实例对象 Class aClass = Class.forName("com.yn.study.chapter1.Chopstick");//获取Class对象 Constructor declaredConstructor = aClass.getDeclaredConstructor();//获取Constru对象 declaredConstructor.setAccessible(true);//跳过private检查 Chopstick chopstick = (Chopstick) declaredConstructor.newInstance();//创建实例对象 System.out.println(instance); System.out.println(chopstick); /**输出结果如下: * com.yn.study.chapter1.Chopstick@7f31245a * com.yn.study.chapter1.Chopstick@6d6f6e28 * 表示这两个对象不是同一个对象 */}
所以,可以在构造方法里面添加判断,让第二次创建过程抛出错误来解决破坏;
class Chopstick{ private static final Chopstick INSTANCE = new Chopstick();//类加载后,自动创建一个Chopstick对象, private Chopstick() {if (INSTANCE!=null)throw new Error("请不要二次创建对象");}//构造器私有化,禁止二次创建 public static Chopstick getInstance(){return INSTANCE;}//获取实例}
如果要使对象变得可序列化,必须声明readResolve方法
如果要使对象变得可序列化,仅仅在声明中加上implements Serializable
是不够的,为了维护Singleton,必须声明所有实例域是transient(瞬时)的,并声明readResolve方法;
否则,每当反序列化一个对象,都会创建一个新的对象;
public static void main(String[] args) throws Exception { //单例破解方案——序列化:将对象存储于文件中,然后从文件中读取 //创建一个单例对象 Chopstick chopstick1 = Chopstick.getInstance(); //将对象写入文件 File file = new File("Chopstick.dat"); FileOutputStream os = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(chopstick1); oos.close();os.close(); //将对象从文件中读取 FileInputStream is = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(is); Chopstick chopstick2 = (Chopstick) ois.readObject();//第二个实例化对象 System.out.println(chopstick1); System.out.println(chopstick2); /** * 输出结果 * com.yn.study.chapter1.Chopstick@2503dbd3 * com.yn.study.chapter1.Chopstick@7ef20235 * 表示这两个对象不是一个对象 */}
声明readResolve方法
private Object readResolve(){return INSTANCE;}
这样,上面的结果获得的将是同一个对象。
com.yn.study.chapter1.Chopstick@2503dbd3com.yn.study.chapter1.Chopstick@2503dbd3
使用
//Chopsticks n.筷子//这里假定筷子只能有一根//这里创建一个单例对象class Chopstick implements Serializable { private static final Chopstick INSTANCE = new Chopstick();//类加载后,自动创建一个Chopstick对象, private Chopstick() {if (INSTANCE!=null)throw new Error("请不要二次创建对象");}//构造器私有化,禁止二次创建 public static Chopstick getInstance(){return INSTANCE;}//获取实例 private Object readResolve(){return INSTANCE;}//写readResolve方法,防止反序列化破坏单例}
枚举类
枚举本就是一个单例对象,而且不可破坏。
enum ChopstickPlus{ INSTANCE; ChopstickPlus getInstance(){return INSTANCE;}}
4. 通过私有构造器,使得类不可实例化
有些类只包含静态方法或静态域,这样的类不希望会被实例化,因为这些类被实例化是没有意义的;
这里我表示疑惑:应该一般情况下没有人会去尝试实例化一个只有静态方法的类,嗯..但是…,书上说有一些时候会无意识的初始化该类??下面继续记笔记。
对于没有特别声明构造器的类,其构造器默认是public的,
这里可以通过将构造器私有化,来避免不必要的实例化。
同样,为避免通过反射创建对象,可以在构造方法里添加抛出错误,防止类实例化。
//EasyMath 一个简单的,无意义的,仅用于学习的,计算类class EasyMath{ public static long sum(long a,long b){return a+b;}//一个求和的静态方法 //不希望不必要的工具类实例化 private EasyMath(){throw new AssertionError();}}
但这样有个缺点:这个类不能有父类。
5. 优先考虑依赖注入引用资源
这里..就只写个标题吧。。
详见Effective Java 第三版 P16页。