03-Java核心類庫_列舉、註解與反射

&再見螢火蟲&發表於2020-10-24

目錄

八,列舉、註解與反射

1,列舉概述

1.1 簡介

2,列舉定義

2.1 Java1.5版本之前

 2.2 Java1.5版本之後

3,列舉常用方法

3.1 歸納

3.2 例項

4,列舉介面

舉例

5,列舉注意事項

6,註解概述

7,內建註解

7.1 概述

7.2 概述

8,自定義註解

8.1 元註解

8.2 自定義註解——註解架構

8.3 自定義註解——定義格式

9,反射概述

10,類載入器與資源目錄

10.1 類載入器

11,Class與載入方式

11.1 所有型別的Class物件

11.2 得到class的幾種方式(將類載入到記憶體的方式)

12,反射中的構造方法

12.1 通過class物件 獲取一個類的構造方法

12.2 Constructor 建立物件

12.3 反射技術打破封裝的例子

13,反射中的方法

13.1 通過class物件 獲取一個類的方法

13.2 Method 執行方法

13.3 舉例

14,反射中的屬性

14.1 通過class物件 獲取一個類的屬性

14.2 Field 屬性的物件型別

15,反射與屬性

15.1  獲取類/屬性/方法的全部註解物件

15.2  根據型別獲取類/屬性/方法的註解物件

15.3 舉例

16,內省

16.1 簡介

16.2 Introspector

16.3 BeanInfo

16.4 MethodDescriptor

16.5 內省的機制


八,列舉、註解與反射

反射和內省,如果不進行框架開發,則很少使用;註解一般使用定義好的,自定義註解用的比較少

1,列舉概述

1.1 簡介

JDK1.5引入了新的型別——列舉。 

在JDK1.5 之前,我們定義常量都是: public static fianl.... 。很難管理。 

列舉,可以把相關的常量分組到一個列舉型別裡,而且列舉提供了比常量更多的方法。 

用於定義有限數量的一組同類常量,例如:

  • 錯誤級別: 低、中、高、急 
  • 一年的四季: 春、夏、秋、冬 
  • 商品的型別: 美妝、手機、電腦、男裝、女裝... 

在列舉型別中定義的常量是該列舉型別的例項

2,列舉定義

2.1 Java1.5版本之前

Level.java

package com.kaikeba;

public class Level {

    // static表示可以通過類名訪問 final表示為常量(變數名稱為大寫)
    public static final Level LOW = new Level(1);
    public static final Level MEDIUM = new Level(2);
    public static final Level HIGH = new Level(3);

    private int levelValue;

    // private修飾的構造方法 表示外界不能通過構造方法例項化物件 同時也不能新增型別
    private Level(int levelValue) {
        this.levelValue = levelValue;
    }

    public int getLevelValue() {
        return levelValue;
    }

    public void setLevelValue(int levelValue) {
        this.levelValue = levelValue;
    }
}

Demo.java

package com.kaikeba;

public class Demo {
    public static void main(String[] args) {
        System.out.println(Level.LOW.getLevelValue());
    }
}

 2.2 Java1.5版本之後

Level.java

package com.kaikeba;
// 注意這裡將class換成enum
public enum Level {
    // 新版本列舉的方法
    LOW(1), MEDIUM(2), HIGH(3);
    // 也可以這樣來表示 直接從順序上判斷優先順序
    // LOW, MEDIUM, HIGH;

    private int levelValue;

    // private修飾的構造方法 表示外界不能通過構造方法例項化物件 同時也不能新增型別
    private Level(int levelValue) {
        this.levelValue = levelValue;
    }

    public int getLevelValue() {
        return levelValue;
    }

    public void setLevelValue(int levelValue) {
        this.levelValue = levelValue;
    }
}

 Demo.java

package com.kaikeba;

public class Demo {
    public static void main(String[] args) {
        System.out.println(Level.LOW.getLevelValue());
    }
}

3,列舉常用方法

3.1 歸納

3.2 例項

Demo.java

package com.kaikeba;

