Java知識回顧(二)

泛型是什麼

集合存儲對象並在使用先進行類型轉換很不方便,泛型是爲了那種情況的發生。他提供了編譯器的類型安全,確保你只能把正確類型的對象放入集合中,避免了在運行時出現ClassCastException。

泛型如何工作

泛型是通過類型擦除來實現的,編譯器在編譯時擦除了所有相關類型的信息,所以在運行時不存在任何類型的相關信息。

泛型中的限定通配符和非限定通配符

有兩種限定通配符

一種是<? extends T>它通過確保類型必須是T的子類來設定類型的上界

一種是<? super T>它通過確保類型必須是T的父類來設定類型的下界

另一方面<?>表示了非限定通配符,<?>可以用任意類型來替代。

編寫泛型方法

泛型方法需要用泛型類型來替代原始類型,比如使用T, E or K,V等被廣泛認可的類型佔位符。

public V put(K key, V value) {
	return cache.put(key, value);
}

異常

異常是在程序執行期間可能發生的錯誤事件,並且會中斷它的正常流程。

異常關鍵字

  • throw——throw關鍵字用於向運行時拋出異常來處理它。
  • throws——在方法中拋出任何已檢查的異常而不處理它時,我們需要在方法簽名中使用throws關鍵字讓調用者程序知道該方法可能拋出的異常。
  • try-catch——在代碼中使用try-catch塊進行異常處理
  • finally——finally塊總是被執行,無論是否發生異常。

異常的層次結構

Throwable是異常的父類,他有Error和Exception兩個子類。

Error 是超出應用程序範圍的特殊情況,並且無法預測並從中恢復,例如硬件故障,JVM崩潰或內存不足錯誤。

Exception 是程序本身可以處理的異常。Exception 類有一個重要的子類 RuntimeException。運行時異常是由錯誤的編程引起的,例如嘗試從Array中檢索超出下標的元素。

throw和throws

throws關鍵字與方法簽名一起用於聲明方法可能拋出的異常,而throw關鍵字用於破壞程序流並將異常對象移交給運行時來處理它。

OutOfMemoryError

Java中的OutOfMemoryError是java.lang.VirtualMachineError的子類,當JVM用完堆內存時,它會拋出它。

註解

註解是綁定到程序源代碼元素的元數據,對運行​​代碼的操作沒有影響。

Java註解又稱爲標註,是支持加入源碼的特殊語法元數據;Java中的類、方法、變量、參數、包都可以被註解。這裏提到的元數據是描述數據的數據。

典型用例:

  • 編譯器的信息 ——使用註解,編譯器可以檢測錯誤或抑制警告
  • 編譯時處理——軟件工具可以處理註解並生成代碼,配置文件等
  • 運行時處理——可以在運行時檢查註解以自定義程序的行爲

註解分類

標準註解:包括 Override, Deprecated, SuppressWarnings

標準 Annotation 是指 Java 自帶的幾個 Annotation。

上面三個分別表示重寫函數不鼓勵使用忽略Warning

元註解

1. @Retention

用於指定被修飾的註解可以保留多長時間,只能修飾Annotation定義。

  • RetentionPolicy.CLASS——編譯器將把Annotation記錄在class文件中。當運行java程序時,JVM不可獲取Annotation信息。
  • RetentionPolicy.RUNTIME——編譯器將把Annotation記錄在class文件中。當運行java程序時,JVM也可獲取Annotation信息,程序可以通過反射獲取該Annotation信息。
  • RetentionPolicy.SOURCE: Annotation只保留在源代碼中(.java文件中),編譯器直接丟棄這種Annotation。

@Target

用於指定被修飾的Annotation能用於修飾哪些程序單元。

  • @Target(ElementType.ANNOTATION_TYPE): 指定該策略的Annotation只能修飾Annotation。
  • @Target(ElementType.TYPE) : 接口、類、枚舉、註解
  • @Target(ElementType.FIELD) : 成員變量(字段、枚舉的常量)
  • @Target(ElementType.METHOD) : 方法
  • @Target(ElementType.PARAMETER): 方法參數
  • @Target(ElementType.CONSTRUCTOR): 構造函數
  • @Target(ElementType.LOCAL_VARIABLE): 局部變量
  • @Target(ElementType.PACKAGE): 修飾包定義

3. Documented

用於指定被修飾的Annotation將被javadoc工具提取成文檔。即說明該註解將被包含在javadoc中。

4. @Inherited

用於指定被修飾的Annotation具有繼承性。即子類可以繼承父類中的該註解。

