坚持看完保证你能深刻理解Java的反射机制

news/2024/5/19 5:20:33 标签: 反射

静态语言 & 动态语言

在讲反射之前先来了解一下什么是静态语言和动态语言

动态语言

是一类在运行时可以改变其结构的语言,例如新的函数、对象、甚至代码都可以被引进,已有的函数可以被删除,或是其他结构上的变化, 通俗地讲就是在运行时代码可以根据某些条件改变自身结构。

主要动态语言:Object-C、C#、JavaScript、PHP、Python等。

静态语言

与动态语言相对应,运行时结构不可变的语言是静态语言,如Java、C、C++

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制获得类似动态语言的特性,这可以让Java程序的编写变得更加灵活。

反射机制介绍

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

Reflection 是Java 被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

Class c = Class.forName("java.lang.String")

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过一个类的Class对象获取该类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们形象地称之为:反射

  • Class类是理解反射机制的关键,这个后面会重点讲解。

一般方式获取一类的对象 vs 通过反射获取一个对象的类信息

正常方式import package 包名 -> 通过new实例化 -> 取得实例化对象

反射方式:实例化对象 -> getClass()方法 -> 得到完整的“包类”名称

Java反射机制提供的功能

这些功能可能一时难以理解,但是首先得知道反射能干什么

在运行时 判断任意一个对象所属的类;

在运行时 构造任意一个类的对象;

在运行时 判断任意一个所具有的成员变量和方法;

在运行时 调用任意一个对象的成员变量和方法;

在运行时 获取泛型信息;

在运行时 处理注解;

生成动态代理。

为什么要有反射

假如有段代码:Object o = new Object();

当JVM启动,代码会编译成一个.class文件,然后被类加载器加载到JVM的内存中,类Object加载到方法区中,创建了Object类的Class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个Class对象,作为方法区类的数据结构的接口。JVM创建对象前,会先检查类是否加载,寻找类对应的Class对象,若加载好,则为对象分配内存,初始化也就是代码:new Object()

为什么要讲这个呢?因为要理解反射必须知道它在什么场景下使用。

上面的程序对象是自己new的,程序相当于写死了给JVM去跑。假如一个服务器上突然遇到某个请求要用到某个类,但没加载进JVM,是不是要停下来自己写段代码,new一下,启动一下服务器。。。

有了反射之后…

当程序在运行时,需要动态地加载一些类,这些类可能之前用不到所以不用加载到JVM,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,下面举几个例子来说明反射的作用:

  1. 我们的项目底层有时是用MySQL,有时用Oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设我们要用com.java.dbtest.MyqlConnection以及com.java.dbtest.OracleConnection这两个类,这时候我们的程序就写得比较动态化,Class tc = Class.forName("com.java.dbtest.TestConnection");通过类的全类名让JVM在服务器中找到并加载这个类,而如果是Oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出Java的特性了!
  2. 在Spring中配置各种各样的bean时,是以配置文件的形式配置的,需要用到哪些bean就配哪些,Spring容器就会根据我们的需求去动态加载,我们的程序就能健壮地运行。
  3. Java的反射机制就是增加程序的灵活性,避免将程序写死到代码里, 例如: 实例化一个 Person()对象, 不使用反射就是 new Person(); 如果想变成实例化其他类, 那么必须修改源代码,并重新编译。 使用反射就是 class.forName("Person").newInstance(); 而且这个类描述可以写到配置文件中,如 .xml,这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。

下面的话很重要 !

方法区存储的是类的信息,不是类对象,建议看一下JVM内存分配,类加载器加载类是通过方法区上类的信息在堆上创建一个类的Class对象,这个Class对象是唯一的(由JVM保证唯一),之后对这个类的创建都是根据这个Class对象来操作的。

可以理解成,某个类存在于方法区中,该类的Class对象存在于堆中,这个Class对象会作为运行时创建该类对象的模版。这个Class对象是唯一对应该类的,要区分所谓的实例和Class对象。为什么需要Class对象,想象一下,如果一个加载进方法区的类,在JVM运行时是动态加载进来的,如果没有这个Class对象,该如何访问一个未知的类并创建对象呢?没错,就是这个Class作为访问接口。

那么什么是Java的反射呢?

要让Java程序能够运行,那么就得让Java类被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了所需要的那个类已经被加载了。

Java的反射机制就是,在编译并不确定是哪个类被加载了,而是在程序运行的时候才去加载、探知、自审。使用在编译期并不知道的类,这样的特点就是反射

