1. 程式人生 > 其它 >java基礎筆記13 - 反射 註解(重新整理)

java基礎筆記13 - 反射 註解(重新整理)

十三 註解,反射

之前整理到這裡之後就沒啥動力了,筆記也記得很亂,需要重新整理一下

為啥整理

註解+反射是很重要的部分,very import 因為他們是框架的基礎‘

以後看的程式碼裡面會有大量的註解

1.註解 Annotation\

1.1 啥是註解

  • jdk5.0引入的新技術,很簡單,但是很重要

  • 註解不是程式本身,但是對程式進行解釋

    • 可以被其他程式(比如:編譯器 )讀取
    • @朱世明,可以新增一些引數值
@SuppressWarnings(value="unchecked")
  • 可以使用在package,class,method,field等上面
  • 相當於添加了額外的資訊
  • 可以通過反射機制變成實現對這些元資料的訪問

1.2 內建註解

1.2.1常見的內建註解

  • @override 定義在java.lang.Override中,表示一個方法宣告打算重寫超類的方法
  • @Depreacted 廢棄的,表示該方法不被推薦使用,或者有更好的替代品
    • 這個註解本身已經被廢棄了,
  • @SuppressWarning 鎮壓警告
//重寫的註解
@Override
public String toString() {
    return super.toString();
}
//不推薦程式設計師使用,但是可以用,呼叫時一般IDE會用刪除線提醒
@Deprecated
public void test(){
    System.out.println("Deprecated");
}

//鎮壓警告, 沒來li會警告未使用,但是使用@SuppressWarning後,不顯示了
@SuppressWarnings("all")
public void test02(){
    List li=new ArrayList();
}

1.2.2 元註解

  • 元註解:解釋其他註解的註解,官方定義四個meta-Annoatation

  • @Target 描述該註解的適用範圍,能被用在哪

  • @Retentinon:表示需要在什麼界別儲存註釋資訊,用於表述註解的宣告週期

    • (SOURCE<CLASS<RUNTIME)
    • 一般自定義的都是作用在RUNTIME上
  • @Documented : 是否將註解生成在文件JavaDoc中

  • @Inherited:表示子類可以繼承父類的註解

1.3 自定義註解

1.3.1怎麼做

  • ​ 使用@interface自定義註解,並自動竭誠了Annotation介面

  • public @interface 註解名{ 定義內容 }

    • 其中每個方法實際上各一個配置引數
    • 方法的名稱就是引數的名稱
    • 註解必須要有值

1.3.2例子

public class StringTest extends Object{
    @MyAnnoatation(name="張醬",schools = {"東大,哈工程"})
    @MyAnnotation2("不用寫value=")
    public  void test(){

    }
}

@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface MyAnnoatation{
    //註解的引數:引數型別+引數名();
    //註解可以有預設值,可以顯示賦值
    //一定注意這裡定義的不是方法,是註解的引數
    String name() default "";
    int age() default 0;
    int id() default -1;//如果預設值為-1,代表不存在
    String[] schools() default {"家裡蹲","哈佛"};
}
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
    String value();//只有一個引數,約定命名成value
    //只有這樣,使用註解的時候就可以隱式的賦值
    //否則,隱式賦值會報錯
}

2.反射

2.1什麼是java Reflection

2.1.1 靜態vs動態語言

  • 動態:可以再執行時,改變自身結構
    • c#,js,php,python ,Object-c
  • 靜態:java,c,c++
  • 因為java有了反射機制,所以成為了準動態語言

Reflection,反射機制允許程式在執行期間藉助Reflection API獲取任何類的內部資訊,並能直接操作任意物件的內部屬性,以及方法.

載入完類之後,在堆記憶體的方法區中就產生了一個Class型別的物件(getClass方法返回的)(一個類只能有個一Class物件)

這個物件就包含了完整的類的結構資訊。可一個通過這個物件看到類的結構,就像一面鏡子,透過物件看到類的結構,所以稱為:反射

Class c=Class.forName("java.lang.String")

![image-20220308203103335](java筆記 13 反射重新整理.assets/image-20220308203103335.png)

2.1.2 反射功能:

  • 執行時判斷任意物件所屬的類
  • 執行時構造人一個類的物件
  • 執行時判斷任意一個類的成員屬性
  • 執行時獲取泛型資訊
  • 執行時呼叫任意一個物件的成員變數和方法
  • 執行時處理註解
  • 生成動態代理 (AOP)

2.1.3 優缺點

  • 靈活性很大
  • 慢,效率低
public class StringTest {
    public static void main(String[] args) throws ClassNotFoundException {
        //通過反射獲取類的class物件
        Class c1 = Class.forName("User");
        System.out.println(c1);
        Class c2 = Class.forName("User");
        //一個類在記憶體中只有一個Class物件
        //一個類被載入後,類的真個結構都會被封裝在Class物件中
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
    }
}
//實體類:pojo
//name,id,age屬性+構造器+getset方法+toString
class User {
    ...
}

總結:通過物件反射求出類的資訊

2.1.4 學反射能幹啥

學之前:

//反射之前對一個類能幹啥
@Test
public void test1(){
    Person p1=new Person("to",12);
    p1.age=14;
    System.out.println(p1.toString());
    p1.show();
}

反射之後

//反射能幹啥
@Test
public void test2() throws InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
    Class c = Person.class;
    //通過反射建立Person類的物件
    Constructor cons = c.getConstructor(String.class, int.class);
    Object obj = cons.newInstance("tom", 12);
    Person p = (Person)obj;
    System.out.println(obj.toString());

    //2.用過反射,呼叫物件指定的屬性
    Field age = c.getDeclaredField("age");
    age.set(p,10);
    System.out.println(p.toString());
    //3.用過反射,呼叫物件指定的屬性
    Method show = c.getDeclaredMethod("show");
    show.invoke(p);

    //4.通過反射,呼叫物件的私有結構!!
    //呼叫私有構造方法
    Constructor cons1 = c.getDeclaredConstructor(String.class);
    cons1.setAccessible(true);
    Person p1 = (Person)cons1.newInstance("jjj");
    System.out.println(p1.toString());
    //呼叫私有屬性
    Field name = c.getDeclaredField("name");
    name.setAccessible(true);
    name.set(p1,"hanry");
    System.out.println(p1.toString());
    //呼叫私有方法
    Method showNation = c.getDeclaredMethod("showNation",String.class);
    showNation.setAccessible(true);
    String nation = (String) showNation.invoke(p1, "中國");
    System.out.println(nation);

}
class Person{
    private String  name;
    public int age;
	...
        
    public void show(){
        System.out.println("我是一個人");
    }
    private String showNation(String Nation){
        System.out.println("我來自"+Nation);
        return Nation;
    }
}

2.2 理解Class類並獲取Class例項

2.2.1 自己的理解:

  • Class類可以理解成描述類的類,這個類的成員包括:類的名稱,屬性,方法等等
  • 編寫的程式碼中,每一個class 類{....}都是Class類的一個物件(系統建立)
  • 比如cat類,Person類,car類,這些都是Class類的物件
  • 程式執行時,在記憶體中生成這些物件,每個編寫出來的class都只能對應一個 物件(應該有點像單例)
  • 一個非法的程式碼用作比如:Class Person=new Class(); Class Cat = new Class();

而反射呢,就是通過Person類建立的物件p1,來獲取Class類的一個物件(即Person)的資訊

2.2.2 Class類的方法

  • ​ static ClassforName(String name)
    • 返回指定類名的Class物件
  • Obejct newInstance()
    • 通過反射,建立一個物件
  • getName()
    • 返回次Class物件所代表的的實體(類,介面,陣列類,void)
  • Class getSuperClass()
    • 返回Class物件的父類的物件
  • Class[] getinterfaces()
  • ClassLoader getConstructors()
  • Method getMothed(String name,Class .. T)
  • Field[] getDeclaredFields()

