第七章 库之谜
解惑56:大问题
BigInteger实例是不可变的。String、BigDecimal以及包装器类型:Integer、Long、Short、Byte、Character、Boolean、Float和Double也是如此。
不要被误导,认为不可变类型是可变的。
在命名不可变类型的方法时,应该选择介词和名词,而不是动词。
解惑57:名字里有什么
无论如何,只要你覆写了equals方法,你就必须同时覆写了hashCode方法。
解惑58:产生它的散列码
HashSet类是使用equals(Object)方法来测试元素的等价性的;Name类中声明一个equals(name)方法HashSet不造成任何影响。
重载为错误和混乱提供了机会。
解惑59:差是什么
为了避免无意识地重载,你应该机械地对你想要覆写的每一个超类方法都拷贝其声明。
以0开头的整数字面常量将被解释为八进制数值。
千万不要在一个整数字面常量的前面加上一个0.
解惑60:一行以毙之
A题:
可以通过把集合中的原集合中的元素置于一个Set中将结合中的所有重复元素削除。
LinkedHashSet实现维护其元素插入的顺序,提供的导入性能接近HashMap。
解决方案:
Static <E> List<E> withoutDuplicates(List<E> original) {
Return new ArrayList<E>(new LinckedHashSet<E>(original));
}
B题:
自1.4版本开始,由于正则表达式被添加到了java平台中(java.util.regex),StringTokenizer开始变得过时了。
Static String[] parse(String string){
Return string.split(“,//S*”);
}
C题:
Arrays.deepToString方法可以达到这个目的。
D题:
整数类型的包装器类(Integer、Long、Short、Byte和Char)现在支持通用的位处理操作。
了解类库中有些什么可以为你节省大量的时间和精力,并且可以提高你的程序的速度和质量。
解惑61:日期游戏
Date将一月恶表示为0,而Calendar延续了这个错误。
Date.getDday返回的是Date实例所表示的星期日期,而不是月份日期。
在使用Calendar或Date的时候一定要当心,千万记着查阅API文档。
解惑62:名字游戏
相等的字符串常量同时也是相同的。
不要使用IdentityHashMap,除非你需要其基于标识的语义;它不是一个通用目的的Map实现。
在任何时候,程序都应该尽量不依赖予这种行为去保证他们的操作正确。
解惑63:更多同样的问题
不要因为偶尔地添加了一个返回类型,而将一个构造器声明变成一个方法声明。
要遵守标准的命名习惯。
如果其参数等于Integer.MIN_VALUE,那么产生的结果与该参数相同。
解惑64:按余数编组
Math.abs不能保证一定会返回非负的结果。
解惑65:疑似排序的惊人传奇
定长的整数没有达到可以保存任意两个同等长度的整数之差的程度。
不要使用基于减法的比较器,除非你能够准确保证要比较的数值之间的差永远不会大于Integer.MAX_VALUE.
应该避免“聪明”的代码。应该努力去编写清晰正确的代码,不要对它进行任何优化,除非该优化被证明是必须的。
解惑66:一件私事
一个覆写方法的访问修饰符所提供的访问权限与覆写方法的访问修饰符所提供的访问权限相比,至少要多一样。
对一个域来说,当它要隐藏另一个域时,如果隐藏域的访问修饰符提供的访问权限比被隐藏域的少,尽管这么做是不可取的,但是它是合法的。
覆写&隐藏:一旦一个方法在子类中被覆写,你就不能在子类的实例上调用它了(除了在子类内部,通过实用super关键字的方法)。然而,你可以通过将子类实例转换为某一个超类类型来访问被隐藏的域,在这个超类中该域未被隐藏。
避免隐藏。
Liskov置换原则:你能够对基类所做的任何事,都同样能够作用于子类。
解惑67:对字符串上瘾
尽管StrungOut有一个被命名为main的方法,但是它却具有错误的签名。一个main方法必须接受一个单一的字符串组参数。VM努力告诉我们的是StrungOut.main接受的是我们的String类所构成的参数,它无论如何都与java.long.String没有任何联系。
要避免重用平台类的名字,并且千万不要重用java.long中的类名。
要避免避免重用类名,尤其是Java平台类的重名。
解惑68:灰色的阴影
当一个变量和一个类型具有相同的名字,并且它们位于相同作用域时,变量名具有优先权。
遵守标准的Java命名习惯的程序员从来都不会遇上这个问题。
解惑69:黑色的渐隐
我们是可以引用一个被掩盖的类型名的,其技巧就是在某一种特殊的语法上下文环境中使用该名字,在该语法上下文环境中允许出现一个类型但是不允许出现一个变量。
要解决由类型被变量掩盖而引发的问题,需要按照标准的命名习惯来重命名类型和变量。
解惑70:一揽子交易
一个包内私有的方法不能被位于另一个包内的某个方法直接覆写。
解惑71:进口税
本身就属于某个范围的成员在该范围内与静态导入相比具有优先权。
程序中会重复地出现另一个类的静态元素,而每一次用到的时候都进行限定又会使程序乱成一锅粥。在这种情况下,静态导入工具可以显著地提高可读性。
应该有节制的使用静态导入,只有在非常需要的情况下才使用它们。
解惑72:终极危险
Final修饰符对于方法和域而言,意味着某些完全不同的事情。对于方法,final意味着不能被覆写(对于实例方法而言)或者隐藏(对于静态方法而言)。对于域,final意味着该域不能被赋值超过一次。
应该避免在不相关的概念之间重用关键字。
解惑73:隐私在公开
重用名字是危险爱你的:应该避免隐藏、遮蔽和掩盖。
解惑74:同一性的危机
如果同一个方法的两个重载版本都可以应用于某些参数,那么它们应该具有相同的行为。
解惑75:头还是尾?
条件操作符(?:)的行为在5.0版本之前是受限的。当第二个和第三个操作符是引用类型时,条件操作符要求他们其中的一个必须是另一个的子类型。在5.0或者更新的版本中,条件操作符在第二个和第三个操作符是引用类型时总是合法的。
应该升级到最新的Java平台版本上。
解惑76:乒乓
当你想调用一个线程的start方法时要多加小心,别错误弄成调用这个线程的run方法了。
解惑77:乱锁之妖
在内部,Thread.join方法在表示正在被链接的那个Thread实例上调用Object.wait方法。这样就在等待期间释放了对象上的锁。
千万不要假设库中的这个类对它的实例或类上的锁会做(或者不会做)某些事情。
如果你需要获得某个锁的完全控制权,那么就要确定没有任何其他人能够访问到它。
解惑78:反射的污染
访问位于其它包中的非公共类型的成员是不合法的。
Object.getClass().getMethod("methodName")这种惯用法虽然很常见,但是却有问题,它不应该被使用。
在使用反射访问某个类型时,请使用表示某种可访问类型的Class对象。
解惑79:狗狗的幸福生活
避免隐藏。
使用Thread(Runnable)构造器来替代对Thread的继承。
解惑80:更深层的反射
Class。newInstance的文档叙述道:如果那个Class对象“代表了一个抽象类,一个接口,一数组类,一个原生类型,或者是空;或者这个类型没有任何空的构造器;或者实例化由于某些其他原因而失败,那么它就会抛出异常”。
除非你确实是需要一个外围实例,否则你应该优先使用静态成员类而不是非静态成员类。
请避免使用反射来实例化内部类。
解惑81:无法识别的字符化
Write(int)是唯一一个在自动刷新功能开启的情况下不刷新PrintStream的输出方法。
尽可能使用熟悉的惯用法:如果你不得不使用陌生的API,请一定要参考相关的文档。
解惑82:啤酒爆炸
由于某些本地平台只提供有限大小的缓冲区,所以如果不能迅速地读取子进程的输出流,就有可能会导致子进程阻塞,甚至是死锁。
为了确保子进程能够结束,你必须排空它的输出流;对于错误流也是一样。
如果你决定不合并输出流和错误流,你必须并行地排空它们。
API应该设计得更容易做出正确的事,而很难或不可能做出错误的事。
解惑83:
一个实现了Serializable的单件类,必须有一个readResolve方法,用以返回它的唯一的实例。
解惑84:嘎然而止
Thread.interrupted方法第一次被调用的时候返回了true,并且清除了线程的中断状态,所以在if-then-else语句的then分支中第二次调用该方法的时候,返回的就是flase
Thread还有一个查询一个线程的中断状态的方法:isInterrpted方法。
不要使用Thread.interrupted方法,除非你想要清除当前线程的中断状态。
解惑85:惰性初始化
在一个线程访问一个类的某个成员的时候,它会去检查这个类是否已经被初始化。在忽略严重错误的情况下,有四种可能的情况:
(1) 这个类尚未被初始化。
(2) 这个类正在被当前线程初始化;这是对初始化的递归请求。
(3) 这个类正在被其他线程而不是当前线程初始化。
(4) 这个类已经被初始化。
要让类的初始化尽可能地简单。
在类的初始化期间等待某个后台线程很可能会造成死锁。