首页 / JAVA / Java程序员都需要懂的「反射」
Java程序员都需要懂的「反射」
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java程序员都需要懂的「反射」,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含11417字,纯文字阅读大概需要17分钟。
内容图文
![Java程序员都需要懂的「反射」](/upload/InfoBanner/zyjiaocheng/643/28d7b9b4812e46609f3c40909bad552e.jpg)
只有光头才能变强。
文本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y
今天来简单写一下Java的反射。本来没打算写反射这个知识点的,只是不少的读者都问过我:“你的知识点好像缺了反射阿。能不能补一下?”
这周末也有点空了,所以来写写我对反射的简单理解。这篇是入门文章,没有高深的知识点,希望能对新人有帮助。如果文章有错的地方,麻烦在评论区友善评论指出~
Java常用和重要的知识点我都写过(现在已有200+篇技术原创),如果想看的同学,不妨关注我的GitHub,即可获取我的所有原创文章。
一、序言
在学习Java基础的时候,一般都会学过反射。我在初学反射的时候,并不能理解反射是用来干嘛的。学了一些API发现:“明明我自己能直接new一个对象,为什么它要绕一个圈子,先拿到Class对象,再调用Class对象的方法来创建对象呢,这不是多余吗?”
相信很多人在初学反射的时候也都会有这个想法(我就不相信就只有我一个人这么蠢!!)
而且在搜索相关资料的时候,一般也仅仅是讲解反射的一系列API,始终是不了解反射究竟是有什么用,这篇文章来告诉你吧。觉得不错,给我点个赞呗
二、引出Class对象
首先我们来看一段代码:
public?class?Demo?{
????//?自建了一个Student类
????class?Student{
????}
????public?static?void?main(String[]?args)?{
????????//?将Object?强转成Student类
????????Object?o?=?new?Object();
????????Student?s?=?(Student)?o;
????}
}
我们在IDE编写这一段代码的时候,不会出现任何的错误。但是等我们执行的时候,我们会知道这肯定强转失败了。
那么“Java”(实质上JVM)是怎么知道我们写的强转有没有问题的呢?可以依赖Class
对象来协助判断。
如果看过我写JVM
的那篇文章的同学应该都知道一个对象的加载过程,如果没看过的同学可以再去看看,顺便在这里给大家复习一下:
-
一个
.java
的文件经过javac
命令编译成功后,得到一个.class的文件
-
当我们执行了初始化操作(有可能是new、有可能是子类初始化 父类也一同被初始化、也有可能是反射…等),会将
.class
文件通过类加载器装载到jvm
中 -
将
.class
文件加载器加载到jvm中,又分了好几个步骤,其中包括 加载、连接和初始化 -
其中在加载的时候,会在Java堆中创建一个java.lang.Class类的对象,这个Class对象代表着类相关的信息。
既然说,Class对象代表着类相关的信息,那说明只要类有什么东西,在Class对象我都能找得到。我们打开IDE看看里边的方法:
于是我们可以通过Class对象来判断对象的真正类型。
三、反射介绍
其实反射就是围绕着Class
对象和java.lang.reflect
类库来学习,就是各种的API
比如上面截图的Method
/Field
/Constructor
这些都是在java.lang.reflect
类库下,正是因为这些类库的学习并不难,所以我才一直没写反射的文章。
我并不是说这些API我都能记住,只是这些API教程在网上有非常非常多,也足够通俗易懂了。在入门的时候,其实掌握以下几种也差不多了:
- 知道获取Class对象的几种途径
- 通过Class对象创建出对象,获取出构造器,成员变量,方法
- 通过反射的API修改成员变量的值,调用方法
/*
????下面是我初学反射时做的笔记,应该可以帮到大家,代码我就不贴了。(Java3y你值得关注)
*/
想要使用反射,我先要得到class文件对象,其实也就是得到Class类的对象
Class类主要API:
????????成员变量??-?Field
????????成员方法??-?Constructor
????????构造方法??-?Method
获取class文件对象的方式:
????????1:Object类的getClass()方法
????????2:数据类型的静态属性class
????????3:Class类中的静态方法:public?static?Class?ForName(String?className)
--------------------------------??
获取成员变量并使用
????????1:?获取Class对象
????????2:通过Class对象获取Constructor对象
????????3:Object?obj?=?Constructor.newInstance()创建对象
????????4:Field?field?=?Class.getField("指定变量名")获取单个成员变量对象
????????5:field.set(obj,"")?为obj对象的field字段赋值
如果需要访问私有或者默认修饰的成员变量
????????1:Class.getDeclaredField()获取该成员变量对象
????????2:setAccessible()?暴力访问??
---------------------------------??????????
通过反射调用成员方法
????????1:获取Class对象
????????2:通过Class对象获取Constructor对象
????????3:Constructor.newInstance()创建对象
????????4:通过Class对象获取Method对象??------getMethod("方法名");
????????5:?Method对象调用invoke方法实现功能
如果调用的是私有方法那么需要暴力访问
????????1:?getDeclaredMethod()
????????2:?setAccessiable();??????????
相信我,去搜索引擎看一会,你就学会了。反射的API并不难学,一般人学不懂反射因为不知道反射究竟能干什么,下面我来讲讲我的讲解。
四、为什么需要反射
在初学Java的时候其实我个人认为还是比较难理解为什么需要反射的,因为没有一定的代码量下,很难理解为什么我要绕一个圈子去搞反射这一套。
我现在认为用反射主要有两个原因:
- 提高程序的灵活性
- 屏蔽掉实现的细节,让使用者更加方便好用
我一直在文章中都在强调,学某一项技术之前,一定要理解为什么要学这项技术,所以我的文章一般会花比较长的幅度上讲为什么。
下面我来举几个例子来帮助大家理解
4.1 案例一(JDBC)
相信大家都写过jdbc
的代码,我贴一小段,大家回顾一下:
Class.forName("com.mysql.jdbc.Driver");
//获取与数据库连接的对象-Connetcion
connection?=?DriverManager.getConnection("jdbc:mysql://localhost:3306/java3y",?"root",?"root");
//获取执行sql语句的statement对象
statement?=?connection.createStatement();
//执行sql语句,拿到结果集
resultSet?=?statement.executeQuery("SELECT?*?FROM?users");
后来为什么要变成下面这种形式呢?
//获取配置文件的读入流
InputStream?inputStream?=?UtilsDemo.class.getClassLoader().getResourceAsStream("db.properties");
Properties?properties?=?new?Properties();
properties.load(inputStream);
//获取配置文件的信息
driver?=?properties.getProperty("driver");
url?=?properties.getProperty("url");
username?=?properties.getProperty("username");
password?=?properties.getProperty("password");
//加载驱动类
Class.forName(driver);
理由很简单,人们不想修改代码。只要存在有变动的地方,我写在配置里边,不香吗?但凡有一天,我的username,password,url甚至是数据库都改了,我都能够通过修改配置的方式去实现。
不需要动我丝毫的代码,改下配置就完事了,这就能提供程序的灵活性。
有人可能会问:“那还是要改啊,我改代码也很快啊,你改配置不也是要改吗”。
其实不一样的,我举个例子:
- 三歪写了一个JDBC组件,把各种配置都写死在代码上,比如上面的driver/username/数据库连接数等等。现在三歪不干了,要跑路了。
- 敖丙来接手三歪的代码,敖丙刚开始接手项目,公司说要换数据库。敖丙给领导说:这玩意,我改改配置就好了,几分钟完事。
- 敖丙找了半天都没找到配置的地方,由于三歪写的代码又臭又烂,找了半天才找到入口和对应的位置。
改代码的风险要比改配置大,即便不知道代码的实现都能通过改配置来完成要做的事。
这种就能通过可配的,其内部很可能就是通过反射来做的。
这里只是说可能,但不全是。有的可配的参数可能就仅仅只是配置,跟反射无关。但上面jdbc的例子,就是通过反射来加载驱动的。
4.2 案例二(SpringMVC)
相信大家学SpringMVC之前都学过Servlet的吧,如果没学过,建议看我的文章再复复习。
我当时学MVC框架的时候给我带来印象最深的是什么,本来需要各种getParameter()
,现在只要通过约定好JavaBean
的字段名,就能把值填充进去了。
还是上代码吧,这是我们当时学Servlet的现状:
//通过html的name属性,获取到值
String?username?=?request.getParameter("username");
String?password?=?request.getParameter("password");
String?gender?=?request.getParameter("gender");
//复选框和下拉框有多个值,获取到多个值
String[]?hobbies?=?request.getParameterValues("hobbies");
String[]?address?=?request.getParameterValues("address");
//获取到文本域的值
String?description?=?request.getParameter("textarea");
//得到隐藏域的值
String?hiddenValue?=?request.getParameter("aaa");
我们学到SpringMVC的时候是怎么样的:
@RequestMapping(value?=?"/save")
@ResponseBody
public?String?taskSave(PushConfig?pushConfig)?{
?????//?直接使用??
???????String?name=?pushConfig.getName();
}
为什么SpringMVC能做到?其实就是通过反射来做的。
相信你也有过的经历:
- 如果你的JavaBean的属性名跟传递过来的参数名不一致,那就“自动组装”失败了。因为反射只能根据参数名去找字段名,如果不一致,那肯定
set
不进去了。所以就组装失败了呀~
如果在使用框架的时候,为什么我们往往写上JavaBean,保持字段名与参数名相同,就能“自动”得到对应的值呢。这就是反射的好处。
屏蔽掉实现的细节,让使用者更加方便好用
五、我们写反射的代码多吗?
大部分程序员都是写业务代码的,大部分程序员都是维护老系统的,其实要我们自己写反射的代码的时候,真的不多。
从上面也看出,什么时候会写反射?写我们自己组件/框架的时候。如果想找个地练手一下反射,我觉得自定义注解是一个不错的选择。
因为现在用注解的地方很多,主要是够清晰简单(再也不用对着一堆的XML文件了,哈哈哈哈~)。
我初学的时候写过一段,可以简单参考一下,思路都差不多的哈。下面是使用的效果(使用自定义注解给不同的接口增加权限)
@permission("添加分类")
/*添加分类*/?void?addCategory(Category?category);
/*查找分类*/
void?findCategory( www.sangyulpt.com String?id);
@permission("查找分类")
/*查看分类*/?List<Category>?getAllCategory(www.huanhua2zhuc.cn);
返回一个代理的Service对象来处理自定义注解:
public?class?ServiceDaoFactory?{
????private?static?final?ServiceDaoFactory?factory?=?new?ServiceDaoFactory();
????private?ServiceDaoFactory()?{
????}
????public?static?ServiceDaoFactory?getInstance()?{
????????return?factory;
????}
????//需要判断该用户是否有权限
????public?<T>?T?createDao(String?className,?Class<www.jiniuyLzc.cn?>?clazz,?final?User?user)?{
????????System.out.println("添加分类进来了!");
????????try?{
????????????//得到该类的类型
????????????final?T?t?=?(T)?Class.forName(className).newInstance();
????????????//返回一个动态代理对象出去
????????????return?(www.baishenjzc.cn? ?)?Proxy.newProxyInstance(ServiceDaoFactory.class.getClassLoader(),?t.getClass().getInterfaces(),?new?InvocationHandler()?{
????????????????@Override
????????????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?NoSuchMethodException,?InvocationTargetException,?IllegalAccessException,?PrivilegeException?{
????????????????????//判断用户调用的是什么方法
????????????????????String?methodName?=?method.getName(www.yasenyulee.cn);
????????????????????System.out.println(methodName);
????????????????????//得到用户调用的真实方法,注意参数!!!
????????????????????Method?method1?=?t.getClass().getMethod(methodName,method.getParameterTypes());
????????????????????//查看方法上有没有注解
????????????????????permission?permis?=?method1.getAnnotation(permission.class);
????????????????????//如果注解为空,那么表示该方法并不需要权限,直接调用方法即可
????????????????????if?(permis?==?null)?{
????????????????????????return?method.invoke(t,?args);
????????????????????}
????????????????????//如果注解不为空,得到注解上的权限
????????????????????String?privilege?=?permis.value();
????????????????????//设置权限【后面通过它来判断用户的权限有没有自己】
????????????????????Privilege?p?=?new?Privilege(www.wangffzc.cn);
????????????????????p.setName(privilege);
????????????????????//到这里的时候,已经是需要权限了,那么判断用户是否登陆了
????????????????????if?(user?==?null)?{
????????????????????????//这里抛出的异常是代理对象抛出的,sun公司会自动转换成运行期异常抛出,于是在Servlet上我们根据getCause()来判断是不是该异常,从而做出相对应的提示。
????????????????????????throw?new?PrivilegeException("对不起请先登陆");
????????????????????}
????????????????????//执行到这里用户已经登陆了,判断用户有没有权限
????????????????????Method?m?=?t.getClass().getMethod("findUserPrivilege",?String.class);
????????????????????List<Privilege>?list?=?(List<Privilege>)?m.invoke(t,?user.getId());
????????????????????//看下权限集合中有没有包含方法需要的权限。使用contains方法,在Privilege对象中需要重写hashCode和equals()
????????????????????if?(!list.contains(p))?{
????????????????????????//这里抛出的异常是代理对象抛出的,sun公司会自动转换成运行期异常抛出,于是在Servlet上我们根据getCause()来判断是不是该异常,从而做出相对应的提示。
????????????????????????throw?new?PrivilegeException("您没有权限,请联系管理员!");
????????????????????}
????????????????????//执行到这里的时候,已经有权限了,所以可以放行了
????????????????????return?method.invoke(t,??shentuylzc.cn args);
????????????????}
????????????});
????????}?catch?(www.shentuylgw.cn Exception?e)?{
????????????new?RuntimeException(e);
????????}
????????return?null;
????}
}
最后
这篇反射跟网上的文章不太一样,网上的反射一般都是介绍反射的API如何使用。如果你觉得还不错,给我点赞吧
内容总结
以上是互联网集市为您收集整理的Java程序员都需要懂的「反射」全部内容,希望文章能够帮你解决Java程序员都需要懂的「反射」所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。