Java基礎之反射的基本使用

簡介

在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意屬性和方法;這種動態獲取信息以及動態調用對象方法的功能稱為Java語言的反射機制。反射讓Java成為了一門動態的語言,是許多Java框架的基石。

為什么要叫“反射”:加載完類之后,在堆中就產生了一個Class類型的對象,叫做類對象,一個類只有一個類對象,這個對象包含了類的完整結構信息,通過這個對象得到類的結構,這個對象就像一面鏡子,透過這個鏡子看到類的結構,所以,形象地稱之為“反射”

作用:反射可以動態的獲取類的信息,是許多Java框架的基礎

反射機制的優缺點:

  • 優點:可以在運行期間動態的執行方法,訪問屬性,增加了java的靈活性
  • 缺點:反射的代碼總是慢于直接執行的代碼

反射的使用

學習反射相關的API時使用的實體類:

public class Box<T extends Number, V> extends ThreadLocal<Box<T, V>> implements Comparable<Box<T, V>> {// 泛型字段private T item;public Box() { }public Box(T item) {this.item = item;}public T getItem() {return item;}public void setItem(T item) {this.item = item;}// 泛型方法public static <T> T genericMethod(T obj) throws Exception {System.out.println("obj = " + obj);return obj;}@Overridepublic int compareTo(Box o) {return this.item.hashCode() - o.item.hashCode();}
}
public class Person extends ThreadLocal<Person> implements Comparable<Person> {private String name;private Integer age;public Person() { }public Person(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Person o) {return 0;}
}

獲取類對象

獲取類對象的三種方法:

  • 類名.class
  • 對象名.getClass()
  • Class.forName(類的全限定名)

案例:

public static void main(String[] args) throws ClassNotFoundException {// 方式1:獲取一個類的類對象Class<Person> personClass = Person.class;System.out.println("personClass = " + personClass);  // personClass = class org.wyj.test.beans.Person// 方式2,獲取一個實例的類對象,getClass是Object類中的方法Person person = new Person();Class<? extends Person> personClass1 = person.getClass();System.out.println("personClass1 = " + personClass1);  // personClass1 = class org.wyj.test.beans.Person// 方式3:加載某個類,根據一個類的全限定名,加載某個類。Class<?> personClass2 = Class.forName("org.wyj.test.beans.Person");System.out.println("personClass2 = " + personClass2);  // personClass2 = class org.wyj.test.beans.Person
}

Class類中的forName方法:public static Class<?> forName(String className) throws ClassNotFoundException :使用類的全限定名對類進行加載,返回類的類對象,默認會對類進行初始化。它是在運行時根據類名字符串加載類,而不是在編譯時直接引用類,這在處理插件、擴展或動態加載驅動程序時非常有用。通過這種方法來獲取類對象是實際開發中使用的最多的。

案例2:通過類對象查看類的信息

