通过反射查看类信息
通过反射获得一个类
每个类被加载之后,系统会为该类生成对应的Class对象。Java程序中获得Class对象通常有以下三种方式。
- 使用Class类的
forName(String className)
静态方法。该方法需要传入该类的全限定类名(包括包的信息)
- 调用某个类的class属性来获得该类对应的Class对象。
- 调用某个对象的
getClass()
方法。该方法是Object类中的方法,所以所有Java对象都可以使用该方法。
建议使用第2种方法去获得一个类,代码更安全。
- 程序在编译阶段就可以检查需要访问的Class对象是否存在。(使用第1种方法需要捕获ClassNotFoundException异常)
- 程序性能更好,因为这种方式无须调用方法,所以性能更好。
从Class中获取信息
反射可以从类中直接拿到该类的构造器,属性,方法等信息。
获取构造器
方法名 |
描述 |
Connstructor< T> getConstructor( Class<?>… parameterTypes) |
返回此 Class 对象对应类的、 带指定形参列表的 public 构造器。 |
Constructor<?>[] getConstructors(): |
返回所有public的构造器 |
Constructor< T> getDeclaredConstructor( Class<?>… parameterTypes) |
返回此 Class 对象对应类的、带指定形参列表的构造器,与构造器的访问权限无关。 |
Constructor<?>[] getDeclaredConstructors() |
返回此 Class 对象对应类的所有构造器,与构造器的访问权限无关。 |
获取方法
方法名 |
描述 |
Method getMethod( String name, Class<?>… parameterTypes) |
返回此 Class 对象对应类的、带指定形参列表的 public 方法。 |
Method[] getMethods() |
返回此 Class 对象所表示的类的所有 public 方法。 |
Method getDeclaredMethod( String name, Class<?>… parameterTypes) |
返回此 Class 对象对应类的、带指定形参列表的方法, 与方法的访问权限无关。 |
Method[] getDeclaredMethods() |
返回此 Class 对象对应类的全部方法,与方法的访问权限无关。 |
获得成员变量
方法名 |
描述 |
Field getField( String name) |
返回此 Class 对象对应类的、指定名称的 public 成员变量。 |
Field[] getFields(): |
返回此 Class 对象对应类的所有 public 成员变量。 |
Field getDeclaredField( String name): |
返回此 Class 对象对应类的、指定名称的成员变量,与成员变量的访问权限无关。 |
Field[] getDeclaredFields() |
返回此 Class 对象对应类的全部成员变量,与成员变量的访问权限无关。 |
获得注解
方法名 |
描述 |
< A extends Annotation> A getAnnotation( Class< A> annotationClass) |
尝试获取该 Class 对象对应类上存在的、指定类型的 Annotation;如果该类型的注解不存在,则返回 null。 |
< A extends Annotation> A getDeclaredAnnotation( Class< A> annotationClass) |
这是 Java 8 新增的方法,该方法尝试获取直接修饰该 Class 对象对应类的、指定类型的 Annotation;如果该类型的注解不存在 ,则返回 null。 |
Annotation[] getAnnotations(): |
返回修饰该 Class 对象对应类上存在的所有 Annotation。 |
Annotation[] getDeclaredAnnotations() |
返回直接修饰该 Class 对应类的所有 Annotation。 |
< A extends Annotation> A[] getAnnotationsByType( Class< A> annotationClass) |
该方法的功能与前面介绍的 getAnnotation() 方法基本相似。但由于 Java 8 增加了重复注解功能,因此需要使用该方法获取修饰该类的、指定类型的多个 Annotation。 |
< A extends Annotation> A[] getDeclaredAnnotationsByType( Class< A> annotationClass) |
该方法的功能与前面介绍的 getDeclaredAnnotations ()方法 基本相似。但由于 Java 8 增加了重复注解功能,因此需要使用该方法获取直接修饰该类的、指定类型的多个Annotation。 |
获得内部类
方法名 |
描述 |
Class<?>[] getDeclaredClasses() |
返回该 Class 对象对应类里包含的全部内部类。如下方法用于访问该 Class 对象对应类所在的外部类。 |
Class<?> getDeclaringClass() |
返回该 Class 对象对应类所在的外部类。如下方法用于访问该 Class 对象对应类所实现的接口。 |
Class<?>[] getInterfaces() |
返回该 Class 对象对应类所实现的全部接口。如下几个方法用于访问该 Class 对象对应类所继承的父类。 |
Class<? super T > getSuperclass() |
返回该 Class 对象对应类的超类的 Class 对象。 |
获取Class对象对应的修饰符、所在包、类名等基本信息
方法名 |
描述 |
int getModifiers() |
返回此类或接口的所有修饰符。修饰符由 public、 protected、 private、 final、 static、 abstract 等对应的常量组成,返回的整数应使用 Modifier 工具类的方法来解码,才可以获取真实的修饰符。 |
Package getPackage() |
获取此类的包。 |
String getName() |
以字符串形式返回此 Class 对象所表示的类的名称。 |
String getSimpleName() |
以字符串形式返回此 Class 对象所表示的类的简称。 |
判断该类是否为接口、枚举、注解类型
方法名 |
描述 |
boolean isAnnotation() |
返回此 Class 对象是否表示一个注解类型(由@ interface 定义)。 |
boolean isAnnotationPresent( Class<? extends Annotation> annotationClass) |
判断此 Class 对象是否使用了 Annotation 修饰。 |
boolean isAnonymousClass() |
返回此 Class 对象是否是一个匿名类。 |
boolean isArray() |
返回此 Class 对象是否表示 一个数组类。 |
boolean isEnum() |
返回此 Class 对象是否表示一个枚举(由 enum 关键字定义)。 |
boolean isInterface() |
返回此 Class 对象是否表示 一个接口(使用 interface 定义)。 |
boolean isInstance( Object obj) |
判断 obj 是否是此Class 对象的实例,该方法可以完全代替 instanceof 操作符。 |
下面是示例程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Arrays;
@Repeatable(Annos.class) @interface Anno{} @Retention(value = RetentionPolicy.RUNTIME) @interface Annos{ Anno[] value(); }
@SuppressWarnings(value = "unchecked") @Deprecated
@Anno @Anno public class ClassTest { private ClassTest() { } public ClassTest(String name) { System.out.println("执行有参数的构造器"); } public void info() { } public void info(String str) { System.out.println("执行有参数的info方法" + ",其str参数值为:"+str); } class Inner{ } public static void main(String[] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException { Class<ClassTest> clazz = ClassTest.class; Constructor[] ctors = clazz.getDeclaredConstructors(); System.out.println("ClassTest的全部构造器如下:"); for (Constructor c:ctors) { System.out.println(c); } Constructor[] publicCtors = clazz.getConstructors(); for (Constructor c:publicCtors) { System.out.println(c); } Method[] mtds = clazz.getMethods(); System.out.println("ClassTest的全部public方法如下:"); for(Method md:mtds) { System.out.println(md); } System.out.println("ClassTest里带一个字符串参数的info方法为:"+clazz.getMethod("info", String.class)); Annotation[] anns = clazz.getAnnotations(); System.out.println("ClassTest的全部Annotation如下:"); for(Annotation an:anns) { System.out.println(an); } System.out.println("该Class元素上的@SuppressWarnings注解为:"+Arrays.toString(clazz.getAnnotationsByType(Anno.class))); Class<?>[] inners = clazz.getDeclaredClasses(); System.out.println("ClassTest的全部内部类如下"); for(Class c:inners) { System.out.println(c); } Class inClazz = Class.forName("clazz.ClassTest$Inner"); System.out.println("inClazz对应类的外部类为:"+inClazz.getDeclaredClasses()); System.out.println("ClassTest的包为:"+clazz.getPackage()); System.out.println("ClassTest的父类为:"+clazz.getSuperclass()); } }
|
方法参数反射
Java8在反射包下新增了一个Executable抽象基类,该对象代表可执行得类成员,该类派生了Constructor、Method两个子类。
Executable类提供了如下两个方法来获取该方法或参数得形参个数及其形参名
方法名 |
描述 |
int getParameterCount() |
获取该构造器或方法的形参个数。 |
Parameter[] getParameters() |
获取该构造器或方法的所有形参。 |
Parameter类的方法
方法名 |
描述 |
getModifiers() |
获取修饰该形参的修饰符。 |
String getName() |
获取形参名。 |
Type getParameterizedType() |
获取带泛型的形参类型。 |
Class<?> getType() |
获取形参类型。 |
boolean isNamePresent() |
该方法返回该类的 class 文件中是否包含了方法的形参名信息。 |
boolean isVarArgs() |
该方法用于判断该参数是否为个数可变的形参。 |
使用javac命令编译Java源文件时,默认生成的class文件并不包含方法的形参名信息,因为调用isNamePresent()
方法将会返回false。如果希望javac命令编译Java源文件时可以保留形参信息,则需要为该命令指定-parameters选项。
下面的程序就示范了参数反射功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.List;
class Test{ public void replace(String str,List<String> list) {} }
public class MethodParameterTest { public static void main(String[] args) throws NoSuchMethodException, SecurityException { Class<Test> clazz = Test.class; Method replace = clazz.getMethod("replace",String.class,List.class); System.out.println("replace方法参数个数:"+replace.getParameterCount()); Parameter[] parameters = replace.getParameters(); int index = 1; for(Parameter p : parameters) { if(p.isNamePresent()) { System.out.println("---第"+index++ +"个参数信息---"); System.out.println("参数名"+p.getName()); System.out.println("形参类型:"+p.getType()); System.out.println("泛型类型:"+p.getParameterizedType()); } } } }
|
该程序只有当你使用正确的方式生成class文件才能正常显示
1
| javac -parameters -d . MethodParameterTest.java
|
使用反射生成并操作对象
创建对象
通过反射创建对象,需要先通过Class对象获取指定的 Constructor 对象(构造器对象),然后通过该对象的newInstance()
方法来创建实例。
下面是一个demo程序(实现了一个简单的对象池,该对象池会根据配置文件的key-value对,然后创建这些对象,并将这些对象放入一个HashMap中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package clazz;
import java.io.FileInputStream; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.Properties;
public class ObjectPoolFactory { private Map<String, Object>objectPool = new HashMap<String, Object>(); private Object createObject(String clazzName) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Class<?> clazz = Class.forName(clazzName); return clazz.getConstructor().newInstance(); } public void initPool(String fileName) { try(FileInputStream fis = new FileInputStream(fileName)) { Properties props = new Properties(); props.load(fis); for(String name : props.stringPropertyNames()) { objectPool.put(name, createObject(props.getProperty(name))); } } catch (Exception e) { e.printStackTrace(); System.out.println("读取"+fileName+"异常"); } } public Object getObject(String name) { return objectPool.get(name); } public static void main(String[] args) { ObjectPoolFactory pf = new ObjectPoolFactory(); pf.initPool("obj.txt"); System.out.println(pf.getObject("a")); System.out.println(pf.getObject("b")); } }
|
为该程序提供如下的属性文件
obj.txt文件
1 2
| a=java.util.Date b=javax.swing.JFrame
|
总结下来使用反射创建实例对象的话有三个步骤
- 获取该类的Class对象
- 利用Class对象的
getConstructor()
方法来获取指定的构造器
- 调用Constructor的
newInstance()
方法来创建Java对象
下面程序利用反射来创建,而且使用了指定的构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;
public class CreateJframe { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class<?> jframeClazz = Class.forName("javax.swing.JFrame"); Constructor ctor = jframeClazz.getConstructor(String.class); Object obj = ctor.newInstance("测试窗口"); System.out.println(obj); } }
|
调用方法
获得Class对象后,通过getMethods()
方法或者getMethod()
来获取全部方法或指定方法。他们的返回值都是Method数组或者对象。
在Mehod中包含一个invoke()
方法
Object invoke(Object obj,Object... args)
该方法中的obj是执行该方法的主要调用对象,args是该方法的实参。
如果程序确实需要调用某个对象的private方法,则可以先调用Method对象的setAccessible(boole flag)
方法,flag为true时,指示该Method在使用时应该取消Java语言的访问权限检查;false则代表要实施访问权限检查,默认为false。该方法属于它的父类AccessibleObject,所以Method、Constructor、Field都可以调用该方法,从而忽视private的效果
下面程序对之前程序是一个加强,允许在配置文件中增加配置对象的成员变量的值,并使用方法为其赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Properties;
public class ExtendedObjectPoolFactory { private Map<String,Object> objectPool = new HashMap<>(); private Properties config = new Properties(); public void init(String fileName) { try(FileInputStream fis = new FileInputStream(fileName)) { config.load(fis); } catch (IOException e) { e.printStackTrace(); System.out.println("读取"+fileName+"异常"); } } private Object createObject(String clazzName) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Class<?> clazz = Class.forName(clazzName); return clazz.getConstructor().newInstance(); } public void initPool() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { for(String name:config.stringPropertyNames()) { if(!name.contains("%")) { objectPool.put(name, createObject(config.getProperty(name))); } } } public void initProperty() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { for(String name:config.stringPropertyNames()) { if(name.contains("%")) { String[] objAndProp = name.split("%"); Object target = getObject(objAndProp[0]); String mtdName = "set" +objAndProp[1].substring(0,1).toUpperCase() +objAndProp[1].substring(1); Class<?> targetClass = target.getClass(); Method mtd = targetClass.getMethod(mtdName, String.class); mtd.invoke(target, config.getProperty(name)); } } } public Object getObject(String name) { return objectPool.get(name); } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory(); epf.init("extObj.txt"); epf.initPool(); epf.initProperty(); System.out.println(epf.getObject("a")); } }
|
该程序还是需要一个txt文件
1 2 3 4
| a=javax.swing.JFrame b=javax.swing.JLabel #这是注释 set the title of a a%title=Test Title
|
最后一句a%title行表明希望调用a对象的setTitle()
方法,Test Title是参数。
访问成员变量值
通过Class对象的的 getFields() 或 getField() 方法可以获取该类所包括的全部成员变量或指定成员变量。
Field提供了如下两组方法来读取和设置成员变量值
注意:此处的 Xxx 对应 8 种基本类型,如果该成员变量的类型是引用类型,则取消 get 后面的Xxx。
方法名 |
描述 |
getXxx( Object obj) |
获取 obj 对象的该成员变量的值。 |
setXxx( Object obj, Xxx val) |
将 obj 对象的该成员变量设置成 val 值。 |
下面是示例程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import java.lang.reflect.Field;
class Person{ private String name; private int age; @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class FieldTest { public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Person p = new Person(); Class<Person> personClazz = Person.class; Field nameField = personClazz.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(p, "Yeeku.H.Lee"); Field ageField = personClazz.getDeclaredField("age"); ageField.setAccessible(true); ageField.setInt(p, 30); System.out.println(p); } }
|
操作数组
在反射包下还提供了Array类,该类可以代表所有的数组。
注意:此处的 Xxx 对应 8 种基本类型,如果该成员变量的类型是引用类型,则取消 get 后面的Xxx。
方法名 |
描述 |
static Object newInstance( Class<?> componentType, int… length) |
创建一个具有指定的元素类型、指定维度的新数组。 |
static xxx getXxx( Object array, int index) |
返回 array 数组中第 index 个元素。 |
static void setXxx( Object array, int index, xxx val) |
将 array 数组中第 index 个元素的值设为 val。 |
下面程序讲了如何使用Array来生成数组,并赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import java.lang.reflect.Array;
public class ArrayTest1 { public static void main(String[] args) { try { Object arr = Array.newInstance(String.class, 10); Array.set(arr, 5, "苹果手机"); Array.set(arr, 6, "安卓手机"); Object book1 = Array.get(arr, 5); Object book2 = Array.get(arr, 6); System.out.println(book1); System.out.println(book2); } catch (Exception e) { e.printStackTrace(); } } }
|
下面的程序使用该类创建一个三维数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import java.lang.reflect.Array;
public class ArrayTest2 { public static void main(String[] args) {
Object arr = Array.newInstance(String.class, 3,4,10); Object arrObj = Array.get(arr, 2); Array.set(arrObj, 2, new String[] { "苹果手机", "安卓手机" }); Object anArr = Array.get(arrObj, 3); Array.set(anArr, 8, "塞班手机"); String[][][] cast = (String[][][])arr; System.out.println(cast[2][3][8]); System.out.println(cast[2][2][0]); System.out.println(cast[2][2][1]); } }
|
反射和泛型
使用Class<?>可以避免使用反射生成的对象需要强制类型转换
泛型和Class类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class BenObjectFactory { public static Object getInstance(String clsName) { try { Class cls = Class.forName(clsName); return cls.getConstructor().newInstance(); } catch (Exception e) { e.printStackTrace(); } return null; } }
|
上面的程序可以创建一个新对象,但是这个对象是Object,所以当使用特定对象时候必须要强转,这就有风险。
如果改进为这样的代码的话就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import java.util.Date;
import javax.xml.crypto.Data;
public class BenObjectFactory2 { public static<T> T getInstance(Class<T> cls) { try { return cls.getConstructor().newInstance(); } catch (Exception e) { e.printStackTrace(); return null; } } public static void main(String[] args) { Date d = BenObjectFactory2.getInstance(Date.class); } }
|
这样获取实例以后就没有风险了,同样的道理,对于数组也是同样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import java.lang.reflect.Array;
public class BenArray { @SuppressWarnings("unchecked") public static<T> T[] newInstance(Class<T> componentType,int length) { return (T[])Array.newInstance(componentType, length); } public static void main(String[] args) { String[] arr = BenArray.newInstance(String.class, 10); int[][] intArr = BenArray.newInstance(int[].class, 5); arr[5] = "苹果手机"; intArr[1] = new int[] {23,12}; System.out.println(arr[5]); System.out.println(intArr[1][1]); } }
|
使用反射来获取泛型信息
前面说到可以用Field来获得成员变量的类型。Class<?> a = f.getType()
这个方法可以获得成员变量f的类型。但是这种方式只对普通类型有效,假如该类型是Map<String,Integer>类型,则不能准确地获取成员变量地泛型参数。
如果想要获得其泛型类型。需要使用Type gtype = f.getGenericType()
,然后把Type对象强制转换为为 ParameterizedType 对象, ParameterizedType 代表被参数化的类型,也就是增加了泛 型限制的类型。 ParameterizedType 类提供了如下两个方法。
方法名 |
描述 |
getRawType() |
返回没有泛型信息的原始类型。 |
getActualTypeArguments() |
返回泛型参数的类型。 |
下面是一个获取泛型的程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map;
public class GenericTest { private Map<String,Integer> score; public static void main(String[] args) throws NoSuchFieldException, SecurityException { Class<GenericTest> clazz = GenericTest.class; Field f = clazz.getDeclaredField("score"); Class<?> a = f.getType(); System.out.println("score的类型是"+a); Type gType = f.getGenericType(); if(gType instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType)gType; Type rType = pType.getRawType(); System.out.println("原始类型是"+rType); Type[] tArgs = pType.getActualTypeArguments(); System.out.println("泛型信息是:"); for(int i=0;i<tArgs.length;i++) { System.out.println("第"+i+"个泛型类型是:"+tArgs[i]); } }else { System.out.println("获取泛型类型出错!"); } } }
|
完结撒花!!!