本节我们主要讲解反射,那么什么是反射呢?
每一个java文件都有对应的class字节码文件,并且有且只有一个,每一个class字节码文件对应生成一个class字节码文件对应的字节码文件对象,当我们拿到这个对象,反过来取使用该类中的成员方法、成员变量、构造方法时,这就叫做反射。
简而言之,反射就是通过字节码文件对象去使用类中的成员。
1: 类加载器
(1)什么是类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
一个类在加载过程中的三部曲:
1. 加载
就是指将class文件读入内存,并为之创建一个Class对象. 任何类被使用时系统都会建立一个Class对象。
2. 连接
验证–是否有正确的内部结构,并和其他类协调一致
准备–负责为类的静态成员分配内存,并设置默认初始化值
解析–将类的二进制数据中的符号引用替换为直接引用
3. 初始化
(2)类的加载时机(掌握)
2.1 创建类的实例
2.2 访问类的静态变量,或者为静态变量赋值
2.3 调用类的静态方法
2.4 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
2.5 初始化某个类的子类
2.6 直接使用java.exe命令来运行某个主类
(3)加载器分类
3.1 类加载起的作用? 负责将.class文件加载到内在中,并为之生成对应的Class对象。
3.2 类加载器的分类
①Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载,比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
②Extension ClassLoader 扩展类加载器,负责JRE的扩展目录中jar包的加载。在JDK中JRE的lib目录下ext目录
③Sysetm ClassLoader 系统类加载器,负责在JVM启动时加载来自java命令的class文件
2: 反射(掌握)
举例:Student.java–Student.class(字节码文件)–看成一个对象,而这个对象就叫字节码文件对象–对应的类是Class
(1) 获取字节码文件对象的三种方式:
A:Object类的getClass()方法
B:数据类型的静态class属性
C:Class类的静态方法forName(…),这里是全类名(带包名)
注意:①在平常写案例的时候,我们直接使用第二种最方便。
②但是实际开发中,我们一般用的都是第三种。是因为第三种接收的是一个字符串类型的参数,我们可以把这个参数作为配置文件的内容进行配置,这样就实现了一个变化的内容。
案例:使用上述三种方式获取类的Class对象
创建一个Person类,体现多种修饰符,默认修饰符,公共修饰符,保护修饰符,私有修饰符等
package com.edu_01;
//创建一个Person类,体现多种修饰符
public class Person {
//创建三个成员变量
String name;
public int age;
private String address;
//创建几个构造方法
public Person(){}
Person(String name){
this.name = name;
}
protected Person(String name,int age){
this.name = name;
this.age = age;
}
private Person(String name,int age,String address){
this.name = name;
this.age = age;
this.address = address;
}
//创建一个成员方法
public void method(){
System.out.println("method");
}
void function(int price){
System.out.println(price);
}
private void show(String husband,String wife){
System.out.println(husband+"--"+wife);
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address
+ "]";
}
}
package com.edu_01;
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//获取Person类对应的Class字节码文件对象
//A:Object类的getClass()方法
Person p1 = new Person();
Person p2 = new Person();
Class c1 = p1.getClass();
Class c2 = p2.getClass();
System.out.println(p1==p2);//false
System.out.println(c1==c2);//true
//每一个类会对应一个字节码文件对象,而这个字节码文件对象就是这个类的原型,每一个类有且仅有一个字节码文件对象
System.out.println("-------------");
//B:数据类型的静态class属性
Class c3 = Person.class;
System.out.println(c2==c3);
System.out.println("---------------");
//C:Class类的静态方法forName()
//public static Class<?> forName(String className),在这里所说的类名是全类名(带包名的类名)
//Class c4 = Class.forName("Person");//java.lang.ClassNotFoundException: Person
Class c4 = Class.forName("com.edu_01.Person");
System.out.println(c3==c4);
}
}
(2) 反射的使用步骤
Class类:
构造方法–封装成了一个对象–Constructor
成员变量–封装成了一个对象–Field
成员方法–封装成了一个对象–Method
(3) 通过反射的方式使用构造方法:
1.通过反射获取构造方法
public Constructor[] getConstructors() 获取公共的构造方法
public Constructor[] getDeclaredConstructors() 获取所有的构造方法(包括私有)
public Constructor getConstructor(Class… parameterTypes) 根据构造参数获取公共的指定构造
public Constructor getDeclaredConstructor(Class
package com.edu_02;
import java.lang.reflect.Constructor;
public class ConstructorDemo {
public static void main(String[] args) throws Exception {
//public Constructor[] getConstructors() 获取公共的构造方法
//获取Peroson类对应的字节码文件对象
Class c = Class.forName("com.edu_01.Person");
//获取Perosn类中对应的构造方法
Constructor[] cons = c.getConstructors();
//遍历所有获取到的构造方法
for (Constructor con : cons) {
System.out.println(con);//public com.edu_01.Person()
}
System.out.println("----------------");
//public Constructor[] getDeclaredConstructors() 获取所有的构造方法(包括私有)
Constructor[] cons2 = c.getDeclaredConstructors();
for (Constructor con : cons2) {
System.out.println(con);
}
System.out.println("---------------");
//public Constructor getConstructor(Class... parameterTypes) 根据构造参数获取公共的指定构造
//获取Person类中的公共的无参数的构造方法
Constructor con = c.getConstructor();
System.out.println(con);
//怎么通过我们刚才获取的无参构造创建对象
//public T newInstance(Object... initargs)
Object obj = con.newInstance();
System.out.println(obj);
System.out.println("-------------");
//获取Person类中的非公共的构造方法
//public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
Constructor con2 = c.getDeclaredConstructor(String.class,int.class,String.class);
//获取构造器的时候,传入的什么参数,在调用获取到的这个构造方法对象的时候也就需要传入什么类型的参数
//取消这个构造器对象所对应的访问权限检测
con2.setAccessible(true);
Object obj2 = con2.newInstance("小李",45,"香港");
System.out.println(obj2);
System.out.println("-----------");
//获取被Protected修饰的构造方法
Constructor con3 = c.getDeclaredConstructor(String.class,int.class);
//取消访问权限的检测
con3.setAccessible(true);
Object obj3 = con3.newInstance("小王",50);
System.out.println(obj3);
}
}
(4) 通过反射获取成员变量并使用
4.1通过反射获取成员变量
public Field[] getFields()获取公有的成员变量
public Field[] getDeclaredFields()获取全部的成员变量,包括私有
public Field getDeclaredField(String name) 传入变量名称返回指定的成员变量对象,包括私有
public Field getField(String name)传入变量名称返回指定的成员变量对象,仅可获取共有的
4.2使用成员变量
public void set(Object obj,Object value)
package com.edu_03;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class FieldDemo {
public static void main(String[] args) throws Exception {
//获取Perosn类对应的字节码文件对象
Class c = Class.forName("com.edu_01.Person");
//利用反射获取一个[Perosn对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//获取所有公共的字段对象
//public Field[] getFields()获取公有的成员变量
Field[] fields = c.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("--------------");
//获取所有的字段对象,包括私有
//public Field[] getDeclaredFields()获取全部的成员变量,包括私有
Field[] fields2 = c.getDeclaredFields();
for (Field field : fields2) {
System.out.println(field);
}
System.out.println("--------------");
//public Field getDeclaredField(String name) 传入变量名称返回指定的成员变量对象,包括私有
//获取String name;字段
Field f = c.getDeclaredField("name");
//使用f这个对象给一个Perosn对象设置姓名
//public void set(Object obj, Object value)
/**
* 参数1:需要设置的对象
* 参数2:需要给这个对象设置什么值
*/
//取消访问修饰符的限制
f.setAccessible(true);
f.set(obj, "张三");
System.out.println("--------------");
//public Field getField(String name)传入变量名称返回指定的成员变量对象,仅可获取公有的
//获取public int age;
Field f2 = c.getField("age");
f2.set(obj, 30);
System.out.println("---------------");
Field f3 = c.getDeclaredField("address");
//取消权限检测
f3.setAccessible(true);
f3.set(obj, "湖南");
System.out.println(obj);
}
}
(5) 通过反射获取成员方法并使用
5.1通过反射获取成员方法
public Method[] getMethods()获取所有公共成员方法,包括父类的公共的成员方法
public Method[] getDeclaredMethods()获取所有成员方法,包括私有,只能获取本类的
public Method getMethod(String name, Class<?>...parameterTypes)参数一:方法名 参数二:方法参数类型.class 获取指定的公共方法
public Method getDeclaredMethod(String name,Class<?>... parameterTypes)参数一:方法名 参数二:方法参数类型.class 获取指定的方法,包括私有
5.2使用成员方法
Object invoke(Object obj, Object… args) 让某一个对象使用这个方法,并且传入参数
package com.edu_04;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
5.通过反射获取成员方法并使用
*/
public class MethodDemo {
public static void main(String[] args) throws Exception {
//1.获取Person类中所有的公有的成员方法
//创建Pwrson类对应的字节码文件对象
Class c = Class.forName("com.edu_01.Person");
//利用反射的方式创建一个Person对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//public Method[] getMethods()获取所有公共成员方法,包括父类的公共的成员方法
Method[] methods = c.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("---------------");
//获取Person类中的所有的成员方法
//public Method[] getDeclaredMethods()获取所有成员方法,包括私有,只能获取本类的所有的成员方法,不能获取父类的
Method[] methods2 = c.getDeclaredMethods();
for (Method method : methods2) {
System.out.println(method);
}
System.out.println("---------------");
// public Method getMethod(String name, Class<?>... parameterTypes)
//参数一:方法名 参数二:方法参数类型.class 获取指定的公共方法
//获取method()这个公有的成员方法
Method m = c.getMethod("method");
System.out.println(m);
System.out.println("---------------");
//public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
//参数一:方法名 参数二:方法参数类型.class 获取指定的方法,包括私有
//需求获取method这个个方法
Method m2 = c.getDeclaredMethod("method");
System.out.println(m2);
//使用m2这个成员方法的对象
//public Object invoke(Object obj,Object... args)
/**
* 参数1:执行m2这个方法的对象
* 参数2:执行m2这个方法的时候,需要传入的参数
*/
m2.invoke(obj);
System.out.println("-----------------");
//获取Person类中function方法
Method m3 = c.getDeclaredMethod("function", int.class);
//取消权限检测
m3.setAccessible(true);
m3.invoke(obj, 10);
System.out.println("-------------------");
Method m4 = c.getDeclaredMethod("show", String.class,String.class);
m4.setAccessible(true);
m4.invoke(obj, "皇上","皇后");
}
}
(4)思考题:
1:通过反射运行配置文件的内容
package com.edu_05;
//比如,创建一个学生类
public class Student {
public void study(){
System.out.println("好好学习,天天向上");
}
}
package com.edu_05;
//再来一个老师类
public class Teacher {
public void teach(){
System.out.println("老师会讲课");
}
}
package com.edu_05;
//测试类
import java.io.FileReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Properties;
public class Test {
public static void main(String[] args) throws Exception {
//1.创建学生对象调用学习的方法
// Student s = new Student();
// s.study();
//2.需要创建老师的对象,调用讲课的方法
// Teacher t = new Teacher();
// t.teach();
/**
* 以上的代码的写法,俗称硬编码(将代码写的太死了)
*
* 尝试去将代码中需要访问到的内容,这个内容是有可能需要不断
* 去动态修改的一个内容,我们可不可以将这些内容存储到一个配置文件中,
* 当我们的程序运行起来之后,直接去配置文件中读取信息就可以了,
* 以后如果需要更改的话,我们只需要更改配置文件中的内容即可,
* 而我们自己的代码不需要改动。。。。
*
* 对上面的代码进行改动:
* 使用反射的方式创建对象,调用方法。。
* 1.创建某一个类对应的字节码文件对象 -- 全类名(className=com.edu_01.Person)
* 2.将对象需要调用的方法,依然利用反射的方式获取该方法的对应的对象 -- 方法名(classMethod=study)
*/
//创建Properties集合
Properties prop = new Properties();
prop.load(new FileReader("prop.txt"));
//从集合中获取对应的全类名和对应的需要执行的方法名
String className = prop.getProperty("className");
String classMethod = prop.getProperty("classMethod");
//创键配置文件中类对应的对象
Class c = Class.forName(className);
Constructor con = c.getConstructor();
Object obj = con.newInstance();
//通过反射获取需要执行的方法对象
Method m = c.getMethod(classMethod);
//执行获取到的方法
m.invoke(obj);
}
}
2:给你ArrayList<Integer>
的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?
package com.edu_06;
import java.lang.reflect.Method;
import java.util.ArrayList;
/**
* 2:我给你ArrayList<Integer>的一个对象,我想在这个集合中添加一个字符串数据,如何实现呢?
* 1.创建ArrayList类对应的字节码文件对象
* 2.通过字节码文件对象获取到add方法对象
* 3.调用add方法的对象,给集合中添加字符串
*
* 泛型:泛型其实是给jvm虚拟机看的,当程序一旦运行起来之后,泛型会自动去掉,这也叫泛型擦出
*
*/
public class ArrTest {
public static void main(String[] args) throws Exception {
//1.创建ArrayList类对应的字节码文件对象
ArrayList<Integer> arr = new ArrayList<>();
Class c = arr.getClass();
//2.通过字节码文件对象获取到add方法对象
Method addM = c.getDeclaredMethod("add", Object.class);
//3.调用add方法的对象,给集合中添加字符串
addM.invoke(arr, "hello");
addM.invoke(arr, "world");
System.out.println(arr);//[hello, world]
}
}