Java 类机制(1)---- 初识 Class

本文介绍了Java的Class类及其在反射机制中的作用。Class对象代表了运行时的类和接口,它是描述类的类,通过Class对象可以实现动态加载和访问类的私有字段及方法。文章详细讲解了Constructor、Method和Field类,用于描述类的构造函数、方法和字段,以及如何通过它们进行反射操作。
摘要由CSDN通过智能技术生成

前言

不知不觉 2018 已经结束了,而 2019 年的 1 月份也已经到底了,先给各位小伙伴送一句迟到的祝福:祝大家新年快乐。回想起我刚刚接触 Java 的时候,对它的反射机制很是好奇,因为通过它可以访问一些类中的非 public 的属性和方法,当时觉得这真是一个非常厉害的黑科技啊,而当我对 Java 有了更深的理解之后,才发现这一切都归功于 Java 中的一个概念:类。

初探 Class

我们都知 Java 程序是运行在 JVM(Java 虚拟机)上的。而虚拟机处理类是采用动态加载机制的,即用到了某个类才将其加载进入内存中,而 Java 虚拟机内存可以分为 5 个部分:程序计数器虚拟机栈本地方法栈方法区。而 Java 中的类就是被虚拟机加载并解析成 Class 对象放进 方法区 中的。这样做很明显的一个好处是节省虚拟机内存,即只有用到了某个类才加载它。而在类加载的过程又有很多的细节,比如要加载某个类之前为了保证安全先得对其进行验证,看其中的数据是否符合一般的类标准,验证完成之后需要加载其父类,再之后解析类结构,提取其中的字段和方法,之后再初始化其中的静态变量和执行静态代码块… 当然我们这里并不深入讨论这个过程,这篇文章中我们来认识一下描述一个类的类:Class 类。可能小伙伴会问了:什么叫描述类的类?有点拗口。确实有点拗口,我们先来这么看:首先,它是不是一个类?我们来看看源码:

public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
   
	// ...
}

我们可以看到,其被定义为 public final class Class<T>,也就是说它是一个名为 Class 的类。那么它的作用是什么?我们来看看源码中对其的描述:

Instances of the class {
   @code Class} represent classes and interfaces in a running Java application.

翻译过来即为:Class 的实例代表了在一个运行中的 Java 程序中的类和接口。即首先它是一个类,第二它是用来描述类和接口的,那么其就是一个用来描述类的类。从这个角度理解的话这个 Class 类的对象就是用来描述一个类的对象(描述类的类 -> 描述类的对象),这个描述的类是哪个类呢?这就和 Class 类中定义的泛型参数 T 有关了,假设我们现在要创建一个描述 Object 类的引用(注意是引用),我们会怎么写呢?根据上面的说法,我们可以写出这段代码:

Class<Object> objClass;

那么我们已经定义了一个指向一个 Class 对象的引用,我们怎么给它赋值呢?很明显我们需要将一个对应类的类对象赋值给它,既然它是描述 Object 类的引用,那么我们当然要赋给它 Object 类的类对象了:

Class<Object> objClass = Object.class;

其实虚拟机在执行这段代码对应的字节码(Java 代码在编译的时候被翻译成字节码,类似于汇编)的时候,虚拟机看到 Object.class 的时候,就会去加载 Object 类的类对象到方法区中,因为 Object 类是一切 Java 类的父类,因此 Object 类的类对象也是第一个被加载进入虚拟机中的。好了,相信现在得到其他类的类对象对你来说已经没有什么难度了。有些小伙伴可能会问了,我能不能通过 new Class(...) 的形式来创建 Class 对象呢?答案是不能,因为 Class 类中没有访问权限为 public 的构造方法:

/*
 * Private constructor. Only the Java Virtual Machine creates Class objects.
 * This constructor is not used and prevents the default constructor being
 * generated.
 */
private Class(ClassLoader loader) {
   
    // Initialize final field for classLoader.  The initialization value of non-null
    // prevents future JIT optimizations from assuming this final field is null.
    classLoader = loader;
}

可以看到,唯一的构造方法被声明为 private 的,而这个构造方法其实是给类加载器在加载类时创建 Class 对象用的。也就是说通过类加载器能创建 Class 对象,那么我们现在已经知道存在两种途径来获取 Class 对象(理论上来说是一种,因为 类名.class 得到的 Class 对象也是通过类加载器加载类得到的)。那么我们得到了描述某个类的 Class 对象有什么用呢?

Class 与反射

这就要提到前文说过的黑科技了:上文说过通过反射机制可以访问一些类中的私有字段和方法,那么具体的实现手段是什么呢?就是通过 Class 对象!其实,通过反射,我们不仅仅可以访问访问一些类的私有字段和方法,还可以做很多事情,比如说不使用 new 关键字来创建对象,通过另一种方式(method.invoke(args...))来调用类和对象的方法等。在了解这些之前,我们先来看看和 Class 有关的一些类:

Constructor

这个类可以代表某个类的构造方法,我们来看看它的官方解释:

/**
 * {@code Constructor} provides information about, and access to, a single
 * constructor for a class.
 */

即为提供了一个类中某个构造函数的信息、访问权限的类。既然说了可以提供构造函数的访问权限,那么也就是说我们可以通过这个方法构造一个类的对象了吗?答案是肯定的,我们来看看里面的相关方法:

public final class Constructor<T> extends Executable {
   
    // ...
    
    /**
     * 创建对应类的一个对象,并且传入对应构造方法的参数,如果参数是基本数据类型,则会进行自动进行包装,
     * 例如:float 会被包装为 java.lang.Float 类型
     */
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
   
        if (!override) {
   
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
   
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
   
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }
}

我们注意到 Constructor 类继承于 Executable 类 ,这个 Executable 是干什么的呢?来看看源码说明:

/**
 * A shared superclass for the common functionality of {@link Method}
 * and {@link Constructor}.
 *
 * @since 1.8
 */
public abstract class Executable extends AccessibleObject implements Member, GenericDeclaration {
   
	// ...
    /**
     * 获取定义当前对象所代表的成员所在的类
     */
    public abstract Class<?> getDeclaringClass();

    /**
     * 获取当前对象所代表的成员在其类中定义的名称
     */
    public abstract String getName();

    /**
     * 获取当前对象所代表的成员在其类中定义的修饰符 (public、protected、private、static ...)
     */
    public abstract int getModifiers();
    
    /**
     * 获取当前可执行对象(方法)的接受的参数类型数组(Type 类型)
     */
    public abstract TypeVariable<?>[] getTypeParameters();

    /**
     * 返回当前对象所代表的方法接受的参数的类型
     */
    public abstract Class<?>[] getParameterTypes();

    /**
     * 返回当前对象所代表的方法接受的参数的个数
     */
    public int getParameterCount() {
   
        throw new AbstractMethodError();
    }
}

看注释就明白了,这个类是 Constructor 类和 Method 类(之后会讲到)的父类,提供了一些通用的方法来获取对应成员字段在其类中的相关属性信息,并且它是一个抽象类,意味着其只能被继承。同时该类还继承于 AccessibleObject 类,那么我们再来看看 AccessibleObject 类的相关信息:

/**
 * The AccessibleObject class is the base class for Field, Method and
 * Constructor objects.  It provides the ability to flag a reflected
 * object as suppressing default Java language access control checks
 * when it is used.  The access checks--for public, default (package)
 * access, protected, and private members--are performed when Fields,
 * Methods or Constructors are used to set or get fields, to invoke
 * methods, or to create and initialize new instances of classes,
 * respectively.
 */

看注释就知道了,这个类是 FieldMethodConstructor 类的共同父类(间接父类),我们来看看这个类提供了哪些方法:

	
public class AccessibleObject implements AnnotatedElement {
   
    // ...

    /**
     * 获取当前对象所代表的类方法 / 字段的可访问性,true 为可访问,false 为不可访问
     * 这个就是我们上面讲过的通过反射访问某个类的私有字段的黑科技的核心代码
     */
    public void setAccessible(boolean flag) throws SecurityException {
   
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
        setAccessible0(this, flag);
    }
    
    /**
     * 获取当前对象所代表的类属性(Constructor, Method, Field...)上所有修饰其的注解,
     * 在重写的方法的时候同样会获取其父类的对应方法的注解
     * @since 1.5
     */
    public Annotation[] getAnnotations() {
   
        return getDeclaredAnnotations();
    }

    /**
     * 获取当前对象所代表的类属性(Constructor, Method, Field...)上直接定义了的的注解,
     * 在重写方法时,不会获取对应方法从父类方法中继承过来的注解
     * @since 1.5
     */
    public Annotation[] getDeclaredAnnotations()  {
   
        throw new AssertionError("All subclasses should override this method");
    }
}

AccessibleObject 类中提供了更改当前对象所代表的类属性的可访问性和 获取修饰其的注解信息的方法,因为在一个类中,字段(Field)和方法(Method)都有可访问性的概念(public, protected, private),同时字段和方法上面都可以添加一些注解来标识该属性的一些特性,我们常见的 @Override 便是其中一种。

上面我们通过 Constructor 类引出了里面相关类的继承关系,接下来来看看用来描述类中其他的属性(方法、字段)的类(其实之前已经遇见过了):

Method

还是先从官方的解释开始:

/**
 * A {@code Method} provides information about, and access to, a single method
 * on a class or interface.  The reflected method may be a class method
 * or an instance method (including an abstract method).
 */  

Constructor 类似,该类提供了描述类中某一方法的信息、调用类中对应方法的途径等。来看看相关代码:

public final class Method extends Executable {
   
    private Class<?>            clazz; // 方法所属的类
    private int                 slot;
    // This is guaranteed to be interned by the VM in the 1.4
    // reflection implementation
    private String              name; // 方法名
    private Class<?>            returnType; // 方法的返回值类型
    private Class<?>[]          parameterTypes; // 方法接受的参数类型
    private Class<?>[]          exceptionTypes; // 方法抛出的异常类型 (throws 关键字)
    private int                 modifiers; // 方法的修饰符(public, protected, private...)
    
    /**
     * 获取当前对象所代表的类字段所在的类
     */
    @Override
    public Class<?> getDeclaringClass() {
   
        return clazz;
    }

    /**
     * 获取当前对象所代表的方法的方法名
     */
    @Override
    public String getName() {
   
        return name;
    }

    /**
     * 获取当前对象所代表的方法的修饰符
     */
    @Override
    public int getModifiers() {
   
        return modifiers;
    }
    
    /**
     * 获取当前对象所代表的方法的返回值类型
     */
    public Class<?> getReturnType() {
   
        return
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值