首页 > 技术文章 > Java反射机制详解

jieshou 2021-03-13 16:36 原文

概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。

总结:反射就是把java类中的各种成分映射成一个个的Java对象。

Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)获得任何一个类的字节码。包括接口、变量、方法等信息。还可以让我们在运行期实例化对象,通过调用get/set方法获取变量的值。

如果由Java的类实例化对象叫做"正",那么由一个对象反推出的类就叫做"反射"。

反射机制能做什么?

反射机制主要提供了以下的功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构建任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法
  • 生成动态代理

·反射和类的关系:

在程序运行状态中,对任意一个类(指class文件),都能够知道这个类的所有属性和方法。

·反射和类对象的关系:

反射对于某一个类的一个对象,都能够调用它的任意一个方法和属性。

·Java反射机制:

通俗地描述Java反射机制:能够动态地获取对象的信息,就称之为反射。

·java反射机制的好处:

极大的提高了应用程序的扩展性。在反射以前,一般使用多态来提高扩展性,将子类对象传递给父类引用来实现的。但这种方式必须要通过new来建立子对象,子类对象必须写死在代码中。而有了反射之后,就可以通过反射来省略掉new子类对象这一个步骤。

反射直接将子类对象的类名以字符串的形式传递给反射技术框架并由反射技术框架来创建这个字符串代表的类的实例。

反射的另类理解

  • 反射就是把Java类中的各个成分映射成相应的类
  • 一个Java类的组成:成员变量、成员方法、构造方法、修饰符、包等。
  • Java中的一个指定的类中的每一个成员都可以用相应的Java反射类的API的一个实例来表示。

006ozJEaly1gj0yi6lanpj30fm0afq42.jpg

Class类简介

Class对象

虚拟机在class文件的加载阶段,把类的信息保存在方法区数据结构中,并在java堆中生成一个Class对象,作为类信息的入口。

006ozJEaly1gj0yidn1ppj30ck0520su.jpg

获取Class对象的三种方式

定义两个类,Student.javaTeacher.java

class Student{
    private String name;
    private int age;
    static{
        System.out.println("Student");
    }
}

class Teacher{
    private String name;
    private int age;
    static{
        System.out.println("Teacher");
    }
}

1.通过实例变量方式

public class Test{
    public static void main(Strint args[]){
        Student student = new Student();
        Class clazz = student.getClass();
    }
}

2.通过类名的方式

public Class Test{
    public static void main(String args[]){
        Class clazz = Student.class;
    }
}

使用这种方法时,只会加载Student类,不会触发其类构造器的初始化。

3.通过Class.forName()的方式

public class Test{
    public static void main(String args[]){
        Class clazz = Class.forName("com.Student");
    }
}

在JDK源码的实现中,forName方法会调用Native方法forName0(),它在JVM中调用findClassFromClassLoader()加载Student类,其原理和ClassLoader相同,将会触发Student类的类构造器初始化。

forName0()方法声明如下:

private static native Class<?> forName0 (String name,booelan intialize,ClassLoader loader,Class<?> caller)

其中intialize参数用来告诉虚拟机是否要对加载的类进行初始化,如果为false,则不会进行初始化。

反射机制reflect

1.获取类字段

Class class_student = Student.class;
Field[] field = class_student.getDeclaredFields();
for(Field field : fields){
    System.out.println(field.getName());
}

2.获取类方法

Class class_student = Student.class;
Method[] methods = class_student.getDeclaredMethods();
for(Method method : methods){
    System.out.println(method);
}

3.获取对应的实例构造器,并生成实例

public class ClassTest{
    public static void main(String args[]) throws NoSuchMethodException {
        Class class_student = Student.class;
        Constructor constructor = class_student.getConstructor(String.class,int.class);
        constructor.newInstance("Tom",10);
    }
}

class Student{
    private String name;
    private int age;

    public Student(String name, int age){
        this.name=name;
        this.age=age;
    }
}

如果没有显示的声明默认构造器,class_student.getConstructor()会抛出NoSuchMethodException异常。

4.通过newInstance()方法生成类实例

Class class_student = Student.class;
Student student = class_student.newInstance();

5.设置私有变量

Class class_student = Student.class;
Field name = class_student.getDeclaredField("name");
name.setAccessible(true);
Student student = (Student)class_student.newInstance();
name.set(student,"name");

6.获取私有变量

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe)f.get(null);

这种方式在使用Unsafe类进行黑魔法时经常用到。

反射应用场景

加载数据库驱动

Class.forName的一个很常见的方法是在加载数据库驱动的时候。

例:

Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
Connection conn = DriverManager.getConnection("jdbc.sqlserver://localhost:1433;DatabaseName=jieshou","jieshou","jieshou");

为什么我们在加载数据库驱动包的时候没有使用newInstance()方法呢?即有的jdbc数据库连接的写法为Class.forName("xx.xx.xx");,而也有的写法为Class.forName("xx.xx.xx").newInstance();.这是因为Class.forName("");的作用为要求JVM查找并加载指定的类,如果在类中有静态初始器的话,JVM就会执行该类的静态代码段。而在jdbc的规范中明确要求这个Driver类必须向DriverManager注册自己,即任何一个JDBCDriver的Driver类代码都必须类似如下:

public class MyJDBCDriver implements Driver{
    static{
        DriverManager.registerDriver(new MyJDBCDriver());
    }
}

既然在静态初始化器中已经完成注册,所以在使用JDBC的时候只需要写Class.forName("");就可以了。

推荐阅读