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操作数据库