Effective Java (创建和销毁对象)
1、斟酌用静态工厂方法代替构造器: 1 public static Boolean valueOf(boolean b) { 2 return b ? Boolean.TRUE : Boolean.FALSE; 3 }
静态工厂方法和构造器不同有以下主要优势: 1 void showExample() { 2 String strEmpty = String::empty(); 3 String strEmpty2 = ""; 4 String strData = String::prellocate(1024); 5 if (strEmpty.isEmpty()) { 6 //TODO: do something 7 } 8 } 9 static String String::emptyString; 10 String& String::empty() { 11 return emptyString; 12 } 13 14 bool String::isEmpty() { 15 if (this->_internal == &emptyString->_internal) 16 return true; 17 //TODO: do other justice to verify whether it is empty. 18 }
在上面的代码中,提供了两个静态工厂方法empty和preallocate用于分别创建1个空对象和1个带有指定分配空间的String对象。从使用方式来看,这些静态方法确切提供了成心义的名称,使用者很容易就能够判断出它们的作用和利用场景,而没必要在1组重载的构造器中去搜索每个构造函数及其参数列表,以找出合适当前场景的构造函数。从效力方面来说,由于提供了唯1的静态空对象,当判读对象实例是不是为空时(isEmpty),直接使用预制静态空对象(emptyString)的地址与当前对象进行比较,如果是同1地址,便可确认当前实例为空对象了。对preallocate函数,顾名思义,该函数预分配了指定大小的内存空间,后面在使用该String实例时,没必要担心赋值或追加的字符过量而致使频繁的realloc等操作。 1 class NutritionFacts { 2 private final int servingSize; 3 private final int servings; 4 private final int calories; 5 private final int fat; 6 private final int sodium; 7 private final int carbohydrate; 8 public static class Builder { 9 //对象的必选参数 10 private final int servingSize; 11 private final int servings; 12 //对象的可选参数的缺省值初始化 13 private int calories = 0; 14 private int fat = 0; 15 private int carbohydrate = 0; 16 private int sodium = 0; 17 //只用少数的必选参数作为构造器的函数参数 18 public Builder(int servingSize,int servings) { 19 this.servingSize = servingSize; 20 this.servings = servings; 21 } 22 public Builder calories(int val) { 23 calories = val; 24 return this; 25 } 26 public Builder fat(int val) { 27 fat = val; 28 return this; 29 } 30 public Builder carbohydrate(int val) { 31 carbohydrate = val; 32 return this; 33 } 34 public Builder sodium(int val) { 35 sodium = val; 36 return this; 37 } 38 public NutritionFacts build() { 39 return new NutritionFacts(this); 40 } 41 } 42 private NutritionFacts(Builder builder) { 43 servingSize = builder.servingSize; 44 servings = builder.servings; 45 calories = builder.calories; 46 fat = builder.fat; 47 sodium = builder.sodium; 48 carbohydrate = builder.carbohydrate; 49 } 50 } 51 //使用方式 52 public static void main(String[] args) { 53 NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).calories(100) 54 .sodium(35).carbohydrate(27).build(); 55 System.out.println(cocaCola); 56 }
对Builder方式,可选参数的缺省值问题也将不再困扰着所有的使用者。这类方式还带来了1个间接的好处是,不可变对象的初始化和参数合法性的验证等工作在构造函数中原子性的完成了。 1 public class Elvis { 2 public static final Elvis INSTANCE = new Elvis(); 3 private Elivs() { ... } 4 public void leaveTheBuilding() { ... } 5 }
这样的方式主要优势在于简洁高效,使用者很快就可以判定当前类为单实例类,在调用时直接操作Elivs.INSTANCE便可,由于没有函数的调用,因此效力也非常高效。但是事物是具有1定的双面性的,这类设计方式在1个方向上走的过于极端了,因此他的缺点也会是非常明显的。如果今后Elvis的使用代码被迁移到多线程的利用环境下了,系统希望能够做到每一个线程使用同1个Elvis实例,不同线程之间则使用不同的对象实例。那末这类创建方式将没法实现该需求,因此需要修改接口和接口的调用者代码,这样就带来了更高的修改本钱。 1 public class Elvis { 2 public static final Elvis INSTANCE = new Elvis(); 3 private Elivs() { ... } 4 public static Elvis getInstance() { return INSTANCE; } 5 public void leaveTheBuilding() { ... } 6 }
这类方法很好的弥补了第1种方式的缺点,如果今后需要适应多线程环境的对象创建逻辑,仅需要修改Elvis的getInstance()方法内部便可,对用调用者而言则是不变的,这样便极大的缩小了影响的范围。至于效力问题,当今的JVM针对该种函数都做了很好的内联优化,因此不会产生因函数频繁调用而带来的开消。 1 public enum Elvis { 2 INSTANCE; 3 public void leaveTheBuilding() { ... } 4 }
就目前而言,这类方法在功能上和公有域方式相近,但是他更加简洁更加清晰,扩大性更强也更加安全。 1 public class UtilityClass { 2 //Suppress default constructor for noninstantiability. 3 private UtilityClass() { 4 throw new AssertionError(); 5 } 6 }
这样定义以后,该类将不会再被外部实例化了,否则会产生编译毛病。但是这样的定义带来的最直接的负面影响是该类将不能再被子类化。 1 public class Person { 2 private final Date birthDate; 3 //判断该婴儿是不是是在生育高峰期诞生的。 4 public boolean isBabyBoomer { 5 Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 6 c.set(1946,Calendar.JANUARY,1,0); 7 Date dstart = c.getTime(); 8 c.set(1965,0); 9 Date dend = c.getTime(); 10 return birthDate.compareTo(dstart) >= 0 && birthDate.compareTo(dend) < 0; 11 } 12 } 13 14 public class Person { 15 private static final Date BOOM_START; 16 private static final Date BOOM_END; 17 18 static { 19 Calender c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 20 c.set(1946,0); 21 BOOM_START = c.getTime(); 22 c.set(1965,0); 23 BOOM_END = c.getTime(); 24 } 25 public boolean isBabyBoomer() { 26 return birthDate.compareTo(BOOM_START) >= 0 && birthDate.compareTo(BOOM_END) < 0; 27 } 28 }
改进后的Person类只是在初始化的时候创建Calender、TimeZone和Date实例1次,而不是在每次调用isBabyBoomer方法时都创建1次他们。如果该方法会被频繁调用,效力的提升将会极其显著。 1 public static void main(String[] args) { 2 Long sum = 0L; 3 for (long i = 0; i < Integer.MAX_VALUE; ++i) { 4 sum += i; 5 } 6 System.out.println(sum); 7 }
本例中由于错把long sum定义成Long sum,其效力下降了近10倍,这其中的主要缘由便是该毛病致使了2的31次方个临时Long对象被创建了。 1 public class Stack { 2 private Object[] elements; 3 private int size = 0; 4 private static final int DEFAULT_INITIAL_CAPACITY = 16; 5 public Stack() { 6 elements = new Object[DEFAULT_INITIAL_CAPACITY]; 7 } 8 public void push(Object e) { 9 ensureCapacity(); 10 elements[size++] = e; 11 } 12 public Object pop() { 13 if (size == 0) 14 throw new EmptyStackException(); 15 return elements[--size]; 16 } 17 private void ensureCapacity() { 18 if (elements.length == size) 19 elements = Arrays.copys(elements,2*size+1); 20 } 21 } 以上示例代码,在正常的使用中不会产生任何逻辑问题,但是随着程序运行时间不断加长,内存泄漏酿成的副作用将会渐渐的显现出来,如磁盘页交换、OutOfMemoryError等。那末内存泄漏隐藏在程序中的甚么地方呢?当我们调用pop方法是,该方法将返回当前栈顶的elements,同时将该栈的活动区间(size)减1,但是此时被弹出的Object依然保持最少两处援用,1个是返回的对象,另外一个则是该返回对象在elements数组中原有栈顶位置的援用。这样即使外部对象在使用以后不再援用该Object,那末它依然不会被垃圾搜集器释放,长此以往致使了更多类似对象的内存泄漏。修改方式以下: 1 public Object pop() { 2 if (size == 0) 3 throw new EmptyStackException(); 4 Object result = elements[--size]; 5 elements[size] = null; //手工将数组中的该对象置空 6 return result; 7 }
由于现有的Java垃圾搜集器已足够只能和强大,因此没有必要对所有不在需要的对象履行obj = null的显示置空操作,这样反而会给程序代码的浏览带来没必要要的麻烦,该条目只是推荐在以下3中情形下需要斟酌资源手工处理问题: 1 public void test() { 2 FileInputStream fin = null; 3 try { 4 fin = new FileInputStream(filename); 5 //do something. 6 } finally { 7 fin.close(); 8 } 9 } 那末在实际的开发中,利用finalizer又能给我们带来甚么样的帮助呢?见下例: 1 public class FinalizeTest { 2 //@Override 3 protected void finalize() throws Throwable { 4 try { 5 //在调试进程中通过该方法,打印对象在被搜集前的各种状态, 6 //如判断是不是仍有资源未被释放,或是不是有状态不1致的现象存在。 7 //推荐将该finalize方法设计成仅在debug状态下可用,而在release 8 //下该方法其实不存在,以免其对运行时效力的影响。 9 System.out.println("The current status: " + _myStatus); 10 } finally { 11 //在finally中对超类finalize方法的调用是必须的,这样可以保证全部class继承 12 //体系中的finalize链都被履行。 13 super.finalize(); 14 } 15 } 16 }
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |