注解
什么是注解
-
Annotation是從JDK5.0開始引入的新技術
-
Annotation的作用:
- 不是程序本身,可以對程序作出解釋。(這一點和注釋(comment)沒什么區別)
- 可以被其他程序(比如:編譯器等)讀取。
-
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:說明該注解被包含在javadoc中
- @Inherited:說明子類可以集成父類中的該注解
自定義注解
- 使用@interface 自定義注解時,自動繼承了java.lang.annotation.Annotation接口
- 分析:
- @interface用來聲明一個注解,格式: public @ interface 注解名 {定義內容}
- 其中的每個方法實際上是聲明了一個配置參數
- 方法的名稱就是參數的名稱
- 返回值類型就是參數的類型(返回值只能是基本類型,Class,String,enum)
- 可以通過default來聲明參數的默認值
- 如果只有一個參數成員,一般參數名為value
- 注解元素必須要有值,我們定義注解元素時,經常使用空字符串,0作為默認值
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//注解的參數:參數類型 + 參數名 ()
String name() default "";
int age() default 0;
int id();
String[] schools() default {"清華大學", "北京大學"};
}
class Test {
//如果沒有默認值,我們就必須給注解賦值
@MyAnnotation(id = 2006)
public void test() {
}
}
反射
反射機制的概述
動態語言
- 是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被杉樹或是其他結構上的變化。通俗點說就是在運行時代碼可以根據某些條件改變自身結構。
- 主要動態語言:Object-C、C#、JavaScript、PHP、Python
靜態語言
-
與動態語言相對應的,運行時結構不可變的語言就是靜態語言。如:Java、C、C++
-
Java不是動態語言,但Java可以稱之為"准動態語言"。即Java有一定的動態性,我們可以利用反射機制獲取類似動態語言的特性。Java的動態性讓編程的時候更加靈活!
-
Reflection(反射)是Java被視為動態語言的關鍵,反射機制運行程序在執行期借助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法。
Class c = Class.forName("java.lang.String")
- 加載完類之后,在堆內存的方法區中就產生了一個Class類型的對象(一個類只能有一個Class對象),這個對象就包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到的類的結構,所以,我們形象的稱之為:反射

