什麼是注解
注解與反射是所有架構的底層,例如Mybatis、Spring等等,注解(Annotation)是給程式看的,注釋(Comment)是給人看的,注解是Java基礎中最簡單的一章,不用感覺很難。
注解的作用
注解由JDK5.0引入,它不是程式本身,但是可以對程式做出解釋,可以被其他程式(例如:編譯器)讀取,注解不是必須的,但有時候因為程式需要是以要寫
注解的格式
以
@注解名
的形式存在,也可以添加一些參數值,例如:
@SuppressWarnings(value="unchecked")
注解在哪裡使用
内置注解
- @Override重寫
- @Deprecated方法廢棄
- @SuppressWarnings(value=“all”)鎮壓警告資訊
元注解
- @Target表示注解可以用在什麼地方
- @Retention表示需要在什麼級别儲存該注釋資訊,用于描述注解生命周期,SOURCE<CLASS<RUNTIME(常用)
- @Documented說明該注解将被包含在javadoc中
- @Inherited說明子類可以繼承父類中的注解
import java.lang.annotation.*;
//測試元注解
public class Test {
@MyAnnotation
public void test() {
}
}
//定義一個注解,這裡METHOD是放在方法上的注解,TYPE可以放在類上
//@Target(value = ElementType.METHOD)
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//注解運作時有效
@Retention(value = RetentionPolicy.RUNTIME)
//将注解生成在javadoc中
@Documented
//子類可以繼承父類的注解
@Inherited
@interface MyAnnotation{
}
自定義注解
使用
@interface
自定義注解的時候,自動繼承了
java.lang.annotation.Annotation
接口
- @interface用來聲明一個注解,格式: public @interface 注解名{定義内容}
- 其中的每一個方法實際上是聲明了—個配置參數
- 方法的名稱就是參數的名稱.
- 傳回值類型就是參數的類型(傳回值隻能是基本類型,Class,String,enum)
- 可以通過default來聲明參數的預設值
- 如果隻有一個參數成員,一般參數名為va|ue
- 注解元素必須要有值,我們定義注解元素時,經常使用空字元串,0作為預設值
import java.lang.annotation.*;
public class Test {
//注解可以顯示指派,如果沒有預設值則必須指派
@MyAnnotation(name = "hello")
public void test() {
}
}
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解的參數:參數類型+參數名+()
String name() default "";
int age() default 0;
int id() default -1;//如果預設值為-1則表示不存在,和indexof異曲同工
}
反射機制
反射讓java變成了動态語言
動态語言
是一類在運作時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被删除或是其他結構上的變化。通俗點說就是在運作時代碼可以根據某些條件改變自身結構。
主要動态語言: Object-C、C#、 JavaScript、PHP、 Python等
靜态語言
與動态語言相對應的,運作時結構不可變的語言就是靜态語言。如Java、C、C++。Java不是動态語言,但Java可以稱之為“準動态語言”。即Java有一定的動态性,我們可以利用反射機制獲得類似動态語言的特性。Java的動态性讓程式設計的時候更加靈活
什麼是反射
Reflection(反射)是Java被視為動态語言的關鍵,反射機制允許程式在執行期借助于 Reflection ap取得任何類的内部資訊,并能直接操作任意對象的内部屬性及方法。
加載完類之後,在堆內存的方法區中就産生了一個Class類型的對象(一個類隻有一個Cass對象),這個對象就包含了完整的類的結構資訊。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,是以,我們形象的稱之為:反射。反射會引起類的主動引用,即類似
new
了個對象
優點:
可以實作動态建立對象和編譯,展現出很大的靈活性
缺點:
對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼并且它滿足我們的要求。這類操作總是
慢于
直接執行相同的操作,比new出來慢了幾十倍。
反射提供的功能
- 在運作時判斷任意一個對象所屬的類
- 在運作時構造任意一個類的對象
- 在運作時判斷任意一個類所具有的成員變量和方法
- 在運作時擷取泛型資訊
- 在運作時調用任意一個對象的成員變量和方法
- 在運作時處理注解
- 生成動态代理
- 等等
反射初體驗
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("User");//類的包路徑
System.out.println(c1);
//一個類在記憶體中隻有一個class對象
//一個類被加載後,類的整個結構都會被封裝在Class對象中
Class c2 = Class.forName("User");
Class c3 = Class.forName("User");
Class c4 = Class.forName("User");
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
class User{
String name;
int age;
int id;
}
Class類
在Object類中定義了以下方法:
以上的方法傳回值的類型是一個C|ass類,此類是Java反射的源頭,實際上所謂反射從程式的運作結果來看也很好了解,即:可以通過對象反射求出類的名稱
常用的方法:
擷取Class的幾種方式
public class Test {
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());
//方式二
Class c2 = Class.forName("Student");
System.out.println(c2.hashCode());
//方式三
Class c3 = Student.class;
System.out.println(c3.hashCode());
//方式四,基本内置類型的包裝類都有一個TYPE屬性
Class c4 = Integer.TYPE;
System.out.println(c4.hashCode());
//獲得父類類型
Class c5 = c1.getSuperclass();
String name = c5.getName();
System.out.println(c5);
//以後還可以用ClassLoader
}
}
class Person{
public String name;
}
class Student extends Person{
Student() {
this.name = "學生";
}
}
哪些類型可以有Class對象
- class:外部類,成員(成員内部類,靜态内部類),局部内部類,匿名内部類。
- interface:接口
- []:數組
- enum:枚舉
- annotation:注解@ interface
- primitive type:基本資料類型
- void
主動引用與被動引用
主動引用
java類的初始化階段,虛拟機規範嚴格規定了5種情況必須立即對類進行初始化。
- 遇到new、getstatic、putstatic或invokestatic這4條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發初始化。
- 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
- 當初始化一個類時,如果發現其父類沒有進行過初始化,則需要先觸發其父類的初始化。
- 當虛拟機啟動時,使用者需要制定一個要執行的主類(包含main()方法的那個類),虛拟機會先初始化這個類。
- 當使用JDK1.7的動态語言支援時,如果一個java.lang.invoke.MethodHandle執行個體最後的解析結果REF_geStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。
被動引用
除了上述5種場景,其他所有類的方式都不會觸發初始化,稱為被動引用。例如:
- 子類調用父類的靜态方法,不會執行個體化子類
- 通過數組定義類引用,不會觸發類的初始化
- 通過調用常量不會觸發此類的初始化,因為常量在連結階段已經存入調用類的常量池中了
反射擷取泛型資訊
反射擷取注解資訊
import java.lang.annotation.*;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> c1 = Class.forName("User");
//反射擷取注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//獲得注解的value值
MyTable table = c1.getAnnotation(MyTable.class);//首先擷取指定注解
System.out.println(table.value());
//獲得類指定的注解
Field f = c1.getDeclaredField("name");
MyField annotation = f.getAnnotation(MyField.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@MyTable("db_User")
class User{
@MyField(columnName = "db_user",type = "varchar",length = 12)
public String name;
@MyField(columnName = "db_user",type = "int",length = 10)
public int age;
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
String columnName();
String type();
int length();
}