反射是个什么鬼?

前言

看到这个题目,你可能要说这个题目是个什么鬼,但是我认为你更应该说反射是个什么鬼。其实反射也不是一个什么鬼,反射其实是和java中的类的加载机制息息相关的,不过这里我们就不深入底层。先直接简单的了解一下Java中的反射机制。先是会用,然后再去理解一下,为什么可以这么用。

对象是类?

其实很久很久之前,我们就提到了反射,并且介绍了一点反射的用法。比如说我们将同步监视器的时候说可以使用类名.class作为类唯一的同步监视器。就在我们说到使用fastjson进对象进行深拷贝之后恢复这个对象的时候,也需要使用类型.class作为参数传入函数。我们发现函数的参数的类型是Class<?> clazz。这里的<?>就先不说了,可以留着以后专门来说。这个Class肯定是一个类名,那么类名.class是一个对象??这个对象是干嘛用的?这是我们的疑问。那么我们就先从Class类的官方文档开始看起。

Class对象

public final class Class<T>
extends Object
implements Serializable, GenericDeclaration, Type, AnnotatedElement

T - 由此类对象建模的类的类型。 例如, String.class的类型是Class<String> 。 如果正在建模的类是未知的,请使用Class<?> 。
  • Class类的对象表示正在运行的Java应用程序中的类和接口。 枚举是一种类,一个注释是一种界面。 每个数组也属于一个反映为对象的类,该对象由具有相同元素类型和维数的所有数组共享。 原始Java类型( booleanbytecharshortintlongfloatdouble ),和关键字void也表示为对象。

上面说了Class类的对象表示一个类?对象和类是怎么表示的?难道是类也可以看成是一个对象吗?要这么说还真的是可以的,毕竟我们创建类的时候也是按照一定的规则写的,比如各种属性,各种函数。不过,类可以看成是一个对象这个说法是错误的,可以说一个类可以对应是一个对象。而这个对象不是其他的,就是类名.class

下面我们创建了一个对象

class Student {
    private String name;
    private int id;

    public Student() {

    }