2.2.3 獲取Class類的例項

  • 已知具體的類,通過類的class屬性獲取,最安全
    • Class clazz = Person.class;
  • 已知一個類的例項,通過呼叫getClass方法獲取
    • Class clazz= person.getClass();
  • 已知一個類的包+類名,通過Class類的靜態方法forname()獲取,可能拋ClassNotFount的異常
    • 誰知道你給的類名對不對啊
    • Class clazz =Class.forName("com.zhang.Person");
  • 基本內建型別的包裝類都有一個TYPE屬性
    • Class clazz=Integer.TYPE;

2.2.4 哪些型別有Class物件

  • ​ class:外部類,成員(成員內部類,靜態內部類),區域性內部類,匿名內部類
  • interface:介面
  • [ ]:陣列
  • enum:列舉
  • annotation:註解@interface
  • primitive type: 基本資料型別
  • void
Class c1 = Object.class;//普通類 結果:class java.lang.Object
Class c2 = Comparable.class;//介面 結果:interface java.lang.Comparable
Class c3 = String[].class;//一維陣列 結果:class [Ljava.lang.String;
Class c4 = int[][].class;//二維陣列 結果:class [[I
Class c5 = Override.class;//註解 結果:interface java.lang.Override
Class c6 = ElementType.class;//列舉型別 結果:class java.lang.annotation.ElementType
Class c7 = Integer.class;//基本資料型別 結果:class java.lang.Integer
Class c8 = void.class;//void 結果:void
Class c9 = Class.class;//Class 結果:class java.lang.Class

System.out.println(c1);

2.3 類的載入與ClassLoader

2.3.1 java記憶體分析

  • ​ java記憶體:
    • 堆:
      • 存放new出來的物件和陣列
      • 可以被所有執行緒共享,不會存放別的物件引用
    • 棧:
      • 存放基本變數型別(包括型別的具體數值)
      • 引用物件的變數(包括引用在堆裡面的地址)
    • 方法區:
      • 可以被所有的執行緒共享
      • 包含了所有的class和static變數

2.3.2 類的載入 連結 初始化

  • 載入:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料換成 方法區的執行時資料結構,然後生成一個代表這個類的java.lang.Class物件
  • 連結:將java類的二進位制程式碼合併到jaaaaavm的執行狀態之中:
    • 驗證,確保載入的類的資訊符合jvm規範
    • 準備:正式為類變數(static)分配記憶體,並設定類變數預設初始值,這些記憶體都分配在方法區中
    • 解析:虛擬機器常量池內的符號引用(常量名)替換為引用(地址)的過程
  • 初始化:
    • 執行類構造器<clinit>()方法的過程,類構造器()方法是由編譯期間自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類資訊的,不是構造該類物件的構造器)
    • 當初始化一個類的時候,如果發現父類還未初始哈, 先觸發父類的初始化
    • 虛擬機器 會保證一個類的<()方法在多執行緒環境中被正確加鎖與同步
public class Test01 {
    public static void main(String[] args) {
        System.out.println(A.m);
        A a=new A();
        System.out.println(A.m);
        /*
            1.載入到記憶體,產生一個類對應Class物件
            2.連結,m=0(初始值
            3.初始化:
                <clinit>(){
                    Sout("A類靜態程式碼")
                    m=30
                    m=10
                }
        */
    }
}
class A{
    static {
        System.out.println("A類靜態程式碼塊");
        m=30;
    }
    static int m=10;
    public A(){
        m=20;
        System.out.println("A的無參構造");
    }
}

結果:

A類靜態程式碼塊
30
A的無參構造
20

2.3.3 啥時類會初始化

