【期末考試季】JAVA進階複習提綱

MOCHIKO發表於2019-01-19

前言

作為一塊後端沒有太多經驗的年糕,下週要考試了,所以我必須得來好好複習一下我的JAVA進階課/(ㄒoㄒ)/~~。這個學期主要是學了:

  • 泛型
  • 反射
  • 執行緒
  • JDBC
  • JAVA WEB基礎
  • Servlet
  • session&cookie
  • 過濾器&監聽器

泛型

定義:Java的引數化型別被稱為泛型。
出現原因:JAVA不支援多繼承,雖然有介面,但還是有約束,必須要實現介面的方法。
注意點:

  1. 虛擬機器沒有泛型型別物件。比如定義了ArrayList<String>,實際上並沒有這個class的存在。
  2. 泛型型別物件之間沒有關係,就算T之間互為父子關係,也沒有任何關係。
  3. 不能用基本型別例項化型別引數。
  4. 執行時型別查詢只適用於原始型別。if( a instanceof Pair<String>) //error
  5. 不能建立引數化的陣列。宣告型別為Pair<String>[]的變數仍是合法的。不過不能用new Pair<String>[10]初始化這個變數,但可以用(Pair<String>[])new Pair<?>來賦值,可能會找不到類。
  6. 不能例項化型別變數。如new T(), new T[...]T.class都是無效的。
  7. 泛型類的靜態上下文中型別變數無效。
  8. 不能捕獲或丟擲泛型類的例項。
    List<String>  l1=new ArrayList<String>();  
    List<Integer>  l2=new ArrayList<Integer>();
    System.out.println(l1.getClass()==l2.getClass());  //true

    Collection c= new ArrayList<String>();
    if(c instanceof ArrayList<List>){}  //報錯

定義方式

泛型類

public class 類名<T>

使用舉例:

Apple<String> a1 = new Apple<String>("蘋果"); 
Apple<Double> a2 = new Apple<Double>(5.67);

注意:不能單獨用來修飾靜態變數和靜態方法(方法定義具體看後面)。

泛型介面派生類、子類:

一定要指明T的型別,或者不寫<T>(編譯器會警告,預設為是Object)

public class  A1  extends Apple<String>{}
public class  A2  extends Apple{}  //等同於<Object>

泛型方法

public <T>  void ArrayToCollection(T[] a, Collection<T> c){
    //...
}

方法中的泛型引數無須顯式傳入實際型別引數。編譯器根據實參推斷型別形參的值。
為了讓編譯器能夠準確的推斷出泛型方法中的形參型別,不能產生多種可能性。

比如:我寫了一個選出三個變數中中間的那個值的函式。我可以傳入字串比較,也可以傳數字,但數字同時有Comparable和Number兩個介面,這樣它無法確定T應該是哪個,應該寫成public static <T extends Comparable<T>> Pair<T> minmax(T[] a)
限定多個用&連線,比如T extends Comparable&Serializable

型別萬用字元

泛型必須傳入具體的型別,但如果不確定,就可以用型別萬用字元,用?表示。?代表可以使任意型別
如:

public void test(List<?>  c){
  for (int i = 0; i < c.size(); i++) {   
    System.out.println(c.get(i));  
  }
}

關係:
List<?>是List的子類,且List<Integer>、List<String>…都是List<?>的子類。

限定:

  1. 設定上限:? extends Shape,必須是Shape/Shape的子類才可以。
  2. 設定下限:? super Apple,必須是Apple/Apple的父類才可以。

易錯:
1.List<?>集合是隻讀的。不能往List<?>中新增除null的任何東西。
[原因]我們假設可以新增的話:

List<String>  is = Arrays.asList("one", "two", "three"); 
List<?>   list=is;

list.add(new String("four"));//Ok
list.add(new Integer(4));//如果假設成立,則是OK的

那麼混入了其他型別的變數我們也沒有辦法判斷,所以要禁止新增。

2.?不是型別變數,不可以代替型別來使用。

public static void swap(Pair<?> p){
      ? t=p.getFirst(); //錯誤
}

類的載入

定義:當程式主動使用某個類時,如果該類還未被載入到記憶體中,系統會通過載入、連線、初始化三個步驟來該類進行初始化,如果沒有意外,JVM將會連續完成這三個步驟,即類的載入/初始化。

三個步驟:

  1. 載入——找到.class檔案並把這個檔案包含的位元組碼載入到記憶體中
  2. 連線——分為驗證、準備和解析
  3. 初始化——類中靜態屬性和靜態塊的執行

JVM程式終止的情況:

  1. 執行到最後正常結束
  2. 執行到使用System.exit()/Runtime.getRuntime().exit()
  3. 遇到未捕獲的異常或錯誤
  4. 所在平臺強制結束JVM程式。

步驟-載入

呼叫ClassLoader的findClass方法,可從不同來源中載入類的二進位制資料,通常由如下來源:

  1. 本地檔案系統
  2. JAR包,例:JDBC程式設計用到的資料庫驅動類
  3. 網路載入,例:Applet
  4. 其他檔案生成,例:JSP檔案生成對應的Class類
  5. 執行時計算生成,例:動態代理技術

