> 文章列表 > Java Class类和Java反射

Java Class类和Java反射

Java Class类和Java反射

Class类和Java反射

Java反射机制是在运行状态中,对于任意一个类,都能获取这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性,这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制
通过Java反射机制,可以在程序中访问已经装载到JVM中的Java对象的描述,实现访问、检测和修改描述Java对象本身的信息的功能

Class类

除了int等基本类型外,Java的其他类型全部都是class(包括interface)
结论:class(包括interface)的本质是数据类型(Type)。无继承关系的数据类型无法赋值

每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来
注意:这里的Class类型是一个名叫Class的class。它长这样:

public final class Class {private Class() {}
}

以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来

Class cls = new Class(String);

这个Class实例是JVM内部创建的,如果查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,自己的Java程序是无法创建Class实例的。所以,JVM持有的每个Class实例都指向一个数据类型(class或interface)

JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等。因此,如果获取了某个Class实例,就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)

Class类的实例表示正在运行的Java程序中的类和接口,它没有公共的构造方法,要创建Class类的对象,有三种方法:Demo为定义的一个类

(1)使用类的class属性

直接通过一个class的静态变量class获取

Class c = Demo.class;

(2)使用Class类的forName方法

如果知道一个class的完整类名,可以通过静态方法Class.forName()获取

try {Class c = Class.forName("com.my.Demo"); //全限定类名
} catch (ClassNotFoundException e) {e.printStackTrace();
}

(3)使用Object对象的getClass方法

有一个实例变量,可以通过该实例变量提供的getClass()方法获取

Demo demo = new Demo();
Class c = demo.getClass();

Class实例在JVM中是唯一的。所以,上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例

用instanceof不但匹配指定类型,还匹配指定类型的子类。而用 == 判断class实例可以精确地判断数据类型,但不能作子类型比较

Integer n = new Integer(123);
boolean b1 = n instanceof Integer; //true,因为n是Integer类型
boolean b2 = n instanceof Number; //true,因为n是Number类型的子类
boolean b3 = n.getClass() == Integer.class;//true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class;//false,因为Integer.class!=Number.class	

通常情况下,应该用instanceof判断数据类型,因为面向抽象编程的时候,不关心具体的子类型。只有在需要精确判断一个类型是不是某个class的时候,才使用==判断class实例

动态加载

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载

public class Main {public static void main(String[] args){if (args.length > 0) {create(args[0]);}}static void create(String name) {Person p = new Person(name);}
}

当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。这就是JVM动态加载class的特性

动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,才能在运行期根据条件加载不同的实现类。例如,Commons Logging总是优先使用Log4j,只有当Log4j不存在时,才使用JDK的logging

// Commons Logging优先使用Log4j:
LogFactory factory = null;
if (isClassPresent("org.apache.logging.log4j.Logger")) {factory = createLog4j();
} else {factory = createJdkLog();
}boolean isClassPresent(String name) {try {Class.forName(name);return true;} catch (Exception e) {return true;}
}

这就是为什么只需要把Log4j的jar包放到classpath中,Commons Logging就会自动使用Log4j的原因

(1)JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息
(2)获取一个class对应的Class实例后,就可以获取该class的所有信息
(3)通过Class实例获取class信息的方法称为反射(Reflection)
(4)JVM总是动态加载class,可以在运行期根据条件来控制加载class

Java反射

Class类获得的对象调用反射方法

包路径、类名称、继承类

方法 返回值 描述
getPackage() Package 对象 获得该类的存放路径
getName() String 对象 获得该类的名称
getSuperclass() Class 对象 获得该类继承的类(直接继承)
// Integer i = ?
// true,因为Integer可以赋值给Integer
Integer.class.isAssignableFrom(Integer.class); 
// Number n = ?
// true,因为Integer可以赋值给Number
Number.class.isAssignableFrom(Integer.class); 
// Object o = ?
// true,因为Integer可以赋值给Object
Object.class.isAssignableFrom(Integer.class); 
// Integer i = ?
// false,因为Number不能赋值给Integer
Integer.class.isAssignableFrom(Number.class); 

通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现

实现接口

方法 返回值 描述
getInterfaces() Class型数组 获得该类实现的所有接口

构造方法

获取类的构造方法可以使用Class类提供的getConstructors方法、getConstructor方法、getDeclaredConstructors方法和getDeclaredConstructor方法实现,它们将返回Constructor类型的对象或数组

构造方法 描述
getConstructors() 返回一个包含某些Constructor对象的数组,这些对象反映此Class对象所表示的类的所有公共构造方法
getConstructor(Class<?>…parameterTypes) 返回对象,获得权限为public的指定构造方法
getDeclaredConstructors() 返回Constructor对象的一个数组,获得所有的构造方法,按声明顺序返回
getDeclaredConstructor(Class<?>…parameterTypes) 返回数组,获得指定构造方法

如果访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。例如,访问一个入口参数类型依次为String和int型的构造方法,以下实现:

objectClass.getDeclaredConstructor(String.class,int.class);
objectClass.getDeclaredConstructor(new Class[]{ String.class,int.class });

通过反射来创建新的实例,可以调用Class提供的newInstance()方法

Person p = Person.class.newInstance();

调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用

Constructor类的常用方法

方法 功能
isVarArgs() 查看该构造方法是否允许带有可变数量的参数,允许返回true
getParameterTypes() 按照声明顺序以Class数组形式获得该构造方法的各个参数的类型
getExceptionTypes() 以Class数组的形式获得该构造方法可能会抛出的异常
newInstance(Object…initargs) 通过该构造方法利用指定参数创建一个该类的对象,如果未设置参数则表示采用默认无参数的构造方法
setAccessible(boolean flag) 如果该构造方法的权限为private,默认为不允许通过反射利用
newInstance(Object…initargs) 创建对象。如果先执行该方法,并将入口参数设置为true,则允许创建
getModifiers() 获得可以解析出该构造方法所采用修饰符的整数

