美國超 35 家民權組織呼籲零售商停止使用人臉識別技術
註解
一、元註解
元註解的作用就是註解其他註解,Java定義了4個標準的meta-annotation型別,他們被用來提供對其他annotation型別做說明。
4個元註解分別為:
-
@Target:用於描述註解的使用範圍
-
ElementType.TYPE 針對類、介面
-
ElementType.FIELD 針對成員變數
-
ElementType.METHOD 針對成員方法
-
ElementType.PARAMETER 針對方法引數
-
ElementType.CONSTRUCTOR 針對構造器
-
ElementType.PACKAGE 針對包
-
ElementType.ANNOTATION_TYPE 針對註解
-
-
@Retention:用於表示需要在什麼級別儲存註解資訊,用於描述註解的生命週期,(SOURCE<CLASS<RUNTIME)
-
RetentionPolicy.SOURCE 原始碼級別,由編譯器處理,處理之後就不再保留
-
RetentionPolicy.CLASS 註解資訊保留到類對應的 class 檔案中
-
RetentionPolicy.RUNTIME 由 JVM 讀取,執行時使用
-
-
@Document:說明該註解將被包含在javadoc(文件)中
-
@Inherited:說明子類可以繼承父類中的該註解
二、自定義註解
public class Test { //如果註解的引數沒有預設值,則必須給該引數賦值 @MyAnnotation(schools = {"北京大學","清華大學"}) public void test(){ } //如果註解只有一個引數且引數名為value,則value可以省略不寫 @MyAnnotation2("aa") public void test2(){ } } //註解的寫法 @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation{ //註解的引數:型別+引數名() [default 預設值]; String name() default ""; int age() default 0; int id() default -1; //如果預設值為-1,代表不存在 String[] schools(); } @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation2{ //如果註解只有一個引數,一般引數名取為value String[] value(); //不成文的一個規範 }
反射
一、反射概述
Java不是動態語言,但Java可以稱為“準動態語言”。因為Java的反射機制可以讓Java獲得類似動態語言的特性,即執行時程式碼可以根據一些條件來改變自身的結構。
反射機制允許程式在執行期間藉助於Reflection API獲取任何類的內部資訊(比如類名,類的介面,類的方法,欄位,屬性…),並且能夠直接操作任意物件的內部屬性及方法。
載入完類之後,在堆記憶體中的方法區中間產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類的結構資訊。我們可以通過這個物件看到類的結構。這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射。
反射機制提供的功能:
- 在執行時判斷任意一個物件所屬的類
- 在執行時構造任意一個類的物件
- 在執行時判斷任意一個類所具有的成員變數和方法
- 在執行時呼叫任意一個物件的成員變數和方法
- 在執行時獲取泛型資訊
- 在執行時處理註解
- 生成動態代理
- ……
二、Class物件
一個類在記憶體中只有一個Class物件,不可能有多個。
一個類被被載入後,類的整個結構都會被封裝在Class物件中。
Class物件只能由系統建立,我們只負責使用。我們要想使用反射機制,首先得要獲得相應的Class物件。
1. 獲得Class物件的方式
(1)如果已知具體的類,通過類的class屬性獲取,是最為安全可靠且效能最高的方法。
Class clz = 類名.class;
(2)如果已知某個類的物件,呼叫此物件的getClass()方法獲取Class物件。
Class clz = 物件名.getClass();
【注】getSuperClass()方法可以返回當前物件的父類Class物件。
(3)已知一個類的全限定類名且該類在類路徑下,可以通過Class類的靜態方法forName()獲取,需要處理異常ClassNotFoundException。
Class clz = Class.forName("類的全限定類名")
(4)內建基本資料型別可以直接使用類名.TYPE,比如:
Class clz = Integer.TYPE; //基本內建型別的包裝類都有一個 TYPE屬性
(5)還可以用ClassLoader。
2. 通過Class物件獲取執行時類的完整結構
(1)獲取類的名字
- clz.getName() 獲取包名+類名
- clz.getSimpleName 獲取類名
(2)獲取類的屬性
-
clz.getFields() 獲取本類public屬性
-
clz.getDeclaredFields() 可以獲取到本類所有屬性,包括私有屬性
-
clz.getDeclaredField("屬性名") 獲取指定的屬性
(3)獲取類的方法
-
clz.getMethods() 獲取本類及其父類的所有public方法
-
clz.getDeclaredMethods() 獲取本類的所有方法,包括私有方法,不能獲取父類的方法
-
clz.getMethod("方法名", ...方法引數型別.class)
clz.getDeclaredMethod("方法名", ...方法引數型別.class)
獲取指定方法,若方法無參,則傳遞null
(4)獲取類的構造方法
- clz.getConstructors() 獲取本類public構造方法
- clz.getDeclaredConstructors() 獲取本類所有構造方法
- clz.getDeclaredConstructor(...構造方法引數型別.class) 獲取指定構造方法
3. 獲取到類的結構資訊後能做什麼
(1)通過反射建立類的物件
方式1:呼叫Class物件的newInstance()方法
前提:
必須要有一個無參構造器;
類的構造器的訪問許可權要夠。
`User user = (User) clz.newInstance(); //本質上呼叫了類的無參構造器`
方式2:通過指定的構造器建立
首先通過Class物件獲取指定的構造器,然後呼叫Constructor物件的newInstance(...構造方法引數值)方法。
Constructor constructor = clz.getDeclaredConstructor(String.class, int.class);
User user = (User) constructor.newInstance("小明", 18);
(2)通過反射呼叫方法
User user = (User) clz.newInstance();
//通過反射獲取一個方法
Method setName = clz.getMethod("setName", String.class);
//invoke: 啟用的意思 (物件, "方法需要的引數")
setName.invoke(user,"小張");
System.out.println(user.getName());//輸出:小張
Object invoke(Object obj, Object ... args)
- invoke方法的返回值對應原方法的返回值,若原方法無返回值,則返回null。
- 若原方法為靜態方法,此時形參Object obj可為null。
- 若原方法形參列表為空,則Object ... args為null。
- 若原方法宣告為private,則需要在呼叫此invoke()方法之前,顯示呼叫Method物件的setAccessible(true)方法,這樣才可以訪問私有方法。
(3)通過反射操作屬性
User user= (User) clz.newInstance();
Field name = clz.getDeclaredField("name");
//不能直接操作私有屬性,我們需要關閉程式的安全檢查,Field或Method物件呼叫setAccessible(true)方法
//setAccessible 預設為false,如果沒有關閉將會報沒有訪問private的許可權
//can not access a member of class com.javacto.reflection.User with modifiers "private"
name.setAccessible(true);
name.set(user,"小紅"); //修改屬性值
System.out.println(user.getName());//輸出:小紅
【注】使用反射機制會影響程式的執行效率。關閉安全檢查在一定程度上能改善一些反射的效率問題。
三、通過反射獲取泛型資訊
Java採用泛型擦除的機制來引入泛型,Java中的泛型僅僅是給編譯器javac使用的,目的是確保資料的安全性和免去強制型別轉換的問題,但是,一旦編譯完成,所有和泛型有關的型別將被全部擦除。
思考該怎麼獲取泛型資訊呢? (之前有說過,類載入的時候就產生了Class物件,故class物件裡面應該是有保留泛型資訊的)
public class Test {
/**
* 通過泛型傳參
* @param map
* @param list
*/
public void test01(Map<String,User> map, List <User> list){
System.out.println("test01");
}
/**
* 通過泛型返回值
* @return
*/
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test.class.getMethod("test01", Map.class, List.class);
//獲取引數型別 即Map 和 List
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("1:"+genericParameterType);
//判斷genericParameterType引數型別 是否屬於 ParameterizedType 引數化型別
if (genericParameterType instanceof ParameterizedType){
//如果屬於引數化型別,獲得他的真實型別 getActualTypeArguments
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
//再次輸出真實的泛型資訊
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("2:"+actualTypeArgument);
}
}
}
//獲得返回值型別
method = Test.class.getMethod("test02",null);
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
//如果genericReturnType返回值型別屬於引數化型別,獲得他的真實型別 getActualTypeArguments
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
//再次輸出真實的泛型資訊
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("3:"+actualTypeArgument);
}
}
}
}
四、通過反射獲取註解資訊
public class Test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class clz = Class.forName("com.javacto.reflection.Student");
//通過反射獲取註解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//獲得註解的value的值 獲取指定註解值
MyTable myTable =(MyTable) c1.getAnnotation(MyTable.class);
String value = myTable.value();
System.out.println(value);
//獲得類指定的註解
System.out.println("=====獲得類指定的註解======");
Field field= clz.getDeclaredField("name");
MyField annotation = field.getAnnotation(MyField.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@MyTable("db_students")
class Student{
@MyField(columnName = "db_id",type = "int",length = 10)
private int id;
@MyField(columnName = "db_age",type = "int",length = 10)
private int age;
@MyField(columnName = "db_name",type = "varchar",length = 50)
private String name;
public Student() {
}
public Student(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;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", age=" + age +
", name=" + name +
'}';
}
}
//類名的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
String value();
}
//屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
String columnName(); //列名
String type(); //型別
int length(); //長度
}