一文带你快速全面掌握Java反射机制面试题

Java反射机制
动态语言

动态语言是指程序在运行时可以改变其内部结构:比如新增成员函数,删除已有删除等结构上的一些变化。常见的动态语言有JavaScript和Ruby、Python等。而C、C++等不属于动态语言。从反射角度而言,Java属于半动态的语言。

什么是反射

反射是指在在运行状态中,对于任意一个类都能够获取这个类的所有属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMVIATAk-1636517914730)(https://s3.bmp.ovh/imgs/2021/11/ad0a73868854d498.png)]

反射的应用场景
编译时类型和运行时类型

在Java程序中许多对象在运行时都会出现两种类型:编译时类型和运行时类型。

  1. 编译时类型是由声名对象是所使用的类型。
  2. 运行时类型是指实际运行时由实际赋值给对象的类型决定
Person  p = new Student()
编译时类型:Person
运行时类型:Student

编译时类型是无法获取具体方法的。

程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现类和对象的真实性信息。然而编译时根本无法预知该对象和类属于哪些类,程序只能依靠在运行时的信息来发现该类和对象的真实信息。此时则必须使用到反射机制。

使用反射机制可以打破封装性,导致了java对象的属性不安全。

Java反射API

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKKRkTwG-1636517914732)(https://s3.bmp.ovh/imgs/2021/11/ad58181a1cf4bcb5.jpg)]


反射API用来生成JVM中的类、接口或对象的信息。
  1. Class类:该类是反射的核心类,可以获取类的属性、方法等信息。
  2. Field类:Java.lang.reflect包中的类,表示该类的成员变量,它可以用来获取和设置类之中的属性值
  3. Method类:Java.lang.reflect包中的类,表示该类的成员方法,它可以用来获取类中的方法信息或执行方法。
  4. Construcor类:Java.lang.reflect包中的类,表示该类的构造方法。
相关API
    getName():获得类的完整名字。
    getFields():获得类的public类型的属性。
    getDeclaredFields():获得类的所有属性。包括private 声明的和继承类
    getMethods():获得类的public类型的方法。
    getDeclaredMethods():获得类的所有方法。包括private 声明的和继承类
    getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes 参数指定方法的参数类型。
    getConstructors():获得类的public类型的构造方法。
    getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes 参数指定构造方法的参数类型。
    newInstance():通过类的不带参数的构造方法创建这个类的一个对象。
反射的使用步骤及方法(获取Class对象,调用对象的方法)
  1. 获取想要操作类的Class的对象,这是反射类的核心,通过该类可以调用任意类的方法。
  2. 调用Class类的方法,就是反射的使用阶段。
  3. 使用反射API来获取这些信息。
获取某个类的Class对象的3种方法
  • 调用某个类的getClass()方法。

    Person p = new Person()
    Class clazz = p.getClass()
    
  • 调用某个类的class属性来获取该类对应的Class对象。

    Class clazz = Person.class//该属性是该类的静态属性
    
  • 使用Class类中的forName()静态方法获取(最安全/性能最好)

    Class clazz = Class.forName('类路径');//最常用
    //但可能抛出 ClassNotFoundException 异常
    
    • 第一种方法是通过类的全路径字符串获取 Class 对象,这也是我们平时最常用的反射获取 Class 对象的方法;
    • 第二种方法有限制条件:需要导入类的包
    • 第三种方法已经有了 Student 对象,不再需要反射。
  • 通过这三种方式获取到的 Class 对象是同一个,也就是说 Java 运行时,每一个类只会生成一个 Class 对象

使用获取到的Class对象获取该类的属性和方法信息
//1.使用最常用的获取某类的Class类对象的方法
Class clazz = Class.forName('reflection.Persson');
//2.使用Class对象获取Person类的所有方法信息
Method[] methods = clazz.getDeclareMethods();
for(Method m:method){
    System.out.println(m.toString());
}
//3.获取Person类的所有成员属性信息(包括私有属性)
Field[] field = clazz.getDeclareFields();
for(Field f:fiedl){
    System.out.println(f.toString());
}
//4.获取 Person 类的所有构造方法信息
Constructor[]constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
 	System.out.println(c.toString());
}
综合实例
//获得类完整的名字
String className = c2.getName();
System.out.println(className);//输出com.ys.reflex.Person

//获得类的public类型的属性。
Field[] fields = c2.getFields();
for(Field field : fields){
   System.out.println(field.getName());//age
}

//获得类的所有属性。包括私有的
Field [] allFields = c2.getDeclaredFields();
for(Field field : allFields){
    System.out.println(field.getName());//name    age
}

//获得类的public类型的方法。这里包括 Object 类的一些方法
Method [] methods = c2.getMethods();
for(Method method : methods){
    System.out.println(method.getName());//work waid equls toString hashCode等
}

//获得类的所有方法。
Method [] allMethods = c2.getDeclaredMethods();
for(Method method : allMethods){
    System.out.println(method.getName());//work say
}

//获得指定的属性
Field f1 = c2.getField("age");
System.out.println(f1);
//获得指定的私有属性
Field f2 = c2.getDeclaredField("name");
//启用和禁用访问安全检查的开关,值为 true,则表示反射的对象在使用时应该取消 java 语言的访问检查;反之不取消
f2.setAccessible(true);
System.out.println(f2);

//创建这个类的一个对象
Object p2 =  c2.newInstance();
//将 p2 对象的  f2 属性赋值为 Bob,f2 属性即为 私有属性 name
f2.set(p2,"Bob");
//使用反射机制可以打破封装性,导致了java对象的属性不安全。
System.out.println(f2.get(p2)); //Bob

//获取构造方法
Constructor [] constructors = c2.getConstructors();
for(Constructor constructor : constructors){
    System.out.println(constructor.toString());//public com.ys.reflex.Person()
}
实践
package com.test.reflection;

public class Student {

    private String studentName;
    public int studentAge;

    public Student() {
    }

    private Student(String studentName) {
        this.studentName = studentName;
    }

    public void setStudentAge(int studentAge) {
        this.studentAge = studentAge;
    }

    private String show(String message) {
        System.out.println("show: " + studentName + "," + studentAge + "," + message);
        return "testReturnValue";
    }
}
// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名
Class studentClass = Class.forName("com.test.reflection.Student");
// 2.获取声明的构造方法,传入所需参数的类名,如果有多个参数,用','连接即可
Constructor studentConstructor = studentClass.getDeclaredConstructor(String.class);
// 如果是私有的构造方法,需要调用下面这一行代码使其可使用,公有的构造方法则不需要下面这一行代码
studentConstructor.setAccessible(true);
// 使用构造方法的newInstance方法创建对象,传入构造方法所需参数,如果有多个参数,用','连接即可
Object student = studentConstructor.newInstance("NameA");
// 3.获取声明的字段,传入字段名
Field studentAgeField = studentClass.getDeclaredField("studentAge");
// 如果是私有的字段,需要调用下面这一行代码使其可使用,公有的字段则不需要下面这一行代码
// studentAgeField.setAccessible(true);
// 使用字段的set方法设置字段值,传入此对象以及参数值
studentAgeField.set(student,10);
// 4.获取声明的函数,传入所需参数的类名,如果有多个参数,用','连接即可
Method studentShowMethod = studentClass.getDeclaredMethod("show",String.class);
// 如果是私有的函数,需要调用下面这一行代码使其可使用,公有的函数则不需要下面这一行代码
studentShowMethod.setAccessible(true);
// 使用函数的invoke方法调用此函数,传入此对象以及函数所需参数,如果有多个参数,用','连接即可。函数会返回一个Object对象,使用强制类型转换转成实际类型即可
Object result = studentShowMethod.invoke(student,"message");
System.out.println("result: " + result);
总结

灵活使用反射能让我们代码更加灵活,这里比如JDBC原生代码注册驱动,hibernate 的实体类,Spring 的 AOP等等都有反射的实现。但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,合理使用才是真!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值