日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

关于Java你不知道的那些事之Java注解和反射

發布時間:2025/3/20 java 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 关于Java你不知道的那些事之Java注解和反射 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊上方?好好學java?,選擇?星標?公眾號

重磅資訊、干貨,第一時間送達

今日推薦:硬剛一周,3W字總結,一年的經驗告訴你如何準備校招!

個人原創100W+訪問量博客:點擊前往,查看更多

作者:輕狂書生FS

https://blog.csdn.net/LookForDream_/

前言

我們在實際的工作中,項目使用的框架中會經常遇到注解和反射。但是我們大部分同學都僅僅限于知道這是注解和反射,卻不太了解它們底層的原理實現。對這部分知識了解有點淺薄和片面,所以這邊文章將會深入理解什么是注解和反射。讓我們達到“知其然,知其所以然”的目的。

什么是注解

  • Annotation是JDK5.0開始引入的新技術

  • Annotation的作用

  • 不是程序本身,可以對程序做出解釋(這一點和注釋沒有什么區別)

  • 可以被其它程序,比如編譯器讀取

  • Annotation的格式

  • 注解以?@注釋名?在代碼中存在的,還可以添加一些參數值

  • 例如:@SuppressWarnings(value = "unchecked")

  • Annotation在那里使用?

  • 可以附加在package、class、method、field等上面,相當于給他們添加了額外的輔助信息

  • 通過反射機制變成實現對這些元數據的控制

內置注解

  • @Override:定義在?java.lang.Override中,此注釋只適用于修飾方法,表示一個方法聲明打算重寫超類中的另一個方法聲明

  • @Deprecated:定義在java.lang.Deprecated中,此注釋可以用于修飾方法,屬性,類,表示不鼓勵程序員使用這樣的元素,通常是因為它很危險,或者存在更好的選擇

  • @SuppressWarnings:定義在java.lang.SuppressWarnings中,用來抑制編譯時的警告信息,與前面的兩個注釋不同,你需要額外添加一個參數才能正確使用,這些參數都是已經定義好了的,我們選擇性的使用就好了。

  • @SuppressWarnings(“all”)

  • @SuppressWarnings(“unchecked”)

  • @SuppressWarnings(value={“unchecked”, “deprecation”})

元注解

元注解的作用就是負責注解其它注解,Java定義了4個標準的meta-annotation類型,他們被用來提供對其它annotation類型作說明。

這些類型和它們所支持的類在?java.lang.annotation包可以找到?@Target?、@Retention、@Documented、@Inherited

  • @Target:用于描述注解的使用范圍,即:被描述的注解可以在什么地方使用

  • @Retention:表示需要什么保存該注釋信息,用于描述注解的生命周期

  • 級別范圍:Source < Class < Runtime

  • @Document:說明該注解被包含在java doc中

  • @Inherited:說明子類可以集成父類中的注解

示例

/***?元注解**?@author:?輕狂書生FS*/ @MyAnnotation public?class?MateAnnotationDemo?{}/***?定義一個注解*/ @Target(value={ElementType.METHOD,?ElementType.TYPE})??//?target表示我們注解應用的范圍,在方法上,和類上有效 @Retention(RetentionPolicy.RUNTIME)???// Retention:表示我們的注解在什么時候還有效,運行時候有效 @Documented???//?表示說我們的注解是否生成在java?doc中 @Inherited???//?表示子類可以繼承父類的注解 @interface?MyAnnotation?{}

自定義注解