5. Repeatable

表示這個註解可以在同一處多次聲明

反射

反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制。

通過反射可以在程序運行時動態創建對象並調用類中的方法。

Java的反射機制可以做3件事:運行時創建對象、運行時調用方法、運行時讀寫屬性。

動態代理

代理類在程序運行時創建的代理方式被稱爲動態代理。
代理類並不是在Java代碼中定義的,而是在運行時根據我們在Java代碼中的指示動態生成的。
相比於靜態代理, 動態代理的優勢在於可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類的函數。

例如:

在執行委託類中的方法之前輸出“before”,在執行完畢後輸出“after”。

public class Vendor implements Sell {
    public void sell() {
        System.out.println("In sell method");
    }
    public void ad() {
        System, out.println("ad method")
    }
} 

使用靜態代理

public class BusinessAgent implements Sell {
    private Vendor mVendor;
    public BusinessAgent(Vendor vendor) {
        this.mVendor = vendor;
    }
    public void sell() {
        System.out.println("before");
        mVendor.sell();
        System.out.println("after");
    }

    public void ad() {
        System.out.println("before");
        mVendor.ad();
        System.out.println("after");
    }
} 

動態代理

需要定義一個位於代理類與委託類之間的中介類,這個中介類被要求實現InvocationHandler接口。

public class DynamicProxy implements InvocationHandler {

    private Object obj; //obj爲委託類對象; 
    public DynamicProxy(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        Object result = method.invoke(obj, args);
        System.out.println("after");
        return result;
    }

} 
public class Main {
    public static void main(String[] args) {
        //創建中介類實例 
        DynamicProxy inter = new DynamicProxy(new Vendor());
        //加上這句將會產生一個$Proxy0.class文件,這個文件即爲動態生成的代理類文件 
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        //獲取代理類實例sell 
        Sell sell = (Sell) (Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[]{Sell.class}, inter));
        //通過代理類對象調用代理類方法,實際上會轉到invoke方法調用 
        sell.sell();
        sell.ad();
    }
} 

線程進程區別

進程是程序運行和資源分配的基本單位,一個程序至少有一個進程,一個進程至少有一個線程。

進程在執行過程中擁有獨立的內存單元,而多個線程共享內存資源,減少切換次數,從而效率更高。

線程是進程的一個實體,是cpu調度和分派的基本單位,是比程序更小的能獨立運行的基本單位,同一進程中的多個線程之間可以併發執行。

守護線程

用戶線程:平常創建的普通線程
守護線程:用來服務於用戶線程;不需要上層邏輯介入。

當線程只剩下守護線程的時候,JVM就會退出;如果還有其他的任意一個用戶線程還在,JVM就不會退出。守護線程最典型的例子就是GC線程。

多線程上下文切換

多線程的上下文切換是指CPU控制權由一個已經正在運行的線程切換到另外一個就緒並等待獲取CPU執行權的線程的過程。

創建線程的方式

  • 實現Runnable——實現Runnable接口的類還可能擴展另一個類.
  • 擴展Thread——擴展Thread類就代表這個子類不能擴展其他類
  • 通過Callable和FutureTask創建線程——有返回值

FutureTask

FutureTask表示一個異步運算的任務。

可以對這個異步運算的任務的結果進行等待獲取、判斷是否已經完成、取消任務等操作。

wait()與sleep()的區別

  • 調用sleep()方法的過程中,線程不會釋放對象鎖。而 調用 wait 方法線程會釋放對象鎖
  • sleep()睡眠後不出讓系統資源,wait讓其他線程可以佔用CPU
  • sleep(milliseconds)需要指定一個睡眠時間,時間一到會自動喚醒.而wait()需要配合notify()或者notifyAll()使用

synchronized和ReentrantLock的區別

synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。

ReentrantLock是類,它提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量。

擴展性體現在:

  • ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖
  • ReentrantLock可以獲取各種鎖的信息
  • ReentrantLock可以靈活地實現多路通知
  • 二者的鎖機制其實也是不一樣的:ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的應該是對象頭中mark word.

兩個線程間共享數據

通過在線程之間共享對象就可以了,然後通過wait/notify/notifyAll、await/signal/signalAll進行喚起和等待,比方說阻塞隊列BlockingQueue就是爲線程之間共享數據而設計的。

ThreadLocal

ThreadLocal就是一種以空間換時間的做法在每個Thread裏面維護了一個ThreadLocal.ThreadLocalMap把數據進行隔離,數據不共享,自然就沒有線程安全方面的問題了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章