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();
}
}
|
运行的结果为: