-
反射
1.1 类的加载
程序在使用一个类型(类或者接口)时,如果这个类型没有在内存中,系统就会通过加载,连接,初始化三个步骤将类型加载进内存中 -
加载 : 将类型的.class文件加载进内存中,.class文件称为字节码文件,系统同时在堆内存中创建一个对应的字节码文件对象, 所有类型在内存中都是具有字节码文件对象
-
连接 : 检测.class文件中的内容的语法格式是否正确,将类中的静态成员加载进内存
-
初始化 : 执行代码中的内容,new …
类的加载机制(什么时候类需要进内存):
- 类中的main方法执行
- 调用了类中的静态成员, 类名.调用 ; 创建对象调用
- 创建对象(new)
- 当前类的父类,随着当前类型的加载需要父类先进内存
1.2 反射机制
反射 : 任意一个类型在内存中运行时,都可以获取到这个类型中的所有的变量和方法,对衣对于这个类型的任意一个对象,都可以使用类型中变量和方法. 这种动态的获取变量和方法的过程,称为反射
翻译 : 任意一个类型,需要在程序中运行,那么就一定需要进入到内存的类型的字节码文件(.class文件),系统会生成这个类型对应的字节码文件对象,通过这个字节码文件对象,将类型中的所有的变量和方法都可以获取到,称为动态的获取方式
1.3 Class类型
一个类型进入到内存后,系统生成的字节码文件对象的类型就是一个Class类型
Class类型 : 来自于java.lang包,表示Class 类的实例表示正在运行的 Java 应用程序中的类和接口
1.4 获取Class类对象的三种方式
- 创建一个Person对象,通过对象中的getClass()获取到Person对应的字节码文件对象
getClass() : 方法来自于父类Object中的方法,为了获取到所有类型的字节码文件对象 - 所有类型在定义时,系统默认给出的一个静态的成员变量,叫做class,每个类型都有,包括基本数据类型都具有class变量
- Class类型中,有一个静态的方法,forName(String className):
forName(String className) : 获取到给定的参数所表示的字节码文件对象
className参数 : 是一个带有完整包名的类名
说明 : 类型在内存中的字节码文件对象只有1个
代码
package com.zgjy.reflect;
public class PersonClass {
public static void main(String[] args) throws ClassNotFoundException {
// 1. 创建一个Person对象,通过对象中的getClass()获取到Person对应的字节码文件对象
// getClass() : 方法来自于父类Object中的方法,为了获取到所有类型的字节码文件对象
Person p = new Person();
Class c = p.getClass();
//class com.zgjy.reflect.Person
System.out.println©;
// 2. 所有类型在定义时,都有一个静态的成员变量,叫做class,每个类型都有,系统默认给出的
// 基本数据类型都是具有class变量
Class c1 = Person.class;
System.out.println(c1);
// 3. Class类型中,有一个静态的方法,forName(String className):
// forName(String className) : 获取到给定的参数所表示的字节码文件对象
// className参数 : 是一个带有完整包名的类名
Class c2 = Class.forName("com.zgjy.reflect.Person");
System.out.println(c2);
// 4. Person类型在内存中的字节码文件对象只有1个
System.out.println(c==c1); // true
System.out.println(c1==c2);// true
}
}
1.5可变参数
可变参数 : 方法的参数是可变的参数
修饰符 返回值类型 方法名(可变参数){
// 方法功能
}
举例:
public static int getSumFinal(int…is){
}
说明:
终极要求 : 求任意个数的int类型整数求和
分析终极要求 : 参数类型固定,参数个数不一定,将参数设计成可变参数
参数设计成可变参数 : 数据类型…变量名,三个.就表示个数可变参数
解释 : getSumFinal方法,实际调用时,传递的int类型的参数个数可以是任意个数
1) 不传参数,可以
2) 传一个参数,可以
3) 多个参数,可以
4) 可变参数变量类型,当做数组类型使用
5) 一个方法只能有一个可变参数,并且这个可变参数必须在所有参数的最后
代码
package com.zgjy.reflect;
public class ChangeParam {
// 客户要求 : 定义方法,功能,做两个int类型整数的求和
// 追加要求 : 三个int类型整数求和
// 继续追加 : 50个int类型整数和
// 终极要求 : 可有求任意个数的int类型整数求和
// 分析终极要求 :从参数类型固定,个数不一定,将参数设计成可变参数
public static int getSum(int x ,int y) {
return x+y;
}
public static int getSum(int x ,int y,int z) {
return x + y + z;
}
// 终极要求 : 求任意个数的int类型整数求和
// 分析终极要求 : 参数类型固定,参数个数不一定,将参数设计成可变参数
// 参数设计成可变参数 : 数据类型...变量名,三个.就表示个数可变参数
// 解释 : getSumFinal方法,实际调用时,传递的int类型的参数个数可以是任意个数
// 1) 不传参数,可以
// 2) 传一个参数,可以
// 3) 多个参数,可以
// 4) 可变参数类型,当做数组类型使用
// 5) 一个方法只能有一个可变参数,并且这个可变参数必须在所有参数的最后
public static int getSumFinal(int...is) {// is就是一个int类型的数组
int sum = 0;
// 数组遍历,将数组中的每一个元素获取到,循环求和
for(int x : is) {
sum = sum +x;
}
return sum;
}
/*
-
错误代码.可变参数只能有一个
-
public static int getSumFinal1(double…t,int…is) {// is就是一个int类型的数组
int sum = 0;
// 数组遍历,将数组中的每一个元素获取到,循环求和
for(int x : is) {
sum = sum +x;
}
return sum;
}*/public static void main(String[] args) {
// 1. 不传递参数
int sum = getSumFinal();// 0
System.out.println(sum);
// 2. 传递1个参数
int sum1 = getSumFinal(45);// 45
System.out.println(sum1);
// 3. 多个参数
int sum2 = getSumFinal(9,18,20,-10);// 37
System.out.println(sum2);
}
}
1.6通过反射获取构造方法并运行
Constructor类: 表示一个构造器,构造函数,构造方法,来自于java.lang.reflect包
获取类型中的构造方法:
- getConstructors() : 获取到当前类中的所有的公共修饰(public)的构造方法,返回值类型构造器数组
- getDeclaredConstructors(): 获取到类型中的所有构造方法,不验证构造方法的修饰符,返回值类型构造器数组
- getConstructor(Class<?>… parameterTypes) : 获取到类中指定的公共修饰的构造方法,返回值类型Constructor构造器类型
- getDeclaredConstructor(Class<?>… parameterTypes) : 获取到类中指定的构造方法,返回值类型Constructor构造器类型
运行构造方法:
Constructor类中寻找运行方法
- newInstance(Object…param): 参数表示要运行的构造方法需要提供的实际参数,方法的返回值类型,是一个Object类型,运行构造方法,就相当于创建一个对象
运行构造方法的快捷方式:
要求 : 类型中包含一个空参数的公共修饰的构造方法
可以直接使用Class这个类型对象中的newInstance()方法,直接运行空参数构造,创建出一个对象
代码
package com.zgjy.reflect;
import java.lang.reflect.Constructor;
public class PersonReflectCon {
public static void main(String[] args) throws Exception {
//getCon2();
//getCon3();
//getCon4();
getFastCon();
}
//1.获取到当前类中的所有的公共修饰的构造方法,返回值类型构造器数组
public static void getCon1() {
// 1. 获取到Person类型的字节码文件对象
Class c = Person.class;
// 2. 通过Class类中的方法 1.getConstructors() : 获取到当前类中的所有的公共修饰的构造方法,返回值类型构造器数组
Constructor[] con = c.getConstructors();
//3. 查看所有公共修饰构造
for(Constructor cc : con) {
System.out.println(cc);
}
}
//2. 获取到当前类中的所有的构造方法,返回值类型构造器数组
public static void getCon2() {
// 1. 获取到Person类型的字节码文件对象
Class c = Person.class;
// 2. 通过Class类中的方法 1.getConstructors() : 获取到当前类中的所有的公共修饰的构造方法,返回值类型构造器数组
Constructor[] con = c.getDeclaredConstructors();
//3. 查看所有公共修饰构造
for(Constructor cc : con) {
System.out.println(cc);
}
}
// 3. 获取指定的公共修饰的构造并运行
public static void getCon3() throws Exception {
// 1. 获取到Person类型的字节码文件对象
Class c = Person.class;
// 2.getConstructor(Class...param) : 获取到类中指定的公共修饰的构造方法
Constructor con = c.getConstructor(String.class,int.class,String.class);
System.out.println(con);
// 3. 运行公共修饰的构造方法,使用Constructor类中的newInstance(构造方法实际参数)
Object obj = con.newInstance("张三",20,"男");
// 4. 将obj转换成Person类型,需要多态的向下转型
Person p = (Person)obj;
// 5. 将Person中的信息获取到
System.out.println(p.getName()+"--"+p.getAge()+"--"+p.getSex());
}
// 4. 获取任意的构造并运行
public static void getCon4() throws Exception {
// 1. 获取到Person类型的字节码文件对象
Class c = Person.class;
// 2.getDeclaredConstructor(Class...param):获取到类中指定的构造方法
// 获取到Person中的私有构造
Constructor con = c.getDeclaredConstructor(String.class);
System.out.println(con);
// 3. 运行私有构造
// 直接运行私有构造方法,报错,私有的权限控制不能执行在外类运行
Object obj = con.newInstance("QQ");
// 4. 将obj转换成Person类型,需要多态的向下转型
Person p = (Person)obj;
// 5. 将Person中的信息获取到
System.out.println(p.getName()+"--"+p.getAge()+"--"+p.getSex());
}
// 5. 快速进行构造方法的运行
public static void getFastCon() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 1. 创建一个Person类型的Class字节码文件对象
Class c = Class.forName("com.zgjy.reflect.Person");
// 2. Person类中有一个公共的空参数的构造,于是快速运行构造方法
// 使用Class类中的方法newInstance();
Object obj = c.newInstance();
// 3. 将obj转换成Person类型,需要多态的向下转型
Person p = (Person)obj;
// 4. 将Person中的信息获取到
System.out.println(p.getName()+"--"+p.getAge()+"--"+p.getSex());
}
}
1.7暴力反射
暴力反射 : 就是将类型中的私有成员通过暴力的方式进行运行
AccessibleObject类型,是Field(变量类),Method(方法类),Constructor(构造器类)的父类,来自于java.lang.reflect包, 主要作用就是: 在进行反射时,可以取消变量或者是方法的权限的检查,公共的,受保护的,默认的,私有的权限,都可以运行
AccessibleObject类型中的方法:
setAccessible(boolean flag): 参数flag设置成true, 功能就是取消变量或者是方法的修饰权限控制---->暴力反射
注意 : 尽量不要使用暴力反射破坏private的安全性
代码
// 获取私有构造,通过暴力反射执行私有构造
public static void getCon4() throws Exception {
// 1. 获取到Person类型的字节码文件对象
Class c = Person.class;
// 2.getDeclaredConstructor(Class…param):获取到类中指定的构造方法
// 获取到Person中的私有构造
Constructor con = c.getDeclaredConstructor(String.class);
System.out.println(con);
// 3. 运行私有构造
// 直接运行私有构造方法,报错,私有的权限控制不能执行在外类运行
// 使用暴力反射进行私有构造方法的运行
// 1) 先将私有的构造器中的权限检查取消,暴力反射
con.setAccessible(true);
Object obj = con.newInstance(“QQ”);
// 4. 将obj转换成Person类型,需要多态的向下转型
Person p = (Person)obj;
// 5. 将Person中的信息获取到
System.out.println(p.getName()+"–"+p.getAge()+"–"+p.getSex());
}
1.8通过反射获取成员变量并赋值
Field类 : 表示类或者是接口中的变量类型,来自于java.lang.reflect包
获取类型中的成员变量
- getFields(): 获取到类型中的使用公共修饰的变量,返回值Field类型数组
- getDeclaredFields(): 获取到类型中的所有变量,返回值Field类型数组
- getField(String name): 获取到类型中的指定使用公共修饰的变量,返回值类型Field
参数name表示成员变量的名字 - getDeclaredField(String name):获取到类型中的指定变量,返回值类型Field
给类型中的成员变量赋值
Field类中方法:
- get(Object obj) : 参数需要一个对象,Class类型的一个实例化对象,返回值类型Object类型,返回当前这个变量的字段值
- set(Object obj,Object value): 表示给当前的字段进行赋值.
- obj : 表示的是对象
- value : 表示的新的字段值
代码
package com.zgjy.reflect;
import java.lang.reflect.Field;
public class PersonFlectField {
public static void main(String[] args) throws Exception {
//getField();
//getField1();
//getField2();
getField3();
}
// 1. 获取Person中的所有公共修饰的成员变量
public static void getField() throws ClassNotFoundException {
// 1. 创建Person的Class类型的对象
Class c = Class.forName("com.zgjy.reflect.Person");
// 2. 获取Person中的公共修饰成员
Field[] f = c.getFields();
// 3. 查看成员变量
for(Field fie : f) {
System.out.println(fie);
}
}
// 2. 获取Person中的所有成员变量
public static void getField1() throws ClassNotFoundException {
// 1. 创建Person的Class类型的对象
Class c = Class.forName("com.zgjy.reflect.Person");
// 2. 获取Person中的公共修饰成员
Field[] f = c.getDeclaredFields();
// 3. 查看成员变量
for(Field fie : f) {
System.out.println(fie);
}
}
// 3. 获取到指定的公共修饰的成员变量
public static void getField2() throws Exception {
// 1. 创建Person的Class类型的对象
Class c = Class.forName("com.zgjy.reflect.Person");
// 2.getField(String name): 获取到类型中的指定使用公共修饰的变量,返回值类型Field
// 参数name表示成员变量的名字
Field age = c.getField("age");
System.out.println(age);
// 3. 查看变量age的字段值
// Field类中get(Object obj)
// 1) 使用Class中的newInstance() 方法获取到一个对象
Object obj = c.newInstance();
// 查看的是哪个对象中的哪个成员变量
// 查看obj这个对象中的age这个变量的值
Integer a = (Integer)age.get(obj);
System.out.println(a);//0
// 4. 给age进行赋值
// set(Object obj,Object value)
// 给obj对象中的age变量赋值,赋的值为value
age.set(obj, 28);
// 5. 使用反射查看age的值
System.out.println(age.get(obj));
// 6.使用get方法进行变量值的查看
Person p = (Person)obj;
System.out.println(p.getAge());
}
// 4. 获取到指定的私有修饰的成员变量
public static void getField3() throws Exception {
// 1. 创建Person的Class类型的对象
Class c = Class.forName("com.zgjy.reflect.Person");
// 2.getField(String name): 获取到类型中的指定的变量,返回值类型Field
// 参数name表示成员变量的名字
Field sex = c.getDeclaredField("sex");
System.out.println(sex);
// 3. 查看私有的成员变量sex的值
// 1) 使用Class中的newInstance() 方法获取到一个对象
// 2) 使用暴力反射,查看并且修改私有成员变量的值
sex.setAccessible(true);
Object obj = c.newInstance();
System.out.println(sex.get(obj));
// 4. 修改私有成员变量的值
sex.set(obj, "nv");
// 5. 查看修改后的变量值
System.out.println(sex.get(obj));
}
}
1.9通过反射获取方法并运行
Method类 : 表示类中的一个方法,来自于java.lang.reflect包
获取类型中的方法:
- getMethods() : 获取到类型中的所有公共修饰的方法(包括从父类继承而来的公共修饰方法)
- getDeclaredMethods(): 获取到类型中的所有的方法,不包括父类继承的方法
- getMethod(String methodname,Class…param) : 获取到类型中的指定公共修饰的方法
methodname : 表示方法名
param : 表示方法的参数列表
需要方法的参数列表的原因是 : 相同的方法名,参数列表不同,方法重载 - getDeclaredMethod(String methodname,Class…param) : 获取到类型中指定的方法
运行方法:
Method类中的方法
- invoke(Object obj,Object…param): 运行一个对象中的某一个方法,返回值类型是Object,表示返回方法的结果
- obj : 表示一个对象
- param : 表示要运行的方法的实际参数
代码
package com.zgjy.reflect;
import java.lang.reflect.Method;
public class PersonReflectMethod {
public static void main(String[] args) throws Exception {
//getMethod1();
//getMethod2();
//getMethod3();
getMethod4();
}
// 1. 获取到所有的公共修饰方法,包括从父类继承来的方法
public static void getMethod1() {
// 1. 创建一个Person对应的Class类型对象
Class c = Person.class;
// 2. 使用getMethods()
Method[] m = c.getMethods();
// 3. 查看所有的公共方法
for(Method me : m) {
System.out.println(me);
}
}
// 2. 获取到所有的方法,不包括从父类继承来的方法
public static void getMethod2() {
// 1. 创建一个Person对应的Class类型对象
Class c = Person.class;
// 2. 使用getMethods()
Method[] m = c.getDeclaredMethods();
// 3. 查看所有的公共方法
for(Method me : m) {
System.out.println(me);
}
}
// 3. 获取到指定的公共修饰的方法
public static void getMethod3() throws Exception {
// 1. 创建一个Person对应的Class类型对象
Class c = Person.class;
// 2. 使用getMethod()
Method eat = c.getMethod("eat");
System.out.println(eat);
// 3. 运行eat方法,需要传递实际参数,而eat没有参数,于是第二个可变参数为空
eat.invoke(c.newInstance());
}
// 4. 获取到私有修饰的方法
public static void getMethod4() throws Exception {
// 1. 创建一个Person对应的Class类型对象
Class c = Person.class;
// 2. 使用getDeclaredMethod
/*3.getDeclaredMethodMethod(String methodname,Class...param) : 获取到类型中的指定公共修饰的方法
methodname : 表示方法名
param : 表示方法的参数列表
需要方法的参数列表的原因是 : 相同的方法名,参数列表不同,方法重载*/
Method m = c.getDeclaredMethod("getSum", int.class,int.class);
System.out.println(m);
// 4. 因为方法时私有的,于是需要暴力反射,将方法的权限检查取消
m.setAccessible(true);
// 5. 运行私有方法
Object methodResult = m.invoke(c.newInstance(), 5,17);
System.out.println("-----"+methodResult);
}
}
1.10反射实现泛型擦除案例
需求 : 创建一个ArrayList,存储字符串类型, 使用反射,向集合中添加Integer类型的数据,将list的结果输出到控制台上
分析:
- 创建一个ArrayList list集合,list集合中调用的add方法,只能添加String类型
- 可以获取到ArrayList对应的字节码文件对象
Class c = Class.forName(“java.util.ArrayList”); - 通过Class获取到add方法
Method m= c.getDeclaredMethod(“add”,Object.class);
m.invoke(obj,12);
代码
package com.zgjy.reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ArrayListReflect {
public static void main(String[] args) throws Exception{
// 1. 创建一个ArrayList集合
ArrayList list = new ArrayList<>();
//2. 向集合中添加字符串
list.add(“abc”);
list.add(“hello”);
//list.add(12);
// 3. 获取到Arraylist的字节码文件对象,Class类型
Class c = Class.forName("java.util.ArrayList");
// 4. 使用反射,获取到ArrayList中的方法add
Method add = c.getDeclaredMethod("add", Object.class);
// 5. 运行add方法
add.invoke(list, 12);
add.invoke(list, -99);
add.invoke(list, new Person());
//6.查看list中的内容
System.out.println(list);
}
}
1.11反射读取配置文件案例
定义配置文件run.properties内容如下:
className=com.zgjy.reflect.Person
methodName=getSum
通过读取配置文件内容,使用反射运行对应Person类中的对应方法getSum
分析:
- 有配置文件—>读取配置文件内容—>提取出有消息,反射需要的信息1) 完整包名的类名 2) getSum方法
- 读取配置文件,使用Hashtable子类,Properties---->load—>将配置文件内容读取到Properties中
- 通过Properties中方法getProperty(key)---->value—>获取到类名和方法名
- 通过类名和方法名使用反射进行Person中的getSum方法运行
代码
package com.zgjy.reflect;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectProperties {
public static void main(String[] args) throws Exception{
// 1. 使用IO流进行文件的内容读取
FileInputStream fis = new FileInputStream(“run.properties”);
// 2.将使用IO流读取到的配置文件内容放置到Properties键值对的集合中
Properties p = new Properties();
// 3. 使用Properties类中的load方法,将IO流中读取到的内容放置到集合中
p.load(fis);
// 4.将配置文件中的类名和方法名获取到
// 获取到带有完整包名的类名
String claName = p.getProperty(“className”);
// 获取到方法名
String mName = p.getProperty(“methodName”);
// 5.使用反射执行类中的指定方法
// 1) 获取到类型的Class类型对象
Class c = Class.forName(claName);
// 2) 通过Class类型获取到类中的方法
Method m = c.getDeclaredMethod(mName, int.class,int.class);
// 3) 因为方法时私有修饰的,于是需要暴力反射,取消的权限验证
m.setAccessible(true);
// 4)执行方法
Object result= m.invoke(c.newInstance(), 6,10);
System.out.println(result);
}
}