反射定义
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
java代码三个阶段:
- 将.java文件编译为.class文件(字节码),源代码阶段
- 将.class文件加载到内存中,Class类对象阶段
- 在java中创建该类的一个对象,运行时阶段
获取Class对象的三种方式
在java中万物皆对象
- Class.forName(“全类名”),例如:Class.forName(“com.xxx.xxx.Person”),该方法将字节码加载到内存中,常在配置文件中使用(因为传一个字符串就可以得到Class对象);
- 类名.class,例如Class cls=Person.class,该方法在字节码加载到内存后获得Class对象;
- 对象.getClass(),例如:new Person().getClass(),该方法在运行时获取Class对象。
测试代码:
建一个Person类:
IDEA快捷键(Alt+Insert),快速创建构造方法和get、set方法
public class Person {
private String name;
private int age;
public Person() {
}
//快捷键Alt+Insert,生成构造方法,getter、setter
public Person(String name,int age) {
this.name = name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试三种获取Class对象的方法:
IDEA快捷键:Ctrl+Alt+V自动补全返回值
/**
* 获取Class对象的三种方式
*/
public class ReflectDemo {
public static void main(String[] args) throws Exception{
//快捷键Ctrl+Alt+v,自动补全返回值
Class class1=Class.forName("Person");
System.out.println(class1);//class Person
Class class2=Person.class;
System.out.println(class2);//class Person
Person person=new Person("xiaoming",5);
Class class3 = person.getClass();
System.out.println(class3);//class Person
//==判断类地址是否相同
System.out.println(class1==class2);//true
System.out.println(class1==class3);//true
}
}
最后“==”判断两个对象的地址是否相同,结果三种方法指向同一地址,即字节码加载到内存中的地址。
Class对象功能
获取成员变量并对其使用get和set方法
获取成员变量的四种方法:
- Field getField(String name)
返回一个 Field对象,只能是public方法- Field[] getFields()
返回一个Field数组,public方法的集合- Field getDeclaredField(String name)
返回一个Field对象,可以是public\protect\private修饰的方法- Field[] getDeclaredFields()
返回 Field数组,是所有方法的集合
测试:
在原Person类中新创建了a,b,c,d四个成员变量
public class Person {
private String name;
private int age;
public String a;
protected String b;
String c;
private String d;
public Person() {
}
//快捷键Alt+Insert,生成构造方法,getter、setter
public Person(String name,int age) {
this.name = name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
'}';
}
}
分别使用四种方法获取成员变量:
public class ReflectDemo2 {
public static void main(String[] args)throws Exception {
/**
* 获取成员变量
*1 Field getField(String name)
返回一个 Field对象反映的类或接口的 类对象表示的指定公共成员。
*2 Field[] getFields()
返回一个数组包含 Field物体反射的类或接口的 类对象代表所有可访问的公共领域。
*3 Field getDeclaredField(String name)
返回一个对象,反映了 Field指定声明字段的类或接口的 类对象表示。
*4 Field[] getDeclaredFields()
返回 Field物体反射所有字段的类或接口的 类对象表示声明数组。
*/
//创建一个Class对象
Class personClass = Person.class;
//方法一:
//getField()和getFields()方法只能得到public修饰的成员变量
Field f1=personClass.getField("a");
System.out.println(f1);//输出:public java.lang.String Person.a
//Field f2=personClass.getField("b");
//System.out.println(f2); 报错:Exception in thread "main" java.lang.NoSuchFieldException: b
//新建一个Person对象
Person person=new Person();
//测试Field类的get和set方法:
//获取成员变量a的值
Object value=f1.get(person);
System.out.println(value);//输出:null
//为对象设置值,Feild对象调用
f1.set(person,"xiaoming");
System.out.println(person.toString());//输出:Person{name='null', age=0, a='xiaoming', b='null', c='null', d='null'}
//方法二:
Field[] f3 = personClass.getFields();
for(Field f:f3)
{
System.out.println(f);//输出:public java.lang.String Person.a ,只有a是public成员变量
}
//方法三:
//getDeclaredField()和getDeclaredFields()方法,可以获取所有成员变量
Field f4=personClass.getDeclaredField("a");
System.out.println(f4);//输出:public java.lang.String Person.a
Field f5=personClass.getDeclaredField("b");
System.out.println(f5);//输出:protected java.lang.String Person.b
Field f6=personClass.getDeclaredField("c");
System.out.println(f6);//输出:java.lang.String Person.c
Field f7=personClass.getDeclaredField("d");
System.out.println(f7);//输出:private java.lang.String Person.d
//方法四:
Field[]f8=personClass.getDeclaredFields();
for(Field f:f8)
{
System.out.println(f);
/*输出:
private java.lang.String Person.name
private int Person.age
public java.lang.String Person.a
protected java.lang.String Person.b
java.lang.String Person.c
private java.lang.String Person.d
*/
}
//忽略访问权限修饰符的安全检查,也称为暴力反射
f7.setAccessible(true);
//获取私有变量的值
Object value2=f7.get(person);
System.out.println(value2);//在未忽略访问权限修饰符的安全检查时会报错:java.lang.IllegalAccessException: Class ReflectDemo2 can not access a member of class Person with modifiers "private"}
}
当我们获取到Field对象时,我们便可以调用get和set方法来获取和设置成员变量的值(当然和成员变量的修饰符有关)。
获取构造函数并创建对象
和获取成员变量一样,获取构造函数也有四种方法:
- Constructor getConstructor(Class<?>… parameterTypes)
返回一个Constructor对象,public修饰的构造函数。- Constructor<?>[] getConstructors()
返回一个Constructor数组,public修饰的构造函数集合。- ConstructorgetDeclaredConstructor(Class<?>…parameterTypes)
返回一个 Constructor对象(public、private修饰的构造函数)。- Constructor<?>[] getDeclaredConstructors()
返回一个Constructor数组,包含所有构造函数(public、private修饰的构造函数)。
测试:
Person还是之前的Person
import java.lang.reflect.Constructor;
/*
使用反射获取类的构造方法
* */
public class ReflectDemo3 {
public static void main(String[] args)throws Exception {
Class cls=Person.class;
Constructor constructor1=cls.getConstructor();
System.out.println(constructor1);//public Person()
Constructor constructor2=cls.getConstructor(String.class, int.class);
System.out.println(constructor2);//public Person(java.lang.String,int)
//创建对象:
//constructor2.newInstance()返回值为Object,在本例中实际就是一个Person对象
System.out.println(constructor2.newInstance("张三",12));//Person{name='张三', age=12, a='null', b='null', c='null', d='null'}
//创建空参对象,可以直接调用Class对象的newInstance()方法
System.out.println(cls.newInstance());//Person{name='null', age=0, a='null', b='null', c='null', d='null'}
}
}
这里只测试了前两种方法,获取构造函数当然是为了初始化对象,在示例中展示了两种创建对象的方法,一种是使用Constructor对象创建的Person对象,另一种是使用Class对象构造的Person对象,这种方式适用于无参构造函数。
获取成员方法并使用该方法
通过上面的学习,我想我们能更轻松的掌握如何获取成员方法以及如何使用该方法了,下面来直接做展示:
同样有四种方法获取成员方法:
- Method getDeclaredMethod(String name, 类<?>… parameterTypes)
返回一个Method 对象反映指定声明方法的类或接口的 类对象表示。- Method[] getDeclaredMethods()
返回一个数组包含 方法物体反射所有声明的方法的类或接口的类对象,代表包括公众、保护,默认(包)的访问,和私有方法,但不包括继承的方法。- Method getMethod(String name, 类<?>… parameterTypes)
返回一个方法对象反映的类或接口的 类对象表示的指定公共成员方法。- Method[] getMethods()
返回一个数组包含 方法物体反射所有的类或接口的类对象表示的公共方法,包括那些由类或接口的超类和超接口继承的声明。
测试:
import java.lang.reflect.Method;
/*
使用反射获取类的成员方法
* */
public class ReflectDemo4 {
public static void main(String[] args)throws Exception {
Class cls=Person.class;
Method method1=cls.getMethod("eat");
System.out.println(method1);//public void Person.eat()
method1.invoke(new Person());//执行eat()方法,输出:eat...
Method[] methods=cls.getMethods();
for(Method method:methods){
System.out.println(method.getName());//获取所有方法,并输出方法名
/*输出:toString
getName
setName
eat
eat
getAge
setAge
wait
wait
wait
equals
hashCode
getClass
notify
notifyAll*/
}
Method method2=cls.getMethod("eat",String.class);
method2.invoke(new Person(),"noodles");//执行eat(String food),方法,输出:eat noodles
}
}
该测试展示了如何获取成员方法,以及如何执行该方法(invoke()方法)。
用反射实现框架实例
在不改动代码,只改动配置文件的情况下就可以创建任意类的对象,执行该类的任意方法
步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建对象
- 执行方法
Person类已经含有一个eat()方法
创建一个Student类,创建一个Sleep()方法:
public class Student {
public Student() {
}
public void Sleep()
{
System.out.println("Sleep...");
}
}
创建一个配置文件命名为pro.properties:
className=Person
methodName=eat
创建一个Frame类,实现通过加载配置文件,来执行配置文件中的类和方法:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
import java.io.InputStream;
public class Frame {
public static void main(String[] args)throws Exception {
//1.加载配置文件
//1.1创建properties对象
Properties properties=new Properties();
//1.2加载配置文件,转化为一个集合
//1.2.1获取class目录下的配置文件
ClassLoader classLoader=Frame.class.getClassLoader();//Frame.class:获取Frame类的字节码文件;getClassLoader()方法获取该字节码的类加载器
InputStream is = classLoader.getResourceAsStream("pro.properties");//找到该文件对应的字节流
properties.load(is);//加载该文件信息
//2.获取配置文件中定义的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//3.加载“className”进内存
Class cls = Class.forName(className);
//4.创建类的对象
Constructor constructor = cls.getConstructor();
Object obj = constructor.newInstance();
//5.执行methodName对应的方法
Method method = cls.getMethod(methodName);
method.invoke(obj);
}
}
执行结果:
将配置文件改为:
className=Student
methodName=Sleep
执行结果为:
总结
本文内容主要来源于B站视频的学习笔记,建议去看视频。
介绍了Java反射原理、获取类对象的三种方式、如何获取类成员变量、构造方法、成员方法,最后通过一个“框架”案例介绍了Java反射的用途。