公用類若是暴露了本身的域,則會致使客戶端濫用該域,而且該公用類在之後的升級中沒法靈活的改變屬性的表達方式。java
設計不可變類應該遵循如下準則:數組
以下代碼使用原生類型:安全
ArrayList a=new ArrayList<String>(); a.add(new Object());
以上代碼編譯和運行均可以經過,可是埋下了不少隱患。併發
List<String>是List的子類,而不是List<Object>的子類。List這種原生類型逃避了類型檢查。app
List中add任何對象都對。List<?>的變量能夠引用任何參數化(非參數也能夠)的List,可是沒法經過該變量添加非null元素。框架
假設Men extends Person, Boy extends Men
:ide
<? extends T>
表示上界,<? super T>
表示下界。ArrayList<? extends Men> ml=new ArrayList<Boy>();
,等號右邊部分能夠是Men與其子類的參數化ArrayList,或者是ArrayList<>()和ArrayList()。初始化時能夠在new ArrayList<Boy>()
中填入Boy對象,此後不能再往ml裏存元素,從ml的視角,ml多是ArrayList<Men>、ArrayList<Boy>等等,存入任何Men或者其子類對象都不合適,爲了安全起見都不容許存。只能取元素,而且取出的元素只能賦值給Men或者其基類的引用,由於其中元素可能存了任何Men的子類,爲了保險起見取出的值用Men或其基類表示。ArrayList<? super Men> ml=new ArrayList<Person>();
,初始化時能夠存Person對象,以後能夠再存入Men(與其子類,這是默認的),可是再存入Person對象是錯誤的。從ml視角,等號右邊能夠是ArrayList<Person>()、ArrayList<Men>()等等,因此最高只能存入Men對象。取出的元素都是Object,由於等號右邊能夠是ArrayList<Object>()。<? extends T>
和<? super T>
是對等號右邊實參數化ArrayList的限制,而不是對ArrayList中可存入元素的描述。由於從引用ml中沒法得知其實際指向的是那種參數化的ArrayList實例,因此再往其中添加元素時會採用最謹慎的選擇。數組是協變的,也就是Fruit[] fs= new Apple[5];
是合法的,由於Apple是Fruit的子類,則數組也成父子關係,而列表則不適用於該規則。數組的這種關係容易引起錯誤,如fs[0]= new Banana()
,編譯時沒錯,這在運行時報錯。 函數
建立泛型數組是非法的,如new E[]; new List<E>[]; new List<String>[]
。泛型參數在運行時會被擦除,List<String>[]數組可能存入List<Integer>對象,由於運行時二者都是List對象,這顯然是錯誤的,因此泛型不容許用在數組中。 工具
以下代碼在編譯時不會出錯,在運行時出錯java.lang.ClassCastException
。測試
ArrayList<String> list=new ArrayList<String>(); for (int i = 0; i < 10; i++) { list.add(""+i); } //該行報錯 String[] array= (String[]) list.toArray(); }
緣由很迷,toArray返回的是Object[]數組,可是不能強制轉化爲String[],明明元素實際類型是String。有的解釋說,在運行時只有List的概念,而沒有List<String>概念。我感受事情沒這麼簡單。
泛型是在整個類上採用泛型,這樣能夠在類內部方便的使用泛型參數。泛型方法是更精細的利用參數類型,將泛型參數設定在每一個方法上。
比較下面兩個接口,體會其中不一樣:
public interface Comparable<T> { public int compareTo(T o); } public interface Comparable2 { public <T> int compareTo2(T o); } public class Apple implements Comparable<Apple>, Comparable2{ @Override public int compareTo(Apple o) { return 0; } @Override public <T> int compareTo2(T o) { //T 能夠爲任何類,因此Apple能夠和任何類比較 return 0; } }
有類:
class Apple implements Comparable<Apple>{ } class RedApple extends Apple{ }
有方法:
public static <T extends Comparable<T>> T get(T t){ return t; }
該方法就採用了遞歸的類型限制,由於泛型T被限制爲 Comparable<T>的子類,而Comparable<T>中又包含了T,這造成一種遞歸的類型限制。Apple類能夠調用該函數,RedApple則會出現錯誤,以下所示。
RedApple ra=new RedApple(); Apple a= get(ra); //正確 RedApple b=get(ra); //錯誤
緣由是在調用泛型函數時,會自動進行類型推斷,第一個get函數根據左邊參數,推斷T爲Apple,符合條件。在第二個get公式中,推斷T爲RedApple,不符合get函數的泛型限制條件。
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
其中<T extends Comparable<? super T>>
描述了T實現了Comparable接口或者其基類實現了該接口,經過繼承得到Comparable的狀況比較常見,這增長了該函數的通用性。參數List<? extends T>
表示List只要存的是T的子類就能夠,這是顯然合理的,一樣加強了該函數的通用性。
一個容器如Set只有1個類型參數,Map只有2個類型參數,可是有時候須要多個類型參數。下面是一個設計巧妙、能夠容納多個類型參數的類。
public static void main(String[] args){ Favorites f =new Favorites(); f.putFavorite(String.class, "Java"); f.putFavorite(Integer.class, 1111); f.putFavorite(Class.class, Favorites.class); int fi=f.getFavorite(Integer.class); } public class Favorites{ private Map<Class<?>, Object> favorites=new HashMap<Class<?>, Object>(); public <T> void putFavorite(Class<T> type, T instance){ if(type==null) throw new NullPointerException("Type is null"); favorites.put(type, instance); } public <T> T getFavorite(Class<T> type){ return type.cast(favorites.get(type)); } }
String.class爲Class<String>的實例,Integer.class爲Class<Integer>的實例,這二者顯然不是同一個類,可是卻能夠放在同一個Map中。Map中採用了通配符?
,按理Map沒法再加入任何元素,可是該通配符並非直接表示Map的類型參數,而是Class<?>。所以Map的鍵值能夠是Class<String>、Class<Integer>等不一樣的類型,所以成爲一個異構容器。
其中Map實例favorites並無限制value必定是key描述的類的實例,而方法putFavorite經過類型參數T,巧妙的限制了二者的關係。
枚舉類型更像是一個不能new的類,只能在定義時就實例化好須要的固定數目的實例。以下所示:
public enum Planet { VENUS(2), EARTH(3), MARS(5); int data; Planet(int i){ this.data=i; } public int getData(){ return data; } }
其構造函數默認是private,而且沒法修改。在枚舉中還能夠定義抽象方法,以下:
public enum Operation{ PLUS { double apply(double x, double y){return x+y;}}, MINUS { double apply(double x, double y){return x-y}}; abstract double apply(double x, double y); }
定義一個用來測試方法是否能拋出目標異常的註解。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ExceptionTest{ Class<? extends Exception>[] value(); }
元註解指明瞭該註解在運行時保留,而且只適用於註解方法。
使用以下:
@ExpectionTest({IndexOutOfBoundsException.class, NullPointerException.class}) public static void doublyBad(){ List<String> list=new ArrayList<String>(); //該方法會拋出IndexOutOfBoundsException list.addAll(5,null); }
測試過程實現以下:
public static void main(String[] args) throws Exception{ int tests=0; int passed=0; Class testClass=Class.forName(args[0]); for(Method m : testClass.getDeclaredMethods()){ if(m.isAnnotationPresent(ExceptionTest.class)){ tests++; try{ m.invoke(null); System.out.printf("Test failed: no exceptions"); }catch( Throwable wrappedExc){ Throwable exc = wrappedExc.getCause(); Class<? extends Exception>[] excTypes=m.getAnnotation(ExceptionText.class).value(); int oldPaassed=passed; for(Class<? extends Exception> excType:excTypes){ if(excType.isInstance(exc)){ passed++; break; } } if(passed==oldPassed) System.out.printf("Test failed"); } } } }
重載方法是靜態的,在編譯時就已經選擇好,根據參數的表面類型,如Collection<String> c=new ArrayList<String>()
,有兩個重載函數,getMax(Collection<?> a)
和getMax(ArrayList<?> a)
,在調用getMax(c)
時,會選擇getMax(Collection<?> a)
方法,該選擇在編譯時就決定好了。當重載方法有多個參數時,狀況會變得更復雜,選擇結果可能出人意料。
而方法的重寫選擇時動態的,在運行時根據調用者的實際類型決定哪一個方法被調用。
可變參數可讓用戶靈活的填入不一樣數量的參數,可是該方法本質上是將參數組織成數組,因此每次調用這些方法時都會涉及數組的建立和銷燬,開銷較大。
除了double和long之外,讀寫變量是原子性的。可是Java沒法保證一個線程的修改對另外一個線程是可見的。
若是一個同步方法在其中調用了一個不禁本身控制的方法,好比客戶傳入的方法,客戶可能在實現方法時申請同步鎖,或者啓動新線程申請鎖,這可能會致使死鎖。
java.util.concurrent包提供了執行框架、併發集合和同步器三種工具,應該儘可能使用這些工具來實現併發功能,而不是使用wait、notify。
若是使用wait,notify則應該採用以下模式:
public void waitA(){ synchronized(a){ //得到鎖 while(a>10) //放在while循環中保證知足條件 try { a.wait(); //釋放鎖、若是被喚醒則須要從新得到鎖 } catch (InterruptedException e) { e.printStackTrace(); } } } //其它線程調用該方法喚醒等待線程 public void notifyA(){ a.notifyAll(); }
notifyAll方法相較於notify方法更安全,它保證喚醒了全部等待a對象的線程,被喚醒不表明會被當即執行,由於還須要得到鎖。
Tread yield(讓步)即當前線程將資源歸還給調度器,可是並不能保證當前線程下面必定不會被選中。線程的優先級設置也是不能保證按你預期的進行調度。