public class Demo {
    public static void main(String[] args) {
        // compareTo方法
        System.out.println(Level.LOW.compareTo(Level.MEDIUM));
        System.out.println(Level.LOW.compareTo(Level.HIGH));
        // name方法
        System.out.println(Level.LOW.name());
        // toString方法
        System.out.println(Level.LOW.toString());
        // ordinal方法
        System.out.println(Level.LOW.ordinal());
        // valueOf方法
        Level x = Enum.valueOf(Level.class, "HIGH");
        System.out.println(x.name());

    }
}

4,列舉介面

所有的列舉都繼承自java.lang.Enum類。由於Java 不支援多繼承,所以列舉物件不能再繼承其他類。 

每個列舉物件,都可以實現自己的抽象方法

舉例

Level.java

main函式

存在的問題:LOW、MEDIUM、HIGH都是一樣的輸出

5,列舉注意事項

一旦定義了列舉,最好不要妄圖修改裡面的值,除非修改是必要的;

列舉類預設繼承的是java.lang.Enum類而不是Object類;

列舉類不能有子類,因為其列舉類預設被final修飾;

只能有private構造方法

switch中使用列舉時,直接使用常量名,不用攜帶類名;

不能定義name屬性,因為自帶name屬性;

不要為列舉類中的屬性提供set方法,不符合列舉最初設計初衷;

6,註解概述

註解與註釋:註釋是為了程式設計師更好的理解程式碼,只能存在於原始檔中,一旦編譯變成.class檔案,便會忽略掉註釋。註解可以理解為讓機器理解的,可以保留到程式碼執行的階段。

Java 註解(Annotation)又稱 Java 標註,是 JDK5.0 引入的一種註釋機制。

Java 語言中的類、方法、變數、引數和包等都可以被標註。和註釋不同,Java 標註可以通過反射獲取標註內容。在編譯器生成類檔案時,標註可以被嵌入到位元組碼中。Java 虛擬機器可以保留標註內容,在執行時可以獲取到標註內容 。 當然它也支援自定義 Java 標註。

主要用於:

  • 編譯格式檢查
  • 反射中解析
  • 生成幫助文件
  • 跟蹤程式碼依賴

7,內建註解

7.1 概述

@Override : 重寫 *

定義在java.lang.Override

@Deprecated:廢棄 *

定義在java.lang.Deprecated

@SafeVarargs

Java 7 開始支援,忽略任何使用引數為泛型變數的方法或建構函式呼叫產生的警告。

@FunctionalInterface: 函式式介面 *

Java 8 開始支援,標識一個匿名函式或函式式介面。

@Repeatable:標識某註解可以在同一個宣告上使用多次

Java 8 開始支援,標識某註解可以在同一個宣告上使用多次。

SuppressWarnings:抑制編譯時的警告資訊。 *

定義在java.lang.SuppressWarnings

三種使用方式

  • @SuppressWarnings("unchecked") [^ 抑制單型別的警告] 
  • @SuppressWarnings("unchecked","rawtypes") [^ 抑制多型別的警告] 
  • @SuppressWarnings("all") [^ 抑制所有型別的警告]

引數列表

  • all 抑制所有警告
  • boxing 抑制裝箱、拆箱操作時候的警告
  • cast 抑制對映相關的警告
  • dep-ann 抑制啟用註釋的警告
  • deprecation 抑制過期方法警告
  • fallthrough 抑制確在switch中缺失breaks的警告
  • finally 抑制finally模組沒有返回的警告
  • hiding 抑制相對於隱藏變數的區域性變數的警告
  • incomplete-switch 忽略沒有完整的switch語句
  • nls 忽略非nls格式的字元
  • null 忽略對null的操作
  • rawtypes 使用generics時忽略沒有指定相應的型別
  • restriction 抑制禁止使用勸阻或禁止引用的警告
  • serial 忽略在serializable類中沒有宣告serialVersionUID變數
  • static-access 抑制不正確的靜態訪問方式警告
  • synthetic-access 抑制子類沒有按最優方法訪問內部類的警告
  • unchecked 抑制沒有進行型別檢查操作的警告
  • unqualified-field-access 抑制沒有許可權訪問的域的警告
  • unused 抑制沒被使用過的程式碼的警告

7.2 概述

1)Override重寫

