覆盖equals方法时要遵守的通用合约

覆盖equals方法并不像看上去那样简单,所以除非必要,还是不要覆盖为好。以下情况下,equals方法是不需要被覆盖的:

1.一个类的所有对象本质上说都是唯一的。比如Thread类,它的对象全都用来执行指令,因而他们在对象层面上的数据并不重要。这种情况下Object本身的equals实现已经足够。

2.对于某些类,我们并不在意对其对象间的"逻辑相等性"。比如java.util.Random。

3.父类已经定义了可正确应用于子类的equals。

4.私有类,并且可以确定其equals方法永远不会被执行。

昨天,巴黎成了世界各地媒体的头条,却不是因为什么好事。三个人闯入漫画杂志Charlie Hebdo位于巴黎11区的总部,拿出自动武器射杀12人,另有多人受伤。原因是此杂志曾多次用漫画取笑先知默罕默德。

原本电视里看着热闹的恐怖袭击一下闯进现实生活里来了,于是赶紧嘱咐领导别带娃到处转悠,尽量窝在家里。我自己倒没啥危险,因为整天就在家和公司这几百米范围内活动,离巴黎还远着呢。

今天袭击者身份被公开,两名仍在逃的主犯都是阿尔及利亚人。不禁感叹,这都是法国人自己酿成的苦酒,现在死伤20几人,又全国哀悼,又集会游行的,何必呢?有这功夫,好好修改一下那引狼入室的移民法案,比啥都管用。

避免使用finalizer

Java语言规范里没有要求finalizer何时被执行,甚至没要求它一定要被执行。各种不同的jvm实现完全可能以它们自己的方式来处理finalizer,所以finalizer里的代码执行没有任何保障,从这点上来说,很多c++程序员把finalizer作为c++析构函数在Java中的对应功能是完全错误的。如果错误地依赖finalizer来释放被占用的资源或者执行某些时序任务,很可能会造成不可预料的错误行为。

Java面试里面一个烂大街的问题就是System.gc()方法有什么作用,其实还有一个类似的方法System.runFinalization(),两者没太大区别,都是向jvm提出gc请求,但根本无法保证jvm会及时作出响应。当然,这两个方法还是提高了finalizer被执行的可能性。

finalizer的另一个问题是异常处理。当其中的代码抛出异常时,如果没有显式定义catch处理,这些异常将被完全忽略,所在线程会瞬间终止,连stack trace都不会打印。

移除过期的对象引用

这节讲的是关于内存泄露的问题。Java有三大主要的内存泄露来源:

1.开发者自己维护的内存

2.缓存

3.listener和callback

第一类:开发者自己维护的内存。这类问题通常是开发者从其它编程语言(例如c++)带来的坏习惯导致,比如下面这段代码:

同事中有个黑人兄弟,比我晚一个月进这个项目,性格温顺,和蔼可亲。今天才知道人原来是高干子弟来着。这外国太子党可比中国的低调多了。

这兄弟来自一个西非小国:佛得角(Cape Verde)。很多中国人可能都没听说过这样一个国家。但玩过大航海时代的都知道,此国其实就是非洲的最西端,游戏里叫做"绿角"的城市。全国由三个岛屿构成,国土面积四千多平方公里,总人口50万左右。

我和这兄弟平时聊得很好,他今天刚休假三个星期回来,是回佛得角跟家人团聚去了。回来就抱怨在那边住得太舒服,回法国郁闷了。首先是天气,那边气温常年稳定在24-25度,回巴黎变成0度,实在不爽。其次是住过他父母刚装修好的豪宅,回法国的蜗居就有点抑郁。我对豪宅向来很感兴趣,于是索要照片看看,看完我也抑郁了。。。

避免创建不必要的对象

最常见的例子是String,一个小小的String对象看起来不起眼,但在复杂的大型系统中,如果不恰当创建,可能会带来极大的性能开销,比如:

String s = new String("stringette"); // DON'T DO THIS!

这行代码的问题是,作为参数的"stringette"本身已经是所需要的String对象了,菜鸟程序员却调用一个String类的构造函数来再次构造它。这行代码每执行一次,就会在栈里面建立一个一模一样的"stringette"对象。在高频执行的环境下,内存开销会非常大,甚至引起stack overflow。正确的做法是:

第10页 共19页