如何避免创建不必要的对像?
充分利用可以重用的对象
1.若对象不可变,它就始终可以重用
先来一个极端的反面例子(一般程序员都不会无聊到这样做):
String s = new String("stringtest");
该语句没执行一次都会创建一个String实例,如果这在一个循环中,将会有很多String实例被创建,显然这是不必要的,显然这个代码应该这样写:
String s = "stringtest";
这种写法只用了一个String实例,而不是每次都创建一个实例。
这样写可以保证,对于说有在同一台虚拟机中运行的到吗,只要它们包含相同的字符串字面常量,该对象就会被重用。
2.可以重用那些一直不不会被修改的对象
例如一下代码,若要获得value,很明显每次都要经过一大堆运算
class Test{
//省略构造函数
public int calculateThatValue(){
int value;//假设这是一个若赋值了就不变的,且要经过很复杂的运算才能算出来的值
//假设这里是很复杂的一段代码,需要运行很长时间
return value;
}
}
然而我们可以这样改进,可以让运行效率变快,只运算一次,而不是每次需要都运算
class Test{
private final int VALUE_COSTLONGTIME;//假设这是一个不变的,且要经过很复杂的运算才能算出来的值
//省略构造函数
//静态方法块,在类第一次被加载时候执行
static{
int value;
//假设这里是很复杂的一段代码,需要运行很长时间
VALUE_COSTLONGTIME = value;//把计算好的值赋值给final常量
}
}
很明显,在这之前的例子讨论到的对象都是显然能被重用的,因为初始化之后不会再改变。
3.当对象重用与否不明显时,考虑使用适配器(也叫视图)来避免创建不必要对象
例如,Map接口的keySet方法返回该Map的对象的Set视图,其中包含该Map中所有的Key。
粗看起来,好像每次调用keySet都应该创建一个新的Set实例。其实不然,对于一个没定的Map对象,实际上每次调用keySet都返回同样的Set实例。
例,HashMap的keySet方法实现
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
可以看到,第一行实现代码是把keySet赋值给ks,return 后面是一个三元表达式,当ks不为空时返回ks,否则创建一个KeySet返回。(也就是keySet为空时创建一个KeySet对象返回)
keySet在HashMap中的定义
transient volatile Set<K> keySet = null;
先解释一下两个修饰符
transient (是一个类型修饰符,只能用来修饰字段):在对象序列化的过程中,标记为transient的变量不会被序列化。
使用场景:假设某个类的成员变量是transient,那么当通过ObjectOutputStream把这个类的某个实例保存到磁盘上时,实际上transient变量的值是不会保存的。
当对象序列化的保存在存储器上时,不希望有些字段数据被保存,为了保证安全性,可以把这些字段声明为transient。
volatile(变量修饰符,只能修饰变量):volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
看完这里,应该就可以解释为什么keySet返回都是同一个对象。
4.避免无意识的使用自动装箱
基本类型优先,当心无意思的自动装箱
例: long 类型声明成Long