    public Student(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
}

其中我们使用类名.class的方式就可以获取对应这个类的Class对象。

Class<Student> studentClass = Student.class;

如果我们需要的不是对象,而是基本数据类型,是不是我们就要使用他们的包装类呢?这里不是的。上面的文档也说了我们可以使用int.classdouble.class甚至你还可以使用void.class。这简直是太神奇了!

不过现在我们需要探讨的这个问题是,对象是不是唯一的呢?获取的方式是不是只有通过类.class这一种方式呢?

其实我们也可以通过对象.getClass()的方式,得到这个类的Class对象。不同的对象是不是一样的呢?或者说这两种返回Class对象的方式返回的是不是一样的呢?

public class Demo1 {
    public static void main(String[] args) {
        Class<Student> studentClass = Student.class;
        Student student = new Student();
        Student student1 = new Student();
        System.out.println(student.getClass() == studentClass); // true
        System.out.println(student.getClass() == student1.getClass()); // true

        System.out.println("123".getClass() == String.class); // true
        System.out.println(Integer.class == int.class); // false
    }
}

我们发现除了最后的一个是输出false其余的都是输出true,那么我们也就可以得知,每一个类对应的对象只有那唯一的一个,而且int.classInteger.class是不一样的。包装类也是类,而int则是基本的数据类型,是不可以划等号的。那么这个类有什么作用呢?那就是要看这个类有什么样的方法。

<U> 类<? extends U> asSubclass(类<U> clazz) 
类这个 类对象来表示由指定的类对象表示的类的子类。  
T cast(Object obj) 
施放一个目的是通过本表示的类或接口 类对象。  
boolean desiredAssertionStatus() 
如果要在调用此方法时初始化该类,则返回将分配给此类的断言状态。  
static 类<?> forName(String className) 
返回与给定字符串名称的类或接口相关联的 类对象。  
static 类<?> forName(String name, boolean initialize, ClassLoader loader) 
使用给定的类加载器返回与给定字符串名称的类或接口相关联的 类对象。  
AnnotatedType[] getAnnotatedInterfaces() 
返回一个 AnnotatedType对象的数组, AnnotatedType使用类型指定由此 AnnotatedType对象表示的实体的超级 类 。  
AnnotatedType getAnnotatedSuperclass() 
返回一个 AnnotatedType对象,该对象表示使用类型来指定由此 类对象表示的实体的 类类。  
<A extends Annotation>
A getAnnotation(类<A> annotationClass) 
返回该元素的,如果这样的注释 ,否则返回null指定类型的注释。  
Annotation[] getAnnotations() 
返回此元素上 存在的注释。  
<A extends Annotation>
A[] getAnnotationsByType(类<A> annotationClass) 
返回与此元素相关 联的注释 。  
String getCanonicalName() 
返回由Java语言规范定义的基础类的规范名称。  
类<?>[] getClasses() 
返回包含一个数组 类表示所有的公共类和由此表示的类的成员接口的对象 类对象。  
ClassLoader getClassLoader() 
返回类的类加载器。  
类<?> getComponentType() 
返回 类数组的组件类型的Class。  
Constructor<T> getConstructor(类<?>... parameterTypes) 
返回一个 Constructor对象,该对象反映 Constructor对象表示的类的指定的公共 类函数。  
Constructor<?>[] getConstructors() 
返回包含一个数组 Constructor对象反射由此表示的类的所有公共构造 类对象。  
<A extends Annotation>
A getDeclaredAnnotation(类<A> annotationClass) 
如果这样的注释 直接存在 ,则返回指定类型的元素注释,否则返回null。  
Annotation[] getDeclaredAnnotations() 
返回 直接存在于此元素上的注释。  
<A extends Annotation>
A[] getDeclaredAnnotationsByType(类<A> annotationClass) 
如果此类注释 直接存在或 间接存在,则返回该元素的注释(指定类型)。  
类<?>[] getDeclaredClasses() 
返回一个反映所有被这个 类对象表示的类的成员声明的类和 类对象的数组。  
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) 
返回一个 Constructor对象,该对象反映 Constructor对象表示的类或接口的指定 类函数。  
Constructor<?>[] getDeclaredConstructors() 
返回一个反映 Constructor对象表示的类声明的所有 Constructor对象的数组 类 。  
Field getDeclaredField(String name) 
返回一个 Field对象,它反映此表示的类或接口的指定已声明字段 类对象。  
Field[] getDeclaredFields() 
返回的数组 Field对象反映此表示的类或接口声明的所有字段 类对象。  
方法 getDeclaredMethod(String name, 类<?>... parameterTypes) 
返回一个 方法对象,它反映此表示的类或接口的指定声明的方法 类对象。  
方法[] getDeclaredMethods() 
返回包含一个数组 方法对象反射的类或接口的所有声明的方法,通过此表示 类对象,包括公共,保护,默认(包)访问和私有方法,但不包括继承的方法。  
类<?> getDeclaringClass() 
如果由此 类对象表示的类或接口是另一个类的成员,则返回表示其声明的类的 类对象。  
类<?> getEnclosingClass() 
返回底层类的即时封闭类。  
Constructor<?> getEnclosingConstructor() 
如果此类对象表示构造函数中的本地或匿名类,则返回表示底层类的立即封闭构造函数的Constructor对象。  
方法 getEnclosingMethod() 
如果此类对象表示方法中的本地或匿名类,则返回表示基础类的即时封闭方法的方法对象。  
T[] getEnumConstants() 
返回此枚举类的元素,如果此Class对象不表示枚举类型,则返回null。  
Field getField(String name) 
返回一个 Field对象,它反映此表示的类或接口的指定公共成员字段 类对象。  
Field[] getFields() 
返回包含一个数组 Field对象反射由此表示的类或接口的所有可访问的公共字段 类对象。  
Type[] getGenericInterfaces() 
返回 Type表示通过由该对象所表示的类或接口直接实现的接口秒。  
Type getGenericSuperclass() 
返回 Type表示此所表示的实体(类,接口,基本类型或void)的直接超类 类 。  
类<?>[] getInterfaces() 
确定由该对象表示的类或接口实现的接口。  
方法 getMethod(String name, 类<?>... parameterTypes) 
返回一个 方法对象,它反映此表示的类或接口的指定公共成员方法 类对象。  
方法[] getMethods() 
返回包含一个数组 方法对象反射由此表示的类或接口的所有公共方法 类对象,包括那些由类或接口和那些从超类和超接口继承的声明。  
int getModifiers() 
返回此类或接口的Java语言修饰符,以整数编码。  
String getName() 
返回由 类对象表示的实体(类,接口,数组类,原始类型或空白)的名称,作为 String 。  
软件包 getPackage() 
获取此类的包。  
ProtectionDomain getProtectionDomain() 
返回 ProtectionDomain 。  
URL getResource(String name) 
查找具有给定名称的资源。  
InputStream getResourceAsStream(String name) 
查找具有给定名称的资源。  
Object[] getSigners() 
获得这个类的签名者。  
String getSimpleName() 
返回源代码中给出的基础类的简单名称。  
类<? super T> getSuperclass() 
返回 类表示此所表示的实体(类,接口,基本类型或void)的超类 类 。  
String getTypeName() 
为此类型的名称返回一个内容丰富的字符串。  
TypeVariable<类<T>>[] getTypeParameters() 
返回一个 TypeVariable对象的数组,它们以声明顺序表示由此 GenericDeclaration对象表示的通用声明声明的类型变量。  
boolean isAnnotation() 
如果此 类对象表示注释类型,则返回true。  
boolean isAnnotationPresent(类<? extends Annotation> annotationClass) 
如果此元素上 存在指定类型的注释,则返回true,否则返回false。  
boolean isAnonymousClass() 
返回 true当且仅当基础类是匿名类时。  
boolean isArray() 
确定此 类对象是否表示数组类。  
boolean isAssignableFrom(类<?> cls) 
确定由此 类对象表示的类或接口是否与由指定的Class 类表示的类或接口相同或是超类或 类接口。  
boolean isEnum() 
当且仅当该类在源代码中被声明为枚举时才返回true。  
boolean isInstance(Object obj) 
确定指定的Object是否与此 Object表示的对象分配 类 。  
boolean isInterface() 
确定指定 类对象表示接口类型。  
boolean isLocalClass() 
返回 true当且仅当基础类是本地类时。  
boolean isMemberClass() 
返回 true当且仅当基础类是成员类时。  
boolean isPrimitive() 
确定指定 类对象表示一个基本类型。  
boolean isSynthetic() 
如果这个类是一个合成类,返回true ; 返回false其他。  
T newInstance() 
创建由此 类对象表示的类的新实例。  
String toGenericString() 
返回描述此 类的字符串,包括有关修饰符和类型参数的信息。  
String toString() 
将对象转换为字符串。  

构造器

我们发现方法多的不得了,但是每个方法似乎都是非常容易的理解的。比如说我们可以通过Class对象来新建类的对象。

@Test
public void test() throws IllegalAccessException, InstantiationException {
    Class<Student> studentClass = Student.class;
    Student student = studentClass.newInstance();
    System.out.println(student);
}

只要使用newInstance方法就可以new一个这个类的对象。不过此时调用的是空参的构造器。不过IDE告诉我们这个方法已经从JDK9开始的时候就被废弃掉了。

@deprecated This method propagates any exception thrown by the
nullary constructor, including a checked exception.  Use of
this method effectively bypasses the compile-time exception
checking that would otherwise be performed by the compiler.
The {@link
java.lang.reflect.Constructor#newInstance(java.lang.Object...)
Constructor.newInstance} method avoids this problem by wrapping
any exception thrown by the constructor in a (checked) {@link
java.lang.reflect.InvocationTargetException}.

反正上面就是说这个方法是怎么怎么有异常。推荐我们使用其他的方式替代他,不过我们现在正有这种想法,如果我们想要调用含有参数的构造函数,该怎么办呢?

可以使用上面提供的getConstructor()方法。这个方法返回的是你想要的构造函数,比如说这里我们想要的是第一个参数是String,第二个参数是int

@Test
public void test1() throws Exception {
    Class<Student> studentClass = Student.class;
    Constructor<Student> constructors = studentClass.getConstructor(String.class, int.class);

    Student sher = constructors.newInstance("sher", 10086);
    System.out.println(sher);
}

上面使用的方法会抛出很多的异常,所以我们直接就throws Exception,不一一的try catch了。在我们使用了getConstructor方法之后,返回的是一个Constructor<Student>代表的是Student类的构造器,此时我们可以使用newInstance的方式来new一个对象,这也是官方文档中推荐我们使用的方式。

如果我们想要获取的是一个private的构造器通过上面的方式是否可以获取呢?

@Test
public void test2() throws Exception {
    Class<Student> studentClass = Student.class;
    Constructor<Student> constructor = studentClass.getConstructor(String.class);
    Student student = constructor.newInstance("sher");
    System.out.println(student);
}

我们发现代码报了一个java.lang.NoSuchMethodException的错误。也就是说没有找到这个构造器,因为这个构造器我们声明是private的。那么是不是就没办法了呢?当然不是,我们可以通过getDeclaredConstructor方法获取到所有的构造器,无论是private public还是其他的,都是可以获取到的。

使用getDeclaredConstructor之后我们发现再次抛出了一个异常。java.lang.IllegalAccessException,我们想要使用一个私有的构造器,但是这是不合法的,所以编译器不允许我们这么干。但是,我们也可以让编译器闭嘴,只有给他一个true就行了。调用方法之前使用constructor.setAccessible(true);强制的将该方法变成可以访问的,这时候问题就解决了。