類分為主動引用和被動引用:

  • ​ 主動引用:
    • 虛擬機器啟動時,main方法所在類
    • new一個類物件
    • 呼叫類的靜態成員(除了final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射呼叫
    • 當初是話一個類的時候,父類沒有初始化,則先初始化父類
  • 類的被動引用(不會發生類的初始化)
    • 訪問靜態域是,只有真正聲明瞭這個域的類才會被初始化,如:子類呼叫父類的靜態變數,不會導致子類初始化
    • 宣告類陣列Person[] ps=new Person[5];
    • 呼叫類的常量屬性(常量在連結階段就存入呼叫類的常量池了

2.3.4 類載入器的作用

類快取:類被載入後會維持載入一段時間,提高效率,jvm可以使用垃圾回收幹掉它

  • 載入器:
    • 引導類載入器:C++編寫,jvm自帶的類載入器,負責java平臺核心庫,用來裝載核心類庫,該載入器無法直接獲取
    • 擴充套件類載入器:負責jre/lib/ext目錄下的jar包或者-D java.ext.dirs 指定目錄下 的jar包裝入工作庫
    • 系統類載入器:負責java -classpath 或 -D java.class.path所指的目錄下的類與jar包裝入工作,是最常用的載入器
public static void main(String[] args) {
    //獲取系統類的載入器
    ClassLoader systeemClassLoader = ClassLoader.getSystemClassLoader();
    System.out.println(systeemClassLoader);

    //獲取系統類載入器的父類載入器---->擴充套件類載入器
    ClassLoader parent=systeemClassLoader.getParent();
    System.out.println(parent);

    //獲取擴充套件類載入器的父類載入器------>根載入器(c/c++)
    ClassLoader root=parent.getParent() ;
    System.out.println(root);
    
    //測試當前類的類載入器
    ClassLoader classLoader = Class.forName("Test01").getClassLoader();
    System.out.println(classLoader);
    
    //測試jdk內建的類的載入器(根載入器)
    classLoader=Class.forName("java.lang.Object").getClassLoader();
    System.out.println(classLoader);

    //獲得系統類載入器可以載入的路徑
    System.out.println(System.getProperty("java.class.path"));

}
jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc
jdk.internal.loader.ClassLoaders$PlatformClassLoader@6e8dacdf
null
jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc
null
C:\Users\NGI\IdeaProjects\test\out\production\test;C:\Users\NGI\.m2\repository\junit\junit\4.13.1\junit-4.13.1.jar;C:\Users\NGI\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar  
    

2.4 建立執行時類的物件

通過反射獲取執行時類的完整結構

Field,Method,Constructor,SuperClass,Interface,Annotation

Class c1 = Class.forName("com.zhang.User");

//獲得類的名字
System.out.println(c1.getName());//包名+類名
System.out.println(c1.getSimpleName());
//獲得類的屬性
Field[] fields=c1.getFields();//這個只能找到public的屬性
fields= c1.getDeclaredFields();//找到全部屬性
for(Field field:fields){
    System.out.println(field);
}
//獲得指定屬性的值
Field name = c1.getField("sex");//同上,這個只能找到public的
System.out.println(name);

System.out.println("------------------------------------------------------");
//獲得類的方法
c1.getMethods();//獲得本類或父類的全部public方法
Method[] methods = c1.getDeclaredMethods();//獲得本類的全部方法(無父類)
for (Method m : methods ){
    System.out.println(m);
}
//獲得指定的方法
//為啥要提供引數型別,因為這個方法可能被過載了
Method getAge = c1.getMethod("getAge", null);//穿的引數是方法的引數型別,不是引數值
Method setAge = c1.getMethod("setAge", int.class);
System.out.println(getAge);
System.out.println(setAge);

//獲得指定的構造器
Constructor[] cons = c1.getConstructors();//獲得全部public的構造方法
cons = c1.getDeclaredConstructors();//獲得全部構造方法
//獲得指定構造器
Constructor con = c1.getConstructor(int.class);

扯淡的來了啊,上面的東西一般開發都不直接用,

那麼反射到底要幹啥?

或者說有了Class物件,做啥?

2.4.1 Class物件的作用

a 建立類的物件
  • 必須有無參構造器才行
  • 構造器的許可權要夠
//通過反射動態的建立物件
public static void main(String[] args) throws Exception {
    Class c1 = Class.forName("com.zhang.User");
    //構造一個物件
    User user = (User)c1.newInstance();//返回的是Object型別
    //本質上是呼叫User的無參構造器,如果原類裡面沒提供,且被有參覆蓋,則報錯。。
    System.out.println(user);
}
User{age=0, name='null', sex='null'}

思考:沒有無參構造怎麼辦,需要建立有具體屬性的物件怎麼辦?

b 通過反射獲取構造器並建立物件
Class c1 = Class.forName("com.zhang.User");

//通過構造器建立物件
Constructor con = c1.getDeclaredConstructor(int.class, String.class, String.class);
User user = (User)con.newInstance(18, "zhang", "漢子");
System.out.println(user);
User{age=18, name='zhang', sex='nan'}
c 通過反射呼叫普通方法
//user物件接上面的變數
//通過反射呼叫普通方法
Method setAge = c1.getMethod("setAge", int.class);
setAge.invoke(user,30);//啟用函式,方法.invoke(方法所屬物件,方法的引數值);
System.out.println(c1.getMethod("getAge").invoke(user));
d 通過反射使用屬性
User user2= (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true);//不能直接操作私有屬性,需要關閉安全檢測,不然會報錯,屬性和方法,構造器物件都有serAccessible方法
name.set(user2,"張大壯");
System.out.println(user2.getName());

2.4.2 反射效能對比

static int  cur = 100000000;//1億

//通過反射動態的建立物件
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
    test01();
    test02();
    test03();
}

//普通方式
public static void test01() {
    User user = new User();
    long starttime = System.currentTimeMillis();
    for (int i = 0; i < cur; i++) {
        user.getName();
    }

    long endtime = System.currentTimeMillis();
    System.out.println((endtime - starttime) + "ms");
}

//反射方式
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    User user = new User();
    Class c1 = user.getClass();
    Method getName = c1.getDeclaredMethod("getName", null);
    long starttime = System.currentTimeMillis();
    for (int i = 0; i < cur; i++) {
        getName.invoke(user,null);
    }

    long endtime = System.currentTimeMillis();
    System.out.println((endtime - starttime) + "ms");
}