Java反射优点及缺点

优点

可以实现动态创建对象和编译,具有很大的灵活性。

缺点

对性能有影响。

使用反射基本上是一种解释操作,我们可以告诉JVM,希望做什么并且它满足我们的要求。这些操作总是慢于直接执行相同的操作。

反射相关的API

java.lang.Class 代表一个类

java.lang.reflect.Method 代表类的方法

java.lang.reflect.Field 代表类的成员变量

java.lang.reflect.Construct 代表类的构造器

Class类

前面一直说Class类,那么Class到底什么?

前言

在Java中一切皆对象,描述任何事物(不管是具体还是抽象),我们都可以用一个类来描述,同样,对于任何一个类,都有相同的特征,比如所有的类一般都包含属性、构造方法、一般方法、该类所继承的接口,该类所实现的接口等等,这些都是这个类所具备的信息,那么如何去描述这些信息,我们就用Class这个类的实例来描述,每个类对应一个Class类的实例。这样,我们可以通过这个实例,将该类复现出来。

Class类简介

Class类是描述类的类。

一个类被加载后,类的整个结构都会被封装在Class对象中。

一个类在内存中只有一个Class对象。

在Object类中定义了下面这个方法 ,此方法将被所有子类继承:

public final class getClass()

以上的方法返回值类型是一个Class类,此类是Java反射的源头。

实际上,所谓反射,从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。

对于每个类,JRE 都为其保留一个不变的Class类型的对象,一个Class对象包含了特定的某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。

Class本身也是一个类;

Class对象只能由系统建立;

一个加载的类在JVM中只会有一个Class实例;

一个Class对象对应的是一个加载到JVM中的.class文件;

每个类的实例都会记得自己是由哪个Class实例所生成;

通过Class可以完整地得到一个类中的所有被加载的结构;

Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。

Class类常用的方法

如下表:

方法名
static ClassforName(String name)返回指定类名name的Class对象
Object newInstance()调用缺省构造函数,返回Class对象的一个实例
getName()返回此Class对象所表示的实体(类、接口、数组类或void)的名称
Class getSuperclass()返回当前Class 对象的父类的Class对象
Class[] getinterfaces()获取当前Class对象的接口
ClassLoader getClassLoader()返回该类的类加载器
Constructor[] getConstructors()返回一个数组,该数组包含某些Constructor对象
Method getMethod(String name, Class..T)返回一个Method对象,此对象的形参类型为paramType
Field[] getDeclaredFields()返回Field对象的一个数组

获取 Class 对象的三种方式

当类中方法定义为私有的时候我们能调用吗?不能!当变量是私有的时候我们能获取吗?不能!但是反射可以,比如源码中有你需要用到的方法,但是那个方法是私有的,这个时候你就可以通过反射去执行这个私有方法,并且获取私有变量。

如果我们想动态获取到这些信息,我们需要依靠 Class 对象。Class 类对象将一个类的方法、变量等信息告诉运行的程序。

Java 提供了三种方式获取 Class 对象:

  1. 已知具体类,通过类的class属性获取,该方法最为安全可靠,程序性能最高:
Class clazz1 = Person.class;  

但是我们一般是不知道具体类的,基本都是通过遍历包下面的类来获取 Class 对象。

  1. 通过 Class.forName()传入类的路径获取:
Class clazz2 = Class.forName("cn.hory.Person");
  1. 通过对象实例获取:
Class clazz3 = p.getClass();  // p为Person类的实例

注意:以上通过不同方式多次获得一个类的Class对象都是同一个。

此外,还有其他方式

  1. 基本内置类型的包装类都有一个TYPE属性,所以我们可以通过TYPE属性获得基本内置类型的Class对象:
Class clazz4 = Integer.TYPE;
  1. 知道其子类,获取其父类类型:
Class clazz5 = c1.getSuperclass();

测试1

package com.hory.testReflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author Hory
 * @Date 2020/9/20
 */
public class Person {
    private String name;
  	private int age;

    public Person() {
        name = "Hory";
    }
  
  	public Person(String name, int age){
      	this.name = name;
      	this.age = age;
    }

    public void publicMethod(String s) {
        System.out.println("public:" + s);
    }

    private void privateMethod() {
        System.out.println("name is " + name);
    }

