3 Class类和反射机制
Class 类和 Class 对象
Java 虚拟机在运行时,知道一块内存到底指向的是什么对象。比如:
| class A{
public A(){}
public void method(){
System.out.println("A's method has been called.");
}
}
// 定义了 B 是 A 的子类
class B extends A{
public B(){}
public void method(){
System.out.println("B's method has been called.");
}
}
// 某个代码段:
A obj = new B();
obj.method();
|
虚拟机知道,obj
指向的内存是一个B
对象(运行时类型),而不是A
对象(引用类型)。这是通过获得obj
的class
成员得知的。所以,根据多态性,实际运行时obj.method()
会调用B
类的method
方法,从而在控制台打印出B's method has been called.
虚拟机是怎么知道obj
指向的内存是一个B
对象的?
每个类都有一个Class
类型的class
成员,比如A.class
就包含了类A
的类型信息。A.class
是在A
类编译时产生的。
Class
类中提供了很多方法。其中,getName
返回该类的完全限定类名,getSimpleName
返回该类的简单类名。
以下面的代码为例:
| package hust.cs.javacourse.ch13;
public class ClassTest {
public static void main(String[] args){
System.out.println(ClassTest.class.getName());
System.out.println(ClassTest.class.getSimpleName());
}
}
|
它的运行结果是:
| hust.cs.javacourse.ch13.ClassTest
ClassTest
|
如何获取 Class 对象
通过类
上文中已经提到,通过类名.class
可以直接获得该类的class
对象。
此外,Class
类提供了静态方法forName(String className)
,可以通过类的完全限定类名className
获得这个类的class
对象;同时,还提供了实例方法getSuperClass()
获取其直接父类的class
对象。
当forName(String className)
无法找到完全限定类名为className
的对象时,方法会抛出必检异常ClassNotFoundException
。所以通过类名.class
获取class
对象是一种比较安全的方法。
| package hust.cs.javacourse.ch13;
class Person{}
class Employee extends Person{}
class Manager extends Employee{}
public class ClassDemo {
public static void main(String[] args){
try {
Class clz = Class.forName("hust.cs.javacourse.ch13.Manager"); //参数是类完全限定名字符串
System.out.println(clz.getName()); //产生完全限定名hust.cs.javacourse.ch13.Manager
System.out.println(clz.getSimpleName()); //产生简单名Manager
Class superClz = clz.getSuperclass(); //获得直接父类型信息
System.out.println(superClz.getName()); //产生完全限定名hust.cs.javacourse.ch13.Employee
System.out.println(superClz.getSimpleName()); //产生简单名Employee
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
|
运行上面的代码,得到以下结果:
| hust.cs.javacourse.ch13.Manager
Manager
hust.cs.javacourse.ch13.Employee
Employee
|
通过对象
任何一个对象在运行时还能够调用getClass()
方法获取该对象的运行时类型。getClass
方法是Object
类中的final
方法。
反射机制
看完上面的内容,你可能觉得class
对象用处不大。但是反射机制中恰恰能够发挥class
对象的用处。
实例化对象
考虑实现下面的功能:从System.in
中获取类的完全限定类名,构建一个相应的类的实例对象。有人可能会这样写:
| // 从System.in中获取输入的字符串input
Object o = null;
if( input.equals("java.lang.String") ){
o = new String("");
}
else if( input.equals("ch13.Student") ){
o = new Student();
}
// ……更多的else if语句
|
这种一个个枚举的方法显然不行,因为事先不知道会输入一个什么类的完全限定名字符串,if
语句不可能列出所有可能的类型。这时,我们可以利用反射机制,编写以下的代码:
| // 从System.in中获取输入的字符串input
try{
Class clz = Class.forName();
Object o = clz.newInstance();// 调用该类的无参构造函数
} catch (Exception e) {
e.printStackTrace();
}
|
即可成功创建实例对象o
。
获取类的构造器或方法
获取所有构造器或方法
Class
类中提供了实例方法getConstructors()
获得某个类的所有构造器,提供了实例方法getMethods()
获得某个类的所有方法(包括从父类继承的方法),提供了实例方法getDeclaredMethods()
获得某个类中定义的所有方法。比如下面的代码:
| package hust.cs.javacourse.ch13;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
class Student{
private String name;
public Student(){
this.name = "unknown";
}
public Student(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Name:" + name;
}
}
public class ReflectDemo {
public static void main(String[] args) {
System.out.println("获取构造器:");
try {
Class clz = Class.forName("hust.cs.javacourse.ch13.Student");
//获取所有的Constructor对象
Constructor[] ctors = clz.getConstructors();
for(Constructor c : ctors){
System.out.println(c.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("获取方法:");
try {
Class clz = Class.forName("hust.cs.javacourse.ch13.Student");
//获取所有的Method
//Method[] methods = clz.getMethods(); //会显示所有方法,包括继承的
Method[] methods = clz.getDeclaredMethods(); //本类定义的方法
for(Method m: methods){
System.out.println(m.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
运行结果为:
| 获取构造器:
public hust.cs.javacourse.ch13.Student()
public hust.cs.javacourse.ch13.Student(java.lang.String)
获取方法:
public java.lang.String hust.cs.javacourse.ch13.Student.getName()
public java.lang.String hust.cs.javacourse.ch13.Student.toString()
public void hust.cs.javacourse.ch13.Student.setName(java.lang.String)
|
获取某个构造器
前面我们已经知道,可以用一个Class
对象的newInstance()
方法,调用它的无参构造函数创建一个实例对象。那如果要调用有参构造函数呢?
Class
类还提供了实例方法getConstructor(Class<?>... parameterTypes)
,按照构造器的参数类型列表parameterTypes
获取对应的构造器,返回一个Constructor
对象。其中参数类型列表是Class
类对象的列表。而Constructor
类又提供了实例方法newInstance(Object ... initargs)
,按照构造器的参数列表initargs
传入实参,从而创建一个实例类型。
例如,按照上面hust.cs.javacourse.ch13.Student
的定义,我们可以使用如下代码创建一个名为John
的学生:
| try {
Class clz = Class.forName("hust.cs.javacourse.ch13.Student");
Student s = (Student)clz.getConstructor(String.class).newInstance("John");
} catch (Exception e) {
e.printStackTrace();
}
|
其中,clz.getConstructor(String.class)
获取了Student
类的参数类型为String.class
的构造器,也就是下面这个构造器:
| public Student(String name){
this.name = name;
}
|
然后对于这个构造器,又调用了newInstance("John")
方法,从而将"John"
传入形式参数name
,构造出了一个Student
实例对象。
对于一个Class
类对象clz
,可以直接使用clz.newInstance()
调用该类的无参构造函数,此时newInstance
是Class
类提供的实例方法;也也可以使用clz.getConstructor().newInstance()
调用该类的无参构造函数,此时newInstance
是Constructor
类提供的实例方法。
获取某个方法
Class
类提供的实例方法getMethod(String name, Class<?>... parameterTypes)
,按照方法名name
以及方法的参数类型列表parameterTypes
获取对应的方法,返回一个Method
对象。而Method
类又提供了实例方法invoke(Object obj, Object... args)
,调用对象obj
的相应方法,并给方法的形参列表传入args
实参列表。
例如,按照上面hust.cs.javacourse.ch13.Student
的定义,我们可以使用如下代码创建一个名为Marry
的学生:
| try {
Class clz = Class.forName("hust.cs.javacourse.ch13.Student");
Student s = (Student)clz.getConstructor().newInstance();
clz.getMethod("setName", String.class).invoke(s, "Marry"); //调用s1对象的setName方法,实参"Marry"
} catch (Exception e) {
e.printStackTrace();
}
|
其中,clz.getMethod("setName", String.class)
获取了Student
类的参数类型为String.class
的、方法名为setName
的方法,也就是下面这个方法:
| public void setName(String name) {
this.name = name;
}
|
然后对于这个方法,又调用了invoke(s,"Marry")
方法,从而将"Marry"
传入形式参数name
,并调用s
对象的该方法。
代码实例
上面的解释可以通过一段代码的运行来体现:
| package hust.cs.javacourse.ch13;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
class Student{
private String name;
public Student(){
this.name = "unknown";
}
public Student(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "Name:" + name;
}
}
public class ReflectDemo {
public static void main(String[] args) {
try {
Class clz = Class.forName("hust.cs.javacourse.ch13.Student");
//实例化对象
//1:如有缺省构造函数,调用Class对象的newInstance方法
Student s1 = (Student)clz.getConstructor().newInstance();
//2. 调用带参数的构造函数
Student s2 = (Student)clz.getConstructor(String.class).newInstance("John");
//invoke method
clz.getMethod("setName", String.class).invoke(s1, "Marry"); //调用s1对象的setName方法,实参"Marry"
System.out.println(s1.toString());
System.out.println(s2.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
|
运行的结果为: