JAVA核心面試知識-JAVA 基礎(五)

5.1.1. JAVA 異常分類及處理

5.1.1.1. 概念

如果某個方法不能按照正常的途徑完成任務,就可以通過另一種路徑退出方法。在這種情況下會拋出一個封裝了錯誤信息的對象。此時,這個方法會立刻退出同時不返回任何值。另外,調用這個方法的其他代碼也無法繼續執行,異常處理機制會將代碼執行交給異常處理器。
在這裏插入圖片描述

5.1.1.2. 異常分類

Throwable 是 Java 語言中所有錯誤或異常的超類。下一層分爲 Error 和 Exception
Error

  1. Error 類是指 java 運行時系統的內部錯誤和資源耗盡錯誤。應用程序不會拋出該類對象。如果出現了這樣的錯誤,除了告知用戶,剩下的就是盡力使程序安全的終止
    Exception(RuntimeException、CheckedException)
  2. Exception 又 有 兩 個 分 支 , 一 個 是 運 行 時 異 常 RuntimeException , 一 個 是
    CheckedException。

RuntimeException 如: NullPointerException 、 ClassCastException ;一個是檢查異常CheckedException,如 I/O 錯誤導致的 IOException、SQLException。 RuntimeException 是那些可能在 Java 虛擬機正常運行期間拋出的異常的超類。 如果出現 RuntimeException,那麼一定是程序員的錯誤.

檢查異常 CheckedException:一般是外部錯誤,這種異常都發生在編譯階段,Java 編譯器會強制程序去捕獲此類異常,即會出現要求你把這段可能出現異常的程序進行 try catch,該類異常一般包括幾個方面:

  1. 試圖在文件尾部讀取數據
  2. 試圖打開一個錯誤格式的URL
  3. 試圖根據給定的字符串查找 class 對象,而這個字符串表示的類並不存在

5.1.1.3. 異常的處理方式

遇到問題不進行具體處理,而是繼續拋給調用者 (throw,throws)
拋出異常有三種形式,一是 throw,一個 throws,還有一種系統自動拋異常。

public static void main(String[]args){
        String s="abc";
        if(s.equals("abc")){
        throw new NumberFormatException();
        }else{
            System.out.println(s);
        }
        }
        int div(int a,int b)throws Exception{
        return a/b;
}

try catch 捕獲異常針對性處理方式

5.1.1.4. Throw 和 throws 的區別:

位置不同

  1. throws 用在函數上,後面跟的是異常類,可以跟多個;而 throw 用在函數內,後面跟的是異常對象。
    功能不同:
  2. throws 用來聲明異常,讓調用者只知道該功能可能出現的問題,可以給出預先的處理方式;throw 拋出具體的問題對象,執行到throw,功能就已經結束了,跳轉到調用者,並將具體的問題對象拋給調用者。也就是說 throw 語句獨立存在時,下面不要定義其他語句,因爲執行不到。
  3. throws 表示出現異常的一種可能性,並不一定會發生這些異常;throw 則是拋出了異常, 執行throw 則一定拋出了某種異常對象。
  4. 兩者都是消極處理異常的方式,只是拋出或者可能拋出異常,但是不會由函數去處理異常,真正的處理異常由函數的上層調用處理。

5.1.2. JAVA 反射

5.1.2.1. 動態語言

動態語言,是指程序在運行時可以改變其結構:新的函數可以引進,已有的函數可以被刪除等結 構上的變化。比如常見的JavaScript 就是動態語言,除此之外 Ruby,Python 等也屬於動態語言, 而 C、C++則不屬於動態語言。從反射角度說JAVA 屬於半動態語言。

5.1.2.2. 反射機制概念 (運行狀態中知道類所有的屬性和方法)

在這裏插入圖片描述
在 Java 中的反射機制是指在運行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法; 並且對於任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能成爲 Java 語言的反射機制。

5.1.2.3. 反射的應用場合

