文章目录
一. 问题背景
在回顾动态代理的时候,涉及了反射知识,今天再来回顾一下Java的反射。
二. 反射
2.1 Java代码经历的3个阶段
Java代码经历3个阶段:源码阶段;class类对象阶段;运行时阶段
解释:如上图所示,首先我们编写的Java代码(是以.java文件保存的)是不能直接被jvm识别的。Java编译器会将java代码编译成字节码(字节码技术),此时是以.class文件保存。编译完成以后,如果程序运行时需要用到Person这个类,那么jvm才会将Person加载进虚拟机的方法区(懒加载)。加载完成后,此时可以得到成员变量数组fields;构造方法数组cons;成员方法数组methods。它们分别存储Person类中所有的成员变量、构造方法、成员方法。当我们需要new Person的时候,将会使用指定的构造器对象创建对象。此时创建出来的对象才会被放入堆中。
2.2 反射到底是什么?
反射,其实是将类的各个组成部分封装为其他对象。有成员变量数组、构造器数组、成员方法数组。
2.3 反射的好处
- 在程序运行的过程中,操作这些对象
- 可以解耦,提高程序的可扩展性
2.4 获取class对象的3种方式
有3种获取class对象的方式:class.forName("全类名");
;类名.class
;对象.getClass();
-
Class.forName("全类名");
: 将字节码文件加载进内存,返回class对象。多用于配置文件、读取文件、加载。 -
类名.class
: 通过类名的class属性获取。多用于参数传值。 -
对象.getClass()
: getClass()方法在Object类中定义着。多用于对象的获取字节码的方式
以上3中方式,其实都是各自对应着一个java代码经历的阶段,如下:
例子: 获取class对象的3种方式
Person.java
package com.atguigu;
public class 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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Main.java
@Test
public void test1() throws Exception{
//1.通过Class.forName("全类名")获取class对象
Class cls1 = Class.forName("com.atguigu.Person");
System.out.println(cls1);
//2.通过类名.class获取class对象
Class cls2 = Person.class;
System.out.println(cls2);
//3.通过对象.getClass()获取class对象
Person person = new Person();
System.out.println(person.getClass());
}
测试效果:
注意:同一个类只会被加载1次,通过3种方式创建出来的对象都是同一个class对象
2.5 获取成员变量
常用的4种获取成员变量的方法,它们的功能会有些许区别:
-
Field[] getFields();
获取所有public修饰的成员变量 -
Field getField(String name);
获取指定名称的public修饰的成员变量 -
Field[] getDeclaredFields();
获取所有的成员变量 -
Field getDeclaredField(String name);
获取指定的成员变量
例子:
Person.java
package com.atguigu;
public class Person {
public 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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Main.java
@Test
public void test2() throws NoSuchFieldException, IllegalAccessException {
Person person = new Person("zhangsan", 18);
Class cls = person.getClass();
Field[] fields = cls.getFields();//获取所有public修饰的成员变量
System.out.println(Arrays.asList(fields));
for (Field f : fields) {
System.out.println(f.get(person));
}
System.out.println("-------------");
Field name = cls.getField("name");//获取指定名称的public修饰的成员变量
System.out.println(name);
System.out.println(name.get(person));
System.out.println("-------------");
Field[] declaredFields = cls.getDeclaredFields();//获取所有的成员变量
System.out.println(Arrays.asList(declaredFields));
for (Field f : declaredFields) {
if(!f.isAccessible()){
f.setAccessible(true);
}
System.out.println(f.get(person));
}
System.out.println("-------------");
Field age = cls.getDeclaredField("age");
System.out.println(age);
age.setAccessible(true);
System.out.println(age.get(person));
}
测试效果:
注意:当成员变量是private修饰时,使用class对象获取到的成员变量是不可访问的。若要访问我们需要设置field.setAccessible(true);
。常常称这种方式为暴力反射。构造器与成员方法被private修饰时,也是需要设置field.setAccessible(true);
才可以访问。
2.6 获取构造器
方法如下:
-
Constructor<?>[] getConstructors();
获取所有public修饰的构造器 -
Constructor<?> getConstructor(Class<?>...parameterTypes);
获取指定的public修饰的构造器 -
Constructor<?> getDeclaredConstructors();
获取所有的构造器 -
Constructor<?> getDeclaredConstructor(Class<?>...parameterTypes);
获取指定的构造器
例子:
@Test
public void test3() throws NoSuchMethodException {
Class cls = Person.class;
Constructor[] constructors = cls.getConstructors();//获取所有的public修饰的构造器
System.out.println(Arrays.asList(constructors));
System.out.println("-------------");
Constructor constructor = cls.getConstructor(null);//获取无参构造器
System.out.println(constructor);
System.out.println("-------------");
Constructor[] declaredConstructors = cls.getDeclaredConstructors();//获取所有构造器
System.out.println(Arrays.asList(declaredConstructors));
System.out.println("-------------");
//获取指定的构造器
Constructor declaredConstructor = cls.getDeclaredConstructor(String.class, Integer.class);
System.out.println(declaredConstructor);
}
2.7 获取成员方法
-
Method[] getMethods();
获取所有public修饰的成员方法 -
MethodgetMethod(String name);
获取指定名称的public修饰的成员方法 -
Method[] getDeclaredMethods();
获取所有的成员方法 -
MethodgetDeclaredMethod(String name);
获取指定的成员方法
例子:
@Test
public void test4() throws NoSuchMethodException {
Class cls = Person.class;
Method[] methods = cls.getMethods();//获取所有的public修饰的成员方法
System.out.println(Arrays.asList(methods));
System.out.println("-------------");
Method method = cls.getMethod("getAge");//获取指定的public修饰的成员方法
System.out.println(method);
System.out.println("-------------");
Method[] declaredMethods = cls.getDeclaredMethods();//获取成员方法
System.out.println(Arrays.asList(declaredMethods));
System.out.println("-------------");
Method declaredMethod = cls.getDeclaredMethod("getName");//获取指定的成员方法
System.out.println(declaredMethod);
}
测试结果:
2.8 使用成员变量对象获取值
@Test
public void test6() throws Exception {
Class cls = Person.class;
Field name = cls.getDeclaredField("name");
Person p = new Person("zhangsan", 19);
System.out.println(name.get(p));
}
2.9 使用构造器对象创建对象
@Test
public void test5() throws Exception {
Class cls = Person.class;
Constructor declaredConstructor =
cls.getDeclaredConstructor(String.class, Integer.class);
Object o = declaredConstructor.newInstance("zhangsan", 19);
System.out.println(o);
}
2.10 使用方法对象调用方法
@Test
public void test7() throws Exception {
Class cls = Person.class;
Method m = cls.getDeclaredMethod("getName");
Person p = new Person("zhangsan", 10);
System.out.println(m.invoke(p));
}
2.11 练习:实现一个框架
需求:写一个“框架”,不改变该类的任何代码,可以帮我们创建任意类的对象,并且执行其中任意方法。
分析如何实现:
- 使用配置文件
- 反射
步骤:
-
将需要创建的对象的全类名以及需要执行的方法 定义 在配置文件中
-
在程序中加载读取配置文件
-
使用反射将类文件加载进内存
-
创建对象、执行方法
代码如下:
Main.java
@Test
public void test8() throws Exception {
//1.加载配置文件
Properties pro = new Properties();
ClassLoader classLoader = Main.class.getClassLoader();//获取类加载器
InputStream is = classLoader.getResourceAsStream("pro.properties");//获取并加载配置文件资源
pro.load(is);
//2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.加载该类进内存
Class<?> cls = Class.forName(className);
Object o = cls.newInstance();
Method m = cls.getDeclaredMethod(methodName);
m.invoke(o);
}
A.java
package com.atguigu;
public class A {
public void sayHello() {
System.out.println("This is sayHello method.");
}
}
pro.properties文件位置如下:
pro.properties代码:
className=com.atguigu.A
methodName=sayHello
测试效果: