四十一、让多重继承成为现实
在Java中一个类可以多重实现,但不能多重继承,也就是说一个类可以同时实现多个接口,但不能同时继承多个类。但有时候我们确实需要继承多个类,比如希望拥有两个类的行为功能,就很难使用单继承来解决了。幸运的是Java提供的内部类可以曲折的解决此问题。
内部类的一个重要特性:内部类可以继承一个与外部类无关的类,保证了内部类的独立性,正是基于这一点,多继承才能成为可能。
四十二、让工具类不可实例化
设置其构造函数位private访问权限。同时为了防止反射实例化该类,还应该抛出异常,代码如下:
public class UtilsClass{
private UtilsClass{
throw new Error("不要实例化我!");
}
}
如此做才能保证一个工具类不会实例化,并且保证所有的访问都是通过类名来进行的。需要注意的一点是:此工具类最好不要做继承的打算,因为如果子类可以实例化的话,那就要调用父类的构造函数,可是父类并没有可被访问的构造函数,于是就会出现问题。
- 注意:如果一个类不允许实例化,就要保证“平常”渠道都不能实例化它。
四十三、避免对向的浅拷贝
我们知道一个类实现了Cloneable接口就表示它具备被拷贝的能力,如果再覆写clone()方法就会完全具备拷贝能力。拷贝是在内存中进行的,所以在性能方面要比直接new生成对象要快的多,特别是在大对象的生成上,这会使性能的提升非常显著。但是对象拷贝也有一个比较容易忽略的问题:浅拷贝(shadow clone,也叫作影子拷贝)存在对象拷贝不彻底的问题。
Object提供了一个对象拷贝的默认方法,即super.clone()方法,但是该方法是有缺陷的,它提供的是一种浅拷贝方式,也就是说它不对把对象所有的属性都拷贝一份,而是有选择性的拷贝,拷贝规则如下:
- 基本类型。如果变量是基本类型,则拷贝其值,比如int,float等
- 对象。如果变量是一个实例对象,则拷贝地址引用,也就是说此时新拷贝出来的对象与原有的对象共享该实例变量,不受访问权限的限制。
- String字符串。这个比较特殊,拷贝的也是一个地址,是个引用,但在修改时, 它会从字符串池中重新生成新的字符串,原有的字符串对象保持不变,此处我们可以认为String是一个基本类型。
注意:浅拷贝只是Java提供的一种简单拷贝机制,不便于直接使用。
四十四、推荐使用序列化实现对象的拷贝
可以通过序列化方式,在内存中通过字节流的拷贝来实现,也就是把母对象写到一个字节流中,再从字节流中将其读出来,这样就可以重建一个新对象了,该对象与母对象之间不存在引用共享的问题,也就相当于深拷贝了一个新对象。代码如下:
public class CloneUtils{
//拷贝一个对象
public static T clone(T obj){
T clonedObj = null;
try{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
clonedObj = (T)ois.readObject(obj);
ois.close();
}catch(Exception e){
e.printStackTrace();
}
return clonedObj;
}
}
此工具类要求被拷贝的对象必须实现 Serializable接口,否则是没有办法拷贝的(反射除外),当然,serialVersionUID常量还是要加上去的,然后我们就可以通过CloneUtils工具进行对象的深拷贝。需要注意两点:
- 对象的内部属性都是可序列化的。如果有内部属性不可序列化,则会抛出序列化异常。
- 注意方法和属性的特殊修饰符。比如final、static变量的序列化问题会被引入到对象拷贝中来,这点需要特别注意,同时transient变量(瞬态变量,不进行序列化的变量)也会影响到拷贝的效果。
当然,采用序列化方式拷贝时还有一个更简单的方式,即使用Apache下的Commons工具包中的SerializationUtils类,直接使用更加简洁方便。
四十五、覆写equals方法时不要识别不出自己
我们在写一个JavaBean时,经常会覆写equals方法,其目的是根据业务规则判断两个对象是否相等,这在DAO层是经常用到的。
equals方法的自反性原则:
对于任何非空引用X, X.equals(X)应该返回true
四十六、equals应该考虑null值情景
equals对称性原则:对于任何引用x和y的情形,如果x.equals(y)返回true, 那么y.equals(x)也应该返回true。
注意:在比较之前先判断要比较的引用是否为null
四十七、在equals中使用getClass进行类型判断
使用getClass代替instanceof进行类型判断。
四十八、覆写equals方法必须覆写hashCode方法
HashMap的底层处理机制是以数组的方式保存Map条目(Map Entry)的,这其中的关键是这个数组的下标处理机制:依据传入元素hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有了Map条目,且与传入的键值相等则不处理,若不相等则覆盖;如果数组位置没有条目则插入,并加入到Map条目的链表中。
重写hashCode代码如下:
@Override
public int hashCode(){
return new HashCodeBuilder.append(name).toHashCode();
}
其中HashCodeBuilder是org.apache.commons.lang.builder包下的一个哈希码生成工具,使用起来非常方便,可以直接在项目中集成。
四十九、推荐覆写toString()方法
当Bean属性较多时,可以使用apache的commons工具包中的ToStringBuilder类,简洁,方便。
五十、 使用package-info类为包服务
Java中有一个特殊的类:package-info类,它是专门为本包服务的。它的特殊主要体现在三个方面:
- 它不能随便被创建。IDE直接创建会报错,可以用记事本创建一个,然后拷贝进去改一下即可,或者是从别的项目中拷贝过来。
- 它服务的对象很特殊。它主要描述和记录本包信息的。
- package-info类不能有实现代码
另外它不可以继承,没有接口,没有类间关系(关联、组合、聚合)等。
主要有三个作用:
- 声明友好类和包内访问常量。
- 为在包上标注注解提供便利。
- 提供包的整体注释说明。
五十一、不要主动进行垃圾回收
System.gc是一个非常危险的动作,因为它要停止所有的响应,才能检查出内存中是否有可回收的对象,这对一个应用系统来说风险很大。