    public static void main(String[] args) throws ClassNotFoundException,
            IllegalAccessException, InstantiationException, NoSuchMethodException,
            InvocationTargetException, NoSuchFieldException {

        /**
         * 获取 Person 类的 Class 对象并且创建Person类实例
         */
        Class<?> personClass = Class.forName("com.hory.testReflect.Person");
        Person personObject = (Person) personClass.newInstance(); //本质上是调用了类的无参构造器
              
        // 也可以通过构造器创建对象
        Constructor constructor = personClass.getDeclaredConstructor(String.class, int.class);
        Person personObject01 = (Person) constructor.newInstance("hory", 25);

        /**
         * 获取所有类中所有定义的方法
         */
        Method[] methods = personClass.getDeclaredMethods();
        for(Method method : methods) {
            System.out.println(method.getName());
        }

        /**
         * 获取指定方法并调用
         */
        Method publicMethod = personClass.getDeclaredMethod("publicMethod", String.class);
        publicMethod.invoke(personObject,"this is a public method ");

        /**
         * 获取指定参数并对参数进行修改
         */
        Field field = personClass.getDeclaredField("name");
        // 不能直接操作私有属性,为了对类中的参数进行修改,需要取消安全检测
        field.setAccessible(true);
        field.set(personObject,"HoryChang");

        /**
         * 调用 private 方法
         */
        Method privateMethod = personClass.getDeclaredMethod("privateMethod");
        // 为了调用private方法我们取消安全检查
        privateMethod.setAccessible(true);
        privateMethod.invoke(personObject);

    }
}

输出:

publicMethod
privateMethod
main
public:this is a public method 
name is HoryChang

注意 : 有读者提到上面代码运行会抛出 ClassNotFoundException 异常,具体原因是你没有下面把这段代码的包名替换成自己创建的 TargetObject 所在的包 。

Class<?> personClass = Class.forName("com.hory.testReflect.Person");

区别

方法说明
getFields只能找到public属性的方法
getDeclaredFields可以找到全部的属性
getMethods获得本类及其父类的全部public属性的方法
getDeclaredMethods获得本类全部方法

setAccessible

  • Method、Field、Constructor对象都有setAccessible()方法
  • setAccessible()作用是启动或者禁用访问安全检查的开关
  • 参数为true的时候则指示反射的对象在使用时应该取消Java语言访问检查
    • 提高反射的效率。如果代码中必须用反射,而该方法需要频繁被调用,那么请设置为true
    • 是的原本无法访问的私有成员也可以被访问
  • 参数为false则指示反射的对象应该实施Java语言访问检查

测试2:

package testReflect;

public class Person {
    private String name;
    private int age;

    public Person() {
    }

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

    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Person();
        System.out.println("包名: " + person.getClass().getPackage().getName() );
        System.out.println("完整类名: " + person.getClass().getName());

        Integer list = new Integer(2);
        System.out.println(list);

        //java中三种class获取方式
      
        //方式一:利用对象调用getClass()方法获取该对象的Class实例
        Class<? extends Person> personClazz01 = person.getClass();
      
        //方式二:使用Class类的静态方法forName(),用类的名字获取一个Class实例
        Class<?> personClazz02 = Class.forName("Person");
      
        //方式三:运用.class的方式来获取Class实例,对于基本数据类型的封装类,
        // 还可以采用.TYPE来获取相对应的基本数据类型的Class实例
        Class<? extends Person> personClazz03 = Person.class;
        Class<? extends Integer> listClazz = Integer.TYPE;
    }
}
package test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class Test {
    private int age;
    private String name;
    private int testint;

    //Test类中我们定义是三个私有变量,生成两个公有的含参构造方法和一个私有的含参构造方法以及一个公有的无参构造方法
    //下面我们通过反射获取这些构造方法
    private Test(String name){
        this.name = name;
    }

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

    public static void main(String[] args) {
//        根据一个字符串得到一个类
//        String name = "ZHR";
//        Class c1 = name.getClass();
//        System.out.println(c1);

//        //获取java.lang.String的类名
//        String name  = "java.lang.String";
//        Class c1 = null;
//        try{
//            c1 = Class.forName(name);
//            System.out.println(c1.getName());
//        }catch (ClassNotFoundException e){
//        }
//        //我们还可以通过c2.getSuperclass()获取到他的父类
//        Class c2 = null;
//        try{
//            c2 = Class.forName(name);
//            System.out.println(c2.getSuperclass());
//        }catch (ClassNotFoundException e){
//        }

        //获取类的所有构造方法
        Test test = new Test();
        Class c3 = test.getClass();
        Constructor[] constructors;
        constructors = c3.getDeclaredConstructors();
        //通过 getDeclaredConstructors 可以返回类的所有构造方法,返回的是一个数组,因为构造方法可能不止一个,
        //通过 getModifiers 可以得到构造方法的类型,
        //通过 getParameterTypes 可以得到构造方法的所有参数,返回的是一个Class数组,
        //所以我们如果想获取所有构造方法以及每个构造方法的参数类型,可以有如下代码
        for(int i=0;i<constructors.length;i++){
            System.out.print( Modifier.toString(constructors[i].getModifiers()) + "参数:");
            Class[] parametertypes = constructors[i].getParameterTypes();
            for(int j=0;j<parametertypes.length;j++){
                System.out.print(parametertypes[j].getName() + " ");
            }
            System.out.println(""); //换行
        }
    }
}

