序言
java是面向对象的语言,把程序代码比作一辆车,车子有自己的颜色、车型号、品牌这些属性,也有正常行驶、倒车、停泊这些功能操作。正常情况下,我们需要为车子配备一个司机,然后按照行为准则规范行驶。那么反射是什么呢?无人驾驶。Class就是行驶规则。
反射的概念:
反射就是获取类的字节码文件,可以使我们在运行状态获取任何一个类的方法和属性。对于任意一个对象,都能够调用它的任意方法和属性。
Class获取
一个类在内存中只有个一个class对象
类被加载后。类的整个结构都会封装在class对象中
1. 通过 Object.getClass()
// 这种方法不适合基本类型如 int、float 等等
Car car = new Car();
Class cls = car.getClass();
2. 通过 .class 标识
Class cls = Car.class;
3. 通过 Class.forName(包名+类名) 方法
try {
Class cls = Class.forName("com.frank.test.Car");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
4. 创建对象
Car car = (Car)cls.newInstance();//必须要有无惨构造器
//构造器
Constructor constructor = cls.getDeclredControct(参数类型)
Car car2 = constructor =cls.newInstance("","")
Class 的名字
Class cls=....
//得到包名+类型 如果是数组({[+包名+类名)内部类(包名+类型$内部类名)
cls.getName();
//得到类名
cls.getSimpleName();
//返回的也是全限定类名,但是对于内部类,不用 $ 开头,而用 .。
cls.getCanonicalName();
Class 获取修饰符
Java 开发中定义一个类,往往是要通过许多修饰符来配合使用的。它们大致分为 4 类。
- 用来限制作用域,如 public、protected、priviate。
- 用来提示子类复写,abstract。
- 用来标记为静态类 static。
- 注解
java 反射提供了 API 去获取这些修饰符。
package com.frank.test;
public abstract class TestModifier {
}
如果定义一个类,名字为 TestModifier,被 public 和 abstract 修饰,调用 Class.getModifiers() 方法就是了,它返回的是一个 int 数值。
System.out.println("modifiers value:"+TestModifier.class.getModifiers());
System.out.println("modifiers :"+Modifier.toString(TestModifier.class.getModifiers()));
打印结果是:
modifiers value:1025
modifiers :public abstract
获取 Class 的成员
一个类的成员包括属性、方法, 构造方法。对应到 Class 中就是 Field、Method、Constructor。
1,获取单个属性
//获取的是 Class 中被 private 修饰的属性,不能获取父类的属性
public Field getDeclaredField(String name)throws NoSuchFieldException,
SecurityException;
//获取的是非私有属性,并且 getField() 在当前 Class 获取不到时会向祖先类获取。
public Field getField(String name)throws NoSuchFieldException,
SecurityException
2,获取所有的属性。
//获取所有的属性,但不包括从父类继承下来的属性
public Field[] getDeclaredFields() throws SecurityException {}
//获取自身的所有的 public 属性,包括从父类继承下来的。
public Field[] getFields() throws SecurityException {
1,获取Field的名 fiele.getName,,
2,获取 Field 的类型,通过 2 个方法:
//法能够获取到泛型类型 eg List<Car> java.util.List<xxx.Car>
public Type getGenericType() {}
eg:List<Car> java.util.List
public Class<?> getType() {}
3,获取Field 也有很多修饰符。同 Class 一样,通过 getModifiers() 方法就可以轻松获取。Modifier.toString(fiele.getModifiers())
public int getModifiers() {}
4,获取Field 值与赋值 如果是私有属性。必须 field.setAccessible(true); 表示可以访问
Field 这个类定义了一系列的 get 方法来获取不同类型的值。
public Object get(Object obj);//如果是静态属性 object是null
public int getInt(Object obj);//以此类推。可以float类型数据,没有getString
Field 又定义了一系列的 set 方法用来对其自身进行赋值。
public void set(Object obj, Object value);
public void getInt(Object obj,int value);
获取 Method
类或者接口中的方法对应到 Class 就是 Method。Method跟Field 类似,parameterTypes 是方法对应的参数。
//获取单个方法。
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
public Method getMethod(String name, Class<?>... parameterTypes)
//获取所有方法
public Method[] getDeclaredMethods() throws SecurityException
public Method[] getMethods() throws SecurityException
方法由下面几个要素构成:
- 方法名
- 方法参数
- 方法返回值
- 方法的修饰符
- 方法可能会抛出的异常
1, 获取方法名,通过 getName() 。
2,获取方法参数,有三个基本方法
public Parameter[] getParameters() {}
返回的是一个 Parameter 数组,在反射中 Parameter 对象就是用来映射方法中的参数。经常使用的方法有:
Parameter.java
// 获取参数名字
public String getName() {}
// 获取参数类型
public Class<?> getType() {}
// 获取参数的修饰符
public int getModifiers() {}
当然,有时候我们不需要参数的名字,只要参数的类型就好了,通过 Method 中下面的方法获取。
Method.java
// 获取所有的参数类型
public Class<?>[] getParameterTypes() {}
// 获取所有的参数类型,包括泛型
public Type[] getGenericParameterTypes() {}
3, Method 获取返回值类型
// 获取返回值类型
public Class<?> getReturnType() {}
// 获取返回值类型包括泛型
public Type getGenericReturnType() {}
4, Method 获取修饰符
public int getModifiers() {}
5,Method 获取异常类型
public Class<?>[] getExceptionTypes() {}
public Type[] getGenericExceptionTypes() {}
6 ,Method 方法的执行 如果是私有方法。必须 field.setAccessible(true); 表示可以访问
这个应该是整个反射机制的核心内容了,很多时候运用反射目的其实就是为了以常规手段执行 Method。
public Object invoke(Object obj, Object... args) {}
Method 调用 invoke() 的时候,存在许多细节:
invoke() 方法中第一个参数 Object 实质上是 Method 所依附的 Class 对应的类的实例,如果这个方法是一个静态方法,那么 ojb 为 null,后面的可变参数 Object 对应的自然就是参数。
invoke() 返回的对象是 Object,所以实际上执行的时候要进行强制转换。
在对 Method 调用 invoke() 的时候,如果方法本身会抛出异常,那么这个异常就会经过包装,由 Method 统一抛出 InvocationTargetException。而通过 InvocationTargetException.getCause() 可以获取真正的异常。
eg
Method methodSetAge =cls.getMethod("setAge",int.class);
methodSetAge.invoke(student,100);
//得到返回值
Method methodGetName=cls.getMethod("getName");
String name = (String) methodGetName.invoke(student);
获取 构造器Constructor
Constructor 不能从父类继承,所以就没有办法通过 getConstructor() 获取到父类的 Constructor。
Constructor 同 Method 差不多,但是它特别的地方在于,它能够创建一个对象。
在 Java 反射机制中有两种方法可以用来创建类的对象实例:Class.newInstance() 和 Constructor.newInstance()。官方文档建议开发者使用后面这种方法,下面是原因。
Class.newInstance() 只能调用无参的构造方法,而 Constructor.newInstance() 则可以调用任意的构造方法。
Class.newInstance() 通过构造方法直接抛出异常,而 Constructor.newInstance() 会把抛出来的异常包装到 InvocationTargetException 里面去,这个和 Method 行为一致。
Class.newInstance() 要求构造方法能够被访问,而 Constructor.newInstance() 却能够访问 private 修饰的构造器。
/*获取构造方法*/
//获取构造方法,包括没写无参的构造方法
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
//获取构造方法。如果没有写无参的构造方法,不可以使用
public Constructor<T> getConstructor(Class<?>... parameterTypes)
/*获取所有的构造方法*/
//已写的构造方法+默认的构造方法
public Constructor<?>[] getDeclaredConstructors() throws SecurityException
//已经写出来的构造方法
public Constructor<?>[] getConstructors() throws SecurityException
eg
Class cls = Student.class;
Constructor[] constructors = cls.getConstructors();
Constructor[] constructors1 = cls.getDeclaredConstructors();
for(int i=0;i<constructors.length;i++){
err("不包括默认构造器=="+constructors[i].toString());
}
for(int i=0;i<constructors1.length;i++){
err("包括默认构造器=="+constructor1.toString());
}
try {
Constructor constr=cls.getConstructor(boolean.class);
Student s= (Student) constr.newInstance(true);
err("带参数=="+s.getName()+"---"+s.getAge());
Constructor constr2=cls.getDeclaredConstructor();
Student s2= (Student) constr2.newInstance();
err("***带参数=="+s2.getName()+"---"+s2.getAge());
Constructor constr1 =cls.getConstructor();
Student s1 = (Student) constr1.newInstance();
err("no带参数=="+s1.getName()+"---"+s1.getAge());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
总结
Java 中的反射是非常规编码方式。
Java 反射机制的操作入口是获取 Class 文件。 有 Class.forName()、 .class 和 Object.getClass() 3 种。
获取 Class 对象后还不够,需要获取它的 Members,包含 Field、Method、Constructor。
Field 操作主要涉及到类别的获取,及数值的读取与赋值。
Method 算是反射机制最核心的内容,通常的反射都是为了调用某个 Method 的 invoke() 方法。
通过 Class.newInstance() 和 Constructor.newInstance() 都可以创建类的对象实例,但推荐后者。因为它适应于任何构造方法,而前者只会调用可见的无参数的构造方法。
数组和枚举可以被看成普通的 Class 对待。