一.註解 1. 註解入門 Annotation是從JDK5.0開始引入的新技術 Annotation的作用: 不是程式本身,可以對程式做出解釋(這一點和註釋(comment)沒什麼區別) 可以被其他程式(比如:編譯器等)讀取 Annotation的格式: 註解是以“@註解名”在代碼中存在的,還可以添 ...
一.註解
1. 註解入門
-
Annotation是從JDK5.0開始引入的新技術
-
Annotation的作用:
- 不是程式本身,可以對程式做出解釋(這一點和註釋(comment)沒什麼區別)
- 可以被其他程式(比如:編譯器等)讀取
-
Annotation的格式:
-
註解是以“@註解名”在代碼中存在的,還可以添加一些參數值,例如:
@SuppressWarnings(value="unchecked")
-
-
Annotation在哪裡使用?
- 可以附加在package,class,method,field等上面,相當於給他們添加了額外的輔助信息,我們可以通過反射機制編程實現對這些元數據的訪問
2. 內置註解
-
@Override
定義在java.lang.Override中,此註解只適用於修辭方法,表示一個方法聲明打算重寫超類中的另一個方法聲明。
-
@Deprecated
定義在java.lang.Deprecated中,此註解可以用於修辭方法,屬性,類,表示不鼓勵程式員使用這樣的元素,通常是因為它很危險或者存在更好的選擇。
-
@SuppressWarnings
定義在java.lang.SuppressWarnings中,用來抑制編譯時的警告信息。
與前兩個註解有所不同,你需要添加一個參數才能正確使用,這些參數都是已經定義好了的,我們選擇性地使用就好了。例如:
@SuppressWarnings("all") @SuppressWarnings("unchecked") @SuppressWarnings(value={"unchecked","deprecation"})
//什麼是註解 public class Test01 extends Object{ //@Override 重寫的註解 @Override public String toString(){ return super.toString(); } //@SuppressWarnings("all") 抑制所有警告信息 @SuppressWarnings("all") public static void main(String[] args) { test(); } //@Deprecated 不推薦程式員使用,但是可以使用,或者存在更好的方式 @Deprecated public static void test(){ System.out.println("Deprecated"); } }
3. 元註解
-
元註解的作用就是負責註解其他註解,Java定義了4個標準的meta-annotation類型,它們被用來提供對其他annotation類型作說明。
-
這些類型和它們所支持的類在java.lang.annotation包中可以找到(@Target,@Retention,@Documented,@Inherited)
-
@Target:用於描述註解的使用範圍(即被描述的註解可以用在什麼地方)
-
@Retention:表示需要在什麼級別保存該註釋信息,用於描述註解的生命周期
(SOURCE < CLASS < RUNTIME)
-
@Document:說明該註解將包含在javaDoc中
-
@Inherited:說明子類可以繼承父類中的該註解
-
package ReflectDemo;
//測試元註解
import java.lang.annotation.*;
@MyAnnotation()
public class Test02 {
public void test(){
}
}
// 定義一個註解
// Target 表示我們的註解可以用在哪些地方
@Target(value = {ElementType.METHOD , ElementType.TYPE})
// Retention 表示我們的註解在什麼地方還有效
// runtime > class > sources
@Retention(value = RetentionPolicy.RUNTIME)
// Documented 表示是否將我們的註解生成在JavaDoc中
@Documented
// Inherited 子類可以繼承父類的註解
@Inherited
@interface MyAnnotation{
}
4. 自定義註解
使用@interface自定義註解時,自動繼承了java.lang.annotation.Annotation介面。
- @interface 用來聲明一個註解,格式:public @interface 註解名
- 其中的每一個方法實際上是聲明瞭一個配置參數
- 方法的名稱就是參數的名稱
- 返回值的類型就是參數的類型(返回值只能是基本類型,Class,String,enum )
- 可以通過default來聲明參數的預設值
- 如果只有一個參數成員,一般參數名為value
- 註解元素必須要有值,我們定義註解元素時,經常使用空字元串、0作為預設值
//自定義註解
public class Test03 {
//註解可以顯式賦值,如果沒有預設值,我們就必須給註解賦值
@MyAnnotation2(age = 18, name = "Jack")
public void test(){}
@MyAnnotation3("John")
public void test2(){}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//註解的參數 : 參數類型 + 參數名 ();
String name() default "";
int age();
int id() default -1; //如果預設值為-1,代表不存在
String[] schools() default {"北京大學", "清華大學"};
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
String value();
}
二.反射
1、靜態語言 VS 動態語言
動態語言:
- 是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被刪除或是其他結構上的變化。通俗點說就是在運行時代碼可以根據某些條件改變自身結構。
- 主要動態語言:Object-C、C#、JavaScript、PHP、Python等。
靜態語言:
- 與動態語言相對應的,運行時結構不可變的語言就是靜態語言,如Java、C、C++。
- Java不是動態語言,但Java可以稱之為“準動態語言”。即Java有一定的動態性,我們可以利用反射機制獲得類似動態語言的特性。Java的動態性讓編程的時候更加靈活。
2、Java Reflection
-
Reflection(反射)是Java被視為動態語言的關鍵,反射機制允許程式在執行期間藉助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法。
Class c = Class.forName("java.lang.String")
-
載入完類之後,在堆記憶體的方法區中就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象地稱之為:反射。
正常方式: 引入需要的“包類”名稱 --> 通過new實例化 --> 取得實例化對象
反射方式: 實例化對象 --> getClass()方法 --> 得到完整的“包類”名稱
3、Java反射機制提供的功能
- 在運行時判斷任意一個對象所屬的類
- 在運行時構造任意一個類的對象
- 在運行時判斷任意一個類所具有的成員變數和方法
- 在運行時獲取泛型信息
- 在運行時調用任意一個對象的成員變數和方法
- 在運行時處理註解
- 生成動態代理
- ......
4、Java反射優點和缺點
優點:
- 可以實現動態創建對象和編譯,體現出很大的靈活性
缺點:
- 對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於直接執行相同的操作。
5、反射相關的主要API
- java.lang.Class:代表一個類
- java.lang.reflect.Method:代表類的方法
- java.lang.reflect.Filed:代表類的成員變數
- java.lang.reflect.Constructor:代表類的構造器
- ......
6、 Class類
在Object類中定義了以下的方法,此方法將被所有子類繼承:
public final Class getClass(){};
以上的方法返回值的類型是一個Class類,此類是Java反射的源頭,實際上所謂的反射從程式的運行結果來看也很好理解,即:可以通過對象反射求出類的名稱。
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
//通過反射獲取類的Class對象
Class c1 = Class.forName("ReflectDemo.User");
System.out.println(c1);
Class c2 =Class.forName("ReflectDemo.User");
Class c3 = Class.forName("ReflectDemo.User");
//一個類在記憶體中只有一個Class對象
//一個類被載入後,類的整個結構都會被封裝在Class對象中
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
}
}
//實體類 pojo
class User {
private int id;
private String name;
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
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;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
對象“照鏡子”後可以得到的信息:某個類的屬性、方法和構造器、某個類到底實現了哪些介面。
對於每個類而言,JRE都為其保留一個不變的Class類型的對象。一個Class對象包含了特定某個結構( class / interface / enum / annotation / primitive type / void / [] )的有關信息。
- Class本身也是一個類
- Class對象只能由系統建立對象
- 一個載入的類在JVM中只會有一個Class實例
- 一個Class對象對應的是一個載入到JVM中的一個.class文件
- 每個類的實例都會記得自己是由哪個Class實例所生成
- 通過Class可以完整地得到一個類中的所有被載入的結構
- Class類是Reflection的根源,針對任何你想動態載入、運行的類,唯有先獲得相應的Class對象
6.1、Class類的常用方法
6.2、獲取Class類的實例(重要,面試常問)
-
若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程式性能最高。
Class clazz = Person.class;
-
已知某個類的實例,調用該實例的getClass()方法獲取Class對象
Class clazz = person.getClass();
-
已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能拋出ClassNotFoundException
Class clazz = Class.forName("demo01.Student");
-
內置基本數據類型可以直接用 類名.Type
-
還可以利用ClassLoader(瞭解)
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("The person is : " + person.name);
//方式一:通過對象獲得
Class c1 = person.getClass();
System.out.println(c1.hashCode());
//方式二:forName獲得
Class c2 = Class.forName("ReflectDemo.Student");
System.out.println(c2.hashCode());
//方式三:通過類名.class獲得
Class<Student> c3 = Student.class;
System.out.println(c3.hashCode());
//方式四:基本內置類型的包裝類都有一個Type屬性
Class c4 = Integer.TYPE;
System.out.println(c4);
//獲得父類類型
Class c5 = c1.getSuperclass();
System.out.println(c5);
}
}
class Person{
int id;
String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
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;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
class Student extends Person{
public Student(){
this.name = "student";
}
}
class Teacher extends Person{
public Teacher(){
this.name = "teacher";
}
}
哪些類型可以有Class對象?
-
class:外部類,成員(成員內部類,靜態內部類),局部內部類,匿名內部類
-
interface:介面
-
[] :數組
-
enum:枚舉
-
annotation:註解@interface
-
primitive type :基本數據類型
-
void
//所有類型的Class
public class Test03 {
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; //Class
System.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
int[] a = new int[10] ;
int[] b = new int[100];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
}
}
6.3、類的載入和初始化
Java記憶體分配
類的載入過程(瞭解)
當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過如下三個步驟來對該類進行初始化:
-
載入:將class文件位元組碼內容載入到記憶體中,並將這些靜態數據轉換成方法區的運行時數據結構,然後生成一個代表這個類的java.lang.Class對象。
-
鏈接:將Java類的二進位代碼合併到JVM的運行狀態之中的過程。
- 驗證:確保載入的類信息符合JVM規範,沒有安全方面的問題
- 準備:正式為類變數(static)分配記憶體並設置類變數預設初始值的階段,這些記憶體都將在方法區中進行分配
- 解析:虛擬機常量池內的符號引用(常量名)替換為直接引用(地址)的過程
-
初始化:
-
執行類構造器
()方法的過程。類構造器 ()方法是由編譯期自動收集類中所有類變數的賦值動作和靜態代碼塊中的語句合併產生的。(類構造器是構造類信息的,不是構造該類對象的構造器) -
當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化
-
虛擬機會保證一個類的
()方法在多線程環境中被正確加鎖和同步
-
public class Test04 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
}
}
/*
1.載入到記憶體,會產生一個類對應Class對象
2.鏈接,鏈接結束後 m = 0
3.初始化
<clinit>(){
System.out.println("A類靜態代碼塊初始化");
m = 300;
m = 100;
}
最終: m = 100
*/
class A{
static{
System.out.println("A類靜態代碼塊初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A類的無參構造函數初始化");
}
}
什麼時候會發生類初始化?(重要)
-
類的主動引用(一定會發生類的初始化)
- 當虛擬機啟動,先初始化main()方法所在的類
- new一個類的對象
- 調用類的靜態成員(除了final常量)和靜態方法
- 使用java.lang.reflect包的方法對類進行反射調用
- 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類
-
類的被動引用(不會發生類的初始化)
- 當訪問一個靜態域時,只有真正聲明這個域的類才會被初始化。如:當通過子類引用父類的靜態變數,不會導致子類初始化
- 通過數組定義類引用,不會觸發此類的初始化
- 引用常量不會觸發此類的初始化(常量在鏈接階段就存入調用類的常量池中了)
public class Test05 {
public static void main(String[] args) throws ClassNotFoundException {
//1.主動引用
// Son son = new Son();
//2.反射也會產生主動引用
// Class.forName("ReflectDemo.Son");
//不會產生類的引用的方法
//System.out.println(Son.b);
//Son[] sons = new Son[5];
//System.out.println(Son.M);
}
}
class Father{
static int b = 2;
static {
System.out.println("父類被載入...");
}
}
class Son extends Father{
static {
System.out.println("子類被載入...");
m = 300;
}
static int m = 100;
static final int M = 1;
}
類載入器
- 類載入器的作用:將class文件位元組碼內容載入到記憶體中,並將這些靜態數據轉換成方法區的運行時數據結構,然後在堆中生成一個代表這個類的java.lang.Class對象,作為方法區中類數據的訪問入口。
- 類緩存:標準的JavaSE類載入器可以按要求查找類,但一旦某個類被載入到類載入器中,它將維持載入(緩存)一段時間。不過JVM垃圾回收機制可以回收這些Class對象
類載入器作用是用來把類(class)裝在進記憶體的。JVM規範定義瞭如下類型的類的載入器:
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException {
//獲取系統類的載入器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
//獲取系統類載入器的父類載入器 --> 擴展類載入器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);
//獲取擴展類載入器的父類載入器 --> 根載入器( C/C++ )
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
//測試當前類是哪個載入器載入的
System.out.println(Class.forName("ReflectDemo.Test06").getClassLoader());
//測試JDK內置的類是誰載入的
System.out.println(Class.forName("java.lang.Object").getClassLoader());
//如何獲得系統類載入器可以載入的路徑
System.out.println(System.getProperty("java.class.path"));
}
}
獲取運行時類的完整結構
通過反射可以獲取運行時類的完整結構:Filed、Method、Constructor、Superclass、Interface、Annotation
- 實現的全部介面
- 所繼承的父類
- 全部的構造器
- 全部的方法
- 全部的Field
- 註解
- ......
//通過反射獲得類的信息
public class Test07 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c1 = Class.forName("ReflectDemo.User");
//獲得類的名字
System.out.println(c1.getName()); //獲得包名 + 類名
System.out.println(c1.getSimpleName()); //獲得類名
//獲得類的屬性
System.out.println("========");
Field[] fields = c1.getFields(); //只能找到public屬性
for (Field field : fields) {
System.out.println(field);
}
fields = c1.getDeclaredFields(); //找到全部的屬性,包括私有屬性
for (Field field : fields) {
System.out.println(field);
}
//獲得類的方法
System.out.println("========");
Method[] methods = c1.getMethods(); //獲得本類及其父類的全部public方法
for (Method method : methods) {
System.out.println("getMethods : " + method);
}
methods = c1.getDeclaredMethods(); //獲得本類的所有方法
for (Method method : methods) {
System.out.println("getDeclaredMethods : " + method);
}
//獲得指定方法 (需要傳遞參數類型,方便有方法重載時定位方法)
Method getName = c1.getMethod("getName",null);
Method setName = c1.getMethod("setName",String.class);
System.out.println(getName);
System.out.println(setName);
//獲得構造器
System.out.println("========");
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("getConstructors : " + constructor);
}
constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println("getDeclaredConstructors : " + constructor);
}
//獲得指定的構造器
Constructor declaredConstructor = c1.getDeclaredConstructor(int.class,String.class );
System.out.println(declaredConstructor);
}
}
6.4、通過反射創建類的對象
(1)調用Class對象的newInstance()方法,要求如下:
- 類必須有一個無參數的構造器;
- 類的構造器的訪問許可權需要足夠。
(2)如果沒有無參構造器或者訪問許可權不足,則可以通過獲取構造器來創建對象。
通過構造器創建對象,步驟如下:
- 通過Class類的getDeclaredConstructor( Class ... parameterTypes )取得本類的指定形參類型的構造器
- 向構造器的形參中傳遞一個對象數組進去,裡面包含了構造器中所需的各個參數
- 通過Constructor實例化對象
調用指定的方法
通過反射,調用類中的方法,通過Method類完成
-
通過Class類的getMethod(String name, Class ... parameterTypes)方法取得一個Method對象,並設置此方法操作時所需要的參數類型;
-
使用Object invoke(Object obj, Object[] args) 進行調用,並向方法中傳遞要設置的obj對象的參數信息。
Object invoke ( Object obj, Object ... args )
- Object對應原方法的返回值,若原方法無返回值,此時返回null
- 若原方法為靜態方法,此時形參Object obj 可為null
- 若原方法形參列表為空,則Object[] args 為null
- 若原方法聲明為private,則需要在調用此invoke方法前,顯示調用方法對象的setAccessible(true)方法,將可訪問private的方法。
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//獲得Class對象
Class c1 = Class.forName("ReflectDemo.User");
//構造一個對象
User user = (User)c1.newInstance();
System.out.println(user);
//通過構造器創建對象
System.out.println("========");
Constructor constructor = c1.getDeclaredConstructor(int.class, String.class);
System.out.println(constructor.newInstance(3, "Lily"));
//通過反射調用普通方法
System.out.println("========");
User user2 = (User)c1.newInstance();
//通過反射獲取一個方法
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user2,"May");
System.out.println(user2.getName());
//通過反射操作屬性
System.out.println("========");
User user3 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true); //不能直接操作私有屬性,我們需要關閉程式的安全監測 屬性或者方法的setAccessible(true)
name.set(user3,"Jack");
System.out.println(user3.getName());
}
}
創建對象的效率
創建對象的效率排序如下:類的實例化方式創建 >> 通過反射的方式創建(setAcccessible(true);) > 通過反射的方式創建。
6.5、反射操作泛型(瞭解即可)
- Java採用泛型擦除的機制來引入泛型,Java中的泛型僅僅是給編譯器javac使用的,確保數據的安全性和免去強制類型轉換問題。但是,一旦編譯完成,所有和泛型有關的類型全部擦除
- 為了通過反射操作這些類型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種類型來代表不能被歸一到Class類中的類型但是又和原始類型齊名的類型
- ParameterizedType:表示一種參數化類型,比如 Collection
- GenericArrayType:表示一種元素類型是參數化類型或者類型變數的數組類型
- TypeVariable:是各種類型變數的公共父介面
- WildcardType:代表一種通配符類型表達式
6.6、反射操作註解(重要,框架基礎)
//反射操作註解
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("ReflectDemo.Stud");
//通過反射獲得註解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//獲得註解的value的值
TableAnnotation annotation = (TableAnnotation) c1.getAnnotation(TableAnnotation.class);
String value = annotation.value();
System.out.println(value);
//獲得類指定的註解
Field name = c1.getDeclaredField("name");
FiledAnnotation annotation1 = name.getAnnotation(FiledAnnotation.class);
System.out.println(annotation1.columnName());
System.out.println(annotation1.type());
System.out.println(annotation1.length());
}
}
@TableAnnotation("db_student")
class Stud{
@FiledAnnotation(columnName ="db_id",type = "int", length = 10)
private int id;
@FiledAnnotation(columnName = "db_name",type = "varchar",length = 10)
private String name;
@FiledAnnotation(columnName = "db_age", type = "int" , length = 3)
private int age;
public Stud() {
}
public Stud(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
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 int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Stud{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
//類名的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableAnnotation{
String value();
}
//屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FiledAnnotation{
String columnName();
String type();
int length();
}
(補充)6.7、雙親委派機制
什麼是雙親委派機制?
雙親委派機制是當類載入器需要載入某一個.class位元組碼文件時,則首先會把這個任務委托給他的上級類載入器,遞歸這個操作,如果上級沒有載入該.class文件,自己才會去載入這個.class。這是一種任務委派模式。
雙親委派機制原理:
如果一個類載入器收到了要載入某個類的請求,它要做的首要事情不是載入,而是將這個請求委托給父類的載入器去執行;
如果父類載入器還存在其父類載入器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類載入器;
如果父類載入器可以完成類載入任務,就成功返回,倘若父類載入器無法完成此載入任務,子載入器才會嘗試自己去載入;
如果最後沒有任何載入器能載入,則報錯"ClassNotFoundException"。
原理圖示:
本博客為博主在學習B站up主狂神關於反射和註解相關課程的學習筆記,博客中大部分內容為課上ppt內容的整理和總結。
在此附上狂神在b站的視頻鏈接(講的是真的好,幫忙宣傳)
關於雙親委派機制的內容,在此附上參考博客地址:雙親委派機制 詳解(手畫詳圖)面試高頻 你值得擁有!!!