当了那么久的小白,今天我也来点干货。
保姆级反射浅谈
什么是反射
JAVA反射机制是在运行状中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
个人对Class<T>
类的理解
顾名思义Class是类的类,这怎么去理解呢,类本来就是属性和行为的抽象,那么Class类就是所有类相同属性和行为的抽象,我的理解是把所以类的共同点都抽离出来构建的一个类,比如类都有自己的方法、属性、构造器等。我们把这些特性剥离出来,抽象出一个Class类。
参数类型
T - 由此类对象建模的类的类型。 例如, String.class的类型是Class<String>
。 如果正在建模的类是未知的,请使用Class<?> 。
获取类的类对象的三种方式
//方法一:
Class<?> clas = Class.forName("java.lang.Object");
//方法二:通过类的名字打打点class,如
Class<?> clas1 = Object.class;
//方法三:通过该类的对象去调用getClass()方法,如
Class<?> clas2 = new Object().getClass();
System.out.println(clas.toString());
//它们的结果都是: class java.lang.Object
通过反射获取类的所有属性、方法和构造器
我们可以调用getDeclaredFields()、getDeclaredMethods()和getDeclaredConstructors()来获取这个类的所有属性和方法。
这里我写了一个封装类Person。
修饰 | 属性/方法 | 数据类型/返回值 | getter | setter |
---|---|---|---|---|
private | id | Integer | √ | √ |
private | name | String | √ | √ |
private | age | String | √ | √ |
public | toString() | String | - | - |
public | printInfo() | - | - | - |
再花点时间做个测试类
//获得一个类的类对象
Class<?> clazz = Person.class;
//获得传递过来的类的所有方法
Method[] methods = clazz.getDeclaredMethods();
//System.out.println(Arrays.toString(methods));
for(Method m : methods){
System.out.println(m);
}
System.out.println("---------------------------------");
//获得类的所有属性
Field[] fields = clazz.getDeclaredFields();
for(Field f : fields){
System.out.println(f);
}
System.out.println("---------------------------------");
//获得类的所有的构造器
Constructor<?>[] cs = clazz.getDeclaredConstructors();
for(Constructor c : cs){
System.out.println(c);
}
}
控制台输出如下:
public java.lang.String cn.reflect.Person.toString()
public java.lang.String cn.reflect.Person.getAddress()
public java.lang.String cn.reflect.Person.getName()
public java.lang.Integer cn.reflect.Person.getId()
public void cn.reflect.Person.setName(java.lang.String)
public void cn.reflect.Person.setId(java.lang.Integer)
public void cn.reflect.Person.setAddress(java.lang.String)
private static void cn.reflect.Person.printInfo()
---------------------------------
private java.lang.Integer cn.reflect.Person.id
private java.lang.String cn.reflect.Person.name
private java.lang.String cn.reflect.Person.address
---------------------------------
public cn.reflect.Person()
public cn.reflect.Person(java.lang.Integer,java.lang.String,java.lang.String)
如果想获得单独的属性我们可以调用getDeclaredField(String name),这里就不赘述了。这里提醒一点,当你想去获取带有参数的方法时要特别注意,例如setName(String name)就带有参数,当调用getDeclaredMethod(String name, Class<?>...parameterTypes)反射前者时,因为方法可能会出现方法重载的情况,所以必须指定其参数列表,即将每个参数所对应的类的类对象添加到Class<?>[]{}之中。(Class<?>...parameterTypes理解为Class<?>[]{} )
Class<?> clas = Person.class;
Method method = clas.getDeclaredMethod("setName", new Class[]{String.class})
常用的反射getDeclaredConstructor方法同理,另外如果该方法没有参数列表,我们可以将其改为new Class[]{}或null(null不会报错,但不推荐)
通过反射的方法赋值
方法一(反射调用构造器setXxx方法)
1)获得目标类的类对象
2)用类对象获得目标类的构造器
3)根据类的默认构造器来获得一个对象
4)获得类的所以方法
5)根据方法名获得属性对应的数据类型
6)反射调用setXxx方法
Class<?> class1 = Class.forName("cn.reflect.Person");
//获得构造器
Constructor<?> constructor = class1.getDeclaredConstructor(new Class[]{});
//根据类的默认构造器来获得一个对象
Object instance = constructor.newInstance(new Object[]{});
System.out.println(instance);
//获得类的所有方法
Method[] methods = class1.getDeclaredMethods();
for(Method m :methods){
//获得方法的名字
String name = m.getName();
//获得是否以set开始
boolean startsWith = name.startsWith("set");
if(startsWith){
//获得到了set字符串的后面的值
String fieldName = name.substring(3);
//获得属性的名字
fieldName = fieldName.substring(0, 1).toLowerCase()+fieldName.substring(1);
//获得方法所对应的属性
Field field = class1.getDeclaredField(fieldName);
//获得属性的具体类型,获得属性对应的类型
Class<?> type = field.getType();
if(type == Integer.class){
//反射调用set方法
m.invoke(instance, new Object[]{1});
}
if(type == String.class && "address".equals(fieldName)){
//反射调用set方法
m.invoke(instance, new Object[]{"北京"});
}
if(type == String.class && "name".equals(fieldName)){
//反射调用set方法
m.invoke(instance, new Object[]{"李华"});
}
}
}
System.out.println(instance);
控制台输出:
Person [id=null, name=null, address=null]
Person [id=1, name=李华, address=北京]
方法二(Method反射调用自身)
1)…
2)…
3)指定setXxx方法
4)通过Method反射调用自身;关键方法invoke(Obkect obj, Object…args)
如果底层方法是实例方法,第一个参数就是该对象实例,第二个参数是方法的参数列表,invoke的返回值就是实例方法的返回值。
如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。
如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null
Class<?> class1 = Class.forName("cn.reflect.Person");
//获得构造器
Constructor<?> constructor = class1.getDeclaredConstructor(new Class[]{});
//根据类的默认构造器来获得一个对象
Object instance = constructor.newInstance(new Object[]{});
Method method = class1.getDeclaredMethod("setName", new Class[]{String.class});
Object invoke = method.invoke(instance, new Object[]{"李华"});
System.out.println(instance);
控制台输出
Person [id=null, name=李华, address=null]
方法三(暴力法)
1)…
2)…
3)根据方法名字来获得属性对象
4)破坏掉私有属性,然后赋值
Class<?> class1 = Class.forName("cn.reflect.Person");
//获得构造器
Constructor<?> constructor = class1.getDeclaredConstructor(new Class[]{});
//根据类的默认构造器来获得一个对象
Object instance = constructor.newInstance(new Object[]{});
//根据方法名字来获得属性对象
Field field = class1.getDeclaredField("name");
//破坏掉私有属性
field.setAccessible(true);
field.set(instance, "李华");
System.out.println(instance);
控制台输出:
Person [id=null, name=李华, address=null]