步驟-連線

  1. 驗證:檢查被載入的類是否有正確的內部結構,並和其他類一致。包括檔案格式驗證、後設資料驗證、位元組碼驗證、符合引用驗證
  2. 準備:為類的靜態屬性分配記憶體和指定初始值(通常情況下為預設初始值)。這些變數所使用的的記憶體在方法區被分配。
  3. 解析:將常量池中的符號引用替換為直接引用的過程。主要針對類和介面、欄位、類方法、介面方法、方法型別、方法控制程式碼和呼叫點限定符。

注意:

  1. public static int value = 123,變數value在準備階段的值是0,注意是分配預設值。假設一個變數的定義如下:
  2. public static final int value = 123;變數value在準備階段的值是123,因為這是一個常量,存放在方法區的常量池中。
  3. 解析過程不一定發生在初始化之前,可以發生在初始化之後再開始。

步驟-初始化

編譯器自動收集類中所有類變數的賦值動作和靜態語句塊中的語句,收集的順序由語句在原始檔中出現的順序所決定的。

public class Test {
    static int a = 5;  //準備階段的初值為0,初始化賦值為5
    static int b; //準備階段的初值為0
    static int c; //準備階段的初值為0
    static{   
     //初始化階段的賦值為6
      b = 6; 
     }
}

初始化一個類的步驟

  1. 類沒有被載入,先載入並連線該類。
  2. 類的直接父類還被初始化,先初始化其直接父類。
  3. 類中有初始化語句,系統依次執行這些初始化語句。

初始化類的5中情況

  1. 建立類的例項;讀取或設定一個類的靜態欄位(放入常量池的除外);呼叫一個類的靜態方法。
  2. 使用java.lang.reflect包方法進行反射呼叫(如果沒有進行過初始化)。例:Class.forName("SuperClass")
  3. 父類沒有進行初始化,則需要先觸發父類的初始化
  4. 虛擬機器啟動,使用者需制定一個執行的主類(包含main()方法的那個類),虛擬機器會先初始化這個類。
  5. 來自JDK1.7:一個MethodHandle例項最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法控制程式碼,且控制程式碼所對應的類沒有進行初始化。

注意

  1. 使用ClassLoader類的loadClass()載入某個類時並不會執行該類的初始化。
  2. 如果final型別的靜態屬性的值不能在編譯時得到,必須等到執行時才能確定該屬性的值,就會觸發初始化。

類載入器

將.class檔案載入到記憶體中,生成對應的java.lang.Class物件。
注意:
只有類是同一個類載入器載入才有可能等於(包含Class物件的equals方法、instanceof)。

類載入器分類

  1. Bootstrap ClassLoader:根類載入器,載入Java的核心類。
  2. Extension ClassLoader:擴充套件類載入器,載入JRE的擴充套件目錄(JAVA_HOME/jre/lib/ext)中的JAR的類包。
  3. System ClassLoader:系統類載入器,載入命令java中的classpath選擇的JAR包和類路徑。

類載入機制

  1. 全盤負責:一個類載入器負責載入Class和它的依賴Class,除非顯示使用另一個載入器。
  2. 父類委託:先讓父類載入該Class,在父類載入器無法載入時從自己的類路徑中載入。(類載入器之間的父子關係不是繼承上的父子關係,是類載入器例項之間的關係。

  1. 快取機制:當程式中需要Class時,先從快取中搜尋,快取中不存在時,才重讀該類對應的二進位制資料,並將其轉換為Class物件,並存入到cache。

反射

使用場合:編譯的時候無法獲悉型別,依靠執行時資訊發現,這時就採用反射。

獲取Class的方法

  1. Class類的forName()靜態方法(可能丟擲ClassNotFoundException)。
  2. 呼叫某個類的class屬性。
  3. 呼叫某個物件的getClass()

獲取建構函式

  1. Constructor<T> getConstructor(Class<?>..ParameterType)獲取Class物件表示類的某個public構造器。
  2. Constructor<?>[] getConstructors()獲取Class物件表示類的所有public構造器。
  3. Constructor<T> getDeclaredConstructor(Class<?>..ParameterType)獲取Class物件表示類的指定構造器。
  4. Constructor<?>[] getDeclaredConstructors()獲取Class物件表示類的所有構造器。

建立物件

  1. Class物件的newInstance()方法:要求該Class物件有預設的構造方法。
  2. 呼叫Constructor物件的newInstance()。

呼叫方法

Class物件的getMethods()方法/getMethod()方法,再呼叫Method Object invoke(Object obj, Object...args),該方法中的obj是執行該方法的主調,後面跟著的是引數。

訪問屬性

獲得Class物件後,通過該Class物件的getFields()方法或getDeclaredFields()方法來獲取全部屬性或指定屬性。

Field nameField = personClazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(p , "Yeeku.H.Lee");

相關文章