首先思考一个问题,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对象。
一、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对象
接下来介绍三种获取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对象获得信息。
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的构造方法或者带参的构造方法均不能直接通过该方法创建实例。
二、使用Field类与Class类访问字段
与之相关的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.weight
getField()、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
false
Field类的get()与set()方法
java为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类调用方法
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
false
调用方法
Method对象提供有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.666
invoke()方法的多态特性
invoke()方法也具有多态的性质,它同样建立在继承和方法重写的前提上,如下例子:
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运用反射机制的例子