首先思考一个问题,java是动态语言还是静态语言?
何谓动态语言?简单来说就是在运行时可以改变自身结构的语言。怎样才算改变了自身机构呢?比如引进了新的函数、对象或者是代码,比如已有的某些功能函数被删除,某些结构被改变等等。举个简单的例子,我用Python语言定义了一个变量a,一开始我让a = 1,a是数值型,但是我让a指向一个字符串可以么?让a指向一个类可以么?可以,当然可以,即便是在代码运行期间,a的指向也可以任意的改动,a的类型完全由它指向的具体内容所决定,这就是典型的动态语言的特征。依此看来,Python是典型的动态语言,同样为动态语言的还有PHP、Javascript等。
那么,看完了关于动态语言的介绍,你觉得java是动态语言么?
显然不是,java中任何变量的声明都需要指明类型,就这一点上它已经与Python等动态语言相差很大。java其实是一种静态语言。最为典型的静态语言莫过于C、C++了。
但作为编程语言里的佼佼者,java真的可能甘愿为地地道道的静态语言么?当然不会,java同样也可以具有类似于动态语言的特性,这让java的代码更加的灵活,而实现这种特性的就是java的反射机制。
java的反射机制可以使java程序在运行期间拿到一个对象的所有信息,包括它的所有字段和方法,看清了是所有,因此不管这个对象中是否含有public、protected还是private,java程序都可以获取它的所有信息并且调用它们。
程序中的对象类型一般在译器就会被确定,但我们正在运行的程序有时也可能需要动态的加载一些类,这些类因为之前没有用到,因此没有被加载到JVM中,这时,java反射机制可以在运行期动态的创建对象并调用其属性。
再引出反射之前,先介绍Class对象。
除了int等基本数据类型外,java中的其它类型都是class(包括interface),因此几乎都需要动态加载。而只有当JVM第一次读取到一种class类型时,才会将其加载进内存中。
而每加载一种class,JVM就会为其创建一个Class类型的实例,并关联起来。这里不要把Class与class混淆了。Class类是JVM内部创建的,每当JVM加载一个新的class,JVM会自动调用Class类的构造方法实例化一个Class对象,Class对象才是与记载的class有关联的,它存储了该class的所有完整信息。
查看java的api文档,你会看到Class类的构造方法是private的,即如下:
public final class Class { private Class() {} }这说明Class对象只有JVM能够创建且是自动创建,不需要java程序员关心这个问题。
再回到Class对象上,刚才说了,Class对象包含了一个被加载进JVM的类的所有完整信息,这包括 name、super、package、interface、field、method 等等。因此你想啊,如果我们获得了该Class对象,那岂不是就等于获得了该class的所有内容?这就是反射(Reflection)。
为了理清思绪,接下来以创建一个学生类对象为例,分步说明Class对象的加载过程。
Student stu = new Student(); 当代码运行到该条语句时,JVM会加载相对应的Student.class字节码文件。因为Student类之前并未被程序所用,所以直到需要用到该对象时才动态创建;JVM沿着本地路径在本地磁盘中查找Student.class文件,并加载进JVM内存中;Student.class被加载进内存后,除了给它分配空间外,JVM自动调用Class类的构造方法创建一个Class对象,该Class对象将保存Student类的相关信息。注意对于同一个类只会产生一个Class对象。接下来的重点似乎就在于如何获得Class对象了。
接下来介绍三种获取Class对象的方法:
#1 直接通过一个class的静态变量class获取。
Class cls = String.class;#2 通过实例变量提供的getClass()方法获取。
Student stu = new Student() Class cls = stu.getClass();#3 如果知道一个class的完整类名(即带包名的类路径),可以通过静态方法Class.forName()获取。
Class cls = Class.forName("java.lang.String");另外注意:Class对象在JVM中是唯一的,它和每个第一次加载进JVM中的class一一对应,这就提醒我们在做Class对象间的比较时应该用 == 而不是 instanceof,因为intanceof不仅匹配当前类型还匹配当前类型的父类。而父类和当前类的Class对象是不同的。
我们使用反射的主要目的是获取某个实例的信息,接下来介绍如何通过Class对象获得信息。
Class代表类的实体,在运行的java程序中代表类和接口,与之相关的方法有:
getName() //获得类的完整路径。 getSimpleName() //获得类的名字 getPackage() //获得类的包 getSuperclass() //获得当前类的父类 getInterfaces() //获得当前类实现的类或接口(不包括父类) newInstance() //创建类的实例 forName() //根据类名返回类的对象 isInstance(obj) //判断是否能强制转换为obj对象 isInterface() //判断是否为接口类型 isEnum() //判断是否为枚举类型 isArray() //判断是否为数组类型 isPrimitive() //判断是否为基本类型 isAnnotation() //判断是否是注解类型 isAnonymousClass() //判断是否是匿名类型示例:
public class Main { public static void main(String[] args) { printClassInfo(String.class); printClassInfo(Integer[].class); printClassInfo(int.class); } static void printClassInfo(Class cls){ System.out.println("Class name: " + cls.getName()); System.out.println("Simple name: " + cls.getSimpleName()); System.out.println("Super name: " + cls.getSuperclass()); Class[] cs = cls.getInterfaces(); for(Class csinterface : cs){ System.out.println(csinterface); } System.out.println("is interface: " + cls.isInterface()); System.out.println("is enum: " + cls.isEnum()); System.out.println("is array: " + cls.isArray()); System.out.println("is primitive: " + cls.isPrimitive()); System.out.println(); } }运行结果:
Class name: java.lang.String Simple name: String Super name: class java.lang.Object interface java.io.Serializable interface java.lang.Comparable interface java.lang.CharSequence interface java.lang.constant.Constable interface java.lang.constant.ConstantDesc is interface: false is enum: false is array: false is primitive: false Class name: [Ljava.lang.Integer; Simple name: Integer[] Super name: class java.lang.Object interface java.lang.Cloneable interface java.io.Serializable is interface: false is enum: false is array: true is primitive: false Class name: int Simple name: int Super name: null is interface: false is enum: false is array: false is primitive: true注意到数组也是一种class,而且不同于Integer.class,它的类名是[Ljava.lang.Integer。另外,JVM为每一种基本类型如int也创建了Class,通过int.class访问。
在第三个例子中,未输出接口,这是因为如果某个类没有实现任何一个接口,那么getInterfaces()方法返回一个空数组。
另外,Class对象的newInstance()方法可用于创建实例,与new的功能类似,如下:
Class cls = String.class; String s = (String)cls.newInstance(); //相当于new String()这种方法虽然可以创建实例,但只限于调用publc的无参构造方法,除此之外的非public的构造方法或者带参的构造方法均不能直接通过该方法创建实例。
与之相关的Class对象的方法有:
getField() //获得某个公有的属性对象 (包括父类) getFields() //获得所有公有的属性对象 (包括父类) getDeclaredField() //获得指定的某个属性对象 (不包括父类) getDeclaredFields() //获得所有属性对象 (不包括父类)示例:
import java.lang.reflect.Field; //Field类需要导入 public class Main { public static void main(String[] args) throws Exception{ Class cls = Student.class; System.out.println(cls.getField("name")); //获得公有name字段 System.out.println(cls.getDeclaredField("score")); //获得私有score字段 Field[] fields = cls.getFields(); for (Field field : fields) { System.out.println(field); } Field[] declearfields = cls.getDeclaredFields(); for (Field declearfield : declearfields) { System.out.println(declearfield); } } } class Person{ public String name; public int age; } class Student extends Person{ private int score; private int weight; }运行结果:
public java.lang.String pack.Person.name private int pack.Student.score public java.lang.String pack.Person.name public int pack.Person.age private int pack.Student.score private int pack.Student.weightgetField()、getDeclaredField()返回一个Field对象,而getFields()、getDeclaredFields()则返回一个Field数组。
注意到一个Field对象由以下三部分组成:
Modifier :字段修饰符。Type :字段类型。Name :字段名称。与之相应的Field对象就有以下三种方法:
getName() :返回字段名称。getType() :返回字段类型。getModifiers() :返回字段修饰符。注意该方法返回的是一个int类型的值,有关它的用法可以参看下面的示例。使用示例如下:
//注意要导入Field类和Modifier类 import java.lang.reflect.Field; import java.lang.reflect.Modifier; ...... Field f = Student.class.getDeclaredField("score"); System.out.println(f.getName()); System.out.println(f.getType()); int t = f.getModifiers(); System.out.println(Modifier.isFinal(t)); System.out.println(Modifier.isPublic(t)); System.out.println(Modifier.isProtected(t)); System.out.println(Modifier.isPrivate(t)); System.out.println(Modifier.isStatic(t));运行结果:
score int false false false true falsejava为Field对象提供有get()与set()方法,用于获取字段的值并动态修改字段的值。
获取字段的值:
import java.lang.reflect.Field; public class Main { public static void main(String[] args) throws Exception { Student stu = new Student(59); Class cls = stu.getClass(); Field f = cls.getDeclaredField("score"); Object value = f.get(stu); System.out.println(value); //59 } } class Student { private int score; Student(int score) { this.score = score; } }在Eclipse环境下,你会发现上面这段代码在编译前未提示错误,但编译期却弹出了java.lang.IllegalAccessException,这是因为score字段是private的,尽管我们可以用getDeclaredField()方法得到该字段,但正常情况下,Main类是无法访问其他类的private字段的值的。要解决这个问题,除了将private权限更改为public之外,也可以在调用f.get()方法之前设置Field对象的setAccessible()方法,如下:
f.setAccessible(true);该方法由Field对象提供,参数设置为true,意思是不管该字段的权限等级如何,一律允许访问。
既然就算是private字段的值也可以访问,那么类的封装是否还有意义呢? 大多数情况下我们都是通过类名.字段的形式来访问字段值,这种访问方式确实能够达到数据封装的目的。而反射是一种非常规的用法,它更多的是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。
此外,setAccessible(true)方法也可能因为SecurityManager的运行期检查而导致失败。
但不可否认的是,通过反射读写字段确实在一定程度上破坏了类的封装。
修改字段的值:
import java.lang.reflect.Field; public class Main { public static void main(String[] args) throws Exception { Student stu = new Student(61); Class cls = stu.getClass(); Field f = cls.getDeclaredField("score"); f.setAccessible(true); f.set(stu,59); Object value = f.get(stu); System.out.println(value); //59 } } class Student { private int score; Student(int score) { this.score = score; } }Class类提供的用于获取Method的方法有:
getMethod(name,Class...) //获取某个public的Method (包括父类) getDeclaredMethod(name,Class...) //获取当前类的某个Method (不包括父类) getMethods() //获取所有public的Method (包括父类) getDeclaredMethods() //获取所有Method (不包括父类)示例:
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Class cls = B.class; //注意如果方法有参数,则需要在getMethod()和getDeclaredMethod()方法里传入所有参数的Class实例 System.out.println(cls.getMethod("f0",int.class)); //因为f0方法有参数,额外传入int.class实例 System.out.println(cls.getMethod("f1",String.class)); //额外传入String.class实例 System.out.println(cls.getDeclaredMethod("f2")); //f2方法无参数,故只传入方法名即可 } } class A { public int f0(int x) { return x; } } class B extends A { public String f1(String s) { return s; } private int f2() { return 0; } }运行结果:
public int pack.A.f0(int) public java.lang.String pack.B.f1(java.lang.String) private int pack.B.f2()可以看到,getMethod()等方法与getField()类似,getMethod()返回一个Method对象,该对象包含以下信息:
Name : 方法名。Modifier :方法修饰符。ReturnType :方法返回值类型。ParameterType :方法参数类型。于是一个Method对象对应有以下方法:
getName() :返回方法名。getModifiers() :返回方法修饰符,返回值与Field对象的该方法类似,为一个int型的值。getReturnType() :返回方法的返回值类型,为一个Class实例,如String.class。getParameterTypes() :返回方法的参数类型,因为参数不止有一个,因此它的返回值是一个Class数组,如{String.class,int.class}。示例:
import java.lang.reflect.Method; import java.lang.reflect.Modifier; ... Class cls = B.class; Method m = cls.getMethod("f1",String.class); System.out.println(m.getName()); System.out.println(m.getReturnType()); Class[] paramtertypes = m.getParameterTypes(); for(Class paramtertype : paramtertypes) System.out.println(paramtertype); int t = m.getModifiers(); System.out.println(Modifier.isFinal(t)); System.out.println(Modifier.isPublic(t)); System.out.println(Modifier.isProtected(t)); System.out.println(Modifier.isPrivate(t)); System.out.println(Modifier.isStatic(t));运行结果:
f1 class java.lang.String class java.lang.String false true false false falseMethod对象提供有invoke()方法,用于运行时动态调用类方法。
invoke()方法第一个参数为需要调用的方法所在的实例对象,因此如果调用一个静态方法时,由于无需指定实例对象,因此第一个参数传入为null。
除第一个参数外,之后依次传入调用的方法需要的参数。
示例如下:
import import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { TextClass tc = new TextClass(); Class cls = tc.getClass(); Method m0 = cls.getMethod("f",int.class); System.out.println(m0.invoke(tc,6)); //调用public的f方法 Method m1 = cls.getMethod("fnum",int.class,int.class); System.out.println(m1.invoke(tc,6,6)); //调用public的fnum方法 Method m2 = cls.getDeclaredMethod("fs",String.class); m2.setAccessible(true); //调用非public方法需使用该语句 System.out.println(m2.invoke(tc,"hahaha")); //调用private的fs方法 Method m3 = cls.getMethod("fd",double.class); System.out.println(m3.invoke(null,6.666)); //调用static的fd方法 } } class TextClass { public int f(int i) { return i; } public int fnum(int a,int b) { return a + b; } private String fs(String s) { return s; } public static double fd(double d) { return d; } }运行结果:
6 12 hahaha 6.666invoke()方法也具有多态的性质,它同样建立在继承和方法重写的前提上,如下例子:
import java.lang.reflect.Method; public class Main { public static void main(String[] args) throws Exception { Method m = Person.class.getMethod("draw"); m.invoke(new Student()); //I am a Student. } } class Person { public void draw(){ System.out.println("I am a Person."); } } class Student extends Person { public void draw(){ System.out.println("I am a Student."); } }从结果可以看到,从Person的Class对象得到的Method对象,调用该Method对象的invoke()方法后,传入子类Student实例,调用的也是子类的draw()方法,而不是父类。
在前面已经提到,通过Class对象newInstance()方法可以创建实例,但它只限于调用public的无参的构造方法来创建实例。为了调用任意的构造方法,java反射机制提供了Constructor对象。
通过Class对象提供的以下四种方法可以获得该对象:
getConstructor() :获取某个public的Constructor getDeclaredConstructor() :获取某个Constructor getConstructors() :获取全部public的Constructor getDeclaredConstructors() :获取全部Constructor利用Constructor对象,我们通过以下步骤就可以创建任意构造方法的对象。
获取Class对象,通过Class对象的getConstructor()方法获得Constructor对象 利用Constructor对象的newInstance()方法调用构造方法 同时,Constructor对象的newInstance()方法会返回类的实例示例:
import java.lang.reflect.Constructor; public class Text { public static void main(String[] args) throws Exception { Class cls = TextClass.class; Constructor cr = cls.getConstructor(String.class); TextClass tc = (TextClass)cr.newInstance("biubiubiu"); System.out.println(tc.getS()); //biubiubiu } } class TextClass { private String s; public TextClass(String s){ this.s = s; } public String getS(){ return s; } }注意getConstructor()方法也需要传入Class参数,如int.class、String.class等,如果调用的是非public的构造方法,则也需要设置 Constructor.setAccessible(true)。
后续相关:
java中的动态代理 java运用反射机制的例子