Java内功修炼系列一反射
“JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。”这是百度百科对JAVA反射的描述,仅凭这句话是没法明白反射的真正含义,所以还需要深入剖析。正如其描述中所讲,反射机制一般体现在运行状态,那么什么是运行状态?这就要追溯到JAVA程序的运行过程。
一、java程序的运行过程:
JAVA程序运行过程分两个阶段:
1、编译阶段:将.java的源文件编译为.class字节码文件;
java中的每一个类都会被编译为一个.class的字节码文件,如果这个类有依赖其他的类,那么首先会检查并编译依赖类,如果找不到依赖类,编译时就会报错。
2、运行阶段:jvm将.class字节码翻译成机器码,然后在操作系统上运行;
运行过程分为类加载和类执行两个阶段:
1⃣️类加载:类加载的过程就是将类的class文件装入内存,并为之创建一个java.lang.Class对象,该对象包含类的成员变量、成员方法、构造方法等所有信息;jvm只有在需要某个类的时候才会加载它,不会在事先将所有的类都加载进来,毕竟加载类是一个消耗内存的事情,如果把有的没的都加载进来,势必降低性能。
2⃣️类执行:类执行的时候,jvm会根据加载时创建的类型Class对象,找到main函数,即程序的入口,从而开始执行;
以上就是一个java 程序执行的基本过程,可参考下面两片博文:
java程序编译和运行过程:https://www.cnblogs.com/qiumingcheng/p/5398610.html
java中类加载和反射技术实例:https://www.cnblogs.com/jiaqingshareing/p/6024541.html
理解了java程序的运行过程之后,也就解决了上述问题,即所谓的运行状态就是类执行阶段的状态,因为这个阶段类的class文件已经加入内存,java.lang.Class对象已被创建,我们就能用这个对象获取的类相关属性、方法等信息。根据以上描述,可以总结出java中给一个类创建实例对象的两种方式:
1、使用new关键字:这种方式是正向的,我们明确知道这个类的所有属性方法,直接new一个对象;
2、通过类的Class对象创建:如果我们要创建某个类的对象实例,但是现在除了这个类的Class对象之外对其一无所知,这时我们就可以借助Class对象反向地创建实例,这就引出了反射的概念。
二、反射的理解
1、对反射的一点理解:看了这么多资料,按照我的理解,反射就是通过某个类编译之后的Class对象获取这个类的实例对象信息。我们现在常用的编辑器,如eclipse、idea等,都有一个代码提示功能,在开发过程中很多类的属性、方法等都是借助这个功能获取和使用的,试想如果没有这个功能,我们怎么知道使用哪个方法?不排除有人不需要借助代码提示就能知道所有类库的中API接口,但是我想对于大部分人还是有困难的,尤其在第一次使用一些第三方库的时候,此时如果没有代码提示,我们就可以使用反射的方式获取某个类中的属性、方法等。我不知道编辑器的代码提示原理是不是基于反射的,但是反射的功能就跟这个差不多,反向推到。(以上内容纯属自己的理解,不可轻信,如有不当之处,欢迎指正)
2、反射的功能
反射的功能有构建对象、反射属性、方法等,下面介绍常用的这三种。第一部分已经说明反射必须要借助类的Class对象,所以在介绍其功能之前首先了解一下获取Class对象的方式:
1⃣️getClass()方法:该方法仅限于已知某个类实例的情况,且该类为引用类型;
2⃣️Class属性:该方法适用于所有类型,包括基本数据类型,对于基本类型的封装类,还可以使用.TYPE方法获取其基本类型的Class实例,如:Integer.TYPE=int.Class;
3⃣️Class.forName()方法:该方法针对已知类的全路径的情况,且该类仅限于引用类型;
以上就是三种获取类的Class实例对象的方式。下面开始介绍反射的功能。
三、反射的常用功能
1、构建对象
前面已经说过,创建类的实例时,除了使用new关键字之外,还可以使用反射的方式。在使用反射时,需要区分构造函数是否含参。
1⃣️构造函数无参数:这种情况比较简单,相当于直接new的时候不需要传任何参数的情况,代码如下:
/**
* 创建一个普通类,通过反射创建其实例对象(无参数)
*/
public class ReflectServiceImpl {
public void sayHello(String name) {
System.out.println("Hello " + name);
}
}
public class ReflectServiceImplTest {
/**
* 通过反射方式创建实例对象
* @return
*/
public ReflectServiceImpl getIntance() {
ReflectServiceImpl object = null;
try {
//1、通过类全名的方式获取Class对象;
//2、构造函数不含参数时直接调用Class对象的newInstance()方法;
object = (ReflectServiceImpl) Class.forName("com.daily.reflect.ReflectServiceImpl").newInstance();
} catch (Exception e) {
e.printStackTrace();
} return object;
} public static void main(String[] args) {
ReflectServiceImpl object = new ReflectServiceImplTest().getIntance();
object.sayHello("World!");//执行结果:Hello World!
}
}
2⃣️构造函数有参数:这种情况需要通过构造函数创建实例对象
/**
* 构造函数包含参数的类
*/
public class ReflectServiceImpl2 {
public String name;
public int age; public ReflectServiceImpl2(String name, int age) {
this.name = name;
this.age = age;
} public void selfIntroduce() {
System.out.println("My name is " + name + " and is am " + age + " years old.");
}
/**
* @author hyc
* 通过反射构建含参类的实例对象
*/
public class ReflectServiceImpl2Test { public ReflectServiceImpl2 getIntance() {
ReflectServiceImpl2 object = null;
try {
//1、通过.class属性的方式获取Class实例;
//2、需要先获取构造函数再构建实例,构造函数传入参数类型(其实时参数类型的Class对象),构建实例时传入参数值;
object = ReflectServiceImpl2.class.getConstructor(String.class,int.class).newInstance("zhangsan",20);
} catch (Exception e) {
e.printStackTrace();
}
return object;
} public static void main(String[] args) {
ReflectServiceImpl2 ob2 = new ReflectServiceImpl2Test().getIntance();
ob2.selfIntroduce();//运行结果:My name is zhangsan and is am 20 years old.
}
}
2、反射属性、方法
反射属性、方法时有两种系列方法,一种是get***,一种是getDeclared***,这两种系列的区别是:前者获取的是所有“公有的”属性或方法,包括当前类继承的和所实现接口中的;而后者获取的是“当前类中声明的”属性或方法,不受访问权限的影响。为了区分这两种方法,先创建一个父类、一个接口和一个子类,子类继承父类并实现接口,父类和接口中都定义公共变量和成员方法,父类中还有含参构造方法。
1⃣️父类
public class ReflectServceImpl1Father {
public String sex; //性别
public String address; //住址
public int height; //身高
public int weight; //体重 //父类中如果没有无参构造函数而含有有参构造函数,子类中必须使用super关键字调用含参构造函数
public ReflectServceImpl1Father(String sex, String address, int height, int weight) {
this.sex = sex;
this.address = address;
this.height = height;
this.weight = weight;
} public void getBaseInfo() {
System.out.println("性别:"+ sex + ";家庭住址:" + address + ";身高:" + height + ";体重:" + height);
}
}
2⃣️接口
/**
* @author hyc
* 声明一个接口,定义三个成员变量
*/
public interface ReflectServiceImpl1Interface {
public static final String weiboAccount = "csu.weibo";
public static final String zhihuAccount = "csu.zhihu";
public static final String weixinAccout = "csu.weixin"; public void login(); //登陆 public void register(); //注册 public void logout(); //登出
}
3⃣️子类(继承父类,实现接口,并声明成员变量、成员函数和含参构造方法)
/**
* @author hyc
* 创建一个有参数的类,通过反射创建其实例对象
*/
public class ReflectServiceImpl1 extends ReflectServceImpl1Father implements ReflectServiceImpl1Interface{
/**
* 声明三个成员变量,两个公有一个私有
*/
public String name;
public String job;
private int age; /**
* 声明三个含参构造函数
* @param name
*/
public ReflectServiceImpl1(String name) {
super("woman","beijing",165,100);
this.name = name;
} public ReflectServiceImpl1(String name,String job) {
super("woman","beijing",165,100);
this.name = name;
this.job = job;
} protected ReflectServiceImpl1(String name,String job,int age) {
super("woman","beijing",165,100);
this.name = name;
this.job = job;
this.age = age;
} /**
* 在子类中定义三个成员函数
*/
public void sayHello() {
System.out.println("Hello " + name);
} public void sayHi() {
System.out.println("Hello " + name + " ,my job is a " + job);
} public void sayHey() {
System.out.println("Hello " + name +" ,i am " + age +" years old and my job is a " + job);
} /**
* 重写接口中的三个方法
*/
public void login() {
System.out.println("用户登陆");
} public void register() {
System.out.println("用户注册");
} public void logout() {
System.out.println("用户登出");
}
}
以上是基础代码,下面开始反射。
1⃣️反射构造函数
/**
* 获取类中的构造函数
*/
public static void getContructors() {
try {
// 返回类中声明的公共构造函数
Constructor<?>[] publicCons = Class.forName("com.daily.reflect.ReflectServiceImpl1").getConstructors();
System.out.println("所有构造方法的个数:" + publicCons.length);
for (Constructor<?> constructor : publicCons) {
System.out.println(constructor.getName());
}
// 返回类中声明的所有构造函数:不受访问权限的影响
Constructor<?>[] declaredCons = Class.forName("com.daily.reflect.ReflectServiceImpl1")
.getDeclaredConstructors();
System.out.println("声明的构造方法的个数:" + declaredCons.length);
for (Constructor<?> constructor : declaredCons) {
System.out.println(constructor.getName());
} } catch (Exception e) {
e.printStackTrace();
}
}
测试输出结果:
public static void main(String[] args) {
ReflectServiceImpl1MethodTest.getContructors();
}
输出结果:
所有构造方法的个数:2
com.daily.reflect.ReflectServiceImpl1
com.daily.reflect.ReflectServiceImpl1
声明的构造方法的个数:3
com.daily.reflect.ReflectServiceImpl1
com.daily.reflect.ReflectServiceImpl1
com.daily.reflect.ReflectServiceImpl1
从上面的结果来看,通过getConstructors获取的构造函数只有两个,而通过getDeclaredConstructors方法获取的构造函数有三个,这是因为前者只获取公共构造函数,但不包括父类中的,而后者则获取的是当前类中声明的所有构造函数,且不受访问权限的影响。因为其中一个构造函数是用protected关键字修饰,所以通过前者无法获取。
2⃣️反射成员变量
/**
* 反射成员变量
*/
public static void getFields() {
try {
// 返回公共的成员变量:包括继承类以及所实现接口中的
Field[] fields = Class.forName("com.daily.reflect.ReflectServiceImpl1").getFields();
System.out.println("成员变量的个数:" + fields.length);
for (Field field : fields) {
System.out.println(field.getName());
}
// 返回本类中声明的成员变量:不包括所实现接口中的和继承类中的,且访问权限不受限制
Field[] declaredFields = Class.forName("com.daily.reflect.ReflectServiceImpl1").getDeclaredFields();
System.out.println("类中声明的成员变量的个数:" + declaredFields.length);
for (Field field : declaredFields) {
System.out.println(field.getName());
}
} catch (Exception e) {
e.printStackTrace();
} }
测试反射结果:
public static void main(String[] args) {
ReflectServiceImpl1MethodTest.getFields();
}
反射结果:
成员变量的个数:9
name
job
weiboAccount
zhihuAccount
weixinAccout
sex
address
height
weight
类中声明的成员变量的个数:3
name
job
age
从上面的结果来看,通过getFields方法获取的成员变量有9个,分别是父类中的4个,接口中的3个和当前类中的2个;而通过getDeclaredFields方法获取的成员变量只有3个,都是当前类中的,这就证明前者获取的是所有公共的成员变量,包括它所继承的父类和所实现接口中的,而后者则获取的是当前类中声明的成员变量,不受访问权限的影响。
3⃣️反射成员方法
/**
* 获取成员方法
*/
public static void getMethods() {
try {
// 获取所有公共的成员函数:包括所实现接口以及继承的父类中的成员函数
Method[] methods = Class.forName("com.daily.reflect.ReflectServiceImpl1").getMethods();
System.out.println("所有成员方法的个数:" + methods.length);
for (Method method : methods) {
System.out.println(method.getName());
} // 获取本类中所有声明的成员函数:包括所实现接口中的,但不包括所继承父类中的,访问权限不受限制
Method[] declaredMethods = Class.forName("com.daily.reflect.ReflectServiceImpl1").getDeclaredMethods();
System.out.println("所有在类中声明的成员方法的个数:" + declaredMethods.length);
for (Method method : declaredMethods) {
System.out.println(method.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
测试反射结果:
public static void main(String[] args) {
ReflectServiceImpl1MethodTest.getMethods();
}
查看反射结果:
所有成员方法的个数:16
register
sayHello
sayHey
logout
login
sayHi
getBaseInfo
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
所有在类中声明的成员方法的个数:6
register
sayHello
sayHey
logout
login
sayHi
从上面的结果来看,通过getMethods获取的方法有16个,除了子类中的3个,父类中的1个和接口中的3个之外,还有9个是Object超类中的方法,因为所有的类都会继承Object类,也就能获取其成员方法;而通过getDeclaredMethods获取的方法只有子类中的3个和所实现接口中的3个,如果重写父类中的方法,也能获取到,所以可以看出前者是获取所有公共方法,包括当前类所继承的父类及所实现接口中的,而后者只是获取当前类中的成员函数,且不受访问权限的限制。
3、反射方法
反射还有一个功能就是反射方法,通过这个功能我们可以在调用某个方法之前判断它是否存在,不存在是进行人性化处理,以防运行是抛出异常影响用户体验。反射方法时,首先依然要获取Class对象,然后利用该对象通过方法名获取Method对象,然后调用Method对象到invoke()方法将其挂在某个对象下面进行调用。所以反射方法需要分三步
1⃣️获取Class对象。在获取Class对象之前还是要进行构建对象实例,这个前面已经介绍,这里不再赘述。
2⃣️获取Method对象。Method对象是通过Class对象获取的,如果某个方法含有参数,则需要传参数类型,否则只需传方法名称。
3⃣️调用invoke方法,将方法挂在某个对象下,这样就能通过对象调用方法。调用该方法时,如果反射的方法有参数,则需要传参数值,否则不需要。
下面以反射上述子类中的sayHey()方法为例,来看代码:
/**
* 通过反射创建对象实例:包含三个参数的构造方法
*
* @param args
*/
public static void reflectMethodHey() {
ReflectServiceImpl1 object = null;
try {
//通过类全名的方式反射出对象实例:因为需要反射的方法中有三个参数,所以使用三个参数的构造函数初始化变量
object = (ReflectServiceImpl1) Class.forName("com.daily.reflect.ReflectServiceImpl1")
.getDeclaredConstructor(String.class, String.class,int.class).newInstance("hyc", "programmer",25);
//获取Class对象并反射方法:因为sayHey方法中没有参数,所以此处无需参数类型
Method method = object.getClass().getMethod("sayHey");
//使用invoke方法调用方法,因为sayHey方法中没有参数,所以此处无需参数值
method.invoke(object);
} catch (Exception e) {
e.printStackTrace();
}
}
测试调用结果:
public static void main(String[] args) {
ReflectServiceImpl1MethodTest.reflectMethodHey();
}
查看结果:
Hello hyc ,i am 25 years old and my job is a programmer
由此可见,方法调用成功,所以反射成功。
在日常开发中,同一个系统会有不同的版本进行维护,不同版本之间可能存在个别后台接口不一致的差异,此时如果有些方法在低版本中不存在而在高版本中存在,这样在做版本兼容的时候,可以通过反射的方式判断某个方式是否存在,存在则为高版本,否则为低版本,需进行特殊处理,以免运行是报错。
以上就是反射中一些比较重要的知识点总结,原创内容,如果有不对的地方,欢迎指正。
Java内功修炼系列一反射的更多相关文章
- Java内功修炼系列一拦截器
在动态代理中,我们知道在代理类中,执行真实对象的方法前后可以增加一些其他的逻辑,这些逻辑并不是真实对象能够实现的方法,比如一个租房的用户希望租一套公寓,但是中介所代理的这个房东并没有可以出租的公寓,那 ...
- Java内功修炼系列一观察者模式
观察者模式又称发布-订阅模式,就是观察者通过订阅被观察者,或关注被观察者,从而实时更新观察者的信息.比如我们玩微博的时候,如果关注了一些博主,那么当博主发动态时,在首页微博列表中就会自动更新这些博主发 ...
- Java内功修炼系列一代理模式
代理模式是JAVA设计模式之一,网上设计模式相关的博文铺天盖地,参考它们有助于自己理解,但是所谓“尽信书不如无书”,在参考的同时也要思考其正确性,写博客也是为了记录自己理解知识点的思路历程和心路历程, ...
- Java内功修炼系列一工厂模式
工厂模式是一种创建型模式,它提供了一种新的创建对象的方式,一般情况下我们都习惯用new关键字直接创建对象.有时候会遇到这种情况,我们需要根据具体的场景选择创建什么类型的对象,可能有多种类型都能选择,但 ...
- Java内功修炼系列一责任链模式
在上一节的拦截器中提到,程序的设计者一般会用拦截器替替代动态代理,将动态代理的逻辑隐藏起来,而把拦截器接口提供给开发者,使开发者不需要关系动态代理的具体实现过程,但是有时候需要多个拦截器,而且拦截器之 ...
- Java 内功修炼 之 数据结构与算法(一)
一.基本认识 1.数据结构与算法的关系? (1)数据结构(data structure): 数据结构指的是 数据与数据 之间的结构关系.比如:数组.队列.哈希.树 等结构. (2)算法: 算法指的是 ...
- Java 内功修炼 之 数据结构与算法(二)
一.二叉树补充.多叉树 1.二叉树(非递归实现遍历) (1)前提 前面一篇介绍了 二叉树.顺序二叉树.线索二叉树.哈夫曼树等树结构. 可参考:https://www.cnblogs.com/l-y-h ...
- Hadoop内功修炼
IT十八掌<大数据内功修炼系列课程>强势推出!由实战派名师徐培成亲自操刀,学完做不了大数据我们负全责!2015.12.21前,优惠价:4999(名额已不多!)2015.12.31前,优惠价 ...
- Java总结篇系列:Java String
String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处. 1 ...
随机推荐
- (转)第04节:Fabric.js用路径画不规则图形
在Canvas上画方形.圆形.三角形都是很容易的,只要调用fabric对应的方法就可以了,但这些都是规则的图形,如果你想画一个不规则的图形,这时候你可以用fabric.js提供的路径绘图方法.所谓路径 ...
- ASP.NET的Validform验证表单使用说明
当我们写提交表单的时候往往需要验证表单是否填写了内容,是否正确,这个插件可以很方便的完成我们需要的验证! 使用方法: 1.引用JS <script type="text/javascr ...
- Git 比较两个分支之间的差异
1.查看 dev 有,而 master 中没有的: git log dev ^master 2.查看 dev 中比 master 中多提交了哪些内容: git log master..dev 注意,列 ...
- spring mvc文件上传报错:Expected MultipartHttpServletRequest: is a MultipartResolver configured?
报错原因:spring-mvc.xml 的配置文件中,配置文件上传id不为 “multipartResolver” 解决:id 改为 “multipartResolver”
- C# 字符串的日期比较
SearchResult = SearchResult.Where(v => (DateTime.Parse(v.CreateTime.ToString("yyyy/MM/dd&quo ...
- matlab-使用技巧
sel(1:100); 1 2 3 4 5 ...100 X(sel, :); 1.......2.......3.......4.......5..........100...... nn_para ...
- 【转载】Kafka介绍及升级经验分享
http://blog.talkingdata.net/?p=3165 背景 当时的现状:开始使用Kafka的时候,使用的版本是0.7.2,当时的目的是为了替代kestrel,主要是使用Kafka来做 ...
- (转)Linux使用RSA密钥登录远程服务器
一切操作都在本机执行,不需要进入远程主机/服务器~~ 1.生成密钥.默认生成的是rsa加密. ssh-keygen 2.私钥是给本地的,公钥是给远程的.下面将公钥上传到远程服务器 ~ ssh-copy ...
- 【颓废篇】人生苦短, 我用python(二)
当时产生学习python的欲望便是在看dalao们写脚本的时候… 虽然dalao们好像用的是js来着.. 不过现在好像很多爬虫也可以用python写啊… 所以学python没什么不妥. 而且csdn整 ...
- html--浮动高度塌陷问题
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...