1. 用静态工厂方法代替构造器 说明 在方法内部添加一个静态方法,用于获取一个对象,代替构造器的功能;
比如,在boolean
包装Boolean类中,就有valueOf
方法可以代替构造方法获得一个Boolean对象;
1 2 3 public static Boolean valueOf (boolean b) { return (b ? TRUE : FALSE); }
优势
静态方法有名字,可以指定一个功能作为方法名;
实现对象重用,优化程序运行;
在对象使用结束后,可以将对象缓存起来,若下次调用可以再次使用;
相对对象重用 ,创建一个新的对象 损耗可能会更大;
在情况允许时,尽量多地使用对象重用 ,减少创建对象造成额外损耗;
如Boolean类:Boolean类加载结束后,默认会创建两个Boolean对象,分别表示true和false,在使用静态工厂创建对象 时,直接将代表true或false的对象返回,以节约内存使用和程序效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public final class Boolean implements java .io.Serializable, Comparable<Boolean> { public static final Boolean TRUE = new Boolean (true ); public static final Boolean FALSE = new Boolean (false ); private final boolean value; public Boolean (boolean value) { this .value = value; } 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开发者,此做法用于代码优化。)
方法返回的对象所属的类,在编写静态工厂方法的类时可以不存在;
缺点
类如果不包含public的构造器,则不能被继承;
静态工厂方法需要程序员主动去寻找,而非构造方法可以直接使用;
1 2 3 4 5 6 7 8 9 10 11 class Apple { private String type; private String status; public Apple (String type, String status) { this .type = type; this .status = status; } public static Apple getNewApple () {return new Apple ("redApple" ,"fresh" );} }
一些惯例
from,从别的类型进行转换,只有一个参数;
of,将多个参数合并;
1 2 3 4 5 6 7 8 public Set<Apple> of (String ...colors) { Set<Apple> 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模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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; } }
Builder模拟了具有名字的可选参数,这样的客户端易于编写,易于阅读;
示例
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 abstract class FriedRice { public enum Ingredient {老干妈,辣条,Egg} private Set<Ingredient> ingredientSet; abstract static class Builder <T extends Builder <T>>{ EnumSet<Ingredient> 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(); } } @ToString(callSuper = true) class FriedRiceWithHam extends FriedRice { public enum Size {SMALL,MEDIUM,LARGE} private Size size; public static class Builder extends FriedRice .Builder<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; } } @ToString(callSuper = true) class FriedRiceWithEgg extends FriedRice { public enum Spicy {LITTLE,GENERAL,VERY} private Spicy spicy; public static class Builder extends FriedRice .Builder<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; } }
使用
1 2 3 4 5 6 7 8 9 10 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方法创建并获取对象。
发展 默认情况下,可以通过以下方式实现单例模式。 1 2 3 4 5 6 7 8 class Chopstick { private static final Chopstick INSTANCE = new Chopstick (); private Chopstick () {} public static Chopstick getInstance () {return INSTANCE;} }
但是,这个单例是可以通过反射进行破坏; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public static void main (String[] args) throws Exception { Chopstick instance = Chopstick.getInstance(); Class<?> aClass = Class.forName("com.yn.study.chapter1.Chopstick" ); Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(); declaredConstructor.setAccessible(true ); Chopstick chopstick = (Chopstick) declaredConstructor.newInstance(); System.out.println(instance); System.out.println(chopstick); }
所以,可以在构造方法里面添加判断,让第二次创建过程抛出错误来解决破坏; 1 2 3 4 5 class Chopstick { private static final Chopstick INSTANCE = new Chopstick (); private Chopstick () {if (INSTANCE!=null )throw new Error ("请不要二次创建对象" );} public static Chopstick getInstance () {return INSTANCE;} }
如果要使对象变得可序列化,必须声明readResolve方法 如果要使对象变得可序列化,仅仅在声明中加上implements Serializable
是不够的,为了维护Singleton,必须声明所有实例域是transient(瞬时)的,并声明readResolve方法;
否则,每当反序列化一个对象,都会创建一个新的对象;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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); }
声明readResolve方法
1 private Object readResolve () {return INSTANCE;}
这样,上面的结果获得的将是同一个对象。
1 2 com.yn.study.chapter1.Chopstick@2503dbd3 com.yn.study.chapter1.Chopstick@2503dbd3
使用 1 2 3 4 5 6 7 8 9 class Chopstick implements Serializable { private static final Chopstick INSTANCE = new Chopstick (); private Chopstick () {if (INSTANCE!=null )throw new Error ("请不要二次创建对象" );} public static Chopstick getInstance () {return INSTANCE;} private Object readResolve () {return INSTANCE;} }
枚举类 枚举本就是一个单例对象,而且不可破坏。
1 2 3 4 enum ChopstickPlus { INSTANCE; ChopstickPlus getInstance () {return INSTANCE;} }
4. 通过私有构造器,使得类不可实例化 有些类只包含静态方法或静态域,这样的类不希望会被实例化,因为这些类被实例化是没有意义的;
这里我表示疑惑:应该一般情况下没有人会去尝试实例化一个只有静态方法的类,嗯..但是…,书上说有一些时候会无意识的初始化该类??下面继续记笔记。
对于没有特别声明构造器的类,其构造器默认是public的,
1 2 3 4 5 6 class EasyMath { public static long sum (long a,long b) {return a+b;} private EasyMath () {throw new AssertionError ();} }
但这样有个缺点:这个类不能有父类。
5. 优先考虑依赖注入引用资源 这里..就只写个标题吧。。
详见Effective Java 第三版 P16页。