java反射
反射的概述
反射的作用就是可以将一个类中的成员变量 成员方法 构造方法给获取处理,并对他们进行操作
1.利用反射可以获取这个类中所有可以调用的方法并展示出来。2.利用反射可以获取这个方法的所有形参并展示出来
但是我们获取的时候不是从java文件中获取的,而是从Class(字节码文件)中获取的
获取Class对象的三种方式
- Class类java已经定义,是用来描述字节码文件的
这三种获取字节码文件对象的方式顺次适用于下面的三个阶段
源代码阶段:是从java文件编译 成了class文件,但class文件还没有被加载到内存中的阶段
三种方式的代码演示
- Student类
package com.cook.reflect;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @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;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
package com.cook.reflect;
public class MyReflect1 {
public static void main(String[] args) throws ClassNotFoundException {
/*
获取Class的对象的三种方式
1.Class.forName("全类名") 此时的字节码文件还没有被加载到内存中
2.类名.class 此时字节码文件被加载(类的加载)但是还没有创建对象
3.对象.getClass() 此时的对象已经被创建
*/
//1.方式一(编译阶段)
final Class<?> clazz1 = Class.forName("com.cook.reflect.Student");//此时Student还每被加载
System.out.println(clazz1);//class com.cook.reflect.Student
//2.方式二类的加载阶段
final Class<Student> clazz2 = Student.class;
System.out.println(clazz2);//class com.cook.reflect.Student
//3.方式三对象被创建阶段
Student s = new Student();
final Class<? extends Student> clazz3 = s.getClass();
System.out.println(clazz3);//class com.cook.reflect.Student
}
}
Class类:描述类的字节码文件的对象
Java中万物皆对象,对于构造方法 成员方法 成员变量都可以用对象来描述
反射获取构造方法
万物皆对象,变量 方法 构造方法 字节码文件都可以用对象来描述
declared私有的公有的都可以获取
反射的作用是从类中获取类中的构造方法 成员变量 成员方法。但我们是从字节码类Class中获取的
注意:
在获取类的构造方法的方法中有一类是获取特定的构造方法,其中这个方法的参数了Class<?>...parameterTypes,这个参数是输入字节码文件对象的可变参数
这个参数对应了构造方法的参数,即输入的是对应构造方法的参数的字节码文件对象,返回的则是该构造方法
代码演示
- Student1类
package com.cook.reflect;
public class Student1 {
private String name;
private int age;
//1.空参构造方法
public Student1() {
}
//2.一个参数的构造方法
public Student1(String name){
this.name = name;
}
//3.所保护的构造方法
protected Student1(int age){
this.age = age;
}
//4.私有的构造方法
private Student1(String name,int age){
this.name = name;
this.age = age;
}
/**
* 获取
* @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;
}
public String toString() {
return "Student1{name = " + name + ", age = " + age + "}";
}
}
package com.cook.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
public class MyReflect2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
/*
Class类中用于获取构造方法的方法
1.Construcotr<?>[]getConstructors()
2.Construcotr<?>[]getDeclaredConstructors()
3.Construcotr<?>[]getConstructor(Class<?>...parameterTypes)参数为字节码文件对象
4.Construcotr<?>[]getDeclaredConstructor(Class<?>...parameterTypes)
Constructor类中用于创建对象的方法:
T newInstance(Object...intarges)
setAccessible(boolean flag)
*/
//获取Student1类的字节码文件对象
final Class<?> clazz = Class.forName("com.cook.reflect.Student1");
//1.获取所有公共权限的构造方法
/*final Constructor<?>[] con1 = clazz.getConstructors();//将获取的公共的构造方法放在数组中
for (Constructor<?> con : con1) {
System.out.println(con);
}*/
//2.获取所有权限的构造方法并放在数组中(包含公共的和非公共的)
final Constructor<?>[] con2 = clazz.getDeclaredConstructors();
for (Constructor<?> c : con2) {
System.out.println(c);
}
//3.获取公共的单个构造方法
//参数传入需要获取的构造方法的字节码文件对象
final Constructor<?> con3 = clazz.getConstructor();
System.out.println(con3);//public com.cook.reflect.Student1()
final Constructor<?> con4 = clazz.getConstructor(String.class);
System.out.println(con4);//public com.cook.reflect.Student1(java.lang.String)
//4.获取有权限的单个构造方法(非公共的和公共的都可以获取)
//基本数据类型也有字节码文件
final Constructor<?> con5 = clazz.getDeclaredConstructor(String.class, int.class);
System.out.println(con5);//private com.cook.reflect.Student1(java.lang.String,int)
final Constructor<?> con6 = clazz.getDeclaredConstructor(int.class);
System.out.println(con6);//protected com.cook.reflect.Student1(int)
System.out.println("------------------------------------------------------");
//获取到构造方法后,我们可以根据构造方法获取一些构造方法的信息
//1.获取构造方法的权限修饰符(是以整数的形式来体现的,具体对应查看帮助文档)
final int modifiers = con6.getModifiers();
System.out.println(modifiers);//4 4==PROTECTED
//2.获取构造方法的参数
final Parameter[] parameters = con6.getParameters();//获取该构造方法的参数并放在参数的对象数组中
for (Parameter parameter : parameters) {
System.out.println(parameter);//int arg0(idea底层获取参数并展示就是这样实现的)
}
//3.用获取的构造方法去创建该类的对象(con5的构造方法是私有的不能直接创建对象,将会报错)
// final Student1 stu =(Student1) con5.newInstance(23);//要求传入的参数和该构造方法的参数一致
//想使用私有的构造方法创建对象的解决方法
con5.setAccessible(true);//暴力反射:临时取消这个构造方法的权限校验
//注意newInstance参数和构造方法不一致将会出现异常
final Student1 stu = (Student1) con5.newInstance("张思",23);//向下转型为学生类
System.out.println(stu);//Student1{name = 张思, age = 23}
}
}
我们获取构造方法的权限修饰符获取参数的作用?
为什么idea怎么知道不能调用私有的获取所保护的构造方法呢?其实在idea的底层就是用反射获取了构造方法并获取了权限修饰符所对应的常量来加以判断的
反射获取成员变量
- Student3类
package com.cook.reflect;
public class Student3 {
private String name;
private int age;
public String gender;
public Student3() {
}
public Student3(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
/**
* 获取
* @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 String getGender() {
return gender;
}
/**
* 设置
* @param gender
*/
public void setGender(String gender) {
this.gender = gender;
}
public String toString() {
return "Student3{name = " + name + ", age = " + age + ", gender = " + gender + "}";
}
}
package com.cook.reflect;
import java.lang.reflect.Field;
public class MyReflect3 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
/*
Class类中用于获取成员变量的方法
Field[]getFields() 返回该类中公共成员变量对象的数组
Field[]getDeclaredFields() 返回该类中所有成员变量对象的数组
Field[]getField(String name) 返回该类中单个公共成员变量对象的数组
Field[]getDeclaredField(String name) 返回该类中单个成员变量对象的数组
Filed类中用于创建对象的方法:
void set(Object obj,Object value)赋值
Object get(Object obj) 获取值
*/
//获取字节码文件对象
final Class<?> clazz = Class.forName("com.cook.reflect.Student3");
//1.获取所有公共的成员变量并放在数组中
final Field[] f1 = clazz.getFields();
for (Field field : f1) {
System.out.println(field);//public java.lang.String com.cook.reflect.Student3.gender
}
//2.获取所有的成员变量包含私有的
final Field[] f2 = clazz.getDeclaredFields();
for (Field field : f2) {
System.out.println(field);
}
//3.获取单个公有的成员变量(不能获取私有,操作获取私有将会报错)
//参数写变量名
final Field gender = clazz.getField("gender");
System.out.println(gender);//public java.lang.String com.cook.reflect.Student3.gender
//4.获取单个私有的的成员变量
final Field name = clazz.getDeclaredField("name");
System.out.println(name);//private java.lang.String com.cook.reflect.Student3.name
//获取到成员变量后,我们可以根据成员变量获取他的一系列信息
System.out.println("----------------------------------------------------------");
//1.获取成员变量的权限修饰符
final int modifiers = name.getModifiers();//获取该成员变量的权限修饰符
System.out.println(modifiers);//2
//2.获取成员变量的变量名
final String name1 = name.getName();
System.out.println(name1);//name
//3.获取其数据类型
final Class<?> type = name.getType();
System.out.println(type);//class java.lang.String
//4.获取成员变量记录的值(这和对象有关)
//4.1.先创建一个对象
Student3 st = new Student3("张三",23,"男");
//final Object o = name.get(st);//意思是获取st这个对象里面name这个成员变量记录的值
//但是上面会报错 a member of class com.cook.reflect.Student3 with modifiers "private"
//因为name是私有的不能直接访问
//解决方案
name.setAccessible(true);//临时放开name的private权限
final Object o = name.get(st);//也可以强转成String
System.out.println(o);//张三
//5.修改成员变量所记录的值
//参数意义:要把哪个对象里面的name,修改成什么
name.set(st,"李四");
System.out.println(st);//Student3{name = 李四, age = 23, gender = 男}
}
}
反射获取成员方法
parameterTypes:参数类型
学习感悟:我们从字节码文件对象中获取的方法 构造方法 和变量 此时我们仅仅是进行获取,这时候我们是不用管他们的权限修饰符的。而当我们用使用私有的这些东西的时候,这时候就受到权限修饰符的限制了,需要进行处理
- Student4类
package com.cook.reflect;
import java.io.IOException;
public class Student4 {
private String name;
private int age;
public Student4() {
}
public Student4(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @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;
}
public String toString() {
return "Student4{name = " + name + ", age = " + age + "}";
}
private void eat(String something) throws IOException,NullPointerException,ClassCastException {//和下面的方法重载
System.out.println("在吃"+something);
}
private void eat (String something ,int a){
System.out.println("在吃"+something);
}
public void sleep(){
System.out.println("睡觉");
}
}
package com.cook.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
//反射获取成员方法
public class MyReflect4 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
/*
Class类中用于获取成员方法的方法:
Method getMothods() 返回所有公共成员方法对象数组,包含继承的
Method getDeclaredMothods() 返回所有成员方法对象数组,不包含继承的
Method getMothod(String name,Class<?>...parameterTypes) 返回单个公共成员方法对象
Method getDeclaredMothod(String name,Class<?>...parameterTypes) 返回单个成员方法对象
Method类中用于创建对象的方法:
Object invoke(Object obj,Object ...args)
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)
*/
//获取类字节码文件的对象
final Class<?> clazz = Class.forName("com.cook.reflect.Student4");
//1.获取所有公共方法的对象(包含父类中所有的公共方法)
/*final Method[] methods1 = clazz.getMethods();
for (Method method : methods1) {
System.out.println(method);
}*/
//2. 获取所有的方法的对象(不能获取父类的,但是可以获取本类私有的)
/* final Method[] methods2 = clazz.getDeclaredMethods();
for (Method method : methods2) {
System.out.println(method);
}*/
//3.获取单个公共方法
//参数一:方法名
//参数二:方法的数据类型的字节码文件对象(为了区分方法重载)
// final Method eat = clazz.getMethod("eat", String.class);//获取重载的第一个eat方法
//System.out.println(eat);这个代码会报错,getMethod不能获取私有的
///4.获取单个成员方法的对象
final Method eat1 = clazz.getDeclaredMethod("eat", String.class);
System.out.println(eat1);//private void com.cook.reflect.Student4.eat(java.lang.String)
//利用获取的方法,获取方法上的信息
//1.获取方法的修饰符
final int modifiers = eat1.getModifiers();
System.out.println(modifiers);//2
//2.获取方法的名字
final String name = eat1.getName();
System.out.println(name);//eat
//3.获取方法的形参
final Parameter[] parameters = eat1.getParameters();//获取方法的所有参数
for (Parameter parameter : parameters) {
System.out.println(parameter);//java.lang.String arg0
}
///4.获取方法抛出的异常
final Class<?>[] exceptionTypes = eat1.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
System.out.println(exceptionType);
/*
class java.io.IOException
class java.lang.NullPointerException
class java.lang.ClassCastException
*/
}
//重点:方法运行(运行我们获取的方法)
/* Object invoke(Object obj,Object ...args)
参数一:用obj对象调用该方法(调用者)
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(如果没有就不写)*/
Student4 s = new Student4();
//参数一s:表示方法的调用者
//参数二:“香蕉” 表示调用该方法时实际传递的参数
eat1.setAccessible(true);//临时关闭该方法的访问权限
eat1.invoke(s,"香蕉");//在吃香蕉(如果运行的方法有返回值视情况而定,用变量进行接收即可)
}
}
综合练习1(保存任意对象数据)
- 老师类
package com.cook.reflect;
public class Techer {
private String name;
private double salary;
public Techer() {
}
public Techer(String name, double salary) {
this.name = name;
this.salary = salary;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return salary
*/
public double getSalary() {
return salary;
}
/**
* 设置
* @param salary
*/
public void setSalary(double salary) {
this.salary = salary;
}
public String toString() {
return "Techer{name = " + name + ", salary = " + salary + "}";
}
}
- 学生类
package cc;
public class Student5 {
private String name;
private int age;
private char gender;
private float height;
private String boody;
public Student5() {
}
public Student5(String name, int age, char gender, float height, String boody) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.boody = boody;
}
/**
* 获取
* @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 float getHeight() {
return height;
}
/**
* 设置
* @param height
*/
public void setHeight(float height) {
this.height = height;
}
/**
* 获取
* @return boody
*/
public String getBoody() {
return boody;
}
/**
* 设置
* @param boody
*/
public void setBoody(String boody) {
this.boody = boody;
}
public String toString() {
return "Student5{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", boody = " + boody + "}";
}
}
- 主程序类
package cc;
import com.cook.reflect.Student5;
import com.cook.reflect.Techer;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
public class MyReflectTest5 {
public static void main(String[] args) throws IOException, IllegalAccessException {
//对任意一个对象,都可以把对象的字段和值保存到文件中去
Student5 s = new Student5("张三",23,'男',176.2f,"睡觉");
Techer t = new Techer("张老师",10000.0);
saveObject(s);
}
//把对象所有的成员变量和值保存到本地文件中
//思路:获取类里面所有的信息,获取之后在做其他的业务逻辑
public static void saveObject(Object obj) throws IllegalAccessException, IOException {
final Class<?> clazz = obj.getClass();//获取传入类的字节码文件对象
//创建IO流
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\ideaprojects\\myThread1\\src\\c.txt"));//使用相对路径
//1.获取该类的所有属性(包含私有的)
final Field[] fields = clazz.getDeclaredFields();
//2.根据属性获取其并将其写入文件中
for (Field field : fields) {
field.setAccessible(true);//打开属性的权限
//获取成员变量的名字
final String name = field.getName();
//获取成员变量对应的值
final Object value = field.get(obj);//这时候我们不知道值的数据类型,不能向下转型
//3.写出数据
bw.write(name +"="+value);//一次写入一个字符串
bw.newLine();//写入换行
}
bw.close();
}
}
备注:该题的路径本来想写相对路劲,但是还是弄错,所以实际是使用绝对路径的。相对路径没有弄懂,在此备注
对java中相对路径的总结:相对路径是相对当前项目的路径,相对路径名不包含项目名。我们以后在idea中创建文件的时直接创建在src目录下
综合练习2(利用反射动态的创建对象和运行方法)
备注:关于Properties类(配置文件还没有学习过,在此备注)
配置文件信息
- 学生类
package com.cook.reflect;
public class Student6 {
private String name;
private int age;
public Student6() {
}
public Student6(String name, int age) {
this.name = name;
this.age = age;
}
public void study(){
System.out.println("学生正在学习!");
}
/**
* 获取
* @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;
}
public String toString() {
return "Student6{name = " + name + ", age = " + age + "}";
}
}
- 教师类
package com.cook.reflect;
public class Teacher6 {
private String name;
private double salary;
public Teacher6() {
}
public Teacher6(String name, double salary) {
this.name = name;
this.salary = salary;
}
public void teach(){
System.out.println("老师在教书");
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return salary
*/
public double getSalary() {
return salary;
}
/**
* 设置
* @param salary
*/
public void setSalary(double salary) {
this.salary = salary;
}
public String toString() {
return "Teacher6{name = " + name + ", salary = " + salary + "}";
}
}
主程序类
package com.cook.reflect;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
//程序在运行的时候创建配置文件中类的对象,并调用配置文件中的方法
//以后要调用其他类里面的方法测试类中的代码不需要修改,只用修改配置文件即可
public class MyReflect6 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//反射可以跟配置文件结合的方式,动态的创建对象,并调用方法
//1.获取配置文件中的内容
Properties pp= new Properties();//创建配置文件的对象
FileInputStream fis = new FileInputStream("src\\pop.properties");
pp.load(fis);//从输出流中读取数据的集合中
fis.close();
//2.获取属性集合中的全类名和方法名
final String className = (String)pp.get("classname");//获取全类名
final String mothodName = (String)pp.get("method");//获取方法名
//3.利用反射创建对象并运行方法
//3.1获取字节码文件对象
final Class<?> clazz = Class.forName(className);
//3.2获取构造方法
final Constructor<?> con = clazz.getDeclaredConstructor();//获取无参构造方法
//3.3.创建对象
final Object o = con.newInstance();
//4.获取成员方法
final Method method = clazz.getDeclaredMethod(mothodName);
//5.运行方法
method.setAccessible(true);//临时关闭访问权限
method.invoke(o);
}
}
在这种情况下,我们不需要修改程序,只用修改我们的属性文件里面的内容,就可以实现动态的调用不同类中的不同方法
属性文件中记录:需要实现的这个类(全类名)调用该类的方法(该类的方法名)
动态创建对象和运行方法需要达到:配置文件中记录的是哪个类就创建哪个类的对象,记录的是哪个方法就运行哪个方法