Java注解和反射
1.注解
- 注解(Annotation)從JDK5.0引入,不是程式本身,可以對程式作出解釋(和注釋(comment)一樣),但可以被其他程式(如編譯器)讀取
- Annotation格式:"@注釋名",可以添加一些參數值
- 使用:可以附加在package,class,method,field等上面,相當于添加額外的輔助資訊,通過反射機制程式設計實作對這些中繼資料的通路。
1.1 内置注解
- @override: 定義在java.lang.Override中,隻适用修辭方法,表示該方法重寫超類中的一個方法
- @Deprecated:java.lang.Deprecated,表示修飾的方法,屬性,類已過時,推薦适用另外的
- @SuppressWarnings:抑制編譯時的警告資訊,需要添加參數。如@SSuppressWarnings(“all”),SuppressWarnings(“unchecked”)等。
1.2 元注解
元注解的作用就是負責注解其他注解,java定義了4個标準的meta-annotation類型,被用來提供對其他annotation類型作說明。在java.lang.annotation包中可以找到。
- @Target:用于描述注解的使用範圍(即:被描述的注解可以用在什麼地方)
- @Retention:表示需要在什麼級别儲存該注釋資訊,用于描述注解的生命周期(SOURCE < CLASS < RUNTIME)
- @Document:說明該注解被包含在javadoc中
- @Inherited:說明子類可以繼承父類中的該注解
1.3 自定義注解
使用@interface自定義注解,且自動繼承java.lang.annotation.Annotation接口
- 注解聲明格式:public @interface 注解名 {定義内容}
- 其中的每一個方法實際是聲明一個配置參數
- 方法名稱就是參數名稱
- 通過default聲明預設參數值
- 一個參數成員,一般參數名為value
- 注解元素必須要有值,可以使用空字元串,0為預設值
//自定義注解
public class Test02 {
//注解可以顯示指派,如果沒有預設值,必須給注解指派
@MyAnnotation2(name = "ann1")
public void test() {}
@MyAnnotation3("mianmianmi")
public void test2(){}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//注解的參數:參數類型 + 參數名();
String name() default "";
int age() default 0;
int id() default -1; //預設值為-1,代表不存在
String[] schools() default {"西北","西南","東北"};
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
String value();//隻有一個參數,使用value命名
}
2.反射
靜态語言(java,c,c++)和動态語言(C#,JavaScript,PHP):根據運作時結構是否可變區分,如引入新的函數、對象,删除已有的函數等,即代碼運作時可以根據某些條件改變自身結構。java利用反射機制獲得類似動态語言的特性。
- Reflection(反射)機制允許程式在執行期間借助Reflection API 獲得任何類的内部資訊,并能直接操作任意對象的内部屬性及方法。
- 加載完類之後,堆記憶體的方法區中就産生了一個Class類型的對象(一個類隻有一個Class對象),這個對象包含了完整的類結構資訊,通過這個對象看到類的結構,類似鏡子,稱之為:反射。
- 反射優點:實作動态建立對象和編譯,靈活性大。缺點:影響性能,使用反射是一種解釋操作,是在告訴JVM希望它完成什麼,慢于直接執行相同操作。
//反射機制示範
public class ReflectionDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
//通過反射擷取類的Class對象
Class c1 = Class.forName("com.jayz.annotationDemo.User");
System.out.println(c1);
Class c2 = Class.forName("com.jayz.annotationDemo.User");
Class c3 = Class.forName("com.jayz.annotationDemo.User");
//一個類在記憶體中隻有一個Class對象,一個類被加載後,類的整個結構都會封裝在Class對象中
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());//二者結果相等,指向同一對象
}
}
//實體類
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;
}
}
Class類:一個Class對象寶華了特定某個結構的有關資訊
- Class對象隻能由系統建立
- 一個加載的類在JVM中隻會有一個Class執行個體,一個Class對象對應的是一個加載到JVM中的.class檔案
- Class類是Reflection的根源,針對任何想動态加載的類,必須獲得Class對象
2.1 擷取Class類的執行個體
- 已知具體類,通過類的class屬性擷取,最安全,性能最高(Class c = Person.class;)
- 已知某個類的執行個體,調用該執行個體的getClass()方法擷取Class對象(Class c = person.getClass()😉
- 已知一個類的全類名,且該類在類路徑下,通過Class類的靜态方法forName() 擷取,需要抛出異常
- 内置基本資料類型直接使用類名.Type
- 利用ClassLoader
- 數組隻要元素類型與次元一樣,就是同一個class
2.2 Java 記憶體
- 堆:存放new出來的對象和數組;可以被所有線程共享,不存放别的對象引用
- 棧:存放基本變量類型;存放引用對象的變量(此引用在堆的具體位址)
- 方法區:可以被所有線程共享;包含所有的class和static變量
類的加載過程:
- 類的加載(Load)
- 将class檔案位元組碼内容加載到記憶體中,并将這些靜态資料轉換成方法區的運作時資料結構,然後生成一個代表該類的java.lang.Class對象
- 類加載器把類(class)裝載進記憶體。有引導類加載器(Bootstap Classloader)(JVM自帶,C++編寫,負責Java平台核心庫,無法直接擷取),擴充類加載器(Extension Classloader)(負責jre/lib/ext目錄下的jar包裝入工作庫),系統類加載器(System Classloader)(負責java -classpath所指的目錄下的類與jar包裝入,最常用的加載器)
- 雙親委派機制確定類的正确加載
//類加載器示範 public class ClassLoaderDemo { public static void main(String[] args) throws ClassNotFoundException { //擷取系統類的加載器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); //擷取系統類加載器的父類加載器--->擴充類加載器 ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent); //擷取擴充類加載器的父類加載器-->根加載器 ClassLoader parent1 = parent.getParent(); System.out.println(parent1); //測試目前類是哪個加載器加載的 ClassLoader classLoader = Class.forName("com.jayz.annotationDemo.ClassLoaderDemo").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")); } }
- 類的連結(Link)
- 将Java類的二進制代碼合并到JVM運作環境中
- 驗證:確定加載的類資訊符合JVM規範,沒有安全問題
- 準備:正式為類變量(static)配置設定記憶體并設定類變量預設初始值,記憶體都将在方法區中配置設定。
- 解析:虛拟機常量池内的符号引用(常量名)替換為直接引用(位址)的過程
- 将Java類的二進制代碼合并到JVM運作環境中
- 類的初始化(Initialize)
- 執行類構造器()方法的過程。類構造器方法是由編譯器自動收集類中所有類變量的指派動作和靜态代碼塊中的語句合并産生(不是類對象的構造器)
- 初始化一個類時,若發現其父類還沒有初始化,先觸發其父類的初始化
- 虛拟機保證一個類的()方法在多線程環境中被正确加鎖和同步。
- 發生類的初始化(類的主動應用),被動引用不會。
- 主動引用:當虛拟機啟動時,先初始化main方法所在的類
- new 一個類的對象
- 調用類的靜态方法和靜态成員
- 使用java.lang.reflect包中的方法對類進行反射調用
- 初始化一個類,若其父類沒有被初始化,先初始化父類
- 被動引用
- 通路一個靜态域時,隻有真正聲明這個域的類才會被初始化(如子類引用父類的靜态變量,不會導緻子類初始化)
- 通過數組定義類引用,不會觸發此類的初始化
- 引用常量不會觸發初始化(常量在連結階段存入常量池)
public class InitiDemo {
static {
System.out.println("Main所在類加載");
}
public static void main(String[] args) throws ClassNotFoundException {
//1.主動引用
//Son son = new Son(); //Main所在類加載 父類被加載 子類被加載
// 反射
//Class.forName("com.jayz.annotationDemo.Son");//Main所在類加載 父類被加載 子類被加載
//被動引用
//System.out.println(Son.b);//Main所在類加載 父類被加載 2
Son[] sons = new Son[5]; //Main所在類加載
}
}
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;
}
2.3 類資訊
示例:擷取類相關資訊(方法,屬性,構造器,父類,接口,注解)
//擷取類的資訊
public class ClassInformation {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.jayz.annotationDemo.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.getDeclaredField("name");
System.out.println(name);
//獲得類的方法
Method[] methods = c1.getMethods(); //獲得本類及父類的所有public方法
for(Method method:methods) {
System.out.println(method);
}
methods = c1.getDeclaredMethods(); //獲得本類的所有方法
for(Method method:methods) {
System.out.println(method);
}
//獲得構造器方法
Constructor constructor = c1.getConstructor();
Constructor[] constructors = c1.getConstructors();
}
}
示例:**通過反射構造對象,調用方法和屬性(私有的,setAccessible(true))。**使用反射會影響程式效率。
//反射操作
public class ReflectionDemo02 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//獲得Class對象
Class c1 = Class.forName("com.jayz.annotationDemo.User");
//構造對象
User u1 = (User)c1.newInstance(); //調用類的無參構造器,若定義了有參構造,沒有定義無參構造,無法使用此方法
System.out.println(u1);
//通過構造器建立對象
Constructor constructor = c1.getConstructor(String.class, int.class, int.class);
User u2 = (User)constructor.newInstance("jay", 01, 18);
System.out.println(u2);
//通過反射調用普通方法
User u3 = (User)c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke 激活方法
setName.invoke(u3,"jay2");
System.out.println(u3.getName());
//反射操作屬性
User u4 = (User)c1.newInstance();
Field name = c1.getDeclaredField("name");//通路私有屬性,權限不夠
//關閉權限
name.setAccessible(true);
name.set(u4,"jay3");
System.out.println(u4.getName());
}
}
2.4 ORM (Object relation Mapping):利用注解和反射完成類和表結構的映射關系
public class ORM_Demo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.jayz.annotationDemo.Student01");
//通過反射獲得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//獲得注解的 value的值
TableJay tableJay = (TableJay)c1.getAnnotation(TableJay.class);
String value = tableJay.value();
System.out.println(value);
//獲得類指定的注解
Field name = c1.getDeclaredField("name");
FieldJay fieldJay = name.getAnnotation(FieldJay.class);
System.out.println(fieldJay.columnName());
System.out.println(fieldJay.length());
System.out.println(fieldJay.type());
}
}
@TableJay("db_student")
class Student01{
@FieldJay(columnName = "db_id",type = "int",length = 10)
private int id;
@FieldJay(columnName = "db_name",type = "varchar",length = 3)
private String name;
@FieldJay(columnName = "db_age",type = "int",length = 10)
private int age;
public Student01(){}
public Student01(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;
}
}
//類名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableJay{
String value();
}
//屬性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldJay{
String columnName();
String type();
int length();
}