谈谈Java反射机制

原文出处: localityjava

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


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

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。
那么问题来了:spa

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()方法:code

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类的对象。对象

2、方法的反射
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关键字都是存在类类型的。接口

案例:ip

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()获取这个方法里面的参数类型的类类型。

3、成员变量的反射
首先咱们须要认识到成员变量也是对象,是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);
}

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

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( ")" );
}
}

5、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

若是之后想用某一个类,不须要从新编译,只须要实现这个标准的接口便可。只须要动态的加载新的东西就好了。
这就是动态加载类

相关文章
相关标签/搜索