为了面试而战-反射
今天来学习新的内容,反射。
反射
什么是反射?官方的解释是反射允许对封装类的字段、方法和构造函数的信息进行编程访问。这里的字段就是成员变量,方法就是成员方法,构造函数就是构造方法。
一个类的里面,常用的就是成员变量、成员方法、构造方法,反射就可以想成是一个人,他可以把这三个东西都获取到,并对他们进行操作。那获取出来有什么用呢?很有用的,比如IDEA中的自动提示功能就是反射,我们创建了一个对象,IDEA就可以把这个对象能调用的成员方法都获取出来并进行展示。还有我们在创建对象或者调用方法时,形参给忘了,按一下ctrl+p就可以获取提示,这也是反射,获取所有的形参并展示出来。说白了,反射就是可以从类里面拿东西,拿什么,就是常用的成员方法、成员变量、构造方法。
利用反射我们可以把成员变量获取出来,就可以得到这个变量的所有信息,比如修饰符、名字、数据类型、赋值或者已经记录的值。利用反射还可以获取构造方法,也能得到所有信息,扒得干干净净, 比如名字、修饰符、形参,甚至可以用获取出来的方法创建对象。也可以获取成员方法,同样干干净净,可以获取到修饰符、名字、形参、返回值,甚至方法抛出的异常,方法上的注解,运行获取的方法。在反射面前,一切都是赤裸裸的。
以上操作可以分成两类,一是获取,而是解剖。获取的时候不是从java文件中获取,而是从字节码文件.class中获取。我们要先学习如果获取.class字节码文件对象。再去从字节码文件对象中获取字段、方法以及构造方法。
获取class对象的三种方式
- Class.forName("全类名"); Class是类名,java定义好了这个类,用来描述字节码文件。forName()是静态方法。
- 类名.class;
- 对象.getClass(); getClass()是定义在Object中,所有对象都可以调用。
那这三种方式什么时候用呢?这三种方式就对应了java里面的三种不同的阶段,或者说我想创建一个对象,是要经历三个阶段的。
第一个阶段:先编写java文件,把他编译成class文件,这个阶段没有把代码加载到内存中,是在硬盘中操作,这个阶段称为源代码阶段。在这个阶段会通过第一种方式获取字节码文件对象。
第二个阶段:运行代码,会把.class文件加载到内存中,这个阶段称为加载阶段,用第二种方式。
第三个阶段:我要创建这个类的对象,此时称为运行阶段,用第三种方式。
看一下代码实现
public class MyReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException {
//第一种方式
//全类名:包名+类名
//最为常用
Class clazz1 = Class.forName("com.itheima.myreflect1.Student");
//第二种
//一般更多的是当做参数,之前学多线程的时候,同步代码块synchronized(对象.class)
Class clazz2 = Student.class;
//第三种
//当我们已经有了这个类的对象时,才可以使用
Student s = new Student();
Class clazz3 = s.getClass();
}
}
Java中万物皆对象,class对象就是Class类。构造方法也可以看成是对象,是Constructor类。成员变量是Field类对象,成员方法是Method类的对象。
利用反射获取构造方法
public class MyReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
/*
Class类中用于获取构造方法的方法
Constructor<?>[] getConstructors() 返回所有公共构造方法对象的数组
Constructor<?>[] getDeclaredConstructors() 返回所有构造方法对象的数组
Constructor<T> getConstructor(Class<?>... parameterTypes) 返回单个公共构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回单个构造方法对象
Constructor类中用于创建对象的方法
T newInstance(Object... initargs) 根据指定的构造方法创建对象
setAccessible(boolean flag) 设置为true,表示取消访问检查
*/
//1.获取class字节码文件对象
Class clazz = Class.forName("com.itheima.myreflect1.Student");
//2.获取构造方法
/*
获取所有公共构造方法
Constructor[] cons = clazz.getConstructors();
for(Constructor con : cons){
System.out.println(con);
}
*/
//获取所有构造方法
//Constructor[] cons = clazz.getDeclaredConstructors();
//获取单个
Constructor con1 = clazz.getDeclaredConstructor();//空参构造
//获取有参构造函数,形参里的参数类型要和类中的构造方法形参类型一致
Constructor con2 = clazz.getDeclaredConstructor(String.class);
Constructor con3 = clazz.getDeclaredConstructor(String.class,Integer.class);
//获取权限修饰符
int modifiers = con3.getModifiers();//private - 2 public - 1 在帮助文段中能查到,搜索常量字段值,就有权限修饰符所对应的数字。修饰权限符的应用场景:IDEA底层会通过反射获取到权限修饰符,如果是私有的就不会给你提示,不让调用
Parameter[] parameters = con3.getParameters();
//拿到构造方法了,可以创建对象。con3获取的有参构造,所以创建对象的时候也得带参数
//表示临时取消权限校验,不做这一步会报错。因为这个构造是private修饰的,之前con3.getDeclaredConstructor()只是能让我们看见这个构造,并不能创建对象。
//这就叫暴力反射,你已经藏起来变成私有的,但我还能给你拿出来。
con4.setAccessible(true);
Student stu = (Student)con3.newInstance("张三",23);
利用反射获取成员变量
public class MyReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
/*
Class类中用于获取成员变量的方法
Field[] getFields(): 返回所有公共成员变量对象的数组
Field[] getDeclaredFields(): 返回所有成员变量对象的数组
Field getField(String name): 返回单个公共成员变量对象
Field getDeclaredField(String name):返回单个成员变量对象
Field类中用于创建对象的方法
void set(Object obj, Object value):赋值
Object get(Object obj) 获取值
*/
//1.还是要先获取字节码文件的对象,因为每一个要获取的东西都是在字节码文件里的
Class clazz = Class.forName("com.itheima.myreflect1.Student");
//2.获取成员变量
//获取公共的成员变量
Field[] fields = clazz.getFields();
//获取所有的成员变量
Field[] fieldsD = clazz.getDeclaredFields();
//获取公共单个成员变量
Field gender = clazz.getField("gender");
//获取单个成员变量
Filed name = clazz.getField("name");
//获取权限修饰符
int modifiers = name.getModifiers();
//获取成员变量名字
String n = name.getName();
//获取成员变量数据类型
Class<?> type = name.getType();
//获取成员变量记录的值
//和对象有关,要先创建一个对象
Student s = new Student("zhangsan",23,"男");
name.setAccessible(true);//临时取消访问权限
Object value = name.get(s);
//修改对象里记录的值
name.set(s,"lisi");
}
}
利用反射获取成员方法
public class MyReflectDemo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
/*
Class类中用于获取成员变量的方法
Field[] getFields(): 返回所有公共成员变量对象的数组
Field[] getDeclaredFields(): 返回所有成员变量对象的数组
Field getField(String name): 返回单个公共成员变量对象
Field getDeclaredField(String name):返回单个成员变量对象
Field类中用于创建对象的方法
void set(Object obj, Object value):赋值
Object get(Object obj) 获取值
*/
//1.获取class字节码文件的对象
Class clazz = Class.forName("com.itheima.myreflect3.Student");
//2.获取所有方法对象
//(包括父类中所有的公共方法)
Method[] methods = clazz.getMethods();
//获取不到父类中的方法
Method[] methods = clazz.getDeclaredMethod();
//获取单个方法
//一定要加参数类型,因为方法可能有重载的情况,不加参数类型不知道是哪个方法
Method m = clazz.getDeclaredMethod("eat",String.class);
// 获取方法的修饰符
int modifiers = m.getModifiers();
System.out.println(modifiers);
// 获取方法的名字
String name = m.getName();
System.out.println(name);
// 获取方法的形参
Parameter[] parameters = m.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter);
}
//获取方法抛出的异常
Class[] exceptionTypes = m.getExceptionTypes();
//方法运行
/*Method类中用于创建对象的方法
Object invoke(Object obj, Object... args):运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)*/
Student s = new Student();
//参数一s:方法的调用者
//参数二“汉堡包”:调用的时候传入的实际参数
m.setAccessible(true);
Onject result = m.invoke(s,"汉堡包");
反射的作用
学习完反射的API,我们来看一下反射的作用是什么
- 获取一个类里面所有的信息,获取到之后,再执行其他的业务逻辑
- 结合配置文件,动态的创建对象并调用方法
用两个练习来说明作用:
保存信息
对于任意一个对象,都可以把对象所有的字段名和值,报错到文件中去。
public class Student {
private String name;
private int age;
private char gender;
private double height;
private String hobby;
public Student() {
}
public Student(String name, int age, char gender, double height, String hobby) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.hobby = hobby;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
/**
* 获取
* @return gender
*/
public char getGender() {
return gender;
}
/**
* 设置
* @param gender
*/
public void setGender(char gender) {
this.gender = gender;
}
/**
* 获取
* @return height
*/
public double getHeight() {
return height;
}
/**
* 设置
* @param height
*/
public void setHeight(double height) {
this.height = height;
}
/**
* 获取
* @return hobby
*/
public String getHobby() {
return hobby;
}
/**
* 设置
* @param hobby
*/
public void setHobby(String hobby) {
this.hobby = hobby;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}";
}
}
public class MyReflectDemo {
public static void main(String[] args) throws IllegalAccessException, IOException {
/*
对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去
*/
Student s = new Student("小A",23,'女',167.5,"睡觉");
Teacher t = new Teacher("播妞",10000 );
saveObject(s);
}
//把对象里面所有的成员变量名和值保存到本地文件中
public static void saveObject(Object obj) throws IllegalAccessException, IOException {
//1.获取字节码文件的对象
Class clazz = obj.getClass();
//创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\a.txt"));
//2.获取所有的成员变量
Field[] fields = clazz.getDeclaredField();
for(Field field : fields){
//不知道哪个是private
field.setAccessible(true);
//获取成员变量的名字
String name = field.getName();
//获取成员变量的值
Object value = field.get(obj);
bw.write(name + "=" + value);
bw.newLine();
}
bw.close();
}
}
练习二
跟配置文件结合动态创建
反射可以跟配置文件结合的方式,动态的创建对象,并调用方法
classname=com.itheima.myreflect6.Teacher //全类名 method=teach //方法名 //表示程序在运行的过程中我要去创建上面这个类的对象,并调用下面这个方法。在以后我们要想调用其他类里的方法,测试类里的代码是不需要改的,只需要修改配置文件信息即可。动态的获取全类名,动态的获取方法,获取到哪个类就创建哪个类的对象,获取到哪个方法就运行哪个方法
public class MyReflectDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1.读取配置文件中的信息
Properties prop = new Properties();
//要把配置文件中的信息加载到集合中
//先定义IO流
FileInputStream fis = new FileInputStream("myreflect\prop.properties");
prop.load(fis);
fis.close();
//2.获取全类名和方法名
String className = (String)prop.get("classname");
String methodName = (String) prop.get("method");
//3.利用反射创建对象并运行方法
Class clazz = Class.forName(className);
//获取构造方法
Constructor con = clazz.getDeclaredConstructor();
Object o = con.newInstance();
//获取成员方法并运行
Method method = clazz.getDeclaredMethod();
method.setAccessible(true);
method.invoke(o);
}
}
总结
学完反射,我们要知道以下内
- 反射的作用
- 获取任意一个类中的所有信息
- 结合配置文件动态创建对象
- 获得class字节码文件对象的三种方式
- Class.forName("全类名");
- 类名.class
- 对象.getClass();
- 如何获取构造方法、成员方法、成员变量
- get:获取
- set:设置
- Constructor:构造方法
- Parameter:参数
- Field:成员变量
- Modifiers:修饰符
- Method:方法
- Declared:私有的
这些就是我学到的反射的基础知识,希望能够帮助大家。
#你觉得今年春招回暖了吗##牛客解忧铺##牛客在线求职答疑中心##23届找工作求助阵地#我是一个转码的小白,平时会在牛客中做选择题,在做题中遇到不会的内容就会去找视频或者文章学习,以此不断积累知识。这个专栏主要是记录一些我通过做题所学到的基础知识,希望能对大家有帮助
查看25道真题和解析