Java反射
什么是Java反射 以及 引出反射的背景?
Java程序中的对象有两种类型,编译时类型 和 运行时类型,而很多时候编译时类型与运行时类型是不一致的。
举个例子给大家看一下:
public class Test1 {
public static void main(String[] args) {
Person person = new Man();
person.shout();
}
}
class Person{
String name;
public void shout(){
System.out.println("person shout");
}
}
class Man extends Person{
String sex;
public void shout(){
System.out.println("Man shout");
}
}
上面代码输出结果 : Man shout
编译类型时Person ,运行时实际时Man; 如果我们有一个方法传入的是一个Person,到底传入的Person具体是哪个类,只有运行的时候才知道,可能是Person类或它的子类。
所以如果现在想要运行的时候知道到底传入的是哪一个类,怎么办?
这个问题就引入了反射这个概念,反射指的是运行期拿到一个对象的所有信息。
Class类
我们编写完java代码后,通过编译生成了.class文件,这个文件里面记录了我们这个类的原始信息,jvm通过读取这个文件,生成对应的一个Class类型的实例。
Class部分源码
public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement, OfField<Class<?>>, Constable {
private static native void registerNatives();
private Class(ClassLoader loader, Class<?> arrayComponentType) {
this.classLoader = loader;
this.componentType = arrayComponentType;
}
public String toString() {
String kind = this.isInterface() ? "interface " : (this.isPrimitive() ? "" : "class ");
return kind.concat(this.getName());
}
可以看见Class类型的构造方法是私有的,所以我们不能new出一个Class对象。public final class Class 可以这样理解,每当我们需要加载一个类的时候,JVM会为其创建一个Class类型的class,两者有高度的关联关系。 前面这句话有点绕,但是请仔细理解阅读。相当于我们加入要加载Person类,JVM会new 出来一个Class类型的实例,这个实例与Person是关联的。
例子: 比如我们加载一个 Person类 ,可以看成下面这种形式 Class cls = new Class(Person);
如何获取一个class的Class实例
下面代码给出了4种方法。
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException {
Class class1 = String.class;
Class class2 = "helloWorld".getClass();
Class class3 = Class.forName("java.lang.String");
Class class4 = ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
}
}
注意:Class类型的实例时单例存储的,所以上面代码里面的class1 - class4 指向的是同一个Class类型的实例。
Class类的使用
java.lang.Class类提供了大量的方法获取该Class对象所对应的类的信息,比如字段,方法,构造方法,注解等
获取属性
- Field getField(name):根据字段名获取某个public的field,包括继承的field
- Field getDeclaredField(name):根据字段名获取当前类的某个field,不包括继承的field
- Field[] getFields():获取所有public的field,包括继承的field)
- Field[] getDeclaredFields():获取当前类的所有field,不包括继承的field
代码案例: (可以自己执行一下,就不讲解代码了)
public class Test3 {
public static void main(String[] args) {
Class<Book> bookClass = Book.class;
Field[] declaredFields = bookClass.getDeclaredFields();
Field[] fields = bookClass.getFields();
for (Field f : declaredFields) {
System.out.println(f);
System.out.println(f.getName());
System.out.println(f.getType());
System.out.println(f.getModifiers());
}
System.out.println("--------------------");
for (Field f : fields) {
System.out.println(f);
System.out.println(f.getName());
System.out.println(f.getType());
System.out.println(f.getModifiers());
}
}
}
class Book{
private String name;
public int id;
public String author;
}
获取调用方法
我们还能从Class的实例种获取Method,Method记录了类中的方法,Class提供了下面常见的方法来获取Method
- Method getMethod(name, Class…):获取某个指定name的public的Method,包括继承的方法
- Method getDeclaredMethod(name, Class…):获取当前类指定name的某个Method,不包括继承的方法
- Method[] getMethods():获取所有public的Method,包括继承的方法
- Method[] getDeclaredMethods():获取当前类的所有Method,不包括继承的方法
一个Method对象包含一个方法的所有信息:
- getName():返回方法名称;
- getReturnType():返回方法返回值类型;
- getParameterTypes():返回方法的参数类型;
- getModifiers():返回方法的修饰符。
实例代码:
public class Test4 {
public static void main(String[] args) {
Class<String> stringClass = String.class;
Method[] methods = stringClass.getMethods();
for (Method method : methods) {
String name = method.getName();
System.out.println(name);
int modifiers = method.getModifiers();
System.out.println(Modifier.toString(modifiers));
Class<?> returnType = method.getReturnType();
System.out.println(returnType.toString());
Class<?>[] parameterTypes = method.getParameterTypes();
System.out.println(Arrays.toString(parameterTypes));
}
}
}
获取注解信息
- public Annotation[ ] getAnnotations( ):返回所有注释。
- public Annotation[ ] getDeclaredAnnotations( ):获取所有注释,忽略继承。
- public <T extends Annotation> T getAnnotation(Class<T> annotationClass):返回指定类型的注解,如果没有则返回null
注意:通过反射能够读取到的注解必须要注解的生命周期是RetentionPolicy.RUNTIME
详细的可以运行这个案例代码
public class Test5 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Class<Human> humanClass = Human.class;
Sex annotation = humanClass.getAnnotation(Sex.class);
// 通过注解给sex注入一个值
Human human = new Human();
Field sex = humanClass.getField("sex");
sex.set(human, annotation.value());
System.out.println(human.sex);
}
}
@Sex
class Human{
String name;
public String sex;
}
@Retention(RetentionPolicy.RUNTIME)
@interface Sex{
String value() default "male";
}
获取构造方法
- public Constructor<T> getDeclaredConstructor(Class<?>…parameterTypes):获取某个构造方法
- public Constructor<?>[ ] getDeclaredConstructors( ):获取所有构造方法
- public Constructor<T> getConstructor(Class<?>…parameterTypes):获取某个public构造方法
- public Constructor<?>[ ] getConstructors( ):获取所有public构造方法。
这个地方代码比较简单,就给一个简单的代码案例
public class Test6 {
public static void main(String[] args) throws Exception {
Constructor cons1 = String.class.getConstructor(int.class);
// 调用构造方法:
String instance = (String) cons1.newInstance(123);
System.out.println(instance);
}
}
反射的应用
反射可以说是框架的灵魂,通过反射,我们得以在运行时生成,操作对象,或者调用对象的任意一个方法,这给了我们极大的自由度。可以让我们写出通用的代码。动态代理就利用了反射技术,这部分待日后补充(补充一些案例,展现反射的强大之处,其实上面解释注解的时候已经通过注解在运行时给类的属性set了一个值)
TODO
- 补充反射的具体应用