图解java反射机制及常用应用场景
在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()的方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者类加载之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为静态的类加载。
但是在有些场景下,我们事先是不知道我们的代码的具体行为的。比如,我们定义一个服务任务工作流,每一个服务任务都是对应的一个类的一个方法。
- 服务任务B执行哪一个类的哪一个方法,是由服务任务A的执行结果决定的
- 服务任务C执行哪一个类的哪一个方法,是由服务任务A和B的执行结果决定的
- 并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式根据不同的条件执行不同的程序,条件本身也是变化的
面对这个情况,我们就不能用代码new 类名()来实现了,因为你不知道用户具体要怎么做配置,这一秒他希望服务任务A执行Xxxx类的x方法,下一秒他可能希望执行Yyyy类的y方法。当然你也可以说提需求嘛,用户改一次需求,我改一次代码。这种方式也能需求,但对于用户和程序员个人而言都是痛苦,那么有没有一种方法在运行期动态的改变程序的调用行为的方法呢?这就是要为大家介绍的“java反射机制”。
那么java的反射机制能够做哪些事呢?大概是这样的几种:
- 在程序运行期动态的根据package名.类名实例化类对象
- 在程序运行期动态获取类对象的信息,包括对象的成本变量和方法
- 在程序运行期动态使用对象的成员变量属性
- 在程序运行期动态调用对象的方法(私有方法也可以调用)
先入个门,大佬可以略过这一段。我们定义一个类叫做Student
如果不用反射的方式,我相信只要学过java的朋友肯定会调用dinner方法
如果是反射的方式我们该怎么调用呢?
通过上面的代码我们看到,com.zimug.java.reflection.Student类名和dinner方法名是字符串。既然是字符串我们就可以通过配置文件,或数据库、或什么其他的灵活配置方法来执行这段程序了。这就是反射最基础的使用方式。
java的类加载机制还是挺复杂的,我们这里为了不混淆重点,只为大家介绍和“反射”有关系的一部分内容。
java执行编译的时候将java文件编译成字节码class文件,类加载器在类加载阶段将class文件加载到内存,并实例化一个java.lang.Class的对象。比如对于Student类在加载阶段会有如下动作:
- 在内存(方法区或叫代码区)中实例化一个Class对象,注意是Class对象不是Student对象
- 一个Class类(字节码文件)对应一个Class对象,并且只有一个
- 该Class对象保存了Student类的基础信息,比如这个Student类有几个字段(Filed)?有几个构造方法(Constructor)?有几个方法(Method)?有哪些注解(Annotation)?等信息。
有了上面的关于Student类的基本信息对象(java.lang.Class对象),在运行期就可以根据这些信息来实例化Student类的对象。
- 在运行期你可以直接new一个Student对象
- 也可以使用反射的方法构造一个Student对象
但是无论你new多少个Student对象,不论你反射构建多少个Student对象,保存Student类信息的java.lang.Class对象都只有一个。下面的代码可以证明。
了解了上面的这些基础信息,我们就可以更深入学习反射类相关的类型和方法了:
- java.lang.Class: 代表一个类
- java.lang.reflect.Constructor: 代表类的构造方法
- java.lang.reflect.Method: 代表类的普通方法
- java.lang.reflect.Field: 代表类的成员变量
- Java.lang.reflect.Modifier: 修饰符,方法的修饰符,成员变量的修饰符。
- java.lang.annotation.Annotation:在类、成员变量、构造方法、普通方法上都可以加注解
虽然有三种方法可以获取某个类的Class对象,但是只有第一种可以被称为“反射”。
Class类对象信息中几乎包括了所有的你想知道的关于这个类型定义的信息,更多的方法就不一一列举了。还可以通过下面的方法
- 获取Class类对象代表的类实现了哪些接口: getInterfaces()
- 获取Class类对象代表的类使用了哪些注解: getAnnotations()
结合上文中的Student类的定义理解下面的代码
- getFields()方法获取类的非私有的成员变量,数组,包含从父类继承的成员变量
- getDeclaredFields方法获取所有的成员变量,数组,但是不包含从父类继承而来的成员变量
- getMethods() : 获取Class对象代表的类的所有的非私有方法,数组,包含从父类继承而来的方法
- getDeclaredMethods() : 获取Class对象代表的类定义的所有的方法,数组,但是不包含从父类继承而来的方法
- getMethod(methodName): 获取Class对象代表的类的指定方法名的非私有方法
- getDeclaredMethod(methodName): 获取Class对象代表的类的指定方法名的方法
上面代码的执行结果如下:
可以看到getMethods获取的方法中包含Object父类中定义的方法,但是不包含本类中定义的私有方法sleep。另外我们还可以获取方法的参数及返回值信息:
- 获取参数相关的属性:获取方法参数个数:getParameterCount()获取方法参数数组对象:getParameters() ,返回值是java.lang.reflect.Parameter数组
- 获取返回值相关的属性获取方法返回值的数据类型:getReturnType()
实际在上文中已经演示了方法的调用,如下invoke调用dinner方法
dinner方法是无参的,那么有参数的方法怎么调用?看看invoke方法定义,第一个参数是Method对象,无论后面Object… args有多少参数就按照方法定义依次传参就可以了。
- 通过配置信息调用类的方法
- 结合注解实现特殊功能
- 按需加载jar包或class
将上文的hello world中的代码封装一下,你知道类名className和方法名methodName是不是就可以调用方法了?至于你将className和 methodName配置到文件,还是nacos,还是数据库,自己决定吧!
大家如果学习过mybatis plus都应该学习过这样的一个注解TableName,这个注解表示当前的实体类Student对应的数据库中的哪一张表。如下问代码所示,Student所示该类对应的是t_student这张表。
下面我们自定义TableName这个注解
有了这个注解,我们就可以扫描某个路径下的java文件,至于类注解的扫描我们就不用自己开发了,引入下面的maven坐标就可以
看下面代码:先扫描包,从包中获取标注了TableName注解的类,再对该类打印注解value信息
输出结果是:
有的朋友会问这有什么用?这有大用处了。有了类定义与数据库表的对应关系,你还能通过反射获取类的成员变量,之后你是不是就可以根据表明t_student和字段名nickName,age构建增删改查的SQL了?全都构建完毕,是不是就是一个基础得Mybatis plus了?
反射和注解结合使用,可以演化出许许多多的应用场景,特别是在框架代码实现方面。等待你去发觉啊!
在某些场景下,我们可能不希望JVM的加载器一次性的把所有classpath下的jar包装载到JVM虚拟机中,因为这样会影响项目的启动和初始化效率,并且占用较多的内存。我们希望按需加载,需要用到哪些jar,按照程序动态运行的需求取加载这些jar。
把jar包放在classpath外面,指定加载路径,实现动态加载。
同样的把.class文件放在一个路径下,我们也是可以动态加载到的
类的动态加载能不能让你想到些什么?是不是可以实现代码修改,不需要重新启动web容器?对的,就是这个原理,因为一个类的Class对象只有一个,所以不管你重新加载多少次,都是使用最后一次加载的class对象(上文讲过哦)。
- 优点:自由,使用灵活,不受类的访问权限限制。可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。在框架开发方面也有非常广泛的应用,特别是结合注解的使用。
- 缺点:也正因为反射不受类的访问权限限制,其安全性低,很大部分的java安全问题都是反射导致的。相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低破坏java类封装性,类的信息隐藏性和边界被破坏
言尽于此,限于笔者的知识结构,可能有不严谨之处,欢迎大家讨论与指正!期待您的关注,我将持续带来更哇塞的作品。
工作中常用到的Java集合有哪些?应用场景是什么?
前言
Java集合是我认为在Java基础中最最重要的知识点了,Java集合是必须掌握的。我在实习/秋招面试的时候,只要是面到Java,那一定是少不了Java集合。
作为一个新人,最关心的其实有一点:这个技术在工作中是怎么用的。换个说法:“工作中常用到的Java集合有哪些,应用场景是什么”
List集合下最常见的集合类有两个:ArrayList和LinkedList
在工作中,我都是无脑用ArrayList。我问了两个同事:“你们在项目中用过LinkedList吗?”他们都表示没有。
众所周知,ArrayList底层是数组,LinkedList底层是链表。数组遍历速度快,LinkedList增删元素快。
为什么在工作中一般就用ArrayList,而不用LinkedList呢?原因也很简单:
- 在工作中,遍历的需求比增删多,即便是增加元素往往也只是从尾部插入元素,而ArrayList在尾部插入元素也是O(1)
- ArrayList增删没有想象中慢,ArrayList的增删底层调用的copyOf()被优化过,加上现代CPU对内存可以块操作,普通大小的ArrayList增删比LinkedList更快。
所以,在开发中,想到要用集合来装载元素,第一个想到的就是ArrayList。
那么来了,LinkedList用在什么地方呢?我们一般用在刷算法题上。把LinkedList当做一个先进先出的队列,LinkedList本身就实现了Queue接口
如果考虑线程安全的问题,可以看看CopyWriteOnArrayList,实际开发用得不多,但我觉得可以了解一下它的思想(CopyWriteOn),这个思想在Linux/文件系统都有用到。
Set集合下最常见的集合类有三个:HashSet、TreeSet、LinkedHashSet
List和Set都是集合,一般来说:如果我们需要保证集合的元素是唯一的,就应该想到用Set集合
比如说:现在要发送一批消息给用户,我们为了减少「一次发送重复的内容给用户」这样的错误,我们就用Set集合来保存用户的userId/phone
自然地,首先要保证最上游的那批用户的userId/phone是没有重复的,而我们用Set集合只是为了做一个兜底来尽可能避免重复发送的问题。
一般我们在开发中最多用到的也就是HashSet。TreeSet是可以排序的Set,一般我们需要有序,从数据库拉出来的数据就是有序的,可能往往写order by id desc比较多。而在开发中也很少管元素插入有序的问题,所以LinkedHashSet一般也用不上。
如果考虑线程安全的问题,可以考虑CopyOnWriteArraySet,用得就更少了(这是一个线程安全的Set,底层实际上就是CopyWriteOnArrayList)
TreeSet和LinkedHashSet更多的可能用在刷算法的时候。
Map集合最常见的子类也有三个:HashMap、LinkedHashMap、TreeMap
如果考虑线程安全问题,应该想到的是ConcurrentHashMap,当然了Hashtable也要有一定的了解,因为面试实在是问得太多太多了。
HashMap在实际开发中用得也非常多,只要是key-value结构的,一般我们就用HashMap。LinkedHashMap和TreeMap用的不多,原因跟HashSet和TreeSet一样。
ConcurrentHashMap在实际开发中也用得挺多,我们很多时候把ConcurrentHashMap用于本地缓存,不想每次都网络请求数据,在本地做本地缓存。监听数据的变化,如果数据有变动了,就把ConcurrentHashMap对应的值给更新了。
不知道大家有没有学过生产者和消费者模式,秋招面试的时候可能会让你手写一段这样的代码。最简单的方式就是用阻塞队列去写。类似下面:
生产者:
消费者:
Main方法测试:
我的项目用阻塞队列也挺多的(我觉得跟个人编写的代码风格习惯有关),类似实现了上面的生产者和消费者模式。
真实场景例子:
- 运营要发一条推送消息,首先需要去用户画像系统圈选一个人群,填写对应的人群ID和发送时间。
- 我通过时间调度,通过RPC拿到人群的信息。遍历HDFS得到这个人群的每个userId
- 将遍历的userId放到一个阻塞队列里边去,用多个线程while(true)取阻塞队列的数据
好处是什么?我在取userId的时候,会有个限制:要么超出了指定的时间,要么达到BatchSize的值。这样我就可以将相同内容的不同userId组成一个Task。
本来100个userId是100个Task,现在我将100个userId放在一个Task里边(因为发送的内容是相同的,所以我可以这么干)。这样再往下游传的时候,并发量就降低了很多。
什么时候考虑线程安全的集合类,那当然是线程不安全的时候咯。那什么时候线程不安全?最常见的是:操作的对象是有状态的
虽然说,我们经常会听到线程不安全,但在业务开发中要我们程序员处理线程不安全的地方少之又少。比如说:你在写Servlet的时候,加过syn/lock锁吗?应该没有吧?
因为我们的操作的对象往往是无状态的。没有共享变量被多个线程访问,自然就没有线程安全问题了。
SpringMVC是单例的,但SpringMVC都是在方法内操作数据的,每个线程进入方法都会生成栈帧,每个栈帧的数据都是线程独有的,如果不设定共享变量,不会有线程安全问题。
上面只是简单举了SpringMVC的例子(只是为了更好的理解);
一句话总结:只要涉及到多个线程操作一个共享变量的时候,就要考虑是不是要用线程安全的集合类。
还是想强调一下,Java集合虽然在工作中不是每个都经常用得到,但是还是得重点学习学习。
如果你学习到了源码,可能你在创建集合的时候就会指定了集合的大小(即便我们知道它能动态扩容)
如果你想要去面试,Java集合是肯定少不了的,必问的一个知识点,你学会了就是送分题。
现在已经工作有一段时间了,为什么还来写Java集合呢,原因有以下几个:
- 我是一个对排版有追求的人,如果早期关注我的同学可能会发现,我的GitHub、文章导航的read.me会经常更换。现在的GitHub导航也不合我心意了(太长了),并且早期的文章,说实话排版也不太行,我决定重新搞一波。
- 我的文章会分发好几个平台,但文章发完了可能就没人看了,并且图床很可能因为平台的防盗链就挂掉了。又因为有很多的读者问我:”你能不能把你的文章转成PDF啊?“
- 我写过很多系列级的文章,这些文章就几乎不会有太大的改动了,就非常适合把它们给”持久化“。
作者:Java3y原文链接:https://juejin.im/post/5e7c05236fb9a009a6764ef9
长篇图解java反射机制及其应用场景
在java的面向对象编程过程中,通常我们需要先知道一个Class类,然后new 类名()方式来获取该类的对象。也就是说我们需要在写代码的时候(编译期或者编译期之前)就知道我们要实例化哪一个类,运行哪一个方法,这种通常被称为「静态的类加载」。
但是在有些场景下,我们事先是不知道我们的代码的具体行为的。比如,我们定义一个服务任务工作流,每一个服务任务都是对应的一个类的一个方法。
- 服务任务B执行哪一个类的哪一个方法,是由服务任务A的执行结果决定的
- 服务任务C执行哪一个类的哪一个方法,是由服务任务A和B的执行结果决定的
- 并且用户不希望服务任务的功能在代码中写死,希望通过配置的方式执行不同的程序
面对这个情况,我们就不能用代码new 类名()来实现了,因为你不知道用户具体要怎么做配置,这一秒他希望服务任务A执行Xxxx类的x方法,下一秒他可能希望执行Yyyy类的y方法。当然你也可以说提需求嘛,用户改一次需求,我改一次代码。这种方式也能需求,但对于用户和程序员个人而言都是痛苦,那么有没有一种方法「在运行期动态的改变程序的调用行为的方法」呢?这就是要为大家介绍的“「java反射机制」”。
那么java的反射机制能够做那些事呢?大概是这样几种:
- 在程序运行期动态的根据package名.类名实例化类对象
- 在程序运行期动态获取类对象的信息,包括对象的成本变量和方法
- 在程序运行期动态使用对象的成员变量属性
- 在程序运行期动态调用对象的方法(私有方法也可以调用)
我们定义一个类叫做Student
如果不用反射的方式,我相信只要学过java的朋友肯定会调用dinner方法
如果是反射的方式我们该怎么调用呢?
通过上面的代码我们看到,com.zimug.java.reflection.Student类名和dinner方法名是字符串。既然是字符串我们就可以通过配置文件,或数据库、或什么其他的灵活配置方法来执行这段程序了。这就是反射最基础的使用方式。
java的类加载机制还是挺复杂的,我们这里为了不混淆重点,只为大家介绍和“反射”有关系的一部分内容。
java执行编译的时候将java文件编译成字节码class文件,类加载器在类加载阶段将class文件加载到内存,并实例化一个java.lang.Class的对象。比如:对于Student类在加载阶段
- 在内存(方法区或叫代码区)中实例化一个Class对象,注意是Class对象不是Student对象
- 一个Class类(字节码文件)对应一个Class对象
- 该Class对象保存了Student类的基础信息,比如这个Student类有几个字段(Filed)?有几个构造方法(Constructor)?有几个方法(Method)?有哪些注解(Annotation)?等信息。
有了上面的关于Student类的基本信息对象(java.lang.Class对象),在运行期就可以根据这些信息来实例化Student类的对象。
- 在运行期你可以直接new一个Student对象
- 也可以使用反射的方法构造一个Student对象
但是无论你new多少个Student对象,不论你反射构建多少个Student对象,保存Student类信息的java.lang.Class对象都只有一个。下面的代码可以证明。
了解了上面的这些基础信息,我们就可以更深入学习反射类相关的类和方法了:
- java.lang.Class: 代表一个类
- java.lang.reflect.Constructor: 代表类的构造方法
- java.lang.reflect.Method: 代表类的普通方法
- java.lang.reflect.Field: 代表类的成员变量
- Java.lang.reflect.Modifier: 修饰符,方法的修饰符,成员变量的修饰符。
- java.lang.annotation.Annotation:在类、成员变量、构造方法、普通方法上都可以加注解
虽然有三种方法可以获取某个类的Class对象,但是只有第一种可以被称为“反射”。
Class类对象信息中几乎包括了所有的你想知道的关于这个类型定义的信息,更多的方法就不一一列举了。还可以通过下面的方法
- 获取Class类对象代表的类实现了哪些接口:getInterfaces()
- 获取Class类对象代表的类使用了哪些注解:getAnnotations()
结合上文中的Student类的定义理解下面的代码
- getFields()方法获取类的非私有的成员变量,数组,包含从父类继承的成员变量
- getDeclaredFields方法获取所有的成员变量,数组,但是不包含从父类继承而来的成员变量
- getMethods() : 获取Class对象代表的类的所有的非私有方法,数组,「包含从父类继承而来的方法」
- getDeclaredMethods() : 获取Class对象代表的类定义的所有的方法,数组,「但是不包含从父类继承而来的方法」
- getMethod(methodName): 获取Class对象代表的类的指定方法名的非私有方法
- getDeclaredMethod(methodName): 获取Class对象代表的类的指定方法名的方法
上面代码的执行结果如下:
可以看到getMethods获取的方法中包含Object父类中定义的方法,但是不包含本类中定义的私有方法sleep。另外我们还可以获取方法的参数及返回值信息:
- 获取参数相关的属性:
- 获取方法参数个数:getParameterCount()
- 获取方法参数数组对象:getParameters() ,返回值是java.lang.reflect.Parameter数组
- 获取返回值相关的属性
- 获取方法返回值的数据类型:getReturnType()
实际在上文中已经演示了方法的调用,如下invoke调用dinner方法
dinner方法是无参的那么有参数的方法怎么调用?看看invoke方法定义,第一个参数是Method对象,无论后面 Object… args有多少参数就按照方法定义依次传参就可以了。
- 通过配置信息调用类的方法
- 结合注解实现特殊功能
- 按需加载jar包或class
将上文的hello world中的代码封装一下,你知道类名className和方法名methodName是不是就可以调用方法了?至于你将className和 methodName配置到文件,还是nacos,还是数据库,自己决定吧!
大家如果学习过mybatis plus都应该学习过这样的一个注解TableName,这个注解表示当前的实例类Student对应的数据库中的哪一张表。如下问代码所示,Student所示该类对应的是t_student这张表。
下面我们自定义TableName这个注解
有了这个注解,我们就可以扫描某个路径下的java文件,至于类注解的扫描我们就不用自己开发了,引入下面的maven坐标就可以
看下面代码:先扫描包,从包中获取标注了TableName注解的类,再对该类打印注解value信息
输出结果是:
有的朋友会问这有什么用?这有大用处了。有了类定义与数据库表的对应关系,你还能通过反射获取类的成员变量,之后你是不是就可以根据表明t_student和字段名nickName,age构建增删改查的SQL了?全都构建完毕,是不是就是一个基础得Mybatis plus了?
反射和注解结合使用,可以演化出许许多多的应用场景,特别是在架构优化方面,等待你去发觉啊!
在某些场景下,我们可能不希望JVM的加载器一次性的把所有的jar包装载到JVM虚拟机中,因为这样会影响项目的启动和初始化效率,并且占用较多的内存。我们希望按需加载,需要用到哪些jar,按照程序动态运行的需求取加载这些jar。
同样的把.class文件放在一个路径下,我们也是可以动态加载到的
类的动态加载能不能让你想到些什么?是不是可以实现代码修改,不需要重新启动容器?对的,就是这个原理,因为一个类的Class对象只有一个,所以不管你重新加载多少次,都是使用最后一次加载的class对象(上文讲过哦)。
- 优点:自由,使用灵活,不受类的访问权限限制。可以根据指定类名、方法名来实现方法调用,非常适合实现业务的灵活配置。
- 缺点:
- 也正因为反射不受类的访问权限限制,其安全性低,很大部分的java安全问题都是反射导致的。
- 相对于正常的对象的访问调用,反射因为存在类和方法的实例化过程,性能也相对较低
- 破坏java类封装性,类的信息隐藏性和边界被破坏
「码文不易,如果您觉得有帮助,请帮忙点击在看或者分享,没有您的支持我可能无法坚持下去!」
本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com
文章为作者独立观点不代本网立场,未经允许不得转载。