应用
47. 不可变的引用类型
- BigInteger total = BigInteger.ZERO;
- total.add(new BigInteger("1"));
- total.add(new BigInteger("10"));
- System.out.println(total);//0
上面程序的结果为11吗?答案是0。
BigInteger实例是不可变的。String、BigDecimal以及包装类型:Integer、Long、Short、Byte、Character、Boolean、Float和Double也是如此。对这些类型的操作将返回新的实例。
不可变类型更容易设计、实现与作用;它们出错的可能性更小,并且更加安全。
本程序修改如下:
- BigInteger total = BigInteger.ZERO;
- total=total.add(new BigInteger("1"));
- total=total.add(new BigInteger("10"));
- System.out.println(total);//11
48. 请同时重写equals()与hashCode()
- class T {
- private String str;
- T(String str) {
- this.str = str;
- }
- public boolean equals(Object obj) {
- if(!(obj instanceof T)){
- return false;
- }
- T t = (T)obj;
- return t.equals(this.str);
- }
- public static void main(String[] args) {
- Set set = new HashSet();
- set.add(new T("str"));
- System.out.println(set.contains(new T("str")));//false
- }
- }
上面的程序不会打印true,而是false,为什么?
hashCode约定要求相等的对象要具有相同的散列码。
无论何时,只要你重写了equals方法,你就必须同时重写hashCode方法。
如果将自定的类型对象放入HashSet、HashMap、Hashtable、LinkedHashSet、LinkedHashMap这此散列集合时,一定需要重写equals与hashCode方法,这样在放入进去之后还能查找出来。如果放入其他非散列类型的集合时,其实只需要
重写equals就可以了。
本程序解决办法重写hashCode()方法:
- public int hashCode() {
- return 37 * this.str.hashCode();
- }
49. 日期设置
- Calendar c = Calendar.getInstance();
- c.set(2010, 12, 31);// 月是从0开始的,11其实表示12月
- System.out.println(c.get(Calendar.YEAR) + " " + c.get(Calendar.MONTH));
- c = Calendar.getInstance();
- c.set(2010, 11, 31);
- System.out.println(c.get(Calendar.YEAR) + " " + c.get(Calendar.MONTH));
本程序较简单,只需注意月是从0开始的就可以了,如果你设置月为12,则会自动转换为下一年。
50. IdentityHashMap
- class T {
- private String str;
- T(String str) {
- this.str = str;
- }
- public int hashCode() {
- return 37 * this.str.hashCode();
- }
- public boolean equals(Object obj) {
- return this.str.equals(((T) obj).str);
- }
- public static void put(Map m) {
- m.put("str", "1");
- /*
- * 由于上面程序将 "str" 放入了字符串常量池,
- * 所以str是同一个对象,不管是什么样类型的
- * Map,即使使用IdentityHashMap都只放入一次
- */
- m.put("str", "2");
- m.put(new T("str"), "3");
- m.put(new T("str"), "4");
- }
- public static void main(String[] args) {
- Map m = new HashMap();
- put(m);
- System.out.println(m.size());// 2
- //IdentityHashMap比较时使用==替换equals()方法
- m = new IdentityHashMap();
- put(m);
- System.out.println(m.size());// 3
- }
- }
51. 静态导入的优先权
- import static java.util.Arrays.toString;
- import java.util.Arrays;
- public class T {
- public static void main(String[] args) {
- prt(1, 2, 3);
- }
- static void prt(Object... args) {
- // 自身继承至Object类的toString的优先级高于静态导入的方法
- //!! System.out.println(toString(args));//不能编译
- System.out.println(Arrays.toString(args));
- }
- }
本身就属于某个范围的成员在该范围内与静态导入相比具有优先权。
52. PrintStream对输出结果的缓冲
- public static void main(String[] args) {
- String str = "Hello World";
- for (int i = 0; i < str.length(); i++) {
- System.out.write(str.charAt(i));
- }
- }
上面的程序没有输出结果。
这里的问题在于System.out是带有缓冲的。输出的结果被写入了System.out的缓冲区,但是缓冲区从来都没有被刷新。大多数人认为,当有输出产生的时候System.out和System.err会自动地进制刷新,但这并不完全正确,这两个流都属于PrintStream类型,请看API DOC描述:一个PrintStream被创建为自动刷新,这意味着当一个字节数组(byte[])被写入、或者某个println方法被调用、或者一个换行字符或字节('\n')被写入之后,PrintStream类型的flush方法就会被自动调用。
令人奇怪的是,如果这个程序用print(char)去替代write(int),它就会刷新System.out并输出结果,这种行为与print(char)的文档是矛盾的,因为其文档叙述道:“打印一个字符,这个字符将根据平台缺省的字符编码方式翻译成一个或多个字节,并且这些字节将完全按照write(int)方法的方式输出。”,但这里没有换行符却也自动的刷新了。
类似的,如果程序改用print(String),它也会对流进行刷新。所以调用print方法也是会自动刷新的。
53. 调用操作系统命令时被阻塞问题
- public static void main(String[] args) throws IOException,
- InterruptedException {
- String command = "java ProcessTest exc";
- if (args.length != 0) {
- for (int i = 0; i < 200; i++) {
- System.out.println(command);
- System.err.println(command);
- }
- } else {
- Process process = Runtime.getRuntime().exec(command);
- int exitValue = process.waitFor();
- System.out.println("exit value = " + exitValue);
- }
- }
执行java ProcessTest发现程序阻塞。
Process文档描述:由于某些本地平台只提供有限大小的缓冲,所以如果不能迅速地读取子进程的输出流,就有可能会导致子进程的阻塞,甚至是死锁。这恰好就是这里所发生的事情:没有足够的缓冲空间来保存这些输出结果。为了结子进程(Process线程),父进程(Main线程)必须排空它的输出流(标准流与错误流都需要排空),即要去缓存中读取结果:
- static void readResult(final InputStream is) {
- new Thread(new Runnable() {
- public void run() {
- try {
- // 排空缓存内容
- while (is.read() >= 0);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
然后在process.waitFor()之前加上
- readResult(process.getErrorStream());
- readResult(process.getInputStream());
即可输出exit value = 0。
另外,只能根据process.waitFor返回的结果来判断操作系统命令执行是否成功(成功:0,失败:1),我们不能根据
错误流中是否有内容来判断是否执行成功。
54. 实现Serializable的单例问题
- class Dog implements Serializable{
- public static final Dog INSTANCE = new Dog();
- private Dog(){}
- }
上面能控制只生成一个单实例吗?
如果对实现了Serializable的对象进行序列化后,再反序列化,内中会不只一个实例了,因为反序列化时会重新生成一个对象。
既然INSTANCE为静态域,那序列化时返回的对象如果也是INSTANCE就可以解决问题了,而打开API我们发现Serializable接口确实有这样两个特殊的方法描述:
将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此 writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。
在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此 readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。
上述两个方法的只要出现,就会履盖以下两个方法(这两个方法本质的意义就是用来替换序列与反序列的对象),虽然会执行它们,但最后得到的结果却是writeReplace、readResolve两个方法写入或读出的对象:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundException;
另外,writeObject与readObject需成对实现,而writeReplace与readResolve则不需要成对出现,一般单独使用。如果同时出现这四个方法,最后写入与读出的结果以writeReplace和readResolve方法的结果为准。
所以下要解决真真单实例问题,我们如下修正:
- class Dog implements Serializable {
- public static final Dog INSTANCE = new Dog();
- private Dog() {}
- private Object readResolve() {
- return INSTANCE;
- }
- }
- public class SerialDog {
- public static void main(String[] args) throws IOException,
- ClassNotFoundException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- new ObjectOutputStream(bos).writeObject(Dog.INSTANCE);
- ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
- Dog dog = (Dog) new ObjectInputStream(bin).readObject();
- System.out.println(dog == Dog.INSTANCE);//true
- }
- }
一个实现了Serializable的单例类,必须有一个readResolve方法,用以返回它的唯一实例。
55. thread. isInterrupted()与Thread.interrupted()
- public class SelfInerruption {
- public static void main(String[] args) {
- Thread.currentThread().interrupt();
- if (Thread.interrupted()) {
- // Interruped:false
- System.out.println("Interruped:" + Thread.interrupted());
- } else {
- System.out.println("Not interruped:" + Thread.interrupted());
- }
- }
- }
上面结果走的是第一个分支,但结果却不是Interruped:true?
Thread.interrupted()为Thread的静态方法,调用它首先会返回当前线程的中断状态(如果当前线程上调用了interrupt()方法,则返回true,否则为false),然后再清除当前线程的中断状态,即将中断状态设置为false。换句话说,如果连续两次调用该方法,则第二次调用将返回 false。
而isInterrupted()方法为实例方法,测试线程是否已经中断,并不会清除当前线程中断状态。
所以这里应该使用isInterrupted()实例方法,就可以修复该问题。
相关推荐
你认为自己了解Java多少?你是个爱琢磨的代码侦探吗?你是否曾经花费数天时间去追踪一个由Java或其类库的陷阱和缺陷而导致的bug?你喜欢智力测验吗?本书正好适合你!.. Bloch和Gafter继承了Effective Jaya一书的传统,...
java 解惑 java 解惑 java 解惑 java 解惑 java 解惑 java 解惑
《Java解惑(中文版)》是一本专为Java初学者设计的学习资料,旨在帮助读者解答在学习Java过程中遇到的各种困惑。"solve65p"可能代表这本书包含65个问题或主题,每个都深入浅出地进行了讲解,旨在解决初学者在编程...
Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑
《Java解惑中文版》是一本专为Java程序员设计的指南,旨在帮助读者解决在编程过程中遇到的各种问题,提升程序的健壮性。本书深入浅出地探讨了Java语言的核心概念、常见疑惑以及最佳实践,旨在使开发者能够编写出更...
"JAVA解惑"的主题针对的是Java学习过程中遇到的一些常见问题和难点,旨在帮助开发者深入理解和解决这些问题。以下是基于这个主题和描述可能涵盖的一些关键知识点: 1. **Java基础**:这可能包括变量、数据类型、...
这份“Java解惑.pdf”文档很可能包含了解决Java开发者在编程过程中遇到的常见问题和困惑的详细解答。以下是可能涵盖的一些Java相关知识点: 1. **基础语法**:Java的基础语法包括变量、数据类型、运算符、流程控制...
"java解惑" PDF版本
文档《java解惑 PDF版》中列举了95个这样的谜题,每个谜题都旨在帮助开发者理解并纠正一些常见的错误理解。以下是根据提供的部分内容解析的几个相关知识点。 ### 表达式谜题与取余操作符(%)的行为 在Java中,...
《Java解惑》 布洛克 著;陈昊鹏 译 扫描清晰带目录,仅供参阅,请支持正版
《JAVA解惑》这本书主要针对Java编程中遇到的各种常见问题和困惑进行了解答,旨在帮助开发者深入理解Java语言,提高编程技巧。以下是一些关键的知识点解析: 1. **异常处理**:Java中的异常处理是通过try-catch-...
《JAVA解惑》是Java开发者领域的一本经典著作,它被广大...总之,《JAVA解惑》是一本涵盖了Java核心知识点、实战技巧和高级特性的宝典,无论你是Java新手还是老手,都能从中受益匪浅,解决你在Java编程中的种种疑惑。
"Java解惑"这个主题,显然旨在帮助开发者解决他们在学习和实践中遇到的问题。在Java的世界里,疑惑可能涵盖语法、类库、框架、并发、内存管理等多个方面。下面,我们将深入探讨一些常见的Java解惑知识点。 1. **...
讲述如何在程序中避免程序缺陷和程序陷阱的,解惑的过程中,介绍了一些Java编程语言中许多不易被掌握的知识点,其阅读价值非常高,适合具有Java知识的学习者和有编程经验的Java程序员阅读。
JAVA解惑
"java解惑"这个主题旨在帮助初学者理解和解决在学习Java过程中遇到的问题,通过实例来深入浅出地讲解Java的基础知识,同时也强调了实用技巧和注意事项。 "Java解惑"的资料可能包含了两部分:`.chm`和`.pdf`格式的...
《Java解惑(中文)》是一本专门为Java开发者编写的指南书籍,旨在解决在实际编程过程中遇到的各种疑惑和难题。本书以中文语言呈现,使得国内的Java程序员能够更轻松地理解并应用其中的知识。通过阅读这本书,读者...
"Java解惑(中文).pdf"这份文档很显然是为了帮助Java开发者解决他们在编程过程中遇到的一些常见困惑和误解。这份高清版、文字版的资料很可能包含了深入浅出的解释和实例分析,以中文的形式呈现,使得国内开发者更容易...