IDEA中每次儲存程式碼,都會將.java檔案轉換為.class檔案, 這時會檢查屬性或方法的註解,檢視其是否符合註解的格式要求,若不符合則給出錯誤資訊:

2)Deprecated廢棄

對於前期編寫的一些方法,後期可能不再需要,若此時將其刪除,則會使之前呼叫此方法的地方出現問題,所以建議擴充套件程式碼,而不是刪除。

package com.kaikeba;

public class Demo {
    public static void main(String[] args) {
        Person p = new Person();
        p.setAge(12);

    }
}
class Person{
    private int age;

    public int getAge() {
        return age;
    }

    /**
     * 此方法已經被廢棄,請通過setAge2進行操作
     * @param age
     */
    @Deprecated
    public void setAge(int age) {
        this.age = age;
    }

    public void setAge2(int age) {
        if(age < 0 || age > 120) {
            throw new RuntimeException("該年齡不合理");
        }
        this.age = age;
    }
}

3)SuppressWarnings:抑制編譯時的警告資訊

8,自定義註解

8.1 元註解

1)簡介

作用在其他註解的註解

2)包括

@Retention - 標識這個註解怎麼儲存,是隻在程式碼中,還是編入class檔案中,或者是在執行時可以通過反射訪問。

@Documented - 標記這些註解是否包含在使用者文件中 javadoc。

@Target - 標記這個註解應該是哪種 Java 成員。

@Inherited - 標記這個註解是自動繼承的

  • 子類會繼承父類使用的註解中被@Inherited修飾的註解
  • 介面繼承關係中,子介面不會繼承父介面中的任何註解,不管父介面中使用的註解有沒有 被@Inherited修飾
  • 類實現介面時不會繼承任何介面中定義的註解

8.2 自定義註解——註解架構

01) Annotation與RetentionPolicy 與ElementType

每 1 個 Annotation 物件,都會有唯一的 RetentionPolicy 屬性;至於 ElementType 屬性,則有 1~n個。

(02) ElementType(註解的用途型別)

"每 1 個 Annotation" 都與 "1~n 個 ElementType" 關聯。當 Annotation 與某個 ElementType 關聯時,就意味著:Annotation有了某種用途。例如,若一個 Annotation 物件是 METHOD 型別,則該Annotation 只能用來修飾方法。

package java.lang.annotation; 
public enum ElementType { 
    TYPE, /* 類、介面(包括註釋型別)或列舉宣告 */ 
    FIELD, /* 欄位宣告(包括列舉常量) */ 
    METHOD, /* 方法宣告 */ 
    PARAMETER, /* 引數宣告 */ 
    CONSTRUCTOR, /* 構造方法宣告 */ 
    LOCAL_VARIABLE, /* 區域性變數宣告 */ 
    ANNOTATION_TYPE, /* 註釋型別宣告 */ 
    PACKAGE /* 包宣告 */ 
}

 

(03) RetentionPolicy(註解作用域策略)

"每 1 個 Annotation" 都與 "1 個 RetentionPolicy" 關聯。

  • a) 若 Annotation 的型別為 SOURCE,則意味著:Annotation 僅存在於編譯器處理期間,編譯器處理完之後,該 Annotation 就沒用了。 例如," @Override" 標誌就是一個 Annotation。當它修飾一個方法的時候,就意味著該方法覆蓋父類的方法;並且在編譯期間會進行語法檢查!編譯器處理完後,"@Override" 就沒有任何作用了。
  • b) 若 Annotation 的型別為 CLASS,則意味著:編譯器將 Annotation 儲存於類對應的 .class 檔案中,它是 Annotation 的預設行為。
  • c) 若 Annotation 的型別為 RUNTIME,則意味著:編譯器將 Annotation 儲存於 class 檔案中,並且可由JVM讀入。
package java.lang.annotation; 
public enum RetentionPolicy { 
    SOURCE, /* Annotation資訊僅存在於編譯器處理期間,編譯器處理完之後就沒有該 Annotation資訊了 */ 
    CLASS, /* 編譯器將Annotation儲存於類對應的.class檔案中。預設行為 */ 
    RUNTIME /* 編譯器將Annotation儲存於class檔案中,並且可由JVM讀入 */ 
}

