java避免创建不必要的对象
本文参考:https://yq.aliyun.com/articles/31856和https://www.cnblogs.com/AlanLee/p/6122416.html
避免创建不必要的对象
1. String s = "123";
而不用String s = new String("123");
因为JVM中会有一个常量池,可以保存字符串常量,直接使用"123"可以直接从常量池中返回对象,而不会重新创建新对象。
更加详细的说明:http://jiangzhengjun.iteye.com/blog/577039
优先使用静态工厂方法
对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象。例如,静态工厂方法Boolean.valueOf(String)几乎总是优先于构造器Boolean(String)。构造器在每次被调用的时候都会创建一个新的对象,而静态工厂方法则从来不要求这样做,实际上也不会这样做。
package com.czgo.effective; /** * 用valueOf()静态工厂方法代替构造器*/ public class Test { public static void main(String[] args) { // 使用带参构造器 Integer a1 = new Integer("1"); Integer a2 = new Integer("1"); //使用valueOf()静态工厂方法 Integer a3 = Integer.valueOf("1"); Integer a4 = Integer.valueOf("1"); //结果为false,因为创建了不同的对象 System.out.println(a1 == a2); //结果为true,因为不会新建对象 System.out.println(a3 == a4); } }
可以见用valueOf不会新建一个对象,实际上很多类默认的valueOf方法都不会返回一个新的实例。
fianl 一个公用的变量
除了重用不可变的对象之外,也可以重用那些已知不会修改的可变对象。
比如下面有一个比较一个日期集是否在2000年之前的方法:
public class Singleton implements Cloneable{ public void printDateIsBefor2000(List<Calendar> days){ Calendar c = Calendar.getInstance(); c.set(2000, Calendar.JANUARY, 1); for (Calendar d : days) { System.out.println(c.after(d)); }
Calendar 对象将会被创建很多次, 他可以认为是一个常量,因此可以按照如下的方式来写:
public class Singleton implements Cloneable{ private final static Calendar LIANGQIAN; static{ LIANGQIAN = Calendar.getInstance(); LIANGQIAN.set(2000, Calendar.JANUARY, 1); } public void printDateIsBefor2000(List<Calendar> days){ for (Calendar d : days) { System.out.println(LIANGQIAN.after(d)); } } }
静态块只会在类加载初始化时执行一次,这样Calendar对象只会被创建一次,提升了效率。
使用视图类的方法
Map接口的keySet方法返回该Map对象的Set视图,其中包含该Map中所有的键(key)。粗看起来,好像每次调用keySet都应该创建一个新的Set实例,但是,对于一个给定的Map对象,实际上每次调用keySet都返回同样的Set实例。虽然被返回的Set实例一般是可改变的,但是所有返回的对象在功能上是等同的:当其中一个返回对象发生变化的时候,所有其他返回对象也要发生变化,因为它们是由同一个Map实例支撑的。虽然创建keySet视图对象的多个实例并无害处,却也是没有必要的
public class TestKeySet { public static void main(String[] args) { Map<String,Object> map = new HashMap<String,Object>(); map.put("A", "A"); map.put("B", "B"); map.put("C", "C"); Set<String> set1 = map.keySet(); Iterator<String> it = set1.iterator(); while(it.hasNext()){ System.out.println(it.next()+"①"); } System.out.println("---------------"); map.put("D", "D"); Set<String>set2 = map.keySet();
//true,同一个map,两次返回同一个set对象
System.out.pringln(set1==set2); it = set2.iterator(); while(it.hasNext()){ System.out.println(it.next()+"②"); } } }
减少自动装箱
有一种创建多余对象的新方法,称作自动装箱(autoboxing),它允许程序员将基本类型和装箱基本类型(Boxed Primitive Type<引用类型>)混用,按需要自动装箱和拆箱。自动装箱使得基本类型和引用类型之间的差别变得模糊起来,但是并没有完全消除。它们在语义上还有着微妙的差别,在性能上也有着比较明显的差别。考虑下面的程序,它计算所有int正值的总和。为此,程序必须使用long变量,因为int不够大,无法容纳所有int正值的总和:
public class TestLonglong { public static void main(String[] args) { Long sum = 0L; for(long i = 0; i < Integer.MAX_VALUE; i++){ sum += i; } System.out.println(sum); } }
这段程序算出的结果是正确的,但是比实际情况要慢的多,只因为打错了一个字符。变量sum被声明成Long而不是long,意味着程序构造了大约2的31次方个多余的Long实例(大约每次往Long sum中增加long时构造一个实例)。将sum的声明从Long改成long,速度快了不是一点半点。结论很明显:要优先使用基本类型而不是引用类型,要当心无意识的自动装箱。
最后,并不是一定不要创建新的对象,JVM现代虚拟机对于小对象的回收非常快,如果频繁使用小对象可以大大增加程序的简洁性和可读性的话也是很应该使用的。