編譯時類型和運行時類型
在 Java 程序中許多對象在運行是都會出現兩種類型:編譯時類型和運行時類型。 編譯時的類型由聲明對象時實用的類型來決定,運行時的類型由實際賦值給對象的類型決定 。如:

Person p=new Student();

其中編譯時類型爲Person,運行時類型爲 Student。

編譯時類型無法獲取具體方法
程序在運行時還可能接收到外部傳入的對象,該對象的編譯時類型爲 Object,但是程序有需要調用該對象的運行時類型的方法。爲了解決這些問題,程序需要在運行時發現對象和類的真實信息。然而,如果編譯時根本無法預知該對象和類屬於哪些類,程序只能依靠運行時信息來發現該對象和類的真實信息,此時就必須使用到反射了。

5.1.2.4. Java 反射API

反射 API 用來生成 JVM 中的類、接口或則對象的信息。

  1. Class 類:反射的核心類,可以獲取類的屬性,方法等信息。
  2. Field 類:Java.lang.reflec 包中的類,表示類的成員變量,可以用來獲取和設置類之中的屬性值。
  3. Method 類: Java.lang.reflec 包中的類,表示類的方法,它可以用來獲取類中的方法信息或者執行方法。
  4. Constructor 類: Java.lang.reflec 包中的類,表示類的構造方法。

5.1.2.5. 反射使用步驟(獲取 Class 對象、調用對象方法)

  1. 獲取想要操作的類的 Class 對象,他是反射的核心,通過 Class 對象我們可以任意調用類的方法。
  2. 調用Class 類中的方法,既就是反射的使用階段。
  3. 使用反射API 來操作這些信息。

5.1.2.6. 獲取 Class 對象的 3 種方法

調用某個對象的 getClass()方法

Person p=new Person();
Class clazz=p.getClass();

調用某個類的 class 屬性來獲取該類對應的 Class 對象

Class clazz=Person.class;

使用 Class 類中的 forName()靜態方法(最安全/性能最好)

Class clazz=Class.forName("類的全路徑"); (最常用)

當我們獲得了想要操作的類的 Class 對象後,可以通過 Class 類中的方法獲取並查看該類中的方法和屬性。

//獲取 Person 類的Class 對象
Class clazz=Class.forName("reflection.Person");
//獲取 Person 類的所有方法信息
Method[] method=clazz.getDeclaredMethods();
 for(Method m:method){
System.out.println(m.toString());
}
//獲取 Person 類的所有成員屬性信息Field[] field=clazz.getDeclaredFields(); 
for(Field f:field){
System.out.println(f.toString());
}
//獲取 Person 類的所有構造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors(); 
for(Constructor c:constructor){
System.out.println(c.toString());
}

5.1.2.7. 創建對象的兩種方法

Class 對象的 newInstance()

  1. 使用 Class 對象的 newInstance()方法來創建該 Class 對象對應類的實例,但是這種方法要求該 Class 對象對應的類有默認的空構造器。
    調用 Constructor 對象的 newInstance()
  2. 先使用Class 對象獲取指定的Constructor 對象,再調用Constructor 對象的 newInstance()
    方法來創建 Class 對象對應類的實例,通過這種方法可以選定構造方法創建實例。
//獲取Person 類的 Class 對象
Class clazz=Class.forName("reflection.Person");
//使用.newInstane 方法創建對象
Person p=(Person) clazz.newInstance();
//獲取構造方法並創建對象
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//創建對象並設置屬性
Person p1=(Person) c.newInstance("李四","男",20);

5.1.3. JAVA 註解

5.1.3.1. 概念

Annotation(註解)是 Java 提供的一種對元程序中元素關聯信息和元數據(metadata)的途徑和方法。Annatation(註解)是一個接口,程序可以通過反射來獲取指定程序中元素的 Annotation 對象,然後通過該Annotation 對象來獲取註解中的元數據信息。

5.1.3.2. 4 種標準元註解

元註解的作用是負責註解其他註解。 Java5.0 定義了 4 個標準的 meta-annotation 類型,它們被用來提供對其它 annotation 類型作說明。
@Target 修飾的對象範圍
@Target 說明了Annotation 所修飾的對象範圍: Annotation 可被用於packages、types(類、接口、枚舉、Annotation 類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch 參數)。在Annotation 類型的聲明中使用了target 可更加明晰其修飾的目標
@Retention 定義被保留的時間長短
Retention 定義了該Annotation 被保留的時間長短:表示需要在什麼級別保存註解信息,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效),取值(RetentionPoicy)由:

  • SOURCE:在源文件中有效(即源文件保留)
  • CLASS:在 class 文件中有效(即 class 保留)
  • RUNTIME:在運行時有效(即運行時保留)

@Documented 描述-javadoc
@ Documented 用於描述其它類型的 annotation 應該被作爲被標註的程序成員的公共 API,因此可以被例如javadoc 此類的工具文檔化。

@Inherited 闡述了某個被標註的類型是被繼承的
@Inherited 元註解是一個標記註解,@Inherited 闡述了某個被標註的類型是被繼承的。如果一個使用了@Inherited 修飾的 annotation 類型被用於一個 class,則這個 annotation 將被用於該
class 的子類。
在這裏插入圖片描述

5.1.3.3. 註解處理器

如果沒有用來讀取註解的方法和工作,那麼註解也就不會比註釋更有用處了。使用註解的過程中, 很重要的一部分就是創建於使用註解處理器。Java SE5 擴展了反射機制的API,以幫助程序員快速的構造自定義註解處理器。下面實現一個註解處理器。

/1:***定 義 注 解*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供應商編號
     */
    public int id() default -1;

    /*** 供應商名稱*/
    public String name() default ""//2:註解使用public class Apple {
    @FruitProvider(id = 1, name = "陝西紅富士集團", address = "陝西省西安市延安路")
    private String appleProvider;

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }

    public String getAppleProvider() {
        return appleProvider;
    }
}
/3:***********註解處理器***************/

public class FruitInfoUtil {
    public static void getFruitInfo(Class<?> clazz) {
        String strFruitProvicer = "供應商信息:";
        Field[] fields = clazz.getDeclaredFields();//通過反射獲取處理註解for (Field field : fields) {
        if (field.isAnnotationPresent(FruitProvider.class)) {
            FruitProvider fruitProvider = (FruitProvider) field.getAnnotation(FruitProvider.class);
            //註解信息的處理地方
            strFruitProvicer = " 供應商編號:" + fruitProvider.id() + " 供應商名稱:"
                    + fruitProvider.name() + " 供應商地址:" + fruitProvider.address();
            System.out.println(strFruitProvicer);
        }
    }
}
public class FruitRun {
    public static void main(String[] args) { FruitInfoUtil.getFruitInfo(Apple.class);
/***********輸出結果***************/
// 供應商編號:1 供應商名稱:陝西紅富士集團 供應商地址:陝西省西安市延
    }
}

5.1.4. JAVA 內部類

Java 類中不僅可以定義變量和方法,還可以定義類,這樣定義在類內部的類就被稱爲內部類。根據定義的方式不同,內部類分爲靜態內部類,成員內部類,局部內部類,匿名內部類四種。

5.1.4.1. 靜態內部類

定義在類內部的靜態類,就是靜態內部類。

public class Out {
    private static int a;
    private int b;

    public static class Inner {
        public void print() {
            System.out.println(a);
        }
    }
}
  1. 靜態內部類可以訪問外部類所有的靜態變量和方法,即使是 private 的也一樣。
  2. 靜態內部類和一般類一致,可以定義靜態變量、方法,構造方法等。
  3. 其它類使用靜態內部類需要使用“外部類.靜態內部類”方式,如下所示:Out.Inner inner = new Out.Inner();inner.print();
  4. Java集合類HashMap 內部就有一個靜態內部類Entry。Entry 是HashMap 存放元素的抽象,
    HashMap 內部維護 Entry 數組用了存放元素,但是 Entry 對使用者是透明的。像這種和外部類關係密切的,且不依賴外部類實例的,都可以使用靜態內部類。

5.1.4.2. 成員內部類

定義在類內部的非靜態類,就是成員內部類。成員內部類不能定義靜態方法和變量(final 修飾的除外)。這是因爲成員內部類是非靜態的,類初始化的時候先初始化靜態成員,如果允許成員內部類定義靜態變量,那麼成員內部類的靜態變量初始化順序是有歧義的。

public class Out {
    private static int a;
    private int b;

    public class Inner {
        public void print() {
            System.out.println(a);
            System.out.println(b);
        }
    }

5.1.4.3. 局部內部類(定義在方法中的類)

定義在方法中的類,就是局部類。如果一個類只在某個方法中使用,則可以考慮使用局部類。

public class Out {
    private static int a;
    private int b;

    public void test(final int c) {
        final int d = 1;
        class Inner {
            public void print() {
                System.out.println(c);
            }
        }
    }
}

5.1.4.4. 匿名內部類(要繼承一個父類或者實現一個接口、直接使用new 來生成一個對象的引用)

匿名內部類我們必須要繼承一個父類或者實現一個接口,當然也僅能只繼承一個父類或者實現一個接口。同時它也是沒有 class 關鍵字,這是因爲匿名內部類是直接使用 new 來生成一個對象的引用。

public abstract class Bird {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract int fly();
}

public class Test {
    public void test(Bird bird) {
        System.out.println(bird.getName() + "能夠飛 " + bird.fly() + "米");
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.test(new Bird() {
            public int fly() {
                return 10000;
            }

            public String getName() {
                return "大雁";
            }
        });
    }
}

5.1.5. JAVA 泛型

泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。泛型的本質是參數化類型,也就是說所操作的數據類型被指定爲一個參數。比如我們要寫一個排序方法, 能夠對整型數組、字符串數組甚至其他任何類型的數組進行排序,我們就可以使用Java 泛型。

5.1.5.1. 泛型方法()

你可以寫一個泛型方法,該方法在調用時可以接收不同類型的參數。根據傳遞給泛型方法的參數類型,編譯器適當地處理每一個方法調用。

// 泛型方法 printArray
public static<E> void printArray(E[]inputArray)
        {
        for(E element:inputArray){System.out.printf("%s ",element);
        }
  1. <? extends T>表示該通配符所代表的類型是 T 類型的子類。
  2. <? super T>表示該通配符所代表的類型是 T 類型的父類。

5.1.5.2. 泛型類

泛型類的聲明和非泛型類的聲明類似,除了在類名後面添加了類型參數聲明部分。和泛型方法一 樣,泛型類的類型參數聲明部分也包含一個或多個類型參數,參數間用逗號隔開。一個泛型參數, 也被稱爲一個類型變量,是用於指定一個泛型類型名稱的標識符。因爲他們接受一個或多個參數, 這些類被稱爲參數化的類或參數化的類型。

public class Box<T> {
    private T t;

    public void add(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}

5.1.5.3. 類型通配符?

類 型 通 配 符 一 般 是 使 用 ? 代 替 具 體 的 類 型 參 數 。 例 如 List<?> 在 邏 輯 上 是
List,List 等所有 List<具體類型實參>的父類。

5.1.5.4. 類型擦除

Java 中的泛型基本上都是在編譯器這個層次來實現的。在生成的 Java 字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。如在代碼中定義的 List和 List等類型,在編譯之後都會變成 List。JVM 看到的只是 List,而由泛型附加的類型信息對 JVM 來說是不可見的。
類型擦除的基本過程也比較簡單,首先是找到用來替換類型參數的具體類。這個具體類一般是 Object。如果指定了類型參數的上界的話,則使用這個上界。把代碼中的類型參數都替換成具體的類。

5.1.6. JAVA 序列化(創建可複用的 Java 對象)

保存(持久化)對象及其狀態到內存或者磁盤
Java 平臺允許我們在內存中創建可複用的 Java 對象,但一般情況下,只有當 JVM 處於運行時, 這些對象纔可能存在,即,這些對象的生命週期不會比 JVM 的生命週期更長。但在現實應用中, 就可能要求在JVM 停止運行之後能夠保存(持久化)指定的對象,並在將來重新讀取被保存的對象。
Java 對象序列化就能夠幫助我們實現該功能。
序列化對象以字節數組保持-靜態成員不保存
使用 Java 對象序列化,在保存對象時,會把其狀態保存爲一組字節,在未來,再將這些字節組裝成對象。必須注意地是,對象序列化保存的是對象的”狀態”,即它的成員變量。由此可知,對象序列化不會關注類中的靜態變量。
序列化用戶遠程對象傳輸
除了在持久化對象時會用到對象序列化之外,當使用 RMI(遠程方法調用),或在網絡中傳遞對象時, 都會用到對象序列化。Java 序列化API 爲處理對象序列化提供了一個標準機制,該API 簡單易用。
Serializable 實現序列化
在 Java 中,只要一個類實現了java.io.Serializable 接口,那麼它就可以被序列化。
ObjectOutputStream 和 ObjectInputStream 對對象進行序列化及反序列化
通過ObjectOutputStream 和 ObjectInputStream 對對象進行序列化及反序列化。
writeObject 和 readObject 自定義序列化策略
在類中增加writeObject 和 readObject 方法可以實現自定義序列化策略。
序列化 ID
虛擬機是否允許反序列化,不僅取決於類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)

序列化並不保存靜態變量
序列化子父類說明
要想將父類對象也序列化,就需要讓父類也實現 Serializable 接口。
Transient 關鍵字阻止該變量被序列化到文件中

  1. 在變量聲明前加上 Transient 關鍵字,可以阻止該變量被序列化到文件中,在被反序列化後,transient 變量的值被設爲初始值,如 int 型的是 0,對象型的是 null。
  2. 服務器端給客戶端發送序列化對象數據,對象中有一些數據是敏感的,比如密碼字符串等,希望對該密碼字段在序列化時,進行加密,而客戶端如果擁有解密的密鑰,只有在客戶端進行反序列化時,纔可以對密碼進行讀取,這樣可以一定程度保證序列化對象的數據安全。

5.1.7. JAVA 複製

將一個對象的引用複製給另外一個對象,一共有三種方式。第一種方式是直接賦值,第二種方式是淺拷貝,第三種是深拷貝。所以大家知道了哈,這三種概念實際上都是爲了拷貝對象。

5.1.7.1. 直接賦值複製

直接賦值。在 Java 中,A a1 = a2,我們需要理解的是這實際上覆制的是引用,也就是說 a1 和 a2 指向的是同一個對象。因此,當 a1 變化的時候,a2 裏面的成員變量也會跟着變化。

5.1.7.2. 淺複製(複製引用但不復制引用的對象)

創建一個新對象,然後將當前對象的非靜態字段複製到該新對象,如果字段是值類型的, 那麼對該字段執行復制;如果該字段是引用類型的話,則複製引用但不復制引用的對象。因此,原始對象及其副本引用同一個對象。

class Resume implements Cloneable {
    public Object clone() {
        try {
            return (Resume) super.clone();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

5.1.7.3. 深複製(複製對象和其應用對象)

深拷貝不僅複製對象本身,而且複製對象包含的引用指向的所有對象。

class Student implements Cloneable {
    String name;
    int age;
    Professor p;

    Student(String name, int age, Professor p) {
        this.name = name;
        this.age = age;
        this.p = p;
    }

    public Object clone() {
        Student o = null;
        try {
            o = (Student) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println(e.toString());
        }
        o.p = (Professor) p.clone();
        return o;
    }
}

5.1.7.4. 序列化(深 clone 一中實現)

在Java 語言裏深複製一個對象,常常可以先使對象實現 Serializable 接口,然後把對象(實際上只是對象的一個拷貝)寫到一個流裏,再從流裏讀出來,便可以重建對象。

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