//關閉檢測後
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    User user = new User();
    Class c1 = user.getClass();
    Method getName = c1.getDeclaredMethod("getName", null);
    long starttime = System.currentTimeMillis();
    getName.setAccessible(true);

    for (int i = 0; i < cur; i++) {

        getName.invoke(user,null);
    }

    long endtime = System.currentTimeMillis();
    System.out.println((endtime - starttime) + "ms");
}

2.4.3 反射操作泛型

  • 泛型;java的一種強制的約束,確保資料的安全性,免去強制型別轉換的問題
  • 但是!編譯成功之後,所有和泛型有關的型別全部擦除
  • 為了用反射操作泛型,java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType集中型別來代表不能被歸一到Class類中的型別但是又和原始型別齊名的型別
    • ParameterType:表示一種引數化型別,比如Collection
    • GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別
    • TypeVariable:是各種型別變數的公共父介面
    • WildcardType:代表一種萬用字元型別表示式
public class Test02 {
    public void test01(Map<String,User> map, List<User> list){
        System.out.println("test01");
    }
    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method m = Test02.class.getMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = m.getGenericParameterTypes();
        for (Type g : genericParameterTypes) {
            System.out.println(g);
            if(g instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) g).getActualTypeArguments();//獲取真實型別引數
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        System.out.println("-------------------------------------------------------------------");
        Method m2 = Test02.class.getMethod("test02", null);
        Type g = m2.getGenericReturnType();
        if(g instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) g).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }

    }

}

2.4.4 反射操作註解

  • getAnnotations
  • getAnnotaion
a. 啥是ORM
  • Object realtionship Mapping -->物件關係對映
  • 類到資料庫的對映
    • 類----表
    • 屬性---欄位
    • 物件----記錄
b. 通過反射獲取註解資訊
public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("Student");
        //通過反射獲得註解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation a : annotations) {
            System.out.println(a);
        }

        //獲得註解的value值
        DBTable table = (DBTable) c1.getAnnotation(DBTable.class);
        System.out.println(table.value());

        //獲得類屬性的指定註解
        Field f = c1.getDeclaredField("name");
        DBField annotation = f.getAnnotation(DBField.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}

@DBTable("db_student")
class Student{
    @DBField(columnName = "db_id",type = "int",length = 10)
    private int id;

    @DBField(columnName = "db_name",type="int",length = 10)
    private String name;
	...
}

//類名的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface DBTable{
    String value();
}

//屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DBField{
    String columnName();
    String type();
    int length();
}