有哪些类型可以有Class对象?

class:外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类

interface:接口

[]:数组(二维数组也有)

enum:枚举

annotation:注解@interface

primitive type:基本数据类型

void

参考:https://blog.csdn.net/qq_40406704/article/details/98060936?utm_source=app


http://www.niftyadmin.cn/n/1690221.html

相关文章

MacOS 终端导入/导出 sql 脚本

终端导入sql脚本 比如导入一个mysql脚本文件casaba.sql&#xff0c;路径为/Users/superfarr/Desktop/casaba.sql 如果脚本中有创建数据库的语句&#xff0c;我们就不需要再次创建&#xff0c;如果没有&#xff0c;我们需要自己创建 下面的流程为针对脚本中没有创建数据库语句…

SpringBoot + Mybatis + Thymeleaf 搭建个人博客——Iceberg-Blog

Iceberg-BlogWhy Iceberg Blog 为什么叫 Iceberg Blog &#xff1f; 学无止境&#xff0c;无论何时&#xff0c;我们都会感到知识就像一座冰山&#xff0c;我们学到的只是冰山一角。 博客简介 项⽬描述&#xff1a;采⽤前后端分离架构实现的博客系统&#xff0c;主体架构采用…

MyBatis 中 #{} 和 ${} 区别

二者区别 #{} 是预编译处理&#xff0c;传进来的数据会加个" " #将传入的数据都当成一个字符串&#xff0c;会对自动传入的数据加一个双引号 ${}就是字符串替换&#xff0c;直接替换掉占位符 $方式一般用于传入数据库对象&#xff0c;例如传入表名 SQL 注入 使…

JavaOJ训练——输入一串以逗号隔开数字然后存入数组中并输出

代码&#xff1a; public class OJ {public static void main(String[] args){Scanner sc new Scanner(System.in);System.out.println("请输入一串用英文逗号隔开的整数&#xff1a;");String input sc.next();String[] strArr input.split(",");int[…

LeetCode系列之「反转链表」

剑指 Offer 24. 反转链表 ListNode // Definition for singly-linked list. public class ListNode {int val;ListNode next;ListNode(int x) { val x; } }一、迭代解法&#xff1a; class Solution {public ListNode reverseList(ListNode head) {if(head null) return n…

Jupyter Notebook在清华镜像下安装Nbextensions 插件

清华镜像下安装Nbextensions Nbextensions 有很多好用的扩展功能,比如显示目录,显示程序执行时间(Execute Time),美化格式(Code prettify),代码折叠(Codefolding)等。 但是直接安装比较慢,我们利用镜像安装。 在终端执行: pip install jupyter_contrib_nbexten…

MAC终端输入换行问题

问题描述 当在Mac终端首行输入命令行过长时&#xff0c;会把第一行前面的信息覆盖&#xff0c;直到将第一行填满才会自动换行&#xff0c;如下图&#xff1a; 问题分析 找了好久才发现是之前配置终端格式的时候造成的&#xff0c;Mac原生的终端界面是这种&#xff1a; 可…

大学生/程序员必备技能——画图!

大学生 / 程序员 必备技能&#xff1a; 画论文示意图 / 流程图画 UML 分享几个最常用的画图的工具 Window 版的 VisioMac 版的 OmniGraffle 为什么画图是很有必要的呢&#xff1f; 在写论文或者在做笔记的过程中&#xff0c;经常需要示意图帮助我们更好地理解知识点&#…