Java反射機制提供的功能
- 在運行時判斷任意一個對象所屬的類
- 在運行時構造任意一個類的對象
- 在運行時判斷任意一個類所具有的成員變量和方法
- 在運行時獲取泛型信息
- 在運行時調用任意一個對象的成員變量和方法
- 在運行時處理注解
- 生成動態代理
- ......
Java反射優點和缺點
- 優點
- 可以實現動態創建對象和編譯,體現出很大的靈活性
- 缺點
- 對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么並且滿足我們的要求。這類操作總是慢於直接執行相同的操作。
反射相關的主要API
- java.lang.Class:代表一個類
- java.lang.reflect.Methode:代表類的方法
- java.lang.reflect.Field:代表類的成員變量
- java.lang.reflect.Constructor:代表類的構造器
- ...... .......
package com.ycy.demo02;
/**
* @author YeCaiYu
* @date 2020-05-04 9:43
*/
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
//通過反射獲取類的Class對象
Class c1 = Class.forName("com.ycy.demo02.User");
System.out.println(c1);
Class c2 = Class.forName("com.ycy.demo02.User");
Class c3 = Class.forName("com.ycy.demo02.User");
Class c4 = Class.forName("com.ycy.demo02.User");
//一個類在內存中只有一個Class對象
//一個類被加載后,類的整個結構都會被封裝在Class對象中
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
//實體類:pojo , entity
class User {
private String name;
private int id;
private int age;
public User() {
}
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
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;
}
}
Class類
在Object類中定義了以下的方法,此方法被所有子類集成
public final Class getClass()
- 以上的方法返回值的類型是一個Class類,此類是Java反射的源頭,實際上所謂的反射從程序的運行結果來看也是很好理解,即:可以通過對象反射求出類的名稱。
對象照鏡子后可以得到的信息:某個類的屬性、方法和構造器、某個類到底實現了那些接口。對於每個類而言,JRE都為其保留一個不變的Class類型的對象。一個Class對象包含了特定某個結構(class/interface/enum/annotation/primitive type/void/[])的有關信息。
- Class本身也是一個類
- Class對象只能由系統建立對象
- 一個加載的類在JVM中只會有一個Class實例
- 一個Class對象對應的是一個加載到JJVM中的一個.class文件
- 每個類的實例都會記得自己是由哪個Class實例所生成
- 通過Class可以完整地得到一個類中所有被加載的結構
- Class類是Reflection的根源,針對任何你想動態加載、運行的類,唯獨先獲得相應Class對象
| 方法名 | 功能說明 |
|---|---|
| static ClassforName(String name) | 返回指定類名name的Class對象 |
| Object newInstance() | 調用缺省構造函數,返回Class對象的一個實例 |
| getName() | 返回此Class對象所表示的實體(類、接口、數組類或void)的名稱 |
| Class getSuperClass() | 返回當前Class對象的父類的Class對象 |
| Class[] getinterfaces() | 獲取當前Class對象的接口 |
| Constructor[] getConstructors() | 返回一個包含某些Constructor對象的數組 |
| ClassLoader getClassLoader() | 返回該類的加載器 |
| Method getMethod(String name.Class.. T) | 返回一個Method對象,此對象的形參類型為paramType |
| Field[] getDeclaredFields() | 返回Field對象的一個數組 |
package com.ycy.demo02;
/**
* @author YeCaiYu
* @date 2020-05-04 10:05
*/
//測試Class類的創建有哪些
public class Test03 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("這個人是:" + person.name);
//方式一: 通用對象獲取
Class c1 = person.getClass();
System.out.println(c1.hashCode());
//方式二: forName獲取
Class c2 = Class.forName("com.ycy.demo02.Student");
System.out.println(c2.hashCode());
//方式三: 通過類名.class獲取
Class 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 {
public String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student extends Person {
public Student() {
this.name = "學生";
}
}
class Teacher extends Person {
public Teacher() {
this.name = "老師";
}
}
類的加載

類初始化
- 類的主動引用(一定會發生類的初始化)
- 當虛擬機啟動,先初始化main方法所在的類
- new一個類的對象
- 調用類的靜態成員(除final常量)和靜態方法
- 使用java.lang.reflect包的方法對類進行反射調用
- 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類
- 類的被動引用(不會發生類的初始化)
- 當訪問一個靜態域時,只有真正聲明這個域的類才會被初始化。如:當通過子類引用父類的靜態變量,不會導致子類初始化
- 通過數組定義類的引用,不會觸發此類的初始化
- 引用常量不會觸發此類的初始化(常量在鏈接階段就存入調用類的常量池中了)
獲取類的運行時結構
package com.ycy.demo02;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* @author YeCaiYu
* @date 2020-05-04 10:54
*/
public class Test05 {
//獲取類的信息
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.ycy.demo02.User");
//獲取類的名字
System.out.println(c1.getName());//獲取包名 + 類名
System.out.println(c1.getSimpleName()); //獲取類名
//獲取類的屬性
Field[] fields = c1.getFields();//只能找到public屬性
for (Field field : fields) {
System.out.println(field);
}
System.out.println("++++++++++++++++++++++");
fields = c1.getDeclaredFields();//能找到全部的屬性
for (Field field : fields) {
System.out.println(field);
}
//獲取指定屬性的值
Field name = c1.getDeclaredField("name");
System.out.println(name);
//獲取類的方法
System.out.println("+++++++++++++++++++++++");
Method[] methods = c1.getMethods();//獲得本類及其父類全部public方法
for (Method method : methods) {
System.out.println(method);
}
System.out.println("+++++++++++++++++++++++");
methods = c1.getDeclaredMethods();//獲取本類的所有方法
for (Method method : methods) {
System.out.println(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(constructor);
}
System.out.println("********************************");
constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
//獲得指定構造器
Constructor constructor = c1.getConstructor(String.class, int.class, int.class);
System.out.println("指定:" + constructor);
}
}
調用執行的方法
通過反射,調用類中的方法,通過Method類完成。
- 通過Class類的getMethod(String name,Class.. parameterTypes)方法取得一個Method對象,並設置此方法操作時所需要的的參數類型。
- 之后使用Object invoke(Object obj,Object[] args)進行調用,並向方法中傳遞設置的obj對象的參數信息

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