23 反射
反射破坏代码的封装性,破坏原有的访问修饰符访问限制
java的封装性:指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,通过该类提供的方法实现对内部信息的操作访问。
反射机制:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性
1. 引出反射
配置文件:
先定义一个Cat类
package com.hspedu;
public class Cat {
private String name = "招财猫";
public void hi(){
System.out.println("hi " + name);
}
}
解决问题:
public class Ques {
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1.使用Properties类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
System.out.println("classfullpath=" + classfullpath);
System.out.println("method=" + methodName);
//2.传统方法创建对象再调用方法,行不通,引出反射机制
//new classfullpath(); //这是非常错误的,classfullpath是一个字符串
//3.使用反射机制解决
//(1)加载类,返回Class类型的对象cls
Class cls = Class.forName(classfullpath);
//(2)通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o的运行类型:" + o);//Cat
//(3)通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName “hi” 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4)通过method1 调用方法,即通过方法对象method1来实现调用方法
System.out.println("============================");
method1.invoke(o);//传统方式是对象.方法(),而反射机制:方法.invoke(对象)
}
}
输出:
反射的好处在于:假如Cat类中还有一个方法cry,定义如上Cat类中,如果你要执行的是cry()方法而不是hi()方法,就只需要修改配置文件,如下,源代码不需要进行任何改动,直接运行原来的:
2. 反射机制
2.1 反射的原理图
编译阶段由.java文件生成.class字节码文件,运行阶段如果new了一个Cat对象,那么就会通过类加载器 ClassLoader 将字节码文件Cat.class加载到内存(JVM内存?)中的堆里面去(即new一个类的对象会导致该类的加载),并生成一个Class类对象(这个Class类对象实际上是一个数据结构,即可以操作的数据),这一步就体现了反射。加载的Class类对象会把成员变量(属性)、构造器和方法当做对象来看待(对应的类分别是 Feild、Constructor和method),由因为可能会具有多个成员变量,多个构造器和多个方法,因此属性、构造器和方法都是以属性数组 Field[]、构造器数组 Constructor[] 和成员方法数组 Method[] 。
2.2 反射的功能
2.3 反射相关的主要类
演示:
public class Cat {
private String name = "招财猫";
public int age = 0;
public Cat(){}//因为定义了有参构造器,所以main中要使用到无参构造器必须显示的声明一下
public Cat(String name) {
this.name = name;
}
public void hi(){
System.out.println("hi " + name);
}
public void cry(){
System.out.println("小猫喵喵叫...");
}
}
public class Reflection01 {
public static void main(String[] args) throws Exception {
//1.使用Properties类,可以读写配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\re.properties"));
String classfullpath = properties.get("classfullpath").toString();
String methodName = properties.get("method").toString();
//3.使用反射机制解决
//(1)加载类,返回Class类型的对象cls
Class cls = Class.forName(classfullpath);
//(2)通过 cls 得到你加载的类 com.hspedu.Cat 的对象实例
Object o = cls.newInstance();
System.out.println("o的运行类型:" + o);//Cat
//(3)通过 cls 得到你加载的类 com.hspedu.Cat 的 methodName “hi” 的方法对象
// 即:在反射中,可以把方法视为对象(万物皆对象)
Method method1 = cls.getMethod(methodName);
//(4)通过method1 调用方法,即通过方法对象method1来实现调用方法
System.out.println("============================");
method1.invoke(o);//传统方式是对象.方法(),而反射机制:方法.invoke(对象)
//java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
//得到Cat类 的字段(属性),注意getField()不能得到私有的属性
Field ageField = cls.getField("age");
System.out.println(ageField.get(o));//传统写法:对象.成员变量;反射写法:成员变量对象.get(对象)
//java.lang.reflect.Constructor:代表类的构造方法,Constructor对象表示某个类的构造器
Constructor constructor = cls.getConstructor();//输出无参构造器。()中可以指定构造器参数类型,这里无参构造器
System.out.println(constructor);
Constructor constructor1 = cls.getConstructor(String.class);//输出有参构造器。这里传入String.class就是String类的Class对象
System.out.println(constructor1);
}
}
输出:
2.4 反射的优点和缺点
- 优缺点
- 优化
/**
* 测试反射调用性能,和优化方案
*/
public class Reflection02 {
public static void main(String[] args) throws Exception {
m1();
m2();
m3();
}
//传统方法调用hi
public static void m1(){
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("m1()的执行时间:" + (end - start));
}
//反射方法调用hi
public static void m2() throws Exception {
Class cls = Class.forName("com.hspedu.Cat");//获取类
Object o = cls.newInstance();//new一个实例(对象)
Method hi = cls.getMethod("hi");//得到hi方法
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//调用hi方法
}
long end = System.currentTimeMillis();
System.out.println("m2()的执行时间:" + (end - start));
}
//反射调用优化 + 关闭访问检测
public static void m3() throws Exception {
Class cls = Class.forName("com.hspedu.Cat");//获取类
Object o = cls.newInstance();//new一个实例(对象)
Method hi = cls.getMethod("hi");//得到hi方法
hi.setAccessible(true);//在反射调用方法时,取消访问检测
long start = System.currentTimeMillis();
for (int i = 0; i < 900000000; i++) {
hi.invoke(o);//调用hi方法
}
long end = System.currentTimeMillis();
System.out.println("m3()的执行时间:" + (end - start));
}
}
//实际上等于没有优化,感觉不出来
3. Class类
3.1 基本介绍
- 对上面第7点的解释:类加载完了之后,在 方法区 还会得到一个 字节码二进制文件,即前面面向对象章节讲的在方法区中加载类信息 。即 类加载 完了之后,除了会在堆生成一个Class类对象(一种数据结构,可以进行操作),还会在方法区生成一个类的字节码二进制数据(又叫元数据,二进制数据操作是很麻烦的)。这两者之间的关系是引用关系,
/**
* 对Class类特点的梳理,对上面1 2 3 4 条的解释
*/
public class Class01 {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class也是类,因此也继承了Object类
//2.Class类对象不是new出来的(不能new Class()),而是系统创建的
//(1)传统的new对象。下面的代码用断点调试,第一次Force Step Into就会跳到下面这个地方
/* ClassLoader类:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
//Cat cat = new Cat();
//(2)放射方式。下面的代码用断点调试。
// 注意类只加载一次,因此必须把上面的new代码部分注释掉,不然上面加载了Cat类,下面的断点调试就不会执行ClassLoader类loadClass
/*
经过断点调试发现,这里仍然是通过ClassLoader类加载Cat类的 Class对象
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
*/
Class cls1 = Class.forName("com.hspedu.Cat");
//3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
Class cls2 = Class.forName("com.hspedu.Cat");
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());//两个输出一样,表示是同一个对象
}
}
3.2 Class类的常用方法
- 演示:
public class Car {
public String brand = "宝马";
public int price = 500000;
public String color = "白色";
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
", color='" + color + '\'' +
'}';
}
}
/**
* 演示Class类的常用方法
*/
public class Class02_ {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
String classFullPath = "com.hspedu.Car";
//1.获取Car类对应的Class对象
//<?> 表示不确定的Java类型,可以删除
Class<?> cls = Class.forName(classFullPath);
//2.输出cls
System.out.println(cls);//直接输出cls对象,输出的是哪个类的Class对象,这里com.hspedu.Car
System.out.println(cls.getClass());//输出cls的运行类型,这里java.lang.Class
//3.得到包
System.out.println(cls.getPackage().getName());//包名:com.hspedu
//4.得到全类名
System.out.println(cls.getName());//全类名:com.hspedu.Car
//5.通过cls创建对象实例
Car car = (Car)cls.newInstance();//这里cls实例化的对象就是一个Car对象了
System.out.println(car);//调用Car的toString()输出
//6.通过反射获取属性 brand(注意,私有属性获取不了)
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//brand属性:宝马
//7.通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println(brand.get(car));//奔驰
//8.通过反射获取所有属性(字段)
System.out.println("=============所有的字段属性==============");
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field.getName());//输出所有属性的名称
}
}
}
4. 获取Class类对象方式
/**
* 演示得到Class对象的6种方式
*/
public class Class03 {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forName(全类名)
String classFullPath = "com.hspedu.Car";//这里一般是读取配置文件获得
Class<?> cls1 = Class.forName(classFullPath);
System.out.println(cls1);
System.out.println(cls1.getClass());
//2.类名.class。应用场景:多用于参数传递
Class cls2 = Car.class;
System.out.println(cls2);
System.out.println(cls2.getClass());
//3.对象.getClass()。应用场景:有实例对象
//注意:之前讲的 对象.getClass() 得到的是该对象的运行类型,现在理解:得到的就是该对象所关联的Class类对象
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);
System.out.println(cls3.getClass());
//4.通过类加载器(有四种)来获取类的Class对象
//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class cls4 = classLoader.loadClass(classFullPath);
System.out.println(cls4);
System.out.println(cls4.getClass());
//cls1、cls2、cls3、cls4 是同一个Class对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
//5.基本数据类型(int、char、boolean、float、double、byte、long、short)按如下方式得到Class类对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);//输出还是int,这里有自动装箱和自动拆箱
//6.基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
Class<Integer> type = Integer.TYPE;
Class<Character> type1 = Character.TYPE;
System.out.println(type);
//注意上面的integerClass和type指向的是同一个类对象
System.out.println(integerClass.hashCode());
System.out.println(type.hashCode());
}
}
- 注意上述的前四种方式对应在不同阶段:
5. 哪些类型有Class对象
/**
* 演示哪些类型有Class对象
*/
public class AllTypeClass {
public static void main(String[] args) {
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
Class<Thread.State> cls6 = Thread.State.class;//注解
Class<Long> cls7 = long.class;//基本数据类型
Class<Void> cls8 = void.class;//void数据类型
Class<Class> cls9 = Class.class;//Class类
System.out.println(cls1);
System.out.println(cls2);
System.out.println(cls3);
System.out.println(cls4);
System.out.println(cls5);
System.out.println(cls6);
System.out.println(cls7);
System.out.println(cls8);
System.out.println(cls9);
}
}
6. 类加载
6.1 基本介绍
import java.util.*;
import java.lang.reflect.*;
public class ClassLoad_{
public static void main(String[] args) throws Exception{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入key:");
String key = scanner.next();
switch(key){
case "1" :
//静态加载,依赖性很强。
//即使这部分代码可能不执行,但是在没有创建Dog类的情况下,编译也不会通过
Dog dog = new Dog();
dog.cry();
break;
case "2" :
//加载Person类(动态加载)。
//动态加载:只有当运行的时候,并且执行到这段代码的时候,才会真正加载Person类,如果没有定义Person类就报错
Class cls = Class.forName("Person");
Object o = cls.newInstance();
Method m = cls.getMethod("hi");
m.invoke(o);
System.out.println("ok");
break;
default :
System.out.println("do nothing...");
}
}
}
//因为 new Dog() 是静态加载,所以必须编写Dog
//Person() 类是动态加载,所以,没有编写Person类也不会报错,只有当动态加载到该类时,才会报错
class Dog{
public void cry(){
System.out.println("小狗汪汪叫...");
}
}
class Person{
public void hi(){
System.out.println("小孩hi...");
}
}
6.2 类加载的时机
6.3 类加载的过程图
- 验证阶段:主要是对文件进行安全的校验,比如,文件格式是否正确?元数据验证是否正确?字节码是否正确?符号引用是否正确?
- 准备阶段:给静态变量分配内存,并且完成一些默认初始化
- 解析阶段:JVM 虚拟机会把常量池中的符号引用替换为直接引用
- 初始化阶段:完成显示初始化。才会真正执行,静态代码块,静态变量的指定显示赋值啊什么的。
- 类加载完成后,内存中就会产生两个重要的部分。一个是在方法区里面,会把编译得到的字节码文件以二进制的形式保存起来。第二个是在堆区生成字节码对应的数据结构,其实就是类的Class对象。并且方法区中的字节码二进制数据和堆区中的Class对象存在引用的关系。
6.3.1 类加载各个阶段
- 加载和连接阶段是由 JVM 机控制的,程序员管不了。初始化就是程序员可以定义的了,比如初始化静态代码什么的。
- 注意:这里整个过程都是类的加载,不是创建(new);上述加载过程 class 文件是由 .java 文件经过编译得到的。
6.3.2 加载
即下面这两个,一个方法区里的二进制文件,一个Class类对象
6.3.3 连接—验证
- 对于第 1 条:
追进去上面这个loadClass,追到下面这里,就是进行安全监测的
-
对于第 2 条:
随便写一个合法的类,将编译后得到的 .class 文件,在 Sublime Text 3 中打开,显示如下,发现不是以oxcafebabe开头的,而是以cafebabe开头的,即为合法 .class 文件。元数据验证、字节码验证类似。
6.3.4 连接—准备
- 默认初始化:不管程序员给它赋什么值,先按照一个默认的值对它进行初始化。比如 int 类型的,默认初始化的值就是 0 。
class A{
//属性-成员变量-字段
//分析,连接阶段的准备阶段,属性如何处理
//1. n1 是实例属性,不是一个静态变量,因此在准备阶段,是不会分配内存的
//2. n2 是静态变量,会分配内存,并且 n2 默认初始化 0(链接结束后的初始化阶段才是20)
//3. n3 是 static final (常量),和静态变量不一样,因为它一旦赋值就不变了,因此在连接的准备阶段就直接 n3 = 30
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
6.3.5 连接—解析
- 大概的解释:假如 A 类中引用了 B 类,在类加载后,堆中会生成 A 的 Class 对象 和 B 的 Class对象,他们都有一个确定的地址,这个时候的 A 引用了 B ,就直接引用的是 B 的 class 对象所处在内存中的地址了。
6.3.6 初始化
public class Ques {
public static void main(String[] args) throws ClassNotFoundException {
//1.加载B类,并生成 B的class对象
//2.连接阶段,准备阶段为num分配内存空间,并且默认初始化num = 0
//3.初始化阶段,依次自动收集类中的所有静态变量的复制动作和静态代码块中的语句
/* 收集:
clinit(){
System.out.println("B 的静态代码块被执行");
num = 300;
num = 100;
}
合并:num = 100;
*/
//System.out.println(B.num);//100。直接使用类的静态属性也会导致类的加载
//如果new了一个B,那么还会执行构造器
//new B();
//看看加载类的时候,是有同步机制控制的(类加载只有一次,这里要注释掉前面的 B.num)
/*
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{
//正因为有这个机制,才能保证某个类在内存中,只有一份Class对象
synchronized (getClassLoadingLock(name)) {
//......
}
}
*/
B b = new B();
}
}
class B{
static {
System.out.println("B 的静态代码块被执行");
num = 300;
}
static int num = 100;
public B(){
System.out.println("B的构造器被执行");
}
}
7. 通过反射获取类的信息
7.1 第一组
public class Reflection02 {
public static void main(String[] args) {
}
//第一组方法API
@Test
public void api_01() throws ClassNotFoundException {
//得到Class对象。因为得到了Class对象后才能得到跟这个Class对象对应的类的相关结构信息
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
//1.getName:获取全类名
System.out.println(personCls.getName());//com.hspedu.reflection.Person
//2.getSimpleName:获取简单类名
System.out.println(personCls.getSimpleName());//Person
//3.getFields:获取所有public修饰的属性,包含本类以及父类的
Field[] fields = personCls.getFields();
for (Field field : fields) {//增强for循环:快捷键iter 或者 fields.for + 回车
System.out.println("本类以及父类的public属性" + field.getName());
}
//4.getDeclaredFields:获取本类中所有的属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类的所有属性:" + declaredField.getName());
}
//5.getMethods:获取所有public修饰的方法,包含本类以及父类的
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println("本类以及父类的所有public方法:" + method.getName());
}
//6.getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类所有的方法:" + declaredMethod.getName());
}
//7.getConstructors:获取本类所有public修饰的构造器,
Constructor<?>[] constructors = personCls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("本类的public构造器:" + constructor.getName());
}
//8.getDeclaredConstructors:获取本类中所有的构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("本类中所有的构造器:" + declaredConstructor.getName());
}
//9.getPackage:以Package形式返回 包信息
System.out.println(personCls.getPackage());//package com.hspedu.reflection
//10.getSuperClass:以Class形式返回父类信息
Class<?> superclass = personCls.getSuperclass();
System.out.println("父类的class对象:" + superclass);
//11.getInterfaces:以Class[]形式返回接口信息
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("Person的接口信息:" + anInterface);
}
//12.getAnnotations:以Annotation[] 形式返回注解信息
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息:" + annotation);
}
}
}
class Father{
public String hobby;//属性
public void hi(){}//方法
public Father(){}//构造器
}
interface IA{}
interface IB{}
@Deprecated
class Person extends Father implements IA,IB{
//4种权限的属性
public String name;
protected int age;
String job;
private double sal;
//3种构造器构造器
public Person(){}
public Person(String name){}
private Person(String name, int age){}//私有构造器
//4种权限的方法
public void m1(){}
protected void m2(){}
void m3(){}
private void m4(){}
}
7.2 第二组
public class Reflection02 {
public static void main(String[] args) {
}
//第二组方法API
@Test
public void api_02() throws ClassNotFoundException{
//得到Class对象。因为得到了Class对象后才能得到跟这个Class对象对应的类的相关结构信息
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
//getDeclaredFields:获取本类中所有的属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类的所有属性:" + declaredField.getName()
+ " 该属性的修饰符值:" + declaredField.getModifiers()
+ " 该属性的类型:" + declaredField.getType());
}
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类所有的方法:" + declaredMethod.getName()
+ " 该方法的访问修饰符值:" + declaredMethod.getModifiers()
+ " 该方法的返回类型:" + declaredMethod.getReturnType());
//输出当前这个方法的形参数组情况
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型:" + parameterType);
}
}
//getDeclaredConstructors:获取本类中所有的构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("=======================");
System.out.println("本类中所有的构造器:" + declaredConstructor.getName());
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该构造器的形参类型:" + parameterType);
}
}
}
}
class Father{
public String hobby;//属性
public void hi(){}//方法
public Father(){}//构造器
}
interface IA{}
interface IB{}
@Deprecated
class Person extends Father implements IA,IB{
//4种权限的属性
public String name;
protected static int age;
String job;
private double sal;
//3种构造器构造器
public Person(){}
public Person(String name){}
private Person(String name, int age){}//私有构造器
//4种权限的方法
public void m1(String name, int age, double sal){}
protected String m2(){ return null;}
void m3(){}
private void m4(){}
}
8. 通过反射创建对象
8.1 案例演示
/**
* 演示通过反射机制创建实例
*/
public class ReflecCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1.先获取User类的Class对象
Class<?> userClass = Class.forName("com.hspedu.User");
//2.通过public的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//3.通过public的有参构造器创建实例:先得到对应的构造器,再传入对应的实参来创建实例
/*
下面的constructor对象就是:
//public 有参构造器
public User(String name){
this.name = name;
}
*/
Constructor<?> constructor = userClass.getConstructor(String.class);
Object user1 = constructor.newInstance("丁狗");
System.out.println(user1);
//4.通过非public的有参构造器创建实例
//4.1 得到private的构造器对象
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
//4.2 创建实例
//暴破【暴力破解】,使用反射可以访问private构造器/方法/属性(反射面前,都是纸老虎)
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(100, "张无忌");
System.out.println(user2);
}
}
class User{//User类
private int age = 10;
private String name = "丁逍";
//public 无参构造器
public User(){}
//public 有参构造器
public User(String name){
this.name = name;
}
//private 有参构造器
public User(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
9. 通过反射访问类中成员
9.1 访问属性
- 解释上述第4条:静态属性是跟类相关的,不是跟对象相关的
/**
* 演示反射操作属性
*/
public class ReflecAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
//1.得到Student类对应的Class对象
Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");
//2.创建对象(无参构造器)
Object o = stuClass.newInstance();
System.out.println("o的运行类型:" + o.getClass());//Student
//3.使用反射得到age属性对象
Field age = stuClass.getField("age");
age.set(o, 88);//通过反射来操作属性
System.out.println(o);
System.out.println(age.get(o));//返回age属性的值
//4.使用反射操作name(private static)属性
Field name = stuClass.getDeclaredField("name");
//对name进行暴破,才能操作private属性
name.setAccessible(true);
name.set(o, "韩顺平");//或者name.set(null, "韩顺平"); //因为name是static的,o可以写成null
System.out.println(o);
System.out.println(name.get(o));//获取属性值
System.out.println(name.get(null));//静态属性才可以置空 来获取属性
}
}
class Student{//学生类
public int age;
private static String name;
public Student(){}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name=" + name +
'}';
}
}
9.2 访问方法
/**
* 演示通过反射调用方法
*/
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//1.得到Boss类对应的Class对象
Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");
//2.创建对象
Object o = bossCls.newInstance();
//3.调用public的hi方法
//3.1 得到hi方法对象
//Method hi = bossCls.getMethod("hi", String.class);//ok
Method hi = bossCls.getDeclaredMethod("hi", String.class);//ok 两种方法获取hi方法都可以
//3.2 调用hi方法
hi.invoke(o, "韩顺平教育");
//4.调用private static方法
//4.1 得到 say 方法对象(注意.getMethod()拿不到私有的方法)
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
//4.2 因为say方法是私有的,所以需要暴破,原理和前面几节讲的一样
say.setAccessible(true);
System.out.println(say.invoke(o, 100, "张三", '男'));
//4.3 因为say方法是static的,所以还可以这样调用,可以传入null
System.out.println(say.invoke(null, 200, "李四", '女'));
//5.在反射中,如果方法有返回值,统一返回object类型(编译类型),但是运行类型是和方法定义的返回类型一致
Object reVal = say.invoke(null, 300, "王五", '男');
System.out.println("reVal 的运行类型:" + reVal.getClass());//String
}
}
class Boss{
public int age;
private static String name;
public Boss(){}
private static String say(int n, String s, char c){
return n + " " + s + " " + c;
}
public void hi(String s){
System.out.println("hi " + s);
}
}