详解Java反射机制------入门篇
一、什么叫Java反射机制?
Java中的反射机制是指在运行状态中,对于任意一个类,能够动态获取这个类中的属性和方法;对于任意一个对象,都能够任意调用它的属性和方法。这种动态获取类的信息以及动态调用对象方法的功能称为Java的反射机制。总结就是:反射可以实现运行时知道任意一个类的属性和方法。
二、Java当中为什么需要反射机制?工作原理是什么?
要明白为什么需要反射机制,首先就得先清楚两个概念:
(1)静态编译:在编译时就必须要知道变量得类型才能编译通过,比如C++,Java等编译之前必须清楚的指明数据类型;
(2)动态编译:显然和上述概念相反,即在编译时不需要立即知道变量的类型,在运行时指明其类型就行。
有上述概念之后便有了静态语言和动态语言之分,而Java,C++就是典型的静态语言,而Python,Ruby等则为动态语言。但是Java虽是一个静态的解释型语言,但其也有动态的性质------反射(Reflection)。用在Java身上指的是运行时加载,探知,使用编译期间未知的classes。
下面探讨一下反射是如何在仅知道类名的情况下能够知道整个类的完整构造的(方法,属性等)。
首先我们都清楚,Java是一种解释型的语言,即编译器首先将源码编译成class文件,然后虚拟机(JVM)再将class文件解释给目标计算机执行。而这里所说的反射机制操作的其实就是源码编译之后的class文件。首先将class文件加载到内存,然后从该文件中解剖出某个具体类的构造函数,方法和属性,从而完全知道某个类的所有构造。下面给出一个简易的demo及字节码文件。demo源码:
public class Student {
String name="xiaoshuang";
String gender;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
private void setGender(String gender) {
this.gender = gender;
}
public Student(String name, String gender) {
this.name = name;
this.gender = gender;
}
public Student(){}
public static void main(String[] args) {
//获取该类的方
try {
Class c = Class.forName("com.tyf.reflect.Student");
Method methods[] = c.getDeclaredMethods();//获取该类的所有方法
Method methods2[]=c.getMethods();//获取该类以及父类的所有public类
for(Method m:methods){
System.out.println(m.getName());
}
}catch (Exception e){
e.printStackTrace();
}
}
字节码文件:
public class com.tyf.reflect.Student {
java.lang.String name;
java.lang.String gender;
public java.lang.String getName();
Code:
0: aload_0
1: getfield #1 // Field name:Ljava/lang/String;
4: areturn
public void setName(java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field name:Ljava/lang/String;
5: return
public java.lang.String getGender();
Code:
0: aload_0
1: getfield #2 // Field gender:Ljava/lang/String;
4: areturn
public com.tyf.reflect.Student(java.lang.String, java.lang.String);
Code:
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #4 // String xiaoshuang
7: putfield #1 // Field name:Ljava/lang/String;
10: aload_0
11: aload_1
12: putfield #1 // Field name:Ljava/lang/String;
15: aload_0
16: aload_2
17: putfield #2 // Field gender:Ljava/lang/String;
20: return
public com.tyf.reflect.Student();
Code:
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #4 // String xiaoshuang
7: putfield #1 // Field name:Ljava/lang/String;
10: return
public static void main(java.lang.String[]);
Code:
0: new #5 // class java/util/HashSet
3: dup
4: invokespecial #6 // Method java/util/HashSet."<init>":()V
7: astore_1
8: aload_1
9: ldc #7 // String tom
11: invokeinterface #8, 2 // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: invokevirtual #9 // Method java/lang/Object.getClass:()Ljava/lang/Class;
21: astore_2
22: aload_2
23: ldc #10 // String add
25: iconst_1
26: anewarray #11 // class java/lang/Class
29: dup
30: iconst_0
31: ldc #12 // class java/lang/Object
33: aastore
34: invokevirtual #13 // Method java/lang/Class.getDeclaredMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
37: astore_3
38: aload_3
39: aload_1
40: iconst_1
41: anewarray #12 // class java/lang/Object
44: dup
45: iconst_0
46: bipush 100
48: invokestatic #14 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
51: aastore
52: invokevirtual #15 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
55: pop
56: aload_1
57: invokeinterface #16, 1 // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
62: astore 4
64: aload 4
66: invokeinterface #17, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
71: ifeq 94
74: aload 4
76: invokeinterface #18, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
81: astore 5
83: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
86: aload 5
88: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
91: goto 64
94: goto 102
97: astore_2
98: aload_2
99: invokevirtual #22 // Method java/lang/Exception.printStackTrace:()V
102: return
Exception table:
from to target type
17 94 97 Class java/lang/Exception
}
从上面的字节码文件中我们很清楚的能看到类里面的一下public方法和属性。非常值得注意的一点是注意类名,在字节码中使用的是该类的全类名(包名+类名)。因此呢,在使用反射时,forName()方法需要指定该类的全类名,从而从该class文件中可以直接获取原类的完整结构(属性,方法等)。
三、反射的使用及入门
在使用反射之前,最好清楚一个这样的概念,即类类型。在编程语言中,各种数据都有其数据类型,那么对于类本身而言,它也有自己的类型,即类类型,其实和普通的数据类型差不多,清楚这一点就行。类是java.lang.Class类的实例对象,而Class是所有类的类(There is a class named Class)。对于普通的类实例化时只需要new一个就行,但是要想用new实例化Class是行不通的,因为Class的构造器是私有的(自行查看源码)。
1、获取某个类(Student)的Class对象的三种方法:
(1)Class c1=Student.class;//通过Student类隐藏的静态变量来获取
(2)Class c2=Student.getClass();//通过Stduent的getClass()方法
(3)Class c3=Student.forName("com.tyf.reflect.Student");//com.tyf.reflect.Student为Student类的全类名。
注意:这里的c1,c2,c3是完全一样的,都是Class的对象(学名 类类型)。这里不必纠结Student和c1,c2,c3是否一样了(都叫Class对象),只需了解类类型是什么就行。
2、反射中的相关操作
(1)如何获取类的方法以及方法的执行:
try{
Class c = Student.class;
Object o = c.newInstance();//初始化一个实例
Method method_setName = c.getDeclaredMethod("setName", String.class);
Method method_getName = c.getDeclaredMethod("getName");
method_setName.invoke(o,"yafneg");
Object name = method_getName.invoke(o);
System.out.println(name);
}catch (Exception e){
e.printStackTrace();
}
如何来获取该类的所有方法呢?可以用如下两种方法:
Method methods[] = c.getDeclaredMethods();//获取该类的所有方法
Method methods2[]=c.getMethods();//获取该类以及父类的所有public类
两者的区别上述注释已经说的很清楚,完整代码如下:
try {
Class c = Class.forName("com.tyf.reflect.Student");
Method methods[] = c.getDeclaredMethods();//获取该类的所有方法
// Method methods2[]=c.getMethods();//获取该类以及父类的所有public类
for(Method m:methods){
System.out.println(m.getName());
}
}catch (Exception e){
e.printStackTrace();
}
执行getDeclaredMethods(),并输出方法名的结果如下:
main
getName
setName
setGender
getGender
从上述结果可以看到,getDeclaredMethods()获取的是该类的所有方法(public+private)。下面的获取属性的方法结果于上述类似,不再赘述。
执行getMethods(),并输出方法名的结果如下:
main
getName
setName
getGender
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
从上面的结果中可以看到,该方法不但获得了Student类的public方法,同时也获得了Student父类(Object)的public方法,注意未获得setGender()方法,因为其是private方法。
(2)获取类属性:
Field field = c.getDeclaredField("name"); //获取指定属性
//Field fields[]=c.getDeclaredFields();//获取所有属性
//Field fields[]=c.getFields();获取所有的public子类及父类属性
field.setAccessible(true);//设置是否允许访问,因为该属性是privated的
完整代码:
try {
Class c = Class.forName("com.tyf.reflect.Student");
Object o = c.newInstance();
Field field = c.getDeclaredField("name");
Field fields[] = c.getDeclaredFields();//获取所有属性
//Field fields[]=c.getFields();获取所有的public子类及父类属性
field.setAccessible(true);//设置是否允许访问,因为该属性是privated的
Object name = field.get(o);//获取该属性值
System.out.println(name);
} catch (Exception e) {
e.printStackTrace();
}
执行结果:
xiaoshuang
至于getDeclaredField()和getField()的区别和上述getDeclardMethod()与getMethods()是一样的,这里不再赘述。
(3)获取类的构造器:
try{
Class c = Class.forName("com.tyf.reflect.Student");
Constructor constructor = c.getDeclaredConstructor(String.class,String.class);//获取含参的构造器
Object o = constructor.newInstance("yafeng","boy");//利用反射得到的构造器实例化对象
Field field = c.getDeclaredField("name");//获取name属性
Object name = field.get(o);//获取属性值
System.out.println(name);
}catch (Exception e){
e.printStackTrace();
}
}
执行结果:
yafeng
四、Java反射机制的应用
当在某集合中需要放多种数据类型的数据时,可以使用反射机制来解决。
为什么可以使用反射机制呢?需要注意的是集合中的泛型只在程序编译阶段有效,而在运行期间无效(相当于没有),而反射机制正好就是运行期间才检测数据检测。看下面代码:
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("tom");
//我要给set中要放一个int型的数据,用set.add(11);是会报错的,那么改怎么做?
Class c = set.getClass();
try {
Method m = c.getDeclaredMethod("add",Object.class);
m.invoke(set,100);
}catch (Exception e){
e.printStackTrace();
}
for(Object s:set){
System.out.println(s);
}
}
执行结果:
tom
100
上述的内容即为反射当中常用到的一些东西,掌握这些知识,Java反射基本已经入门。当然Java反射不止这些东西,需要学的还有很多,还需要day day up。