使用?@interface自定義注解時,自動繼承了?java.lang.annotation.Annotation接口

  • @interface 用來聲明一個注解,格式:public @interface 注解名 {定義內容

  • 其中的每個方法實際上是申明了一個配置參數

  • 方法的名稱j就是參數的類型

  • 返回值類型就是參數的類型(返回值只能是基本數據類型,Class,String,enum)

  • 通過default來申明參數的默認值

  • 如果只有一個參數成員,一般參數名為 value

  • 注解元素必須要有值,我們定義元素時,經常使用空字符串或者0作為默認值

/***?自定義注解**?@author:?輕狂書生FS*/ public?class?MateAnnotationDemo?{//?注解可以顯示賦值,如果沒有默認值,我們就必須給注解賦值@MyAnnotation(schools?=?{"大學"})public?void?test(){}}/***?定義一個注解*/ @Target(value={ElementType.METHOD,?ElementType.TYPE})??//?target表示我們注解應用的范圍,在方法上,和類上有效 @Retention(RetentionPolicy.RUNTIME)???// Retention:表示我們的注解在什么時候還有效,運行時候有效 @Documented???//?表示說我們的注解是否生成在java?doc中 @Inherited???//?表示子類可以繼承父類的注解 @interface?MyAnnotation?{//?注解的參數:參數類型?+?參數名()String?name()?default?"";int?age()?default?0;//?如果默認值為-1,代表不存在int?id()?default?-1;String[]?schools(); }

反射機制

動態語言與靜態語言

動態語言

動態語言是一類在運行時可以改變其結構的語言:例如新的函數,對象,甚至代碼可以被引進,已有的函數可以被刪除或是其它結構上的變化。通俗點說就是在運行時代碼可以根據某些條件改變自身結構

主要的動態語言有:Object-c、C#、JavaScript、PHP、Python等

靜態語言

與動態語言相比,運行時結構不可變的語言就是靜態語言。例如Java、C、C++

Java不是動態語言,但是Java可以稱為“準動態語言”。即Java有一定的動態性,我們可以利用反射機制來獲取類似于動態語言的 特性,Java的動態性讓編程的時候更加靈活。

Java反射機制概述

什么是反射

Java Reflection:Java反射是Java被視為動態語言的關鍵,反射機制運行程序在執行期借助于Reflection API 去的任何類內部的信息,并能直接操作任意對象的內部屬性及方法。

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

在加載完類后,在堆內存的方法區就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就包含了完整的類的結構信息,我們可以通過這個對象看到類的結構,這個對象就像一面鏡子,透過這個鏡子看到類的結構,所以我們形象的稱之為:反射

在這里插入圖片描述

tip:反射可以獲取到private修飾的成員變量和方法

反射的應用

  • 在運行時判斷任意一個對象所屬類

  • 在運行時構造任意一個類的對象

  • 在運行時判斷任意一個類所具有的成員變量和方法

  • 在運行時獲取泛型信息

  • 在運行時調用任意一個對象的成員變量和方法

  • 在運行時候處理注解

  • 生成動態代理

Java反射的優缺點

  • 優點:可以實現動態創建對象和編譯,體現出很大的靈活性

  • 缺點:對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求,這類操作總是慢于直接執行相同的操作。也就是說new創建和對象,比反射性能更高

反射相關的主要API

  • java.lang.Class:代表一個類

  • java.lang.reflect.Method:代表類的方法

  • java.lang.reflect.Field:代表類的成員變量

  • java.lang.reflect.Constructor:代表類的構造器

理解Class類并獲取Class實例

Class類

我們下面通過Class.forName來獲取一個實例對象

/***?反射Demo**?@author:?輕狂書生FS*/ public?class?ReflectionDemo?{public?static?void?main(String[]?args)?throws?ClassNotFoundException?{//?通過反射獲取類的Class對象Class?c1?=?Class.forName("com.moxi.interview.study.annotation.User");Class?c2?=?Class.forName("com.moxi.interview.study.annotation.User");Class?c3?=?Class.forName("com.moxi.interview.study.annotation.User");System.out.println(c1.hashCode());System.out.println(c2.hashCode());System.out.println(c3.hashCode());} }/***?實體類:pojo,entity*/ class?User?{private?String?name;private?int?id;private?int?age;public?User()?{}public?String?getName()?{return?name;}public?void?setName(String?name)?{this.name?=?name;}public?int?getId()?{return?id;}public?void?setId(int?id)?{this.id?=?id;}public?int?getAge()?{return?age;}public?void?setAge(int?age)?{this.age?=?age;}@Overridepublic?String?toString()?{return?"User{"?+"name='"?+?name?+?'\''?+",?id="?+?id?+",?age="?+?age?+'}';} }

上面我們通過反射獲取了三個對象,我們輸出對應對象的hashcode碼,會發現

1173230247 1173230247 1173230247

它們的hashcode碼是一樣的,這就說明了:

  • 一個類在內存中只有一個Class對象

  • 一個類被加載后,類的整體結構都會被封裝在Class對象中

在Object類中定義了以下的方法,此方法將被所有子類繼承

public?final?Class?getClass()

以上方法的返回值的類型是一個Class類,此類是Java反射的源頭,實際上所謂反射從程序的運行結果來看也很好理解,即:可以通過對象反射求出類的名稱。

也就是說,我們通過對象來獲取到它的Class,相當于逆過程

通過對照鏡子我們可以得到的信息:某個類的屬性,方法和構造器,某個類到底實現了那些接口。對于每個類而言,JRE都為其保留一個不變的Class類型對象,一個CLass對象包含了特定某個結構的有關信息

  • Class本身也是一個類

  • Class對象只能由系統建立對象

  • 一個加載的類在JVM中只會有一個Class實例

  • 一個Class對象對應的是一個加載到JVM中的一個.class文件

  • 每個類的實例都會記得自己是由哪個Class實例所生成

  • 通過Class可以完整地得到一個類中所有被加載的結構

  • Class類是Reflection的根源,針對任何你想動態加載、運行的類、唯有先獲得相應的Class對象

Class類常用的方法

  • ClassforName(String name):返回指定類name的Class對象

  • newInstance():調用缺省構造函數,返回Class對象的一個實例

  • getName():返回此Class對象所表示的實體(類,接口,數組或void)的名稱

  • getSuperClass():返回當前Class對象的父類Class對象

  • getinterfaces():返回當前對象的接口

  • getClassLoader():返回該類的類加載器

  • getConstructors():返回一個包含某些Constructor對象的數組

  • getMethod(String name, Class… T):返回一個Method對象,此對象的形參類型為paramsType

  • getDeclaredFields():返回Field對象的一個數組

獲取對象實例的方法

  • 若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程序性能最高

  • Class clazz = Person.class;

  • 已知某個類的實例,調用該實例的getClass()方法獲取Class對象

  • Class clazz = person.getClass()

  • 已經一個類的全類名,且該類在類路徑下,可以通過Class類的靜態方法forName()獲取,HIA可能拋出ClassNotFoundException

  • Class clazz = Class.forName(“demo01.Sutdent”)

  • 內置數據類型可以直接通過 類名.Type

  • 還可以利用ClassLoader

/***?Class類創建的方式**?@author:?輕狂書生FS*/ class?Person?{public?String?name;public?Person()?{}public?Person(String?name)?{this.name?=?name;}@Overridepublic?String?toString()?{return?"Person{"?+"name='"?+?name?+?'\''?+'}';} }class?Student?extends?Person{public?Student()?{this.name?=?"學生";} }class?Teacher?extends?Person?{public?Teacher()?{this.name?=?"老師";} }public?class?ClassCreateDemo?{public?static?void?main(String[]?args)?throws?ClassNotFoundException?{Person?person?=?new?Student();System.out.println("這個人是:"?+?person.name);//?方式1:通過對象獲得Class?c1?=?person.getClass();System.out.println("c1:"?+?c1.hashCode());//方式2:通過forName獲得Class?c2?=?Class.forName("com.moxi.interview.study.annotation.Student");System.out.println("c2:"?+?c2.hashCode());//?方式3:通過類名獲取(最為高效)Class?c3?=?Student.class;System.out.println("c3:"?+?c3.hashCode());//?方式4:基本內置類型的包裝類,都有一個Type屬性Class?c4?=?Integer.TYPE;System.out.println(c4.getName());//?方式5:獲取父類類型Class?c5?=?c1.getSuperclass();} }

哪些類型可以有Class對象

class:外部類,成員(成員內部類,靜態內部類),局部內部類,匿名內部類

interface:接口

[]:數組

enum:枚舉

annotation:注解@interface

primitive type:基本數據類型

void

/***?獲取Class的方式**?@author:?輕狂書生FS*/ public?class?GetClassDemo?{public?static?void?main(String[]?args)?{Class?c1?=?Object.class;?//?類Class?c2?=?Comparable.class;?//?接口Class?c3?=?String[].class;?//?數組Class?c4?=?int[][].class;?//?二維數組Class?c5?=?Override.class;?//?注解Class?c6?=?ElementType.class;?//?枚舉Class?c7?=?Integer.class;?//?基本數據類型Class?c8?=?void.class;?//?void,空數據類型Class?c9?=?Class.class;?//?ClassSystem.out.println(c1);System.out.println(c2);System.out.println(c3);System.out.println(c4);System.out.println(c5);System.out.println(c6);System.out.println(c7);System.out.println(c8);System.out.println(c9);} }

最后運行結果為:

class?java.lang.Object interface?java.lang.Comparable class?[Ljava.lang.String; class?[[I interface?java.lang.Override class?java.lang.annotation.ElementType class?java.lang.Integer void class?java.lang.Class

同時需要注意,只要類型和維度一樣,那就是同一個Class對象

int?[]?a?=?new?int[10]; int?[]?b?=?new?int[10]; System.out.println(a.getClass().hashCode()); System.out.println(b.getClass().hashCode());

這兩個的hashcode是一樣的

Java內存分析

java內存分為以下三部分

  • 存放new的對象和數組

  • 可以被所有的線程共享,不會存放別的對象引用

  • 存放基本變量(會包含這個基本類型的具體數值)

  • 引用對象的變量(會存放這個引用在對堆里面的具體地址)

  • 方法區

  • 可以被所有線程共享

  • 包含了所有的class和static變量

類的加載與ClassLoader的理解

類加載過程

當程序主動使用某個類時,如果該類還未被加載到內存中,則系統會通過如下三個步驟對該類進行初始化:

在這里插入圖片描述
  • 加載:將class文件字節碼內容加載到內存,并將這些靜態數據轉換成方法區的運行時數據結構,然后生成一個代表這個類的?java.lang.Class?對象。

  • 鏈接:將Java類的二進制代碼合并到JVM的運行狀態之中的過程。

  • 驗證:確保加載的類信息符合JVM規范,沒有安全方面的問題

  • 準備:正式為類變量(static)分配內存并設置類變量默認初始值的階段,這些內存都將在方法區中進行分配。

  • 解析:虛擬機常量池的符號引用(常量名)替換為直接引用(地址)的過程

  • 初始化:

  • 執行類構造器方法的過程,類構造器 方法是由編譯期自動收集類中所有類變量的賦值動作和靜態代碼塊中的語句合并產生的。(類構造器是構造類信息的,不是構造該類對象的構造器)

  • 當初始化一個類的時候,如果發現其父類還沒有初始化完成,則需要先觸發其父類的初始化

  • 虛擬機會保證一個類的方法在多相差環境中被正確的加鎖和同步

下面一段代碼,分別說明了static代碼塊,以及子類和父類構造方法的執行流程

/***?類加載流程**?@author:?輕狂書生FS*/ class?SuperA?{static?{System.out.println("父類靜態代碼塊初始化");}public?SuperA()?{System.out.println("父類構造函數初始化");} } class?A?extends?SuperA{static?{System.out.println("靜態代碼塊初始化");m?=?300;}static?int?m?=?100;public?A()?{System.out.println("A類的無參構造方法");}} public?class?ClassLoaderDemo?{public?static?void?main(String[]?args)?{A?a?=?new?A();System.out.println(a.m);} }

最后的結果為:

父類靜態代碼塊初始化 靜態代碼塊初始化 父類構造函數初始化 A類的無參構造方法 100

說明靜態代碼塊都是執行的,并且父類優先

類加載步驟

  • 加載到內存,會產生一個類對應Class對象

  • 鏈接,鏈接結束 m = 0

  • 初始化:

<clinit>()?{syso("A類靜態方法")m?=?300;m?=?100; } 在這里插入圖片描述

什么時候發生類初始化

類的主動引用(一定發生初始化)

  • 當虛擬機啟動,先初始化main方法所有在類

  • new 一個類的對象

  • 調用類的靜態成員(除了 final常量)和靜態方法

  • 使用 java.lang.reflect包的方法對類進行反射調用

  • 當初始化一個類,如果其父類沒有被初始化,則會先初始化它的父類

類的被動引用(不會發生初始化)

  • 當訪問一個靜態域時,只有真正的申明這個域的類才會被初始化,如:當通過子類引用父類的靜態變量,不會導致子類初始化

  • 通過數組定義類引用,不會觸發此類的初始化

  • 引用常量不會觸發此類的初始化(常量在鏈接階段就存入調用類的常量池了)

類加載器的作用

  • 類加載的作用:將class文件字節碼內容加載到內存中,并將這些靜態數據轉換成方法區的運行時數據結構,然后在堆中生成了一個代表這個類的?java.lang.Class對象,作為方法區中類數據的訪問入口。

  • 類緩存:標準的JavaSE類加載器可以按要求查找類,但是一旦某個類被加載到類加載器中,它將維持加載(緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象

在這里插入圖片描述

類加載器作用是用來把類(Class)裝載進內存的,JVM規范定義了如下類型的類的加載器

在這里插入圖片描述

代碼如下:

/***?類加載器的種類**?@author:?輕狂書生FS*?@create:?2020-09-29-11:51*/ public?class?ClassLoaderTypeDemo?{public?static?void?main(String[]?args)?{//當前類是哪個加載器ClassLoader?loader?=?ClassLoaderTypeDemo.class.getClassLoader();System.out.println(loader);//?獲取系統類加載器ClassLoader?classLoader?=?ClassLoader.getSystemClassLoader();System.out.println(classLoader);//?獲取系統類加載器的父類加載器?->?擴展類加載器ClassLoader?parentClassLoader?=?classLoader.getParent();System.out.println(parentClassLoader);//?獲取擴展類加載器的父類加載器?->?根加載器(C、C++)ClassLoader?superParentClassLoader?=?parentClassLoader.getParent();System.out.println(superParentClassLoader);//?測試JDK內置類是誰加載的ClassLoader?loader2?=?Object.class.getClassLoader();System.out.println(loader2);} }

運行結果:我們發現,根加載器我們無法獲取到

sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@45ee12a7 null null

獲取類加載器能夠加載的路徑

//?如何獲取類加載器可以加載的路徑 System.out.println(System.getProperty("java.class.path"));

最后輸出結果為:

????????//?如何獲取類加載器可以加載的路徑System.out.println(System.getProperty("java.class.path"));/*E:\Software\JDK1.8\Java\jre\lib\charsets.jar;E:\Software\JDK1.8\Java\jre\lib\deploy.jar;E:\Software\JDK1.8\Java\jre\lib\ext\access-bridge-64.jar;E:\Software\JDK1.8\Java\jre\lib\ext\cldrdata.jar;E:\Software\JDK1.8\Java\jre\lib\ext\dnsns.jar;E:\Software\JDK1.8\Java\jre\lib\ext\jaccess.jar;E:\Software\JDK1.8\Java\jre\lib\ext\jfxrt.jar;E:\Software\JDK1.8\Java\jre\lib\ext\localedata.jar;E:\Software\JDK1.8\Java\jre\lib\ext\nashorn.jar;E:\Software\JDK1.8\Java\jre\lib\ext\sunec.jar;E:\Software\JDK1.8\Java\jre\lib\ext\sunjce_provider.jar;E:\Software\JDK1.8\Java\jre\lib\ext\sunmscapi.jar;E:\Software\JDK1.8\Java\jre\lib\ext\sunpkcs11.jar;E:\Software\JDK1.8\Java\jre\lib\ext\zipfs.jar;E:\Software\JDK1.8\Java\jre\lib\javaws.jar;E:\Software\JDK1.8\Java\jre\lib\jce.jar;E:\Software\JDK1.8\Java\jre\lib\jfr.jar;E:\Software\JDK1.8\Java\jre\lib\jfxswt.jar;E:\Software\JDK1.8\Java\jre\lib\jsse.jar;E:\Software\JDK1.8\Java\jre\lib\management-agent.jar;E:\Software\JDK1.8\Java\jre\lib\plugin.jar;E:\Software\JDK1.8\Java\jre\lib\resources.jar;E:\Software\JDK1.8\Java\jre\lib\rt.jar;C:\Users\Administrator\Desktop\LearningNotes\校招面試\JUC\Code\target\classes;C:\Users\Administrator\.m2\repository\org\projectlombok\lombok\1.18.10\lombok-1.18.10.jar;C:\Users\Administrator\.m2\repository\cglib\cglib\3.3.0\cglib-3.3.0.jar;C:\Users\Administrator\.m2\repository\org\ow2\asm\asm\7.1\asm-7.1.jar;E:\Software\IntelliJ?IDEA\IntelliJ?IDEA?2019.1.2\lib\idea_rt.jar*/

我們能夠發現,類在加載的時候,都是有自己的加載區域的,而不是任何地方的類都能夠被加載。

搜索Java知音公眾號,回復“后端面試”,送你一份Java面試題寶典.pdf

獲取運行時候類的完整結構

通過反射能夠獲取運行時類的完整結構

  • 實現的全部接口

  • 所繼承的父類

  • 全部的構造器

  • 全部的方法

  • 全部的Field

  • 注解

/***?獲取運行時類信息*?@author:?輕狂書生FS*/ public?class?GetClassInfo?{public?static?void?main(String[]?args)?throws?ClassNotFoundException,?NoSuchFieldException,?NoSuchMethodException?{Class?clazz?=?Class.forName("com.moxi.interview.study.annotation.User");//?獲取類名字System.out.println(clazz.getName());?//?包名?+?類名System.out.println(clazz.getSimpleName());?//?類名//?獲取類屬性System.out.println("================");//?只能找到public屬性Field?[]?fields?=?clazz.getFields();//?找到全部的屬性Field?[]?fieldAll?=?clazz.getDeclaredFields();for?(int?i?=?0;?i?<?fieldAll.length;?i++)?{System.out.println(fieldAll[i]);}//?獲取指定屬性的值Field?name?=?clazz.getDeclaredField("name");//?獲取方法Method?[]?methods?=?clazz.getDeclaredMethods();?//?獲取本類和父類的所有public方法Method?[]?methods2?=?clazz.getMethods();?//?獲取本類所有方法//?獲得指定方法Method?method?=?clazz.getDeclaredMethod("getName",?null);//?獲取方法的時候,可以把參數也丟進去,這樣因為避免方法重載,而造成不知道加載那個方法Method?method2?=?clazz.getDeclaredMethod("setName",?String.class);} }

雙親委派機制

如果我們想定義一個:java.lang.string 包,我們會發現無法創建

因為類在加載的時候,會逐級往上

也就是說當前的系統加載器,不會馬上的創建該類,而是將該類委派給 擴展類加載器,擴展類加載器在委派為根加載器,然后引導類加載器去看這個類在不在能訪問的路徑下,發現 sring包已經存在了,所以就無法進行,也就是我們無法使用自己自定義的string類,而是使用初始化的stirng類

當一個類收到了類加載請求,他首先不會嘗試自己去加載這個類,而是把這個請求委派給父類去完成,每一個層次類加載器都是如此,因此所有的加載請求都應該傳送到啟動類加載其中,只有當父類加載器反饋自己無法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的Class),子類加載器才會嘗試自己去加載。

采用雙親委派的一個好處是比如加載位于rt.jar 包中的類java.lang.Object,不管是哪個加載器加載這個類,最終都是委托給頂層的啟動類加載器進行加載,這樣就保證了使用不同的類加載器最終得到的都是同樣一個Object 對象

在這里插入圖片描述

有了Class對象,我們能夠做什么?

創建類的對象:通過調用Class對象的newInstance()方法

  • 類必須有一個無參數的構造器

  • 類的構造器的權限需要足夠

如果沒有無參構造器就不能創建對象?

只要在操作的時候明確的調用類中的構造器,并將參數傳遞進去之后,才可以實例化操作。

步驟如下:

  • 通過Class類的getDeclaredConstructor(Class … parameterTypes)取得本類的指定形參類型的構造器

  • 向構造器的形參中,傳遞一個對象數組進去,里面包含了構造器中所需的各個參數

  • 通過Constructor實例化對象

調用指定方法

通過反射,調用類中的方法,通過Method類完成。

  • 通過Class類的getMethod方法取得一個Method對象,并設置此方法操作是所需要的參數類型

  • 之后使用Object invoke進行調用,并向方法中傳遞要設置的obj對象的參數信息

Invoke方法

  • Object invoke(Object obj, Object … args)

  • Object對應原方法的返回值,若原方法無返回值,此時返回null

  • 若原方法為靜態方法,此時形參Object 可以為null

  • 若原方法形參列表為空,則Object[] args 為 null

  • 若原方法聲明private,則需要在調用此invoke() 方法前,顯示調用方法對象的setAccessible(true)方法,將可訪問private的方法

setAccessible方法

  • Method和Field、Constructor對象都有setAccessible()方法

  • setAccessible作用是啟動和禁用訪問安全檢查的開關

  • 參數值為true則指示反射對象再使用時應該取消Java語言訪問檢查

  • 提高反射效率,如果代碼中必須使用反射,而這句代碼需要頻繁被嗲用,那么設置成true

  • 使得原本無法訪問的私有成員也可以訪問

  • 參數值為false則指示反射的對象應該實行Java語言訪問檢查

在這里插入圖片描述

完整代碼:

/***?通過反射獲取對象**?@author:?輕狂書生FS*/ public?class?GetObjectByReflectionDemo?{public?static?void?main(String[]?args)?throws?ClassNotFoundException,?IllegalAccessException,?InstantiationException,?NoSuchMethodException,?InvocationTargetException,?NoSuchFieldException?{//?獲取ClassClass?clazz?=?Class.forName("com.moxi.interview.study.annotation.User");//?構造一個對象,newInstance調用的是無參構造器,如果沒有無參構造器的話,本方法會出錯 //????????User?user?=?(User)clazz.newInstance();//?獲取class的有參構造器Constructor?constructor?=?clazz.getDeclaredConstructor(String.class,?int.class,?int.class);User?user2?=?(User)?constructor.newInstance("小溪",?10,?10);System.out.println(user2);//?通過反射調用普通構造方法User?user3?=?(User)clazz.newInstance();//?獲取setName?方法Method?setName?=?clazz.getDeclaredMethod("setName",?String.class);//?執行setName方法,傳入對象?和?參數setName.invoke(user3,?"小白");System.out.println(user3);System.out.println("============");Field?age?=?clazz.getDeclaredField("age");//?關閉權限檢測,這樣才能直接修改字段,因為?set方法不能直接操作私有變量age.setAccessible(true);age.set(user3,?10);System.out.println(user3);} }

運行結果

User{name='小溪',?id=10,?age=10} User{name='小白',?id=0,?age=0} ============ User{name='小白',?id=0,?age=10}

反射性能對比

下面我們編寫代碼來具體試一試,使用反射的時候和不適用反射,在執行方法時的性能對比

/***?反射性能**?@author:?輕狂書生FS*/ public?class?ReflectionPerformance?{/***?普通方式調用*/public?static?void?test01()?{User?user?=?new?User();long?startTime?=?System.currentTimeMillis();for?(int?i?=?0;?i?<?1000000000;?i++)?{user.getName();}long?endTime?=?System.currentTimeMillis();System.out.println("普通方式執行10億次getName的時間:"?+?(endTime?-?startTime)?+?"?ms");}/***?反射方式調用*/public?static?void?test02()?throws?Exception?{Class?clazz?=?Class.forName("com.moxi.interview.study.annotation.User");Method?getName?=?clazz.getDeclaredMethod("getName",?null);User?user?=?(User)?clazz.newInstance();long?startTime?=?System.currentTimeMillis();for?(int?i?=?0;?i?<?1000000000;?i++)?{getName.invoke(user,?null);}long?endTime?=?System.currentTimeMillis();System.out.println("反射方式執行10億次getName的時間:"?+?(endTime?-?startTime)?+?"?ms");}/***?反射方式調用,關閉權限檢查*/public?static?void?test03()?throws?Exception?{Class?clazz?=?Class.forName("com.moxi.interview.study.annotation.User");Method?getName?=?clazz.getDeclaredMethod("getName",?null);User?user?=?(User)?clazz.newInstance();long?startTime?=?System.currentTimeMillis();getName.setAccessible(true);for?(int?i?=?0;?i?<?1000000000;?i++)?{getName.invoke(user,?null);}long?endTime?=?System.currentTimeMillis();System.out.println("反射方式執行10億次getName的時間:"?+?(endTime?-?startTime)?+?"?ms");}public?static?void?main(String[]?args)?throws?Exception?{test01();test02();test03();} }

運行結果:

普通方式執行10億次getName的時間:3?ms 反射方式執行10億次getName的時間:2554?ms 反射方式執行10億次getName的時間:1365?ms

我們上面分別是執行了 10億次 getName的方法,從里面可以看出,通過直接實例化對象后,調用getName耗時最短,同時關閉了 權限檢查后的比不關閉能提高一倍的性能。

反射操作泛型

Java采用泛型擦除機制來引入泛型,Java中的泛型僅僅是給編譯器Java才使用的,確保數據的安全性和免去強制類型轉換的問題,但是一旦編譯完成后,所有的泛型有關的類型全部被擦除

為了通過反射操作這些類型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種類型來代表不能被歸一到Class類中的類型但是有何原始類型齊名的類型。

  • ParameterizedType:表示一種參數化類型,比如Collection

  • GenericArrayType:表示一種元素類型是參數化類型或者類型變量的數組類型

  • TypeVariable:是各種類型變量的公共父接口

  • WildcardType:代表一種通配符類型的表達式

下面我們通過代碼來獲取方法上的泛型,包括參數泛型,以及返回值泛型

/***?通過反射獲取泛型**?@author:?輕狂書生FS*/ public?class?GenericityDemo?{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?Exception{Method?method?=?GenericityDemo.class.getMethod("test01",?Map.class,?List.class);//?獲取所有的泛型,也就是參數泛型Type[]?genericParameterTypes?=?method.getGenericParameterTypes();//?遍歷打印全部泛型for?(Type?genericParameterType?:?genericParameterTypes)?{System.out.println("?#?"?+genericParameterType);if(genericParameterType?instanceof?ParameterizedType)?{Type[]?actualTypeArguments?=?((ParameterizedType)?genericParameterType).getActualTypeArguments();for?(Type?actualTypeArgument?:?actualTypeArguments)?{System.out.println(actualTypeArgument);}}}//?獲取返回值泛型Method?method2?=?GenericityDemo.class.getMethod("test02",?null);Type?returnGenericParameterTypes?=?method2.getGenericReturnType();//?遍歷打印全部泛型if(returnGenericParameterTypes?instanceof?ParameterizedType)?{Type[]?actualTypeArguments?=?((ParameterizedType)?returnGenericParameterTypes).getActualTypeArguments();for?(Type?actualTypeArgument?:?actualTypeArguments)?{System.out.println(actualTypeArgument);}}} }

得到的結果

?#?java.util.Map<java.lang.String,?com.moxi.interview.study.annotation.User> class?java.lang.String class?com.moxi.interview.study.annotation.User#?java.util.List<com.moxi.interview.study.annotation.User> class?com.moxi.interview.study.annotation.User ################### class?java.lang.String class?com.moxi.interview.study.annotation.User

反射操作注解

通過反射能夠獲取到 類、方法、字段。。。等上的注解

  • getAnnotation

  • getAnnotations

ORM對象關系映射

ORM即為:Object relationship Mapping,對象關系映射

  • 類和表結構對應

  • 屬性和字段對應

  • 對象和記錄對應

在這里插入圖片描述

下面使用代碼,模擬ORM框架的簡單使用

/***?ORMDemo**?@author:?輕狂書生FS*/ @TableKuang("db_student") class?Student2?{@FieldKuang(columnName?=?"db_id",?type="int",?length?=?10)private?int?id;@FieldKuang(columnName?=?"db_age",?type="int",?length?=?10)private?int?age;@FieldKuang(columnName?=?"db_name",?type="varchar",?length?=?10)private?String?name;public?Student2()?{}public?Student2(int?id,?int?age,?String?name)?{this.id?=?id;this.age?=?age;this.name?=?name;}public?int?getId()?{return?id;}public?void?setId(int?id)?{this.id?=?id;}public?int?getAge()?{return?age;}public?void?setAge(int?age)?{this.age?=?age;}public?String?getName()?{return?name;}public?void?setName(String?name)?{this.name?=?name;}@Overridepublic?String?toString()?{return?"Student2{"?+"id="?+?id?+",?age="?+?age?+",?name='"?+?name?+?'\''?+'}';} }/***?自定義注解:類名的注解*/ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface?TableKuang?{String?value(); }/***?自定義注解:屬性的注解*/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface?FieldKuang?{String?columnName();String?type();int?length()?default?0; } public?class?ORMDemo?{public?static?void?main(String[]?args)?throws?Exception{//?獲取Student?的?Class對象Class?c1?=?Class.forName("com.moxi.interview.study.annotation.Student2");//?通過反射,獲取到全部注解Annotation?[]?annotations?=?c1.getAnnotations();for?(Annotation?annotation?:?annotations)?{System.out.println(annotation);}//?獲取注解的value值TableKuang?tableKuang?=?(TableKuang)c1.getAnnotation(TableKuang.class);String?value?=?tableKuang.value();System.out.println(value);//?獲得類指定的注解Field?f?=?c1.getDeclaredField("name");FieldKuang?fieldKuang?=?f.getAnnotation(FieldKuang.class);System.out.println(fieldKuang.columnName());System.out.println(fieldKuang.type());System.out.println(fieldKuang.length());} }


商城小程序演示圖

商城管理后端演示圖

源碼地址獲取方法,老規矩啦!

識別下方二維碼,關注后回復【A107】

即可獲取下載鏈接

總結

以上是生活随笔為你收集整理的关于Java你不知道的那些事之Java注解和反射的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。