1-概念
何为反射:在程序运行期间可以拿到一个对象的所有信息,如对象的名称-参数-方法-构造方法等.
除了int等基本类型外,Java的其他类型全部都是class.
拿到的流程是怎么样的呢?
以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后为String类创建一个Class实例并关联起来,我们可以暂且理解为我们创建的每个类JVM运行时都创建了一个隐形的静态变量class,此隐形变量的类型是Class,我们通过隐形变量和方法能够获取到当前对象的所有信息.
class Person{
String name = "Hello";
//类型是Class的隐形静态变量class
static Class class;
//返回类型是Class的隐形方法getClass
Class getClass(){
.......
}
}
JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,获取对应的Class实例方法有三种
Class<String> class1 = String.class;//根据类名获取Class实例对象
String str2 = new String("Hello");
Class class2 = str2.getClass();//根据实例对象获取Class实例对象
Class class3 = Class.forName("java.lang.String");//根据完整类名获取Class对象
上面我们知道了怎么获取某一个类对应的Class实例对象后,就可以根据此Class实例对象获取到对应类的类名、包名、方法、字段等所有属性
public class fanshe {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
example example = new example();
example.getExample();
}
}
class example{
@SuppressWarnings({ "unused", "rawtypes" })
void getExample() throws ClassNotFoundException {
Class<String> class1 = String.class;//根据类名获取Class实例对象
String str = new String("Hello");
Class class2 = str.getClass();//根据实例对象获取Class实例对象
Class class3 = Class.forName("java.lang.String");//根据完整类名获取Class对象
System.out.println("Class name:"+class1.getName());//获取类名 java.lang.String
System.out.println("Simple name:" +class1.getSimpleName());//简化的类名 String
System.out.println("Package name:" +class1.getPackage().getName());//包名 java.lang
}
}
动态加载:上述代码中当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Example.class,除非程序执行到getExample()方法,JVM发现需要加载Example类时,才会首次加载Example.class。如果没有执行getExample()方法,那么Example.class根本就不会被加载。
2-访问字段
Field getField(name):根据字段名获取某个public的field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
Field[] getFields():获取所有public的field(包括父类)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
通过上述方法先获取到Field对象,再通过Filed对象获取字段名、类型、字段的值、修改字段的值
注意:如果一个字段在类中用的private声明的,需调用Field.setAccessible(true),意思是别管这个字段是不是public,一律允许访问
public class fanshe {
public static void main(String[] args) throws InstantiationException, IllegalAccessException,
ClassNotFoundException, NoSuchFieldException, SecurityException {
fanshe fanshe = new fanshe();
fanshe.getName(new Student2());
}
void getName(Object obj)
throws InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException {
// 先获取对应的Class对象
Class classObj = obj.getClass();
// 实例化对象用getClass().newInstance()
Object student = obj.getClass().newInstance();
// 根据字段名获取某个public的field(包括父类)
Field field1 = classObj.getField("name");
// 结果:字段名name:public java.lang.String com.Sinokj.miaosha.controller.Persion2.name
System.out.println("字段名修改后的name:" + field1);
// 设置name的值
field1.set(student, "Hello");
// 根据字段名获取当前类的某个field(不包括父类)
Field field2 = classObj.getDeclaredField("score");// 结果:字段名name:public int
// com.Sinokj.miaosha.controller.Student2.score
System.out.println("字段名name:" + field2);
// 获取所有public的filed(包括父类)
Field[] fields3 = classObj.getFields();
for (Field field : fields3) {
System.out.println(field.getName());// 字段名
System.out.println(field.getType());// 字段类型
// 获取每个字段的值 field.get(classObj)
System.out.println(field.get(student));
}
// 获取当前类的所有field(不包括父类)
Field[] fields4 = classObj.getDeclaredFields();
// 获取实例对象 getClass().newInstance()
Object object = obj.getClass().newInstance();
// 用instanceof不但匹配指定类型,还匹配指定类型的子类
if (object instanceof Persion2) {
// getClass().newInstance()用来实例化对象
Persion2 perison = (Persion2) object;
System.out.println(perison.getName());
}
}
}
class Persion2 {
public String name = "Persion2";
void setName(String name) {
this.name = name == null ? null : name.trim();
}
String getName() {
return this.name;
}
}
class Student2 extends Persion2 {
public int score;
public int grade;
}
调用方法
我们已经能通过Class实例获取所有Field对象,同样的,可以通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method:
Method getMethod(name, Class…):获取某个public的Method(包括父类)
Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)
Method[] getMethods():获取所有public的Method(包括父类)
Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)
public class fansheMethod {
public static void main(String[] args) throws Exception {
Class stdClass = Student5.class;
Method method1 = stdClass.getMethod("getScore", String.class);
// 获取public方法getScore,参数为String:
// 结果:public int com.Student5.getScore(java.lang.String)
System.out.println(method1);
// 获取继承的public方法getName,无参数:
Method method2 = stdClass.getMethod("getName");
System.out.println(method2);
// 获取private方法getGrade,参数为int:
Method method3 = stdClass.getDeclaredMethod("getGrade", int.class);
System.out.println(method3);
// 返回方法名称,例如:"getScore";
String name = method1.getName();
// 返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
TypeVariable<Method>[] typeParameters = method1.getTypeParameters();
// 返回方法返回值类型,也是一个Class实例,例如:String.class
Class returnType = method1.getReturnType();
// 返回方法的修饰符,它是一个int,不同的bit表示不同的含义。
int modifiers = method1.getModifiers();
}
}
class Student5 extends Person5 {
public int getScore(String type) {
return 99;
}
private int getGrade(int year) {
return 1;
}
}
class Person5 {
public String getName() {
return "Person";
}
}
调用构造方法
我们通常使用new操作符创建新的实例:
Person p = new Person();
如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:
Person p = Person.class.newInstance();
调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:
public class fansheConstruction {
@SuppressWarnings({ "unchecked", "rawtypes", "unused" })
public static void main(String[] args) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
//获取Person6对应的class实例对象
Class person = Person6.class;
//class实例对象的getConstructor方法,得到Constructor对象,其中String.class为构造传入类型,
Constructor constructors = person.getConstructor(String.class);
//通过Constructor对象生成此构造函数对象
Person6 newInstance = (Person6) constructors.newInstance("hello");
System.out.println(newInstance.getName());
}
}
class Person6 {
private String name;
public Person6(String name) {
this.name = name;
}
String getName() {
return this.name;
}
}
通过Class实例获取Constructor的方法如下:
getConstructor(Class…):获取某个public的Constructor;
getDeclaredConstructor(Class…):获取某个Constructor;
getConstructors():获取所有public的Constructor;
getDeclaredConstructors():获取所有Constructor。
注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。
动态代理
我们先定义了接口Hello,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
public class ProxyExample {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
interface Hello {
void morning(String name);
}
在运行期动态创建一个interface实例的方法如下:
1-定义一个InvocationHandler实例,它负责实现接口的方法调用;
2-通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
(1)使用的ClassLoader,通常就是接口类的ClassLoader;
(2)需要实现的接口数组,至少需要传入一个接口进去;
(3)用来处理接口方法调用的InvocationHandler实例。
3-将返回的Object强制转型为接口。