原文出处: locality

写在前面:
什么是java反射机制?我们又为什么要学它?
当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。我们认为java并不是动态语言,但是它却有一个非常突出的动态相关机制,俗称:反射。
IT行业里这么说,没有反射也就没有框架,现有的框架都是以反射为基础。在实际项目开发中,用的最多的是框架,填的最多的是类,反射这一概念就是将框架和类揉在一起的调和剂。所以,反射才是接触项目开发的敲门砖!


一、Class类
什么是Class类?
在面向对象的世界里,万事万物皆是对象。而在java语言中,static修饰的东西不是对象,但是它属于类。普通的数据类型不是对象,例如:int a = 5;它不是面向对象,但是它有其包装类 Integer 或者分装类来弥补了它。除了以上两种不是面向对象,其余的包括类也有它的面向对象,类是java.lang.Class的实例化对象(注意Class是大写)。也就是说:
Class A{}
当我创建了A类,那么类A本身就是一个对象,谁的对象?java.lang.Class的实例对象。
那么这个对象又该怎么表示呢?
我们先看一下下面这段代码:

1
2
3
4
public class Demo(){
F f=new F();
}
class F{}

这里的F的实例化对象就可以用f表达出来。同理F类也是一个实例化对象,Class类的实例化对象。我们可以理解为任何一个类都是Class类的实例化对象,这种实例化对象有三种表示方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo(){
F f=new F();
//第一种表达方式
Class c1=F.class;//这种表达方式同时也告诉了我们任何一个类都有一个隐含的静态成员变量class
//第二种表达方式
Class c2=f.getClass();//这种表达方式在已知了该类的对象的情况下通过getClass方法获取
//第三种表达方式
Class c3 = null;
try {
c3 = Class.forName("com.text.F");//类的全称
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
class F{}

以上三种表达方式,c1,c2,c3都表示了F类的类类型,也就是官方解释的Class Type。
那么问题来了:

1
System.out.println(c1 == c2)?  or  System.out.println(c1 == c3)?

答案是肯定的,返回值为ture。这表明不论c1 or c2 or c3都代表了F类的类类型,也就是说一个类只可能是Class类的一个实例对象。
理解了Class的概念,我们也可以通过类的类类型创建该类的对象实例,用c1 or c2 or c3的newInstance()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
Public class Demo1{
try {
Foo foo = (Foo)c1.newInstance();//foo就表示F类的实例化对象
foo.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}}
class F{
void print(){
}
}

这里需要注意的是,c1是F类的类类型,创建出来的就是F类的对象。如果a是A类的类类型,那么创建出来的对象也应该与之对应,属于A类的对象。

二、方法的反射
Class类有一个最简单的方法,getName():

1
2
3
4
5
6
7
8
9
10
11
public class Demo2 {
public static void main(String[] args) {
Class c1 = int.class;//int 的类类型
Class c2 = String.class;//String类的类类型
Class c3 = void.class;
System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c2.getSimpleName());
System.out.println(c3.getName());
}
}

本的数据类型以及void关键字都是存在类类型的。

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ClassUtil {
public static void printClassMethodMessage(Object obj){
//要获取类的信息》》首先我们要获取类的类类型
Class c = obj.getClass();
//我们知道Object类是一切类的父类,所以我们传递的是哪个子类的对象,c就是该子类的类类型。
//接下来我们要获取类的名称
System.out.println("类的名称是:"+c.getName());
/*
*我们知道,万事万物都是对象,方法也是对象,是谁的对象呢?
* 在java里面,方法是Method类的对象
*一个成员方法就是一个Method的对象,那么Method就封装了对这个成员
*方法的操作
*/
//如果我们要获得所有的方法,可以用getMethods()方法,这个方法获取的是所有的Public的函数,包括父类继承而来的。如果我们要获取所有该类自己声明的方法,就可以用getDeclaredMethods()方法,这个方法是不问访问权限的。
Method[] ms = c.getMethods();//c.getDeclaredMethods()
//接下来我们拿到这些方法之后干什么?我们就可以获取这些方法的信息,比如方法的名字。
//首先我们要循环遍历这些方法
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(")");
}
}
}

总结思路:
通过方法的反射得到该类的名称步骤:
1.获取该类的类类型
2.通过类类型获取类的方法(getMethods())
3.循环遍历所获取到的方法
4.通过这些方法的getReturnType()得到返回值类型的类类型,又通过该类类型得到返回值类型的名字
5.getName()得到方法的名称,getParameterTypes()获取这个方法里面的参数类型的类类型。

三、成员变量的反射
首先我们需要认识到成员变量也是对象,是java.lang.reflect.Field类的对象,那么也就是说Field类封装了关于成员变量的操作。既然它封装了成员变量,我们又该如何获取这些成员变量呢?它有这么一个方法:

1
2
3
4
5
public class ClassUtil {
public static void printFieldMessage(Object obj){
Class c = obj.getClass();
//Field[] fs = c.getFields();
}

这里的getFields()方法获取的所有的public的成员变量的信息。和方法的反射那里public的成员变量,也有一个获取所有自己声明的成员变量的信息:
Field[] fs = c.getDeclaredFields();

我们得到它之后,可以进行遍历(既然封装了Field的信息,那么我们就可以得到Field类型)

1
2
3
4
5
6
7
8
for (Field field : fs) {
//得到成员变量的类型的类类型
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成员变量的名称
String fieldName = field.getName();
System.out.println(typeName+" "+fieldName);
}

四、构造函数的反射
不论是方法的反射、成员变量的反射、构造函数的反射,我们只需要知道:要想获取类的信息,首先得获取类的类类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void printConMessage(Object obj){
Class c = obj.getClass();
/*
* 首先构造函数也是对象,是java.lang.Constructor类的对象
* 也就是java.lang. Constructor中封装了构造函数的信息
* 和前面说到的一样,它也有两个方法:
* getConstructors()方法获取所有的public的构造函数
* getDeclaredConstructors()方法得到所有的自己声明的构造函数
*/
//Constructor[] cs = c.getConstructors();
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(")");
}
}

五、Class类的动态加载类
如何动态加载一个类呢?
首先我们需要区分什么是动态加载?什么是静态加载?我们普遍认为编译时刻加载的类是静态加载类,运行时刻加载的类是动态加载类。我们举一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
Class A{
Public static void main(String[] args){
if("B".equal(args[0])){
B b=new B();
b.start();
}
if("C".equal(args[0])){
C c=new C();
C.start();
}
}
}

上面这一段代码,当我们在用eclipse或者myeclipse的时候我们并不关心是否能够通过编译,当我们直接在cmd使用javac访问A.java类的时候,就会抛出问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
A.java:7:错误:找不到符号
B b=new B();
符号:  类B
位置: 类A
A.java:7:错误:找不到符号
B b=new B();
符号:  类B
位置: 类A
A.java:12:错误:找不到符号
C c=new C();
符号:  类C
位置: 类A
A.java:12:错误:找不到符号
C c=new C();
符号:  类C
位置: 类A
4个错误

或许我们理所当然的认为这样应该是错,类B根本就不存在。但是如果我们多思考一下,就会发现B一定用吗?不一定。C一定用吗?也不一定。那么好,现在我们就让B类存在

1
2
3
4
5
Class B{
Public static void start(){
System.out.print("B...satrt");
}
}

现在我们就先 javac B.class,让B类先开始编译。然后在运行javac A.class。结果是:

1
2
3
4
5
6
7
8
9
A.java:12:错误:找不到符号
C c=new C();
符号:  类C
位置: 类A
A.java:12:错误:找不到符号
C c=new C();
符号:  类C
位置: 类A
2个错误

我们再想,这个程序有什么问题。如果你说没有什么问题?C类本来就不存在啊!那么问题来了B类已经存在了,假设我现在就想用B,我们这个程序用得了吗?答案是肯定的,用不了。那用不了的原因是什么?因为我们这个程序是做的类的静态加载,也就是说new创建对象是静态加载类,在编译时刻就需要加载所有的,可能使用到的类。所以不管你用不用这个类。
现在B类是存在的,但是我们这个程序仍然用不了,因为会一直报C类有问题,所以B类我也用不了。那么在实际应用当中,我们肯定需要如果B类存在,B类我就能用,当用C类的时候,你再告诉我错了。如果说将来你有100个类,只要其中一个类出现问题,其它99个类你都用不了。所以这并不是我们想要的。
我们想要的就是我用那个类就加载那个类,也就是常说的运行时刻加载,动态加载类。如何实现动态加载类呢?我们可以建这么一个类:

1
2
3
4
5
6
7
8
9
10
11
Class All{
Public static void start(){
try{
Class cl= Class.forName(args[0]);
//通过类类型,创建该类的对象
cl.newInstance();
}catch(Exception e){
e.printStackTrace();
}
}
}

前面我们在分析Class实例化对象的方式的时候,Class.forName(“类的全称”),它不仅仅表示了类的类类型,还表示了动态加载类。当我们javac All.java的时候,它不会报任何错误,也就是说在编译的时候是没有错误的。只有当我们具体用某个类的时候,那个类不存在,它才会报错。
如果加载的类是B类,就需要:

1
B bt = (B) cl.newInstance();

万一加载的是C类呢,可以改成

1
C ct = (C) cl.newInstance();

但是如果我想用很多的类或者加载很多的类,该怎么办?我们可以统一一个标准,不论C类还是B类或者其他的类,比如定义一个标准