public static void main(String[] args) {try {Class<?> personClass = Class.forName("org.wyj.reflex.beans.Person");// 獲取類名System.out.println("類名 = " + personClass.getName());  // 類的二進制名稱System.out.println("類名簡寫 = " + personClass.getSimpleName());// 獲取父類System.out.println("獲取父類 = " + personClass.getSuperclass());  // class java.lang.ThreadLocal// 獲取類所實現的接口System.out.println("獲取類所實現的接口 = " +Arrays.toString(personClass.getInterfaces())); // [interface java.lang.Comparable]// 獲取類的加載器System.out.println("類的加載器 = " +personClass.getClassLoader()); // sun.misc.Launcher$AppClassLoader@18b4aac2} catch (ClassNotFoundException e) {throw new RuntimeException(e);}
}

這里僅僅介紹如何獲取類的父類、類實現的接口,還有許多其他操作,隨后再介紹。

創建類的實例

具體步驟是,通過反射,獲取類對象,然后獲取類的構造方法,最后調用構造方法來創建一個類對象

案例:

public static void main(String[] args)  {// 獲取一個類的類對象Class<Person> personClass = Person.class;// 通過類對象提供的API實例化一個類,它默認調用類中的無參構造Object person;try {person = personClass.newInstance();} catch (InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}System.out.println("person = " + person);  // person = Person{name='null', age=0}
}

Class類中的newInstance方法:它默認獲取類中的無參構造,通過無參構造來創建類的實例

獲取類中的構造方法

通過反射獲取類中的構造方法,然后通過構造方法來實例化類

案例:

public static void main(String[] args) {// 獲取一個類的類對象Class<Person> personClass = Person.class;try {// 方式1:獲取類中的無參構造Constructor<Person> constructor = personClass.getConstructor();Person person1 = constructor.newInstance();System.out.println("person1 = " + person1);  // person1 = Person{name='null', age=0}// 方式2:獲取指定類型的構造器,通過構造器來實例化一個類Constructor<Person> con = personClass.getConstructor(String.class, int.class);Person person2 = con.newInstance("張三", 18);System.out.println("person2 = " + person2);    // person2 = Person{name='張三', age=18}} catch (Exception e) {throw new RuntimeException(e);}
}

獲取構造方法時,通過參數類型來指定需要獲取哪個構造方法,如果沒有傳,表示獲取無參構造

獲取類中的字段

這里演示如何通過反射來操作類中的字段。

案例:

public class Demo4FieldTest {public static int publicStaticField = 10;public int publicField = 20;private int privateField = 30;public static void main(String[] args) {try {Class<Demo4FieldTest> aClass = Demo4FieldTest.class;// 1、獲取類中的靜態字段Field staticField = aClass.getDeclaredField("publicStaticField");// 查看字段信息System.out.println("字段類型 = " + staticField.getType());  // intSystem.out.println("字段所在的類 = " + staticField.getDeclaringClass());  // class org.wyj.reflex.Demo4FieldTestSystem.out.println("字段名稱 = " + staticField.getName()); // publicStaticField// 獲取字段的值,靜態字段不屬于任何實例,所以,操作靜態字段的值時,對象參數的值傳null即可System.out.println("獲取靜態字段的值 = " + staticField.get(null)); // 10// 設置字段的值staticField.set(null, 20);System.out.println("獲取靜態字段的值 = " + staticField.get(null)); // 20// 2、獲取類中的成員字段Demo4FieldTest demo4FieldTest = aClass.newInstance();Field publicField = aClass.getDeclaredField("publicField");// 獲取字段的值,操作成員字段時,需要傳入對象參數,表示操作哪個對象中的字段System.out.println("獲取成員字段的值 = " + publicField.get(demo4FieldTest)); // 20// 設置字段的值publicField.set(demo4FieldTest, 30);System.out.println("獲取成員字段的值 = " + publicField.get(demo4FieldTest)); // 30// 3、獲取類中的私有字段Field privateField = aClass.getDeclaredField("privateField");// 設置私有字段為可訪問,這里是通過用戶API來重寫訪問權限,jdk1.8中才支持,之后應該不允許這么做了。privateField.setAccessible(true);// 獲取字段的值System.out.println("獲取私有成員字段的值 = " + privateField.get(demo4FieldTest)); // 30// 設置字段的值privateField.set(demo4FieldTest, 40);System.out.println("獲取私有成員字段的值 = " + privateField.get(demo4FieldTest)); // 40} catch (Exception e) {throw new RuntimeException(e);}}
}

總結:

  • 獲取靜態字段和獲取成員字段的方式是一致的,只是操作方式不同,操作靜態字段時,對象參數傳null即可,操作成員字段時,需要明確指定是操作哪個對象中的字段
  • 操作私有字段時,需要設置為可訪問,調用對象的 setAccessible 方法,參數為true

調用類中的方法

案例:

public class Demo5MethodTest {public static void main(String[] args) {Class<Demo5MethodTest> aClass = Demo5MethodTest.class;try {// 1、操作類中的靜態方法Method publicStaticMethod = aClass.getMethod("publicStaticMethod", String.class, int.class);// 獲取方法信息System.out.println("方法名 = " + publicStaticMethod.getName()); // publicStaticMethodSystem.out.println("獲取方法所在的類 = " +publicStaticMethod.getDeclaringClass());  // class org.wyj.reflex.Demo5MethodTestSystem.out.println("方法的參數信息,參數類型和參數名 = " +Arrays.toString(publicStaticMethod.getParameters()));  // [java.lang.String name, int age]System.out.println("參數個數 = " + publicStaticMethod.getParameterCount());  // 2System.out.println("方法的參數類型信息 = " +Arrays.toString(publicStaticMethod.getParameterTypes()));  // [class java.lang.String, int]System.out.println("方法上聲明的異常 = " +Arrays.toString(publicStaticMethod.getExceptionTypes()));  // [class java.lang.Exception]System.out.println("方法的返回值類型 = " + publicStaticMethod.getReturnType());  // void// 調用方法publicStaticMethod.invoke(null, "張三", 18);   // Public Static Method 張三 18// 2、操作類中的成員方法Demo5MethodTest demo5MethodTest = aClass.newInstance();Method publicMethod = aClass.getMethod("publicMethod", Double.class, Float.class);publicMethod.invoke(demo5MethodTest, 1.0D, 1.4F);  // Public Method 1.0 1.4// 3、操作類中的私有方法Method privateMethod = aClass.getDeclaredMethod("privateMethod");privateMethod.setAccessible(true);privateMethod.invoke(demo5MethodTest);  // Private Method} catch (Exception e) {throw new RuntimeException(e);}}public static void publicStaticMethod(String name, int age) throws Exception {System.out.println("Public Static Method " + name + " " + age);}public void publicMethod(Double a, Float b) {System.out.println("Public Method " + a + " " + b);}private void privateMethod() {System.out.println("Private Method");}
}

總結:通過反射操作類中的方法,和通過反射操作類中的字段沒有什么不同,操作字段是設置字段的值,操作方法是調用方法。

獲取泛型信息

泛型的使用場景:泛型類、泛型接口、泛型方法

獲取泛型信息:泛型的底層原理,泛型信息只存在于代碼編譯階段,在進入jvm之前,與泛型相關的信息將會被擦除。依據這種原理,獲取的泛型信息分為兩種:

  • 泛型形參:聲明在類或方法上的泛型,是一個泛型形參,在運行時,這個泛型信息會被擦除,通過反射,僅僅可以獲得泛型形參,例如 public class Container<T>,通過反射,僅僅可以獲得“T”這個字符
  • 泛型實參:使用一個泛型類或泛型方法,傳入的參數是泛型實參,這種情況下,在編譯時指定了泛型實參,通過反射,可以在運行時獲得泛型實參,例如,public class Son implements Father<String, Integer>,通過反射,可以獲得String和Integer的類對象

獲取到的泛型信息是一個泛型形參還是一個泛型實參,取決于要獲取哪個位置的泛型,在那個位置上聲明了一個泛型形參還是泛型實參。

  • 獲取到的是泛型形參的場景:類或接口或方法聲明的泛型
  • 獲取到的是泛型實參的場景:成員變量的泛型、方法參數的泛型、方法返回值的泛型、父類的泛型

案例1:獲取類上的泛型形參,例如,public class Box<T>,這里T就是泛型形參

public static void main(String[] args) {try {Class<?> boxClass = Class.forName("org.wyj.reflex.beans.Box");TypeVariable<? extends Class<?>>[] typeParameters = boxClass.getTypeParameters();System.out.println("類上聲明的泛型 = " + Arrays.toString(typeParameters));  // [T, V]for (TypeVariable<? extends Class<?>> typeParameter : typeParameters) {// 泛型標識的名稱 = T, 泛型的上界 = [class java.lang.Number]// 泛型標識的名稱 = V, 泛型的上界 = [class java.lang.Object]System.out.println("泛型標識的名稱 = " + typeParameter.getName() +", 泛型的上界 = " + Arrays.toString(typeParameter.getBounds()));}} catch (Exception e) {throw new RuntimeException(e);}
}

總結:TypeVariable,用于表示類或方法上聲明的泛型形參,它可以獲取泛型標識的名稱、泛型的上界、泛型所在位置

案例2:獲取泛型實參,例如 List<String>,這里String,就是一個泛型實參。

ublic static void main(String[] args) {try {Class<?> boxClass = Class.forName("org.wyj.reflex.beans.Box");// 獲取類所實現的接口,包括接口上的泛型信息System.out.println("獲取類所實現的接口 = " +Arrays.toString(boxClass.getInterfaces()));  // [interface java.lang.Comparable]Type[] genericInterfaces = boxClass.getGenericInterfaces();for (Type genericInterface : genericInterfaces) {if (genericInterface instanceof ParameterizedType) {ParameterizedType parameterizedType = (ParameterizedType) genericInterface;// 原始類型 + 泛型實參String s = parameterizedType.getRawType() + " " + Arrays.toString(parameterizedType.getActualTypeArguments());// interface java.lang.Comparable [org.wyj.reflex.beans.Box<T, V>]System.out.println("獲取類所實現的接口,包括接口上的泛型信息 = " + s);  }}} catch (Exception e) {throw new RuntimeException(e);}
}

總結:ParameterizedType,用于獲取泛型實參,它可以獲取到原始類型和傳遞給類型的泛型實參,例如,List<String>,這里的原始類型是List.class,類型實參是String

public class Demo6GenericTest {public static void main(String[] args) {try {Class<?> boxClass = Class.forName("org.wyj.reflex.beans.Box");// 1、操作泛型類TypeVariable<? extends Class<?>>[] typeParameters = boxClass.getTypeParameters();System.out.println("類上聲明的泛型 = " + Arrays.toString(typeParameters));  // [T, V]// 2、操作帶泛型信息的字段Field boxListField = boxClass.getDeclaredField("boxList");boxListField.setAccessible(true);Type genericType1 = boxListField.getGenericType();if (genericType1 instanceof ParameterizedType) {System.out.println("字段上的泛型信息(泛型實參) = " + genericType1);}Field containerListField = boxClass.getDeclaredField("containerList");containerListField.setAccessible(true);Type genericType = containerListField.getGenericType();if (genericType instanceof ParameterizedType) {System.out.println("字段上的泛型信息(泛型實參) = " + genericType);}Field itemField = boxClass.getDeclaredField("item");containerListField.setAccessible(true);Type itemFieldType = itemField.getGenericType();if (itemFieldType instanceof TypeVariable) {System.out.println("字段上的泛型信息(泛型形參) = " + itemFieldType);}// 2、操作泛型方法Method genericMethod = boxClass.getMethod("genericMethod", Object.class);// 獲取方法的參數類型Parameter[] parameters = genericMethod.getParameters();System.out.println("方法的形參(類型和名稱) = " + Arrays.toString(parameters));      // [T obj]Class<?>[] parameterTypes = genericMethod.getParameterTypes();System.out.println("方法的形參(類型信息) = " + Arrays.toString(parameterTypes));  // [class java.lang.Object]Type[] genericParameterTypes = genericMethod.getGenericParameterTypes();System.out.println("方法的形參,帶泛型信息 = " + Arrays.toString(genericParameterTypes)); // [T]// 獲取方法的返回值類型System.out.println("方法的返回值類型 = " + genericMethod.getReturnType());  // class java.lang.ObjectType genericReturnType = genericMethod.getGenericReturnType();if (genericReturnType instanceof TypeVariable) {TypeVariable<?> genericReturnType1 = (TypeVariable<?>) genericReturnType;// 方法的返回值類型,帶泛型信息:泛型名稱T,泛型上界 [class java.lang.Object],泛型的聲明位置 = // public static java.lang.Object org.wyj.reflex.beans.Box.genericMethod(java.lang.Object) throws java.lang.ExceptionSystem.out.println("方法的返回值類型,帶泛型信息:泛型名稱 " + genericReturnType1.getName() +",泛型上界 " + Arrays.toString(genericReturnType1.getBounds()) +",泛型的聲明位置 = " + genericReturnType1.getGenericDeclaration());  // T}// 調用方法Box<?, ?> box = (Box<?, ?>) boxClass.newInstance();Object returnValue = genericMethod.invoke(box, "aaa");System.out.println("returnValue = " + returnValue);  // aaa// 3、獲取父類,包括父類上的泛型信息System.out.println("獲取父類 = " + boxClass.getSuperclass());  // class java.lang.ThreadLocalType genericSuperclass = boxClass.getGenericSuperclass();System.out.println("獲取父類,包括父類上有泛型信息 = " +genericSuperclass);  // java.lang.ThreadLocal<org.wyj.reflex.beans.Box<T, V>>// 4、獲取類所實現的接口,包括接口上的泛型信息System.out.println("獲取類所實現的接口 = " +Arrays.toString(boxClass.getInterfaces()));  // [interface java.lang.Comparable]Type[] genericInterfaces = boxClass.getGenericInterfaces();for (Type genericInterface : genericInterfaces) {if (genericInterface instanceof ParameterizedType) {ParameterizedType parameterizedType = (ParameterizedType) genericInterface;String s = parameterizedType.getRawType() + " " + Arrays.toString(parameterizedType.getActualTypeArguments());System.out.println("獲取類所實現的接口,包括接口上的泛型信息 = " + s);  // interface java.lang.Comparable [org.wyj.reflex.beans.Box<T, V>]}}} catch (Exception e) {throw new RuntimeException(e);}}
}

總結:通過反射獲取帶泛型的元素信息和不帶泛型的元素信息,上面的演示都涉及到了,API比較多,這里只做簡單介紹,需要的時候可以參考。重點是知道如何獲取泛型實參,這是實際開發中用的最多的。

獲取注解信息

public static void main(String[] args) {try {Class<?> aClass = Class.forName("org.wyj.reflex.beans.Son");// 1、獲取類上的注解MyAnno annotation = aClass.getAnnotation(MyAnno.class);System.out.println("類上的注解 = " + annotation);  // @org.wyj.reflex.beans.MyAnno(value=bbb)// 獲取注解中存儲的數據System.out.println("注解中存儲的數據 = " + annotation.value());  // bbb// 獲取注解上的注解MyAnno2 annotation1 = annotation.annotationType().getAnnotation(MyAnno2.class);System.out.println("注解上的注解 = " + annotation1);  // @org.wyj.reflex.beans.MyAnno2(value=aaa)// 2、獲取方法上的注解Method annoTestMethod = aClass.getDeclaredMethod("annoTest", String.class, Integer.class);System.out.println("方法上的注解 = " +Arrays.toString(annoTestMethod.getDeclaredAnnotations()));  // [@org.wyj.reflex.beans.MyAnno(value=方法注解)]System.out.println("方法中參數上的注解 = " +Arrays.deepToString(annoTestMethod.getParameterAnnotations()));  // [[@org.wyj.reflex.beans.MyAnno(value=參數1)], [@org.wyj.reflex.beans.MyAnno(value=參數2)]]// 3、獲取字段上的注解Field annoField = aClass.getDeclaredField("annoField");System.out.println("字段上的注解 = " +Arrays.toString(annoField.getAnnotations()));  // [@org.wyj.reflex.beans.MyAnno(value=字段注解)]System.out.println("字段上的注解 = " +Arrays.toString(annoField.getDeclaredAnnotations()));  // [@org.wyj.reflex.beans.MyAnno(value=字段注解)]// getAnnotations、getDeclaredAnnotations,它們的主要區別在于是否考慮注解的繼承,不過在這里它們的行為是一致的,因為字段無法繼承注解// 4、獲取父類上的注解System.out.println("父類上的注解 = " +Arrays.toString(aClass.getSuperclass().getAnnotations()));  // [@org.wyj.reflex.beans.MyAnno(value=父類上的注解)]} catch (Exception e) {throw new RuntimeException(e);}
}

注解在開發中用于存儲配置信息,代替配置文件,熟悉spring的肯定很有認同.通過反射,獲取代碼中的注解信息,實際開發中使用的很多。這里對相關API做一個演示

獲取內部類信息

案例:

public static void main(String[] args) {try {// 1、通過反射操作靜態內部類Class<?> staticInnerClass = Class.forName("org.wyj.reflex.beans.OuterClass$StaticInnerClass");// 創建靜態內部類的實例Object staticInnerObj = staticInnerClass.newInstance();System.out.println("靜態內部類的實例 = " + staticInnerObj);  // org.wyj.reflex.beans.OuterClass$StaticInnerClass@4b67cf4d// 2、通過反射操作成員內部類Class<?> innerClass = Class.forName("org.wyj.reflex.beans.OuterClass$NonStaticInnerClass");// 創建外部類的實例Class<?> outerClass = Class.forName("org.wyj.reflex.beans.OuterClass");System.out.println("outerClass.getEnclosingClass() = " + outerClass.getEnclosingClass());OuterClass outerObj = (OuterClass) outerClass.newInstance();// 獲取成員內部類的構造方法。注意,內部類的實例依賴外部類的實例,所以成員內部類的無參構造實際上有一個參數,// 是外部類的實例Constructor<?> constructor = innerClass.getConstructor(OuterClass.class);Object innerObj = constructor.newInstance(outerObj);System.out.println("成員內部類的實例 = " + innerObj);  // org.wyj.reflex.beans.OuterClass$NonStaticInnerClass@7ea987ac// 3、獲取內部類的外部類Class<?> enclosingClass = innerClass.getEnclosingClass();System.out.println("內部類所屬的外部類 = " + enclosingClass);  // class org.wyj.reflex.beans.OuterClass} catch (Exception e) {throw new RuntimeException(e);}
}

通過反射操作內部類,內部類的名稱是 OuterClass$InnerClass,通過美元符連接外部類名和內部類名,還有一點,調用成員內部類的構造器,需要傳入外部類的實例,

數組

案例:

public static void main(String[] args) throws ClassNotFoundException {// 1、通過反射創建數組,Array類中還提供了get、set方法來操作數組Object arr = Array.newInstance(int.class, 5);System.out.println("通過反射創建的數組= " + arr);  // [I@4b67cf4d// 2、操作數組中的元素Array.set(arr, 0, 1);System.out.println("獲取數組中的元素 " + Array.get(arr, 0));    // 1// 3、通過反射獲取數組中元素的類型Class<?> aClass = arr.getClass();System.out.println("數組中的元素類型 = " + aClass.getComponentType()); // int
}

通過反射操作數組,主要使用到了Array類中的API

其它API

反射中一些不常見的api,但是很有用,偶爾會使用到。

Class類的 getMethods和getDeclaredMethods

這兩個方法的區別主要在于搜索范圍和訪問權限:

  • getMethods:獲取當前類和父類中的公共方法
    • 搜索范圍:當前類、父類
    • 訪問權限:public方法
  • getDeclaredMethods:獲取當前類中的所有方法,包括私有方法,但需要設置 setAccessible(true) 來訪問。
    • 搜索范圍:當前類
    • 訪問權限:所有方法

其它類似的API也一樣,例如,字段 getFields、getDeclaredFields,構造器 getConstructors、getDeclaredCongtructors。

通過子類的類對象,是否可以獲取到父類中的元素,首先要看該元素是否可以被繼承、被外部訪問。

Class類的 getName和getCanonicalName

案例:

    public static void main(String[] args) {try {Class<?> innerClass = Class.forName("org.wyj.reflex.beans.OuterClass$NonStaticInnerClass");// 類的二進制名稱和規范名稱,通常使用的都是二進制名稱,這里對于規范名稱僅做介紹。System.out.println("類的二進制名稱 = " + innerClass.getName());  // org.wyj.reflex.beans.OuterClass$NonStaticInnerClassSystem.out.println("類的規范名稱 = " + innerClass.getCanonicalName());  // org.wyj.reflex.beans.OuterClass.NonStaticInnerClass} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}

getName方法獲取的是類的二進制名稱,通常用戶使用的也是這個名稱,getCanonicalName獲取的是類的規范名稱,它會對二進制名稱做一些修改

getName:返回類的二進制名稱,包括包名。對于內部類,返回包含 $ 的字符串。對于數組類型,返回帶有方括號的字符串。更適合用于需要類的二進制名稱的場合,尤其是在反射和字節碼操作中。

getCanonicalName:返回類的規范名稱,包括包名。對于內部類,返回包含外部類名的字符串。對于匿名類和局部類,返回 null。更適合用于需要類的完整信息的場合,尤其是代碼生成和文檔生成工具。

Class類的 isAssignableFrom

判斷當前類是否是指定類對象的父類,a is assignable from b ,a可以由b賦值,所以a一定是b的父類或b本身

格式:父類.isAssignableFrom(子類)

案例:

public void test1() {boolean assignableFrom = Object.class.isAssignableFrom(String.class);  // 判斷Object是不是可以由String賦值System.out.println("assignableFrom = " + assignableFrom);              // trueboolean assignableFrom1 = Object.class.isAssignableFrom(Object.class);  System.out.println("assignableFrom1 = " + assignableFrom1);            // true
}

isAssignableFrom方法是Class類中的一個native方法:

// Class類
public native boolean isAssignableFrom(Class<?> cls);

Modifier 訪問修飾符

類的訪問修飾符。

相關源碼:

public class Modifier {// 每一個訪問修飾符都使用一個數字來表示,這里的數字是16進制的。public static final int PUBLIC           = 0x00000001;  // 000001public static final int PRIVATE          = 0x00000002;  // 000010public static final int PROTECTED        = 0x00000004;  // 000100public static final int STATIC           = 0x00000008;  // 001000public static final int FINAL            = 0x00000010;  // 010000public static final int SYNCHRONIZED     = 0x00000020;  // 100000public static final int VOLATILE         = 0x00000040;  // 以此類推public static final int TRANSIENT        = 0x00000080;public static final int NATIVE           = 0x00000100;public static final int INTERFACE        = 0x00000200;public static final int ABSTRACT         = 0x00000400;public static final int STRICT           = 0x00000800;// 訪問修飾符的計算,這里計算一個元素是否是publicpublic static boolean isPublic(int mod) {// 按位與,遇0則0,例如,如果字段的修飾符是 public static,那么它的訪問修飾符是 1001,// 它和PUBLIC(0001)按位與,結果是0001,不為0,證明字段被public關鍵字修飾。因為按位與遇0則0,// 只有傳入的訪問修飾符PUBLIC位上不為0,按位與的結果才不為0return (mod & PUBLIC) != 0;   }
}

通過Modifier類,可以判斷一個元素有什么訪問修飾符。

它的原理是,每一個訪問修飾符使用一個2的倍數來表示,如果一個元素有多個訪問修飾符,那么就是多個訪問修飾符執行或運算,組合在一起,如果需要判斷元素上有沒有指定訪問修飾符,就是元素的訪問修飾符和修飾符常量進行與運算,結果不為0就表示元素上有該修飾符。這應該算是比較簡單的位運算,很有參考價值,在設計狀態值枚舉的時候,尤其是一個實體可以同時擁有多種狀態的情況下很有用,否則使用簡單的枚舉類就可以了。

合成方法 isSynthetic

合成方法是由編譯器自動生成的方法,如內部類訪問外部類的私有成員時,編譯器會自動在外部類中生成一個合成方法,java正是通過這個合成方法來解決內部類無法訪問外部類私有成員的問題的。

案例:

// 外部類和內部類
public class OuterClass {private int value = 10;// 成員內部類,測試合成方法public class InnerClass {public void accessPrivateMember() {System.out.println("value = " + value);}}
}// 測試
public static void main(String[] args) {try {Class<?> aClass = Class.forName("org.wyj.reflex.beans.OuterClass");Method[] methods = aClass.getDeclaredMethods();for (Method method : methods) {if (method.isSynthetic()) {  // 如果方法是合成方法// static int org.wyj.reflex.beans.OuterClass.access$000(org.wyj.reflex.beans.OuterClass)System.out.println("method = " + method);System.out.println("method.getModifiers() = " + method.getModifiers());  // 4104}}} catch (ClassNotFoundException e) {throw new RuntimeException(e);}
}

使用javap反編譯代碼,查看字節碼中的合成方法:

 static int access$000(org.wyj.reflex.beans.OuterClass);descriptor: (Lorg/wyj/reflex/beans/OuterClass;)Iflags: ACC_STATIC, ACC_SYNTHETIC         # 注意這里方法的修飾符,表示它是一個靜態的合成方法Code:stack=1, locals=1, args_size=10: aload_01: getfield      #1                  // Field value:I   # 在合成方法的內部訪問私有變量4: ireturnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0    x0   Lorg/wyj/reflex/beans/OuterClass;

在使用反射獲取方法時,有時候會獲取到合成方法,需要過濾掉這種方法,在這里介紹一下它的作用。

橋接方法 isBridge

編譯器自動生成的方法,作用在方法重寫時。方法重寫時對返回值的要求分兩種情況,一,父子類的返回值完全一致;二,子類返回值可以是父類返回值的子類。如果是第二種情況,jvm會自動生成一個橋接方法,被synthetic bridge修飾,專門供jvm使用,程序員不可見。例如,父類中方法返回Object,子類中重寫父類的方法后,返回String,這在語法上是支持的,java內部會生成一個橋接方法,來支持這種重寫。

橋接方法的作用:在橋接方法中調用重寫的方法,作為中間層,進行過渡

案例:

// 接口
public interface Action {Object play(String action);
}// 子類實現接口中的方法
public class Children implements Action {// 測試橋接方法@Overridepublic String play(String action) {return "children play basketball....";}
}// 測試
public static void main(String[] args) {try {Class<?> aClass = Class.forName("org.wyj.reflex.beans.Children");Method[] declaredMethods = aClass.getDeclaredMethods();for (Method declaredMethod : declaredMethods) {if (declaredMethod.isBridge()) {// public java.lang.Object org.wyj.reflex.beans.Children.play(java.lang.String)System.out.println("橋接方法 = " + declaredMethod);}}} catch (ClassNotFoundException e) {throw new RuntimeException(e);}
}

使用javap反編譯字節碼,查看生成的橋接方法

  public java.lang.Object play(java.lang.String);descriptor: (Ljava/lang/String;)Ljava/lang/Object;flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC    # 橋接方法,同時也是一個合成方法Code:stack=2, locals=2, args_size=20: aload_01: aload_12: invokevirtual #3                  // Method play:(Ljava/lang/String;)Ljava/lang/String;5: areturnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       6     0  this   Lorg/wyj/reflex/beans/Children;MethodParameters:Name                           Flagsaction                         synthetic

橋接方法也是在使用反射獲取方法時可能遇到的,需要過濾掉,這里僅做介紹,

反射相關的源碼

相關組件

1、頂層接口

Type:所有類型的頂層接口,包括原始類型、帶泛型的類型、數組類型等

public interface Type {// 獲取類型名稱default String getTypeName() {return toString();}
}

AnnotatedElement:代表一個被注解標注的元素

public interface AnnotatedElement {// 獲取指定類型的注解<T extends Annotation> T getAnnotation(Class<T> annotationClass);// 獲取當前元素上的注解Annotation[] getAnnotations();// 獲取當前元素上的注解,不考慮繼承Annotation[] getDeclaredAnnotations();
}

GenericDeclaration:一個同時有泛型、有注解的元素

public interface GenericDeclaration extends AnnotatedElement {// 返回元素上的泛型信息public TypeVariable<?>[] getTypeParameters();
}

總結:

  • Type是頂層接口,代表任何類型
  • AnnotatedElement,繼承了Type接口,代表一個有注解信息的元素
  • GenericDeclaration,繼承了AnnotatedElement接口,代表一個有泛型信息的元素

2、中間層,提供了公共抽象

AccessibleObject:代表一個可訪問的對象,Constructor、Field、Method的公共父類

public class AccessibleObject implements AnnotatedElement {boolean override;   // 一個標識,表示是否由用戶來重寫訪問權限,setAccessible方法就是操作的這個變量
}

Executable:代表一個可執行的對象,Constructor和Method的抽象父類

public abstract class Executable extends AccessibleObjectimplements Member, GenericDeclaration {
}

Member:代表類中的一個成員,它是Field的父接口

public interface Member {// 獲取成員所在類public Class<?> getDeclaringClass();// 獲取名稱public String getName();// 獲取訪問修飾符public int getModifiers();// 是否合成,表示當前成員是否由編譯器引入。public boolean isSynthetic();
}

3、實現類,用戶使用的API

Class:代表一個類的類對象

public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type,AnnotatedElement {
}

Constructor:代表一個構造器的類對象

public final class Constructor<T> extends Executable {private Class<T>            clazz;  // 構造器所屬的類private Class<?>[]          parameterTypes;  // 形參列表
}

Field:代表一個字段的類對象

public final
class Field extends AccessibleObject implements Member {private Class<?>            clazz;  // 字段所屬的類private String              name;   // 字段名private Class<?>            type;   // 字段類型private int                 modifiers;  // 字段的修飾符
}

Method:代表一個方法的類對象

public final class Method extends Executable {private Class<?>            clazz;   // 方法所屬的類對象private int                 slot;    // // This is guaranteed to be interned by the VM in the 1.4// reflection implementationprivate String              name;    // 方法名稱private Class<?>            returnType;   // 返回值類型private Class<?>[]          parameterTypes;  // 形參列表private Class<?>[]          exceptionTypes;  // 方法上聲明的異常private int                 modifiers;       // 方法的修飾符
}

Parameter:代表方法上的參數

public final class Parameter implements AnnotatedElement {private final String name;  // 參數名private final int modifiers;private final Executable executable;  // 參數所在的方法private final int index;   // 參數的下標
}

4、工具類

Array:通過反射操作數組的工具類

public final class Array {private Array() {}
}

5、泛型相關

TypeVariable:代表泛型形參,如果java檢測到解析的泛型是一個泛型形參,例如,public class List<T>,此時T就是一個泛型形參,java會使用TypeVariable來封裝解析好的結果

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {// 泛型的上界,例如 T extents Number,此時Number就是泛型的上界Type[] getBounds();// 泛型的聲明位置D getGenericDeclaration();// 泛型的名稱,例如T、EString getName();
}

ParameterizedType:代表泛型實參,如果java檢測到解析的泛型是一個泛型實參,例如,List<String>,此時String就是一個泛型實參,java會使用ParameterizedType來封裝解析好的結果

public interface ParameterizedType extends Type {// 泛型實參,例如List<String>中的StringType[] getActualTypeArguments();// 聲明了泛型的類Type getRawType();
}

總結:這里僅僅介紹一些API,主要是學習第三方框架時會看到很多這種API,熟悉一下相關接口。這里不介紹具體流程,主要一些本地方法,以及本地緩存,它的內部會把解析好的結果緩存到成員變量中。

實戰案例

獲取父接口上的泛型實參

案例:類實現了一個帶泛型信息的接口,獲取接口上的泛型實參

// 接口1:帶泛型的接口
public interface MyInf<T> { }  // T 是泛型形參
// 接口2:不帶泛型的接口
public interface MyInf2 { }
// 實現類,注意,接口上聲明的 T 是泛型形參,這里實現接口后指定的是泛型實參
public class MyObj implements MyInf<String>, MyInf2 {  }

獲取類所實現的所有接口的泛型信息

public static void main(String[] args) {// 1. 獲取類對象Class<MyObj> myObjClass = MyObj.class;// 2. 獲取類實現的所有接口Type[] genericInterfaces = myObjClass.getGenericInterfaces();for (Type genericInterface : genericInterfaces) {// 3. 判斷接口有沒有帶泛型信息if (genericInterface instanceof ParameterizedType) {// 4. 獲取接口的原始類型和泛型實參ParameterizedType parameterizedType = (ParameterizedType) genericInterface;Type rawType = parameterizedType.getRawType();  // 去掉泛型信息后的原始類型Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); // 接口的泛型實參System.out.println("ParameterizedType = " + rawType + " " + Arrays.toString(actualTypeArguments));} else {System.out.println("Type = " + genericInterface);}}
}

總結:這是在學習spring事件時使用到的操作,事件的監聽器需要繼承ApplicationListener,它是一個泛型接口,實現類需要指定接口的泛型實參,例如,public class MyAppListener implements ApplicationListener<MyEvent>,在這個監聽器中,它只可以處理MyEvent類型的事件,spring會獲取監聽器上ApplicationListener接口的泛型實參,通過它來判斷監聽器可以處理什么類型的事件。

解析json數據

這里使用gson來解析json數組,要求解析過程中指定泛型信息。

第一步:添加依賴

<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.8</version>
</dependency>

第二步:解析數據

public static void main(String[] args) {String json = "[{\"id\":1,\"name\":\"Alice\"},{\"id\":2,\"name\":\"Bob\"}]";Gson gson = new Gson();Type listType = new TypeToken<List<Person>>() { }.getType();  // 泛型類型List<Person> personList = gson.fromJson(json, listType);for (Person person : personList) {System.out.println("ID: " + person.getId() + ", Name: " + person.getName());}
}

注意看,解析過程中是如何指定泛型類型的,new TypeToken<List<Person>>() { }.getType(),通過繼承了TypeToken的匿名內部類,這里實際上是通過子類,獲取父類上的泛型實參,通過這種方式來指定json數組的類型。

反射的優化

緩存反射相關的實例、關閉安全檢查

踩坑記錄

通過反射,獲取方法的參數值

這是不可以的,反射只可以獲取到類對象相關的信息,參數值,是實例中的值,所以無法獲取到。反射只可以獲取到形參相關的數據,獲取不到實參相關的數據。

通過反射,無法獲取方法的參數名

java 通過Method實例中的method.getParameters(),無法獲取參數名稱

案例:

public class MethodTest {public static void main(String[] args) throws NoSuchMethodException {Method testMethod = MethodTest.class.getDeclaredMethod("test", String.class, int.class);Parameter[] parameters = testMethod.getParameters();for (Parameter parameter : parameters) {System.out.println("parameter.getName() = " + parameter.getName());}}public void test(String name, int age) {}
}

結果:

parameter.getName() = arg0
parameter.getName() = arg1

原因:java代碼編譯為字節碼后,在字節碼中沒有保留方法的參數名稱,反編譯字節碼

  public void test(java.lang.String, int);descriptor: (Ljava/lang/String;I)Vflags: ACC_PUBLICCode:stack=0, locals=3, args_size=30: returnLineNumberTable:line 18: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       1     0  this   Lorg/wyj/reflex/practice/Practice2MethodParameterTest;0       1     1  name   Ljava/lang/String;0       1     2   age   IMethodParameters:Name                           Flags     # 參數名nameage

這是有參數名的情況,這個問題是偶發的,我沒有截取到沒有參數名的情況,但是在沒有參數名的情況下,參數名默認是arg0、arg1等。

解決方案:

  • 編譯時加上配置:build -> compiler -> java compiler,在additional command line parameters中加上參數“-parameters”

在這里插入圖片描述

  • Maven配置編譯插件,編譯時保留方法的參數名稱
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><compilerArgs><arg>-parameters</arg></compilerArgs></configuration>
</plugin>

這個問題是偶發的,開發中偶爾就會遇到。我在測試環境中也遇到過類似的問題,但是比這更復雜,最終排查,發現打包生成的字節碼中有參數名,但是運行時加載到的字節碼中沒有參數名,最后發現,類被javaAgent重寫了,導致失去了參數名稱

注解的繼承

注解的繼承規則:Java 注解本身不支持繼承,但可以通過 @Inherited 元注解實現類級別注解的繼承。@Inherited 注解只適用于類級別的注解,不會影響方法、構造函數或字段上的注解。注解繼承只對直接父類有效,不會跨多個繼承層級。

獲取注解時要注意注解的繼承,它用的不多,但有時候會用到。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75459.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75459.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75459.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

AI產品的上層建筑:提示詞工程、RAG與Agent

上節課我們拆解了 AI 產品的基礎設施建設&#xff0c;這節課我們聊聊上層建筑。這部分是產品經理日常工作的重頭戲&#xff0c;包含提示詞、RAG 和 Agent 構建。 用 AI 客服產品舉例&#xff0c;這三者的作用是這樣的&#xff1a; 提示詞能讓客服很有禮貌。比如它會說&#x…

藍橋杯刷題記錄【并查集001】(2024)

主要內容&#xff1a;并查集 并查集 并查集的題目感覺大部分都是模板題&#xff0c;上板子&#xff01;&#xff01; class UnionFind:def __init__(self, n):self.pa list(range(n))self.size [1]*n self.cnt ndef find(self, x):if self.pa[x] ! x:self.pa[x] self.fi…

海外SD-WAN專線網絡部署成本分析

作為支撐企業國際業務的重要基石&#xff0c;海外SD-WAN專線以其獨特的成本優勢和技術特性&#xff0c;正成為企業構建高效穩定的全球網絡架構的首選方案。本文將從多維度解構海外SD-WAN專線部署的核心成本要素&#xff0c;為企業的全球化網絡布局提供戰略參考。 一、基礎資源投…

操作系統(二):實時系統介紹與實例分析

目錄 一.概念 1.1 分類 1.2 主要指標 二.實現原理 三.主流實時系統對比 一.概念 實時系統&#xff08;Real-Time System, RTS&#xff09;是一類以時間確定性為核心目標的計算機系統&#xff0c;其設計需確保在嚴格的時間約束內完成任務響應。 1.1 分類 根據時間約束的嚴…

Golang的消息中間件選型

# Golang的消息中間件選型 消息中間件的作用 消息中間件是一種用于分布式系統中應用程序之間進行通信的基礎架構工具&#xff0c;它能夠有效地解耦發送者和接收者&#xff0c;并提供高可用性和可靠性的消息傳遞機制。在Golang應用程序中&#xff0c;選擇適合的消息中間件對于構…

大模型中的參數規模與顯卡匹配

在大模型訓練和推理中&#xff0c;顯卡&#xff08;GPU/TPU&#xff09;的選擇與模型參數量緊密相關&#xff0c;需綜合考慮顯存、計算能力和成本。以下是不同規模模型與硬件的匹配關系及優化策略&#xff1a; 一、參數規模與顯卡匹配參考表 模型參數量訓練階段推薦顯卡推理階…

帶頭結點 的單鏈表插入方法(頭插法與尾插法)

帶頭結點的單鏈表插入方法&#xff08;頭插法與尾插法&#xff09; 在單鏈表的操作中&#xff0c;插入是最常見的操作之一&#xff0c;本文介紹 帶頭結點的單鏈表 如何實現 后插法 和 前插法&#xff08;包括 插入法 和 后插數據交換法&#xff09;&#xff0c;并提供完整的 C …

Prometheus的工作流程

Prometheus 是一個開源的監控和告警系統&#xff0c;專為監控分布式系統而設計。它的工作流程主要包括以下幾個關鍵步驟&#xff1a; 1. 數據采集 (Scraping) 目標發現 (Service Discovery)&#xff1a; Prometheus 自動或手動配置監控目標&#xff0c;通過 DNS、Kubernetes、…

軟件工程面試題(二十二)

1、常用的設計模式有哪些&#xff1f;并寫出一段程序代碼 Factory(工廠模式)&#xff0c;Adapter(適配器模式)&#xff0c;Singleton(單例模式)&#xff0c;State(狀態模式)&#xff0c;Observer(觀察者模式) 等。 單例模式 public class Singleton{ private static Singleton …

【Pandas】pandas DataFrame select_dtypes

Pandas2.2 DataFrame Attributes and underlying data 方法描述DataFrame.index用于獲取 DataFrame 的行索引DataFrame.columns用于獲取 DataFrame 的列標簽DataFrame.dtypes用于獲取 DataFrame 中每一列的數據類型DataFrame.info([verbose, buf, max_cols, …])用于提供 Dat…

如何利用ATECLOUD測試平臺的芯片測試解決方案實現4644芯片的測試?

作為多通道 DC-DC 電源管理芯片的代表產品&#xff0c;4644 憑借 95% 以上的轉換效率、1% 的輸出精度及多重保護機制&#xff0c;廣泛應用于航天航空&#xff08;衛星電源系統&#xff09;、醫療設備&#xff08;MRI 梯度功放&#xff09;、工業控制&#xff08;伺服驅動單元&a…

Python 編程實戰:打造高效便捷的目錄結構生成器

Python 編程實戰&#xff1a;打造高效便捷的目錄結構生成器 相關資源文件已經打包成EXE文件&#xff0c;可雙擊直接運行程序&#xff0c;且文章末尾已附上相關源碼&#xff0c;以供大家學習交流&#xff0c;博主主頁還有更多Python相關程序案例&#xff0c;秉著開源精神的想法&…

移動端六大語言速記:第6部分 - 錯誤處理與調試

移動端六大語言速記:第6部分 - 錯誤處理與調試 本文將對比Java、Kotlin、Flutter(Dart)、Python、ArkTS和Swift這六種移動端開發語言在錯誤處理與調試方面的特性,幫助開發者理解和掌握各語言的異常處理機制。 6. 錯誤處理與調試 6.1 異常處理 各語言異常處理的語法對比:…

PyTorch優化器

PyTorch 提供了多種優化算法用于神經網絡的參數優化。以下是對 PyTorch 中主要優化器的全面介紹&#xff0c;包括它們的原理、使用方法和適用場景。 一、基本優化器 1. SGD (隨機梯度下降) torch.optim.SGD(params, lr0.01, momentum0, dampening0, weight_decay0, nesterov…

C++的UDP連接解析域名地址錯誤

背景 使用c開發一個udp連接功能的腳本&#xff0c;可以接收發送數據&#xff0c;而且地址是經過內網穿透到外網的 經過 通常發送數據給目標地址&#xff0c;需要把目的地址結構化&#xff0c;要么使用inet_addr解析ip地址&#xff0c;要么使用inet_pton sockaddr_in target…

Spark,上傳文件

上傳文件 1.上傳 先使用命令打開HDFS的NameNode [roothadoop100 hadoop-3.1.3]$ sbin/start-dfs.sh [roothadoop100 hadoop-3.1.3]$ sbin/stop-dfs.sh 和YARN的Job [roothadoop101 hadoop-3.1.3]$ sbin/start-yarn.sh [roothadoop101 hadoop-3.1.3]$ sbin/stop-yarn.sh 在Nam…

如何為Linux/Android Kernel 5.4和5.15添加 fuse passthrough透傳功能 ?

背景 參考&#xff1a;Google文檔 FUSE 透傳 參考此文檔&#xff0c;目前kernel.org提供的fuse passthrough補丁在6.9版本之后&#xff0c;但想要在5.4和5.15版本內核做移植應該如何簡單點呢&#xff1f;文檔中提到 Android的內核為5.4 和 5.15版本內核做了fuse passthrough功…

Ubuntu 防火墻配置

Ubuntu 的防火墻配置可以參考文章&#xff1a;Firewall - Ubuntu Server documentation 22 端口 需要注意的是&#xff0c;在啟動防火墻之前&#xff0c;需要先開放 22 端口。 否則 SSH 將會拒絕你連接防火墻。 開放 22 端口的命令為&#xff1a;sudo ufw allow 22 添加端…

Jetson 設備卸載 OpenCV 4.5.4 并編譯安裝 OpenCV 4.2.0

?一、卸載 OpenCV 4.5.4? 清除已安裝的 OpenCV 庫? sudo apt-get purge libopencv* python3-opencv # 卸載所有APT安裝的OpenCV包?:ml-citation{ref"1,3" data"citationList"}sudo apt autoremove # 清理殘留依賴?:ml-citation{ref"1,4"…

《AI大模型應知應會100篇》第57篇:LlamaIndex使用指南:構建高效知識庫

第57篇&#xff1a;LlamaIndex使用指南&#xff1a;構建高效知識庫 摘要 在大語言模型&#xff08;LLM&#xff09;驅動的智能應用中&#xff0c;如何高效地管理和利用海量知識數據是開發者面臨的核心挑戰之一。LlamaIndex&#xff08;原 GPT Index&#xff09; 是一個專為構建…