深入分析java中的反射机制

简介: 对于java中的反射机制,面试的时候也是会经常的提问到,在网上看了很多文章也查了很多资料,于是花了一部分时间整理了一下,也算是查漏补缺吧。

一、反射概念


在正式讲解反射之前,为了很好的去理解它我们先从一个案例说起。请看下面的代码:

public class User {
    private String name;
    private int age;
    public User(){}
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void test(){
        System.out.println("年龄是:"+name);
    }
}

这是一个最简单不过的类,当我们使用的时候直接new出来一个User对象即可。因为这个类是我们自己定义的,所以在使用的时候我们知道User有两个字段name和age,还有无参和有参构造方法,另外的test方法我们也可以直接调用(因为其是public)。


现在出现一个问题,如果这个user类不是我们自己定义的,我们从外部看不到里面有什么东西,而且我们又想去知道内部长什么样,比如说有几个字段、方法、构造方法、共有还是私有的等等,这时候该怎么办呢?这时候java语言在设计的时候为我们提供了一个机制,就是反射机制。他能够很方便的去解决我们的问题。


通过上面的例子我相信你也能对java反射机制的功能了解一二了,现在我们再来整理一下:

java反射机制允许我们程序员在 程序运行的时候获取一个类的各种内部信息,比如说modifiers(诸如public, static 等)、superclass(例如Object)、实现之interfaces(例如Cloneable),也包括fields和methods的所有信息。 更重要的是我们还能够修改这些信息

下面我们就来好好看一下,java中的反射机制是如何在运行时获取这些类的内部信息的。


二、深入分析java反射机制


1、获取Class类


在java中万事万物皆对象,User user=new User()一行代码我们知道了user是User类的实例对象,通过Student stu=new Student()我们知道了stu是Student的实例对象,但是我们想过没,User和Student又是谁的对象呢?没错就是Class类的实例对象。那这个Class类是什么东西,内部长什么样子呢?这时候我们很自然的联想到使用反射机制。使用反射机制就可以获取到这个class。


这里有三种方式可以获取这个Class,我们来看一下代码:

//这里假设我们之前不知道User类的内部状态
public class Test {
    public static void main(String[] args) {
        User user = new User();
        // 第一种表示方式:通过类名
        Class c1 = User.class;
        // 第二中表达方式:通过对象
        Class c2 = user.getClass();
        //第三种表达方式
        try {
            Class c3 = Class.forName("com.fdd.reflecttest.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

上面的c1、c2、c3都是Class类的实例,表示的都是User类。

当然,不仅仅是User这些类,对于基本数据类型甚至是包括void我们也可以使用这个方法。

Class c1 = int.class;
Class c2 = String.class;
Class c3 = double.class;
Class c4 = Double.class;
Class c5 = void.class;

现在就可以直接创建User类的实例了。

User user = (User)c1.newInstance();
//需要有无参数的构造方法

现在我们对反射机制中获取Class类的方法进行一个总计

v2-653423aa9f7f3fa1a9dbdbd13ad11ef4_1440w.jpg

2、获取类的方法


现在通过反射看一下User类内部的样子,打印一下(把这个操作封装在了一个方法中):

//打印类的信息,包括类的成员函数、成员变量(只获取成员函数)
    public static void printClassMethodMessage(Object obj){
        //1、首先要获取类的类类型
        Class c = obj.getClass();
        //2、获取类的名称
        System.out.println("类的名称是:"+c.getName());
        //3、获取方法信息
        Method[] ms = c.getMethods();
        for(int i = 0; i < ms.length;i++){
            //第一步:得到方法的返回值类型的类类型
            Class returnType = ms[i].getReturnType();
            System.out.print(returnType.getName()+" ");
            //第二步:得到方法的名称
            System.out.print(ms[i].getName()+"(");
            //第三步:获取方法参数类型--->得到的是参数列表的类型的类类型
            Class[] paramTypes = ms[i].getParameterTypes();
            for (Class class1 : paramTypes) {
                System.out.print(class1.getName()+",");
            }
            System.out.println(")");
        }
    }

下面我们把我们的User类传进去,打印一下。

public class Test {
    public static void main(String[] args) {
        User user=new User("18",20);
        ClassUtil.printClassMethodMessage(user);
    }
}
//output
//类的名称是:com.fdd.reflecttest.User
//void test()
//void wait()
//void wait(long,int,)
//void wait(long,)
//boolean equals(java.lang.Object,)
//java.lang.String toString()
//int hashCode()
//java.lang.Class getClass()
//void notify()
//void notifyAll()

v2-9f78f0452d0da3f4a3d171f82ece7033_1440w.jpg

3、获取类的属性


(1)获取所有属性

public static void printFieldMessage(Object obj) {
        Class c = obj.getClass();
        //Field类封装了关于成员变量的操作
        //  getFields()方法获取的是所有的public的成员变量的信息
        //  getDeclaredFields获取的是该类自己声明的成员变量的信息
        //Field[] fs = c.getFields();
        Field[] fs = c.getDeclaredFields();
        for (Field field : fs) {
            //1、获取字段类型
            Class fieldType = field.getType();
            String typeName = fieldType.getName();
            //2、获取字段名称
            String fieldName = field.getName();
            //打印字段类型和字段名称
            System.out.println(typeName+" "+fieldName);
        }
    }

上面有两种获取属性的方法。重点是for循环。我们来测试一下

public class Test {
    public static void main(String[] args) {
        User user=new User("18",20);
        ClassUtil.printFieldMessage(user);
    }
}
//output
//java.lang.String name
//int age

直接就会输出我们的字段类型和名称。

(2)获取指定属性

在这里我们的User类中name、age字段增加getter和setter方法

public static Object printFieldMsgBySelf(Object obj) {
        Class c = obj.getClass();
        try {
            //1、获取指定字段
            Field age_field = c.getDeclaredField("age");
            //2、实例化一个User类
            Object object=c.newInstance();
            //3、使用反射机制打破封装
            age_field.setAccessible(true);
            //4、给我们实例化的对象重新设置年龄
            age_field.set(object, 100);
            //5、返回这个object
            return object;
        }  catch (Exception e) {
            e.printStackTrace();
        }
        return c;
}

然后我们测试一下

public class Test {
    public static void main(String[] args) {
        User userBefore=new User("18",20);
        System.out.println(userBefore.getAge());
        User userAfter=(User) ClassUtil.printFieldMsgBySelf(userBefore);
        System.out.println(userAfter.getAge());
    }
}
//output
//20
//100

在这里,我们在printFieldMsgBySelf方法中通过反射重新设置了age年龄的值,输出之后已成功更改。

v2-19e1d5b76da12466f6d0ebb6aabef389_1440w.jpg

4、获取类的构造方法


public static void printConMessage(Object obj){
        Class c = obj.getClass();
        //Constructor中封装了构造函数的信息
        //  (1)getConstructors获取所有的public的构造函数
        //  (2)getDeclaredConstructors得到所有的构造函数
        //第一步:获取构造函数
        Constructor[] cs = c.getDeclaredConstructors();
        for (Constructor constructor : cs) {
            //获取构造函数名字
            System.out.print(constructor.getName()+"(");
            //获取构造函数的参数列表
            Class[] paramTypes = constructor.getParameterTypes();
            for (Class class1 : paramTypes) {
                System.out.print(class1.getName()+",");
            }
            System.out.println(")");
        }
}

然后我们同样的测试一下

public class Test {
    public static void main(String[] args) {
        User user=new User("18",20);
        ClassUtil.printConMessage(user);
    }
}
//output
//com.fdd.reflecttest.User()
//com.fdd.reflecttest.User(java.lang.String,int,)

跟我们之前的构造方法一样。

v2-c009b3c7d1108881aaa0fa5c5660c5a4_1440w.jpg

5、获取User类的父类和接口


我们在这里定义一个Human类(里面什么也没有),然后定义一个UserInterface接口,让User继承它就好了。

//取得父类和接口
public static void getUserSuperClassAndInterface(Object obj) {
        Class c = obj.getClass();
        //取得父类:父类只能有一个
        Object superClass = c.getSuperclass();
        System.out.println(superClass);
        //取得接口:接口可以有很多
        Object[] superInterface = c.getInterfaces();
        for (Object myInter:superInterface) {
            System.out.println(myInter);
        }
}
//调用这个方法后的输出结果:
//class com.fdd.reflecttest.Human
//interface com.fdd.reflecttest.UserInterface

然后我们在Test中去测试一下就可以了,测试方法很简单,我们只需要调用这个方法就可以。


小结:在上面的案例中,我们使用反射机制能够获取类的方法、字段、构造方法、父类和接口,当然也可以获取一些其他的信息。但是这里有一点重要的知识,那就是我们不仅可以获取上面的这些信息,还可以修改它,这对一个类来说是极其的不安全的。这一点我们需要注意。


下面我们就来看看反射到底用什么用途。


三、使用反射机制


1、通过反射了解泛型的本质


java中集合的泛型是防止错误输入的;只在编译阶段有效,只要绕过编译就无效啦。比如下面的代码:

ArrayList list1=new ArrayList();
ArrayList<String> list2=new ArrayList<String>();

对于list1来说我们可以添加任何对象,但是对于list2来说,就必须输入String类型对象,这就是泛型作用。(当然这里只是简单的提一下,泛型还有很多知识)。在运行时候,是不区分输入什么的。

下面我们就来验证一下。

public class Test {
    public static void main(String[] args) throws Exception {
        ArrayList list1=new ArrayList();
        ArrayList<String> list2=new ArrayList<String>();
        Class c1=list1.getClass();
        Class c2=list2.getClass();
        //在运行期间泛型无效,所以c1和c2应该是一样的。
        System.out.print(c1==c2);//true
        //由于泛型失效,所以此时list当然可以添加任何对象
        Method m=c2.getMethod("add",Object.class);
        m.invoke(list2,20);//向list2集合中添加一个int 型的值;绕过编译
    }
}
//output
//true

上面的输出已经说明一切。


2、spring中使用


学习Spring的时候,我们知道Spring主要有Ioc和AOP两大思想,它利用的是反射机制,依赖注入就不用多说了,而对于Spring的核心AOP来说,使用了动态代理,其实底层也是反射。


当然还有动态代理模式、web拦截器等等,使用极其广泛。但是这里有一个窍门需要我们注意一下,那就是面试的时候,使用反射机制往往能实现违反java语言设计原则的事,比如说String类型是不可变类型的,我们使用反射机制就可以使他变成可变类型的。

OK,反射原理基本上先到这里,对于其用途,在相应的文章中会提到,这里算是反射机制的基础知识吧,因为最终是要去用的。

相关文章
|
2月前
|
人工智能 缓存 安全
Java中的反射机制:深入探索与应用
Java反射机制是程序运行时动态获取类信息并操作类成员的特性,具备高度灵活性,但也伴随性能与安全风险。本文详解反射的基本用法、高级应用及最佳实践,助你掌握这一强大工具的正确使用方式。
121 0
|
4月前
|
设计模式 人工智能 安全
AQS:Java 中悲观锁的底层实现机制
AQS(AbstractQueuedSynchronizer)是Java并发包中实现同步组件的基础工具,支持锁(如ReentrantLock、ReadWriteLock)和线程同步工具类(如CountDownLatch、Semaphore)等。Doug Lea设计AQS旨在抽象基础同步操作,简化同步组件构建。 使用AQS需实现`tryAcquire(int arg)`和`tryRelease(int arg)`方法以获取和释放资源,共享模式还需实现`tryAcquireShared(int arg)`和`tryReleaseShared(int arg)`。
180 32
AQS:Java 中悲观锁的底层实现机制
|
4月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
143 0
|
5月前
|
监控 Java Unix
6个Java 工具,轻松分析定位 JVM 问题 !
本文介绍了如何使用 JDK 自带工具查看和分析 JVM 的运行情况。通过编写一段测试代码(启动 10 个死循环线程,分配大量内存),结合常用工具如 `jps`、`jinfo`、`jstat`、`jstack`、`jvisualvm` 和 `jcmd` 等,详细展示了 JVM 参数配置、内存使用、线程状态及 GC 情况的监控方法。同时指出了一些常见问题,例如参数设置错误导致的内存异常,并通过实例说明了如何排查和解决。最后附上了官方文档链接,方便进一步学习。
509 4
|
2月前
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
143 4
|
2月前
|
存储 Java 大数据
Java 大视界 -- Java 大数据在智能家居能源消耗模式分析与节能策略制定中的应用(198)
简介:本文探讨Java大数据技术在智能家居能源消耗分析与节能策略中的应用。通过数据采集、存储与智能分析,构建能耗模型,挖掘用电模式,制定设备调度策略,实现节能目标。结合实际案例,展示Java大数据在智能家居节能中的关键作用。
|
3月前
|
数据采集 搜索推荐 算法
Java 大视界 -- Java 大数据在智能教育学习社区用户互动分析与社区活跃度提升中的应用(274)
本文系统阐述 Java 大数据技术在智能教育学习社区中的深度应用,涵盖数据采集架构、核心分析算法、活跃度提升策略及前沿技术探索,为教育数字化转型提供完整技术解决方案。
|
2月前
|
人工智能 安全 Java
掌握Java反射:在项目中高效应用反射机制
Java反射是一种强大功能,允许程序在运行时动态获取类信息、创建对象、调用方法和访问字段,提升程序灵活性。它在框架开发、动态代理、注解处理等场景中广泛应用,如Spring和Hibernate。但反射也存在性能开销、安全风险和代码复杂性,应谨慎使用。
|
3月前
|
Java 数据库连接 API
互联网大厂校招 JAVA 工程师笔试题解析及常见考点分析
本文深入解析互联网大厂校招Java工程师笔试题,涵盖基础知识(数据类型、流程控制)、面向对象编程(类与对象、继承与多态)、数据结构与算法(数组、链表、排序算法)、异常处理、集合框架、Java 8+新特性(Lambda表达式、Stream API)、多线程与并发、IO与NIO、数据库操作(JDBC、ORM框架MyBatis)及Spring框架基础(IoC、DI、AOP)。通过技术方案讲解与实例演示,助你掌握核心考点,提升解题能力。
148 2
|
3月前
|
人工智能 Java
Java中的反射机制:深入探索与应用
本文介绍了Java反射机制的基本概念、用途及其实现方式。反射机制允许程序在运行时动态获取类的属性和方法,并调用它们,适用于处理私有成员或权限受限的情况。文章详细讲解了`Class`类的功能,包括获取类的方法、属性、注解、构造器等信息,以及通过四种方式获取`Class`对象的示例代码。此外,还探讨了类加载器、继承关系判断、动态代理等高级内容,展示了如何在运行时创建接口实例并处理方法调用。文末提供了完整的代码示例以加深理解。
Java中的反射机制:深入探索与应用

热门文章

最新文章