1
Stand s = (Stand) cl.newInstance();

只要B类和C类都是这个标准的就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Class All{
Public static void start(){
try{
Class cl= Class.forName(args[0]);
//通过类类型,创建该类的对象
Stand s = (Stand) cl.newInstance();
s.start();
}catch(Exception e){
e.printStackTrace();
}
}
}
interface Stand {
Public void start();
}

现在如果我想要用B类,我们只需要:

1
2
3
4
5
Class B implements Stand{
Public void start(){
System.out.print("B...satrt");
}
}

加载B类,编译运行。

1
2
3
javac B.java
javac Stand.java
java Stand B

结果:

1
B...satrt

如果以后想用某一个类,不需要重新编译,只需要实现这个标准的接口即可。只需要动态的加载新的东西就行了。
这就是动态加载类

谈谈Java反射机制的更多相关文章

  1. Java反射机制剖析(三)-简单谈谈动态代理

    通过Java反射机制剖析(一)和Java反射机制剖析(二)的学习,已经对反射有了一定的了解,这一篇通过动态代理的例子来进一步学习反射机制. 1.     代理模式 代理模式就是为其他对象提供一种代理来 ...

  2. java反射机制浅谈

    一.Java的反射机制浅谈 最近研究java研究得很给力,主要以看博文为学习方式.以下是我对java的反射机制所产生的一些感悟,希望各位童鞋看到失误之处不吝指出.受到各位指教之处,如若让小生好好感动, ...

  3. Java 反射机制(一)

    阅读<Core Java Volume I --- Fundamentals>反射部分,总觉得许多概念艰涩难懂.模棱两可.我想造成这个结果的主要原因可能是Cay S. Horstmann和 ...

  4. 第28章 java反射机制

    java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...

  5. Java反射机制

    Java反射机制 一:什么事反射机制 简单地说,就是程序运行时能够通过反射的到类的所有信息,只需要获得类名,方法名,属性名. 二:为什么要用反射:     静态编译:在编译时确定类型,绑定对象,即通过 ...

  6. java基础知识(十一)java反射机制(上)

    java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...

  7. java基础知识(十一)java反射机制(下)

    1.什么是反射机制? java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用他的属性和方法,这种动态获取属性和方法的功能称为java的反射机制. ...

  8. Java反射机制专题

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

  9. java反射机制深入详解

    java反射机制深入详解  转自:http://www.cnblogs.com/hxsyl/archive/2013/03/23/2977593.html 一.概念 反射就是把Java的各种成分映射成 ...

随机推荐

  1. jquery事件 on(),live(),delegate(),blind()

    jQuery推出on()的目的有2个,一是为了统一接口,二是为了提高性能, 所以从现在开始用on()替换bind(), live(), delegate吧. 尤其是不要再用live()了,因为它已经处 ...

  2. fafu 1411

    想了好久都没想到怎么去判断当分类dp的时候大于或者等于要求的 值时应该怎么半 后来经过停了 qlx的想法 然后就 敲了出来 这题说的是 一个整数 分解成几个素数的和  按这个数的含有的最大素数 进行排 ...

  3. 【Redis学习之二】Redis:redis.conf 配置详解

    参数说明redis.conf 配置项说明如下:1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程  daemonize no2. 当Redis以守护进程方式运行 ...

  4. Linux基础命令---gunzip

    gunzip 解压缩被gzip压缩过的文件.此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.SUSE.openSUSE.Fedora. 1.语法      gunzip [-ac ...

  5. 利用canvas来绘制一个会动的图画

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. Putty Technical Note

    转载自:http://libai.math.ncu.edu.tw/bcc16/6/putty/puttyt.html Terminal panel Terminal 面板可調整 PuTTY 對於模擬終 ...

  7. SQL学习之MYSQL的常用命令和增删改查语句和数据类型

    连接命令:mysql -h[主机地址] -u[用户名] -p[用户密码] 创建数据库:create database [库名] 显示所有数据库: show databases; 打开数据库:use [ ...

  8. 怎么解决深入学习PHP的瓶颈

    PHP给学习者的感觉是:初学的时候很容易,但是学了2-3年,就深刻感觉遇到了瓶颈,很难深入,放弃又可惜.所谓"鸡肋,食之无味弃之可惜"的感觉很是贴切. 经常会有这种感觉:不学,看似 ...

  9. 02: vue.js常用指令

    目录:Vue其他篇 01: vue.js安装 02: vue.js常用指令 目录: 1.1 vuejs简介 1.2 选择器:根据id.class等查找 1.3 静态绑定数据 data 1.4 插值 { ...

  10. 20145225 《网络对抗》逆向及Bof基础实践

    实践目标 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串. 该程序同时包含另一个代码片段,getShe ...