通过java.lang.reflect.Modifier类可以解析出getModifier()方法的返回值所表示的修饰符信息,在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以以字符串的形式获得所有修饰符

Modifier类中的常用解析方法

方法 功能
isPublic(int mod) 查看是否被public修饰符修饰,如果是返回true
isProtected(int mod) 查看是否被protected修饰符修饰,如果是返回true
isPrivate(int mod) 查看是否被privaate修饰符修饰
isStatic(int mod) 查看是否被static修饰符修饰
isFinal(int mod) 查看是否被final修饰符修饰
toString(int mod) 以字符串的形式返回所有修饰符

判断对象constructor所代表的构造方法是否被private修饰,以及以字符串形式获得该构造方法的所有修饰符的代码如下:

int mod=constructor.getModifiers();
String str=Modifier.isPrivate(mod);
String embe=Modifier.toString(mod);

成员变量(字段)

方法 描述
Field getField(name) 根据字段名获取某个public的field(包括父类)
Field[] getFields() 获取所有public的field(包括父类)
Field getDeclaredField(name) 根据字段名获取当前类的某个field(不包括父类)
Field[] getDeclaredFields() 获取当前类的所有field(不包括父类)

如果访问指定的成员变量,可以通过该成员变量的名称来访问。如访问一个名称为birthday的成员变量

Class cs = StringOne.class;
cs.getDeclaredField("birthday");

Filed类的常用方法

方法 功能
getName() 获得该成员变量的名称
getType() 获得表示该成员变量类型的Class对象
get(Object obj) 获得指定对象obj中成员变量的值,返回值为Object型
get(Object obj,Object value) 将指定对象obj中成员变量的值设置为value
getInt(Object obj) 获得指定对象obj中类型为int的成员变量的值
setInt(Object obj,int i) 将指定对象obj中类型为int的成员变量的值设置为i
getFlaot(Object obj) 获得指定对象obj中类型为Flaot的成员变量的值
setFlaot(Object obj,Flaot f) 将指定对obj中类型为flaot的成员变量的值设置为f
getBoolean(Object obj) 获得指定对象obj中类型为boolean的成员变量的值
setBoolean(Object obj,boolean b) 将指定obj中类型为boolean的成员变量的值设置为b
setAccessible(boolean flag) 此方法可以设置是否忽略权限限制直接访问private等私有权限的成员变量
getModifier() 获得可以解析出该成员变量所采用修饰符的整数

获取方法

方法 描述
Method getMethod(name, Class…) 获取某个public的Method(包括父类)
Method[] getMethods() 获取所有public的Method(包括父类)
Method getDeclaredMethod(name, Class…) 获取当前类的某个Method(不包括父类)
Method[] getDeclaredMethods() 获取当前类的所有Method(不包括父类)

如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问。例如,访问一个名称为print、入口参数类型依次为String和int型的方法,两种方法实现:

objectClass.getDeclaredMethod("print",String.Class,int.class);
objectClass.getDeclaredMethod("print",new Class[]{ String.class,int.class }
objectClass是Class类的对象

Method类的常用方法

方法 功能
getName() 获得该方法的名称
getParameterTypes() 按照声明顺序以Class数组形式获得该方法的各个参数类型
getReturnType() 以Class对象的形式获得该方法的返回值类型
getExceptionType() 以Class数组的形式获得该方法可能抛出的异常类型
Invoke(Object obj,Object…args) 利用指定参数args执行指定对象obj中的该方法,返回Object型
isVarArgs() 查看该构造方法是否允许带有可变数量的参数,如果允许则允许返回true
getModifiers() 获得可以解析出该方法所采用修饰符的整数

在反射中执行具有可变数量的参数的构造方法时,需要将入口参数定义成二维数组

调用方法
当获取到一个Method对象时,就可以对它进行调用

// String对象:
String s = "Hello world";
// 获取String substring(int)方法,参数为int:
Method m = String.class.getMethod("substring", int.class);
// 在s对象上调用该方法并获取结果:
String r = (String) m.invoke(s, 6);
// 打印调用结果:
System.out.println(r);

注意到substring()有两个重载方法,获取的是String substring(int)这个方法。思考一下如何获取String substring(int, int)方法
对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错

调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null

// 获取Integer.parseInt(String)方法,参数为String:
Method m = Integer.class.getMethod("parseInt", String.class);
// 调用该静态方法并获取结果:
Integer n = (Integer) m.invoke(null, "12345");
// 打印调用结果:
System.out.println(n);

调用非public方法
和Field类似,对于非public方法,虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,通过Method.setAccessible(true)允许其调用

public class Main {public static void main(String[] args) throws Exception {Person p = new Person();Method m = p.getClass().getDeclaredMethod("setName", String.class);m.setAccessible(true);m.invoke(p, "Bob");System.out.println(p.name);}
}class Person {String name;private void setName(String name) {this.name = name;}
}

此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全

多态调用

// 获取Person的hello方法:
Method h = Person.class.getMethod("hello");
// 对Student实例调用hello方法:
h.invoke(new Student());class Person {public void hello() {System.out.println("Person:hello");}
}class Student extends Person {public void hello() {System.out.println("Student:hello");}
}

使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)

内部类、内部类的声明类

方法 返回值 描述
getClasses() Class型数组 获得所有权限为public的内部类
getDeclaredClasses() Class型数组 获得所有内部类
getDeclaringClass() Class对象 如果该类为内部类,则返回它的成员变量,否则返回null


上一篇:Java泛型                        下一篇:JDBC操作数据库