8.3 自定義註解——定義格式

@interface 自定義註解名{}

注意事項

1. 定義的註解,自動繼承了java.lang,annotation.Annotation介面

2. 註解中的每一個方法,實際是宣告的註解配置引數

  • 方法的名稱就是 配置引數的名稱
  • 方法的返回值型別,就是配置引數的型別。只能是:基本型別/Class/String/enum

3. 可以通過default來宣告引數的預設值

4. 如果只有一個引數成員,一般引數名為value

5. 註解元素必須要有值,我們定義註解元素時,經常使用空字串、0作為預設值。

舉例 

 

註解傳參

也可以不傳遞全部引數

9,反射概述

JAVA反射機制是在執行狀態(程式已經執行起來後)中,獲取任意一個類的結構 , 建立物件 , 得到方法,執行方法 , 屬性 !;

這種在執行狀態動態獲取資訊以及動態呼叫物件方法的功能被稱為java語言的反射機制。

spring是很早就編寫的框架,用它來管理現在建立的物件,所用到的就是反射機制

10,類載入器與資源目錄

10.1 類載入器

.java檔案通過編譯得到.class位元組碼檔案,再通過類載入器載入到記憶體中,才可以執行,根據類建立物件

Java類載入器(Java Classloader)是Java執行時環境(Java Runtime Environment)的一部分, 負責動態載入Java類到Java虛擬機器的記憶體空間中。 

java預設有三種類載入器,BootstrapClassLoader、ExtensionClassLoader、App ClassLoader。 

  • BootstrapClassLoader(引導啟動類載入器): 嵌在JVM核心中的載入器,該載入器是用C++語言寫的,主要負責載入JAVA_HOME/lib下的類庫,引 導啟動類載入器無法被應用程式直接使用。 
  • ExtensionClassLoader(擴充套件類載入器): ExtensionClassLoader是用JAVA編寫,且它的父類載入器是Bootstrap。 是由sun.misc.Launcher$ExtClassLoader實現的,主要載入JAVA_HOME/lib/ext目錄中的類 庫。 它的父載入器是BootstrapClassLoader 
  • App ClassLoader(應用類載入器): App ClassLoader是應用程式類載入器,負責載入應用程式classpath目錄下的所有jar和class文 件。它的父載入器為Ext ClassLoader

類通常是按需載入,即第一次使用該類時才載入。

由於有了類載入器,Java執行時系統不需要知道檔案與 檔案系統。學習類載入器時,掌握Java的委派概念很重要。 

雙親委派模型:如果一個類載入器收到了一個類載入請求,它不會自己去嘗試載入這個類,而是把這個請求 轉交給父類載入器去完成。每一個層次的類載入器都是如此。因此所有的類載入請求都應該傳遞到最頂層的 啟動類載入器中,只有到父類載入器反饋自己無法完成這個載入請求(在它的搜尋範圍沒有找到這個類) 時,子類載入器才會嘗試自己去載入。

委派的好處就是避免有些類被重複載入。

獲得類載入器

通過類載入器讀取配置檔案內容

建立新資料夾resource(截圖中應該命名為resource)

將該資料夾設定為資源根目錄

將config.txt檔案放入剛建立的資料夾中,並修改內容

再次執行程式碼

 

11,Class與載入方式

11.1 所有型別的Class物件

要想了解一個類,必須先要獲取到該類的位元組碼檔案物件.

在Java中,每一個位元組碼檔案,被載入到記憶體後,都存在一個對應的Class型別的物件

11.2 得到class的幾種方式(將類載入到記憶體的方式)

1. 如果在編寫程式碼時, 指導類的名稱, 且類已經存在, 可以通過 

  • 包名.類名.class 得到一個類的 類物件 

2. 如果擁有類的物件, 可以通過 

  • Class 物件.getClass() 得到一個類的 類物件 

3. 如果在編寫程式碼時, 知道類的名稱 , 可以通過 

  • Class.forName(包名+類名): 得到一個類的 類物件

上述的三種方式, 在呼叫時, 如果類在記憶體中不存在, 則會載入到記憶體 ! 如果類已經在記憶體中存在, 不 會重複載入, 而是重複利用 ! 

舉例

package com.kaikeba;

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException {
        // 方式1:通過包名.類名.class載入類
        Class<Book> c1 = com.kaikeba.Book.class;
        System.out.println(c1);

        // 方式2:通過類的物件獲取類的資訊(此時類已經載入到記憶體中了)
        Book b = new Book();
        Class<Book> c2 = (Class<Book>) b.getClass();
        System.out.println(c2);

        // 方式3:通過forName+類的字串名稱載入類
        Class<Book> c3 = (Class<Book>) Class.forName("com.kaikeba.Book");
        System.out.println(c3);

        System.out.println(c1 == c2 && c1 == c3);

    }
}

當把Book類刪除後

所以一些框架可以通過這種方法將類載入到記憶體中去:

12,反射中的構造方法

12.1 通過class物件 獲取一個類的構造方法

1. 通過指定的引數型別, 獲取指定的單個構造方法 

getConstructor(引數型別的class物件陣列) 

例如:

構造方法如下: Person(String name,int age) 

得到這個構造方法的程式碼如下: 

Constructor c = p.getClass().getConstructor(String.class,int.class); 

2. 獲取構造方法陣列 

getConstructors(); 

3. 獲取所有許可權的單個構造方法 

getDeclaredConstructor(引數型別的class物件陣列) 

4. 獲取所有許可權的構造方法陣列 

getDeclaredConstructors();

12.2 Constructor 建立物件

newInstance(Object... para) 

呼叫這個構造方法, 把對應的物件建立出來 

引數: 是一個Object型別可變引數, 傳遞的引數順序 必須匹配構造方法中形式引數列表的順 序! 

setAccessible(boolean flag) 

如果flag為true 則表示忽略訪問許可權檢查 !(可以訪問任何許可權的方法)

舉例——無參構造方法

package com.kaikeba;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 將類載入到記憶體中
        Class<Person> pClass = (Class<Person>) Class.forName("com.kaikeba.Person");
        // 獲得無參構造方法
        Constructor<Person> c1 = pClass.getConstructor();
        // 使用無參構造方法例項化物件
        Person p = c1.newInstance();

        System.out.println(p);
    }
}

 舉例——全參構造方法

package com.kaikeba;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 將類載入到記憶體中
        Class<Person> pClass = (Class<Person>) Class.forName("com.kaikeba.Person");
        // 獲得全參構造方法
        // Constructor<Person> c2 = pClass.getConstructor(new Class[]{String.class, int.class});
        Constructor<Person> c2 = pClass.getConstructor(String.class, int.class);
        // 使用全參構造方法例項化物件
        Person p = c2.newInstance("張三", 18);

        System.out.println(p);

    }
}

12.3 反射技術打破封裝的例子

1)在Person類中新增私有化的構造方法(必須用getDeclaredConstructor方法獲取私有的構造方法)

執行提示報錯

 忽略許可權檢查

13,反射中的方法

通過反射找到類中的方法,再通過物件執行類的方法。

13.1 通過class物件 獲取一個類的方法

1. getMethod(String methodName , class.. clss) 

根據引數列表的型別和方法名, 得到一個方法(public修飾的) 

2. getMethods(); 

得到一個類的所有方法 (public修飾的) 

3. getDeclaredMethod(String methodName , class.. clss) 

根據引數列表的型別和方法名, 得到一個方法(除繼承以外所有的:包含私有, 共有, 保護, 預設) 

4. getDeclaredMethods(); 

得到一個類的所有方法 (除繼承以外所有的:包含私有, 共有, 保護, 預設)

13.2 Method 執行方法

invoke(Object o,Object... para) : 

  • 引數1. 要呼叫方法的物件 
  • 引數2. 要傳遞的引數列表 

getName()

  • 獲取方法的方法名稱 

setAccessible(boolean flag)

  • 如果flag為true 則表示忽略訪問許可權檢查 !(可以訪問任何許可權的方法)

13.3 舉例

package com.kaikeba;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 將類載入到記憶體中
        Class pClass = Class.forName("com.kaikeba.Person");
        // 獲得私有的構造方法
        Constructor c1 = pClass.getConstructor();
        // 建立物件
        Object o = c1.newInstance();
        // 獲取類的方法
        Method setName = pClass.getMethod("setName", String.class);
        // 執行setName方法(引數一:呼叫方法的物件,引數二:呼叫方法時傳遞的引數)
        setName.invoke(o, "haha");
        System.out.println(o);

    }
}

 獲得private修飾的方法

忽略許可權獲得private修飾的方法,並執行

package com.kaikeba;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 將類載入到記憶體中
        Class pClass = Class.forName("com.kaikeba.Person");
        // 獲得私有的構造方法
        Constructor c1 = pClass.getConstructor();
        // 建立物件
        Object o = c1.newInstance();
        // 獲取類的方法
        Method setName = pClass.getMethod("setName", String.class);
        Method setAge = pClass.getDeclaredMethod("setAge", int.class);
        setAge.setAccessible(true);// 忽略許可權檢查
        // 執行setName方法(引數一:呼叫方法的物件,引數二:呼叫方法時傳遞的引數)
        setName.invoke(o, "haha");
        setAge.invoke(o, 18);
        System.out.println(o);

    }
}

14,反射中的屬性

14.1 通過class物件 獲取一個類的屬性

1. getDeclaredField(String filedName) 

  • 根據屬性的名稱, 獲取一個屬性物件 (所有屬性) 

2. getDeclaredFields() 

  • 獲取所有屬性 

3. getField(String filedName) 

  • 根據屬性的名稱, 獲取一個屬性物件 (public屬性) 

4. getFields() 

  • 獲取所有屬性 (public)

14.2 Field 屬性的物件型別

常用方法: 

1. get(Object o ); 獲取指定物件的此屬性值 

  • 引數: 要獲取屬性的物件 

2. set(Object o , Object value);設定指定物件的屬性的值 

  • 引數1. 要設定屬性值的 物件 
  • 引數2. 要設定的值 

3. getName() 獲取屬性的名稱 

4. setAccessible(boolean flag) 

  • 如果flag為true 則表示忽略訪問許可權檢查 !(可以訪問任何許可權的屬性)

 

15,反射與屬性

通過反射獲取註解的內容

15.1  獲取類/屬性/方法的全部註解物件

Annotation[] annotations01 = Class/Field/Method.getAnnotations(); 
    for (Annotation annotation : annotations01) { 
    System.out.println(annotation); 
}

15.2  根據型別獲取類/屬性/方法的註解物件

註解型別 物件名 = (註解型別) c.getAnnotation(註解型別.class);

 

15.3 舉例

TableAnnotation.java

package com.kaikeba.demo;

import java.lang.annotation.*;

@Target(ElementType.TYPE)               // 允許使用在類上
@Retention(RetentionPolicy.RUNTIME)     // 持久化策略
@Documented                             // 允許寫在文件中
public @interface TableAnnotation {
    /**
     * 用於標註類對應的表格名稱(與資料庫關聯)
     * @return
     */
    String value();
}

ColumnAnnotation.java

package com.kaikeba.demo;

import java.lang.annotation.*;

@Target(ElementType.FIELD)// 允許使用在屬性上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ColumnAnnotation {
    /**
     * 描述列名
     * @return
     */
    String columnName();

    /**
     * 描述型別
     * @return
     */
    String type();

    /**
     * 描述資料的長度
     * @return
     */
    String length();
}

Book.java

package com.kaikeba.demo;

import java.util.Objects;
// 內部原理:通過反射找到類,並找到類上的註解,最終基於註解中的內容與資料庫中的表格進行繫結
// 這裡雖然加上了自定義的四個註解,但是缺少背後與資料庫關聯的邏輯,所以沒有實際作用
@TableAnnotation("test_Book")// 通過註解描述 這個類和資料庫中的Book對應
public class Book {
    @ColumnAnnotation(columnName = "id", type = "int", length = "11")
    private int id;
    @ColumnAnnotation(columnName = "name", type = "varchar", length = "50")
    private String name;
    @ColumnAnnotation(columnName = "info", type = "varchar", length = "1000")
    private String info;

