Java 解惑閱讀筆記 第10章(部分)
簡述:
讀《Java解惑》 第10章 閱讀筆記
謎題 86 新增括號導致的編譯器錯誤
新增括號會產生編譯期錯誤的情況
int, 或者long, 最小值的絕對值比正數絕對值要大1
添加了括號後
謎題 87 緊張的關係 “==” 符號的非自反性
Transitive.java
package com.anialy.test.java_puzzlers.chapter_10.緊張的關係_87; public class Transitive { public static void main(String[] args) { long x = Long.MAX_VALUE; double y = (double) Long.MAX_VALUE; long z = Long.MAX_VALUE - 1; System.out.println("x == y : " + (x == y)); System.out.println("y == z : " + (y == z)); System.out.println("x == z : " + (x == z)); System.out.println("Float.NaN == Float.NaN : " + (Float.NaN == Float.NaN)); } }
輸出:
謎題 88 原始型別的處理
程式中的p是屬於原始型別Pair的,所以它的所有例項方法都要執行這種擦除。在一個例項方法宣告中出現的每個引數化的型別都要被其對應的原始部分所取代。我們程式中的變數p是屬於原始型別pair的,所以它的所有例項方法都要執行這種擦除。
謎題 89 泛型迷藥,外圍類和內部類使用相同的型別引數名
首先看這樣一段程式碼
run之後報錯資訊如下,
說明:
避免內部類遮蔽外部類的型別引數名字,一個泛型的內部類可以訪問到他的外圍類的型別引數
優化:
優先使用靜態成員類而不是非靜態成員類,LinkedList.Node的一個例項不僅含有value和next欄位,還有一個隱藏的欄位,包含對外圍的LinkedList例項的引用。雖然外部類的例項在例項在構造階段會被用來讀取和修改head,但是一旦構造完成,它就變成一個甩不掉的包袱。
使用static修飾Node之後的程式碼
package com.anialy.test.java_puzzlers.chapter_10.泛型迷藥_89; public class LinkedList<E> { private Node<E> head; private static class Node<T> { T value; Node<T> next; Node(T value, Node<T> next) { this.value = value; this.next = next; } } public void add(E e){ head = new Node<E>(e, head); } public void dump(){ for(Node<E> n = head; n != null; n = n.next){ System.out.println(n.value + ", "); } } public static void main(String[] args) { LinkedList<String> list = new LinkedList<String>(); list.add("world"); list.add("Hello"); list.dump(); } }
謎題90 荒謬痛苦的超類 引用外層類super函式的問題
編譯後就不通過,原因如下:
要想例項化一個內部類,入Inner1, 需要提供一個外圍類的例項給構造器。一般情況下,它是隱式傳遞給構造器的,但是它也可以以exception.super(args)的方式通過超類構造器呼叫。如果外圍類例項是隱式傳遞的,編譯器會自動產生表示式:它使用this來指代最內部的超類是一個成員變數的外圍類。在本例中,那個超類就是Inner1.因為當前類Inner2間接擴充套件了Outer類,Inner1便是他的一個繼承而來的成員。
顯示傳遞合理的外圍類例項:
但是這個成員類真的需要使用外圍類例項?如果答案是否定的,那麼應該把它設為靜態成員類
儘量使用靜態巢狀類而少用非靜態的。
謎題 91 序列殺手 序列化過程中,呼叫序列化物件的方法,由於狀態不一致導致的問題
SerialKiller.java
package com.anialy.test.java_puzzlers.chapter_10.序列殺手_91;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerialKiller {
public static void main(String[] args){
Sub sub = new Sub(666);
sub.checkInvariant();
Sub copy = (Sub)deepCopy(sub);
copy.checkInvariant();
}
public static Object deepCopy(Object obj){
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
new ObjectOutputStream(bos).writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
return new ObjectInputStream(bin).readObject();
} catch(Exception e) {
throw new IllegalArgumentException();
}
}
}
Sub.java
package com.anialy.test.java_puzzlers.chapter_10.序列殺手_91;
public class Sub extends Super {
private int id;
public Sub(int id){
this.id = id;
set.add(this);
}
@Override
public int hashCode (){
return id;
}
@Override
public boolean equals(Object o){
return (o instanceof Sub) && (id == ((Sub)o).id);
}
public void checkInvariant(){
if(!set.contains(this)){
throw new AssertionError("invariant violated");
}
}
}
Super.java
package com.anialy.test.java_puzzlers.chapter_10.序列殺手_91;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class Super implements Serializable {
final Set<Super> set = new HashSet<Super>();
}
執行後輸出:
說明:
HashSet類有一個readObject方法,它建立一個空的HashMap,並且使用HashMap的put方法,針對集合中的每個元素在HashMa中插入的一個鍵-值對。put方法會呼叫鍵的hashCode方法以確定它所在的單元格。三列對映表中唯一的鍵就是Sub的例項,而它的set欄位正在被反序列化。這個例項的子類欄位,即id,尚未被初始化,所以它的值為0,即所有int欄位的預設初始值,Sub的hashCode方法將返回這個值,而不是最後儲存在這個欄位中的值666.因為hashCode返回了錯誤的值,相應的鍵-值對條目將會放入錯誤的單元格中。當id被初始化為666時,一切都太遲了。當Sub例項在HashMap中的時候,改變這個欄位的值就會破壞這個欄位,進而破壞HashSet,破壞Sub例項。
關鍵點:
1. 如果一個HashSet、Hashtable或HashMap被序列化,那麼請確認它們的內容沒有直接或間接地引用它們自身。
2. 在readObject或readResolve方法中,請避免直接或間接地在正在進行反序列化的物件上呼叫任何方法
謎題 92 私有成員變數不會繼承
Twisted.java
package com.anialy.test.java_puzzlers.chapter_10.雙絞線_92;
public class Twisted {
private String name;
public Twisted(String name) {
this.name = name;
}
private void reproduce(){
new Twisted("reproduce"){
void printName() {
System.out.println(name);
}
}.printName();
}
public static void main(String[] args) {
new Twisted("main").reproduce();
}
}
輸出:
說明:
私有成員不會被繼承,所以在這個程式中,name方法並沒有被繼承到reproduce方法中的匿名類中。所以,匿名類中對name的取值就只能關聯到到外圍(“main")例項而不是當前("reproduced")例項。這就是最小閉合作用域。