@Test
public void test2() throws Exception {
    Class<Student> studentClass = Student.class;
    Constructor<Student> constructor = studentClass.getDeclaredConstructor(String.class);
    constructor.setAccessible(true);
    Student student = constructor.newInstance("sher");
    System.out.println(student);
}

其实constructor.setAccessible(true)不仅仅是使我们可以使用私有的方法,还有一个比较大的作用。当我们需要多次执行一个方法的时候,即使我们知道这个方法是public的,每次执行都会进行检查,而且这个检查非常的耗时间。此时我们就可以使用setAccessible(true)的方式关闭这个访问限定符的检查,从而提高效率。

方法

上面说到的是构造器,我们也可以使用相似的方式得到方法然后调用。

@Test
public void test2() throws Exception {
    Class<Student> studentClass = Student.class;
    Constructor<Student> constructor = studentClass.getDeclaredConstructor(String.class);
    constructor.setAccessible(true);
    Student student = constructor.newInstance("sher");
    System.out.println(student);

    Method setName = studentClass.getMethod("setName", String.class);
    setName.invoke(student, "hony");

    Method method = studentClass.getMethod("getName");
    Object invoke = method.invoke(student);

    System.out.println(invoke);
}

此时调用方法的时候就不是使用newInstance,而是使用invoke方法。其中第一个参数是需要调用的对象。后面就是函数的参数。如果没有的话就不用填。可是为什么需要提供函数的调用的对象呢?因为我们这里我们获取的是非静态的方法需要对象才可以调用。如果我们获取的是静态的方法呢?此时没有调用的对象,我们直接将第一个参数赋值为null就行了。

@Test
public void test3() throws Exception {
    Class<Student> studentClass = Student.class;
    Method say = studentClass.getMethod("say", String.class);
    say.invoke(null, "hello world");
}

public static void say(String string) {
    System.out.println(string);
}

如果方法是私有的了的话,我们可以使用和上面的相同的方式来调用方法。使用getDeclaredMethod,并且使用setAccessible(true)就可以了。

我们看到Class类中的方法中,我们介绍的函数都有一个对应的加上s的函数。很容易理解使用getConstructors就是得到这个类的所有的构造器(public可以访问的)。如果是使用getDeclaredConstructors就是真正的返回所有的声明的方法。返回的类型是一个数组,我们可以使用增强的for循环来处理这个数组。

@Test
public void test4() throws Exception {
    Class<Student> studentClass = Student.class;
    Method[] declaredMethods = studentClass.getDeclaredMethods();
    for (Method method : declaredMethods) {
        System.out.println(method.getName());
    }
}

属性

类中除了方法,构造器之外,最常用的就是属性了。其实学会了一个之后学习其他的都是非常的轻松的,所以这里的属性其实是不用怎么说都应该很容易的明白的。

@Test
public void test5() throws Exception {
    Student sher = new Student("sher", 11);
    Class<?> studentClass = sher.getClass();
    Field name = studentClass.getDeclaredField("name");
    name.setAccessible(true);
    name.set(sher, "hony");
    System.out.println(sher);

    Field staticField = studentClass.getDeclaredField("staticField");
    staticField.set(null, "i am reflect");
    System.out.println(Student.staticField);
}

注解

除此之外,我们还可以获取某一个元素中注解。不仅仅是类中的注解,我们可以对Field Constructor Method对象调用getDeclaredAnnotations

@Test
public void test6() throws Exception {
    Class<Student> studentClass = Student.class;
    Annotation[] declaredAnnotations = studentClass.getDeclaredAnnotations();
    System.out.println(declaredAnnotations.length);

    for (Annotation annotation : declaredAnnotations) {
        System.out.println(annotation);
    }
}

这种方式只可以得到类上面的注解,对于类中的方法的注解通过这种方法是得不到的。需要注意的是此时Dwclared的含义发生了变化,因为注解是没有私有的这一个说法的。getAnnotations是获取所有的注解,包括自己声明的注解和继承的注解,但是使用getDeclaredAnnotaions就是获取自己声明的所有的注解。

我们也可以获取特定的注解,方式和上面是差不多的。

这里关于注解就不多讲了。其实使用反射来解析注解是一个非常重要的内容,但是放在这儿讲是非常的不合适的。

总结

上面是极其极其简单的介绍了一下反射的使用,其实反射要讲的东西复杂的很,而且多得很,但是现在这个阶段是不合适的,所以我们这里只是简单的介绍一下,将来是肯定要深入了解反射这个东西的机会的。


一枚小菜鸡