    public Book(int id, String name, String info) {
        this.id = id;
        this.name = name;
        this.info = info;
    }

    public Book() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", info='" + info + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return id == book.id &&
                Objects.equals(name, book.name) &&
                Objects.equals(info, book.info);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, info);
    }
}

Demo.java

package com.kaikeba.demo;

import java.io.File;
import java.lang.reflect.Field;
import java.text.Annotation;

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c = Class.forName("com.kaikeba.demo.Book");
        TableAnnotation ta = (TableAnnotation) c.getAnnotation(TableAnnotation.class);
        String value = ta.value();
        System.out.println("表名:" + value);      // 類名作為資料庫中表格的名稱

        Field[] fs = c.getDeclaredFields();       // 屬性名作為資料庫中的欄位名稱
        for(Field f : fs) {
            ColumnAnnotation ca = f.getAnnotation(ColumnAnnotation.class);
            System.out.println(f.getName() + "屬性,對應資料庫中的欄位:" + ca.columnName() + ";資料型別:" + ca.type() + ";資料長度:"  + ca.length());
        }

    }
}

執行效果 

 

一些ORM框架的原理:當使用提供的註解時,框架通過找到類,來獲取註解的值(類名以及屬性),然後就可以根據值在資料庫中建立表(將類名與屬性作為表格與欄位的名稱)

 

16,內省

16.1 簡介

基於反射 , java所提供的一套應用到JavaBean的API 

  • 一個定義在包中的類 , 
  • 擁有無參構造器 
  • 所有屬性私有, 
  • 所有屬性提供get/set方法 
  • 實現了序列化介面 

這種類, 我們稱其為 bean類 . (一些沒有業務邏輯的類)

Java提供了一套java.beans包的api , 對於反射的操作, 進行了封裝

16.2 Introspector

獲取Bean類資訊 

方法:

  • BeanInfo getBeanInfo(Class cls) 
  • 通過傳入的類資訊, 得到這個Bean類的封裝物件 .

16.3 BeanInfo

常用的方法: 

  • MethodDescriptor[] getPropertyDescriptors(): 
  • 獲取bean類的 get/set方法 陣列

16.4 MethodDescriptor

常用方法: 

1. Method getReadMethod(); 

  • 獲取一個get方法 

2. Method getWriteMethod(); 

  • 獲取一個set方法 

有可能返回null 注意 ,加判斷 !

16.5 內省的機制

 程式碼

Book.java

package com.kaikeba.demo;

import java.io.Serializable;
import java.util.Objects;

/**
 * 實現序列化介面
 */
public class Book implements Serializable {
    /**
     * 所有屬性必須私有 且通過Getter和Setter方法訪問
     */
    private int id;
    private String name;
    private String info;
    private boolean flag;

    public Boolean getFlag() {
        return flag;
    }

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }

    public Book(int id, String name, String info) {
        this.id = id;
        this.name = name;
        this.info = info;
    }

    /**
     * 必須有無參構造方法
     */
    public Book() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", info='" + info + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Book book = (Book) o;
        return id == book.id &&
                Objects.equals(name, book.name) &&
                Objects.equals(info, book.info);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, info);
    }
}

Demo.java

package com.kaikeba;

import com.kaikeba.demo.Book;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws IntrospectionException {
        Class c = Book.class;
        BeanInfo bi = Introspector.getBeanInfo(c);
        PropertyDescriptor[] pds = bi.getPropertyDescriptors();
        for(PropertyDescriptor pd : pds) {
            Method get = pd.getReadMethod();
            Method set = pd.getWriteMethod();
            System.out.println("屬性名稱:" + pd.getName());
            System.out.println("屬性型別:" + pd.getPropertyType());
            System.out.println("屬性方法:");
            System.out.println(get);
            System.out.println(set);
            System.out.println();
        }

    }
}

課堂上老師演示的程式碼說,Boolean型別獲取到的方法分別是:is方法名,set方法名,分別對應於get方法名,set方法名。但是我測試時並沒有顯示這樣的差別;

通過內省機制,可以更快速的操作Bean物件獲得Getter和Setter方法(相比於用基礎的反射操作)

相關文章