Java源码之Modifier

1,076 阅读7分钟

Javadoc解释

The Modifier class provides {@code static} methods and constants to decode class and member access modifiers.The sets of modifiers are represented as integers with distinct bit positions representing different modifiers. The values for the constants representing the modifiers are taken from the tables in sections 4.1, 4.4, 4.5, and 4.7 of The Java™ Virtual Machine Specification.

Modifier这个类针对解码之后的类和成员访问修饰符提供了一些静态方法和常量。这些修饰符集合用整数表示,用不同的位图上的位置来表示不同的修饰符。修饰符的值是用从Java虚拟机规范当中的4.1,4.4,4.5和4.7章节中获取的。

所谓修饰符

修饰符这个词,想必各位在学习Java基础的时候就接触过了,无论是各种大学教程书还是其他各类社会书籍都会介绍。在我们平时使用最多的莫过于: public、private等等,这些属于访问修饰符,然后还有诸如static、final、synchronized等等。修饰符就是用来表示属性、方法等一些特性的关键词。 在Modifier当中一共定义了12修饰符,当然这个其实也是由JVM规范所决定的。

修饰符关键词十六进制表示二进制表示修饰符描述
public0x00000001000000000001公共的访问修饰符,都可以访问
private0x00000002000000000010私有的访问修饰符,只有当前类可以访问
protected0x00000004000000000100受保护的访问修饰符,继承环境中访问
static0x00000008000000001000静态的修饰符,静态属性、方法、代码块等
final0x00000010000000010000终态的修饰符,修饰的对象不能被修改、重写、继承等操作
synchronized0x00000020000000100000同步的修饰符,用户多线程环境中的原子同步操作
volatile0x00000040000001000000也属于同步语义的修饰符,保证线程之间数据的可见性(但不保证线程安全)
transient0x00000080000010000000序列化的修饰符,被修饰的属性不会被加入序列化操作
native0x00000100000100000000本地方法的修饰符,其实现有C/C++来实现
interface0x00000200001000000000糟糕,至今不懂
abstract0x00000400010000000000抽象属性的修饰符,用来标明是抽象属性
strictfp0x00000800100000000000浮点数操作的修饰符,用来统一不同平台间的规范,在浮点操作中严格遵守IEEE-754规范。

在Modifier这个类里面,提供了许多的方法来判断这个这个方法当中有哪些修饰符,例如:

/**
 * Return {@code true} if the integer argument includes the
 * {@code public} modifier, {@code false} otherwise.
 *
 * @param   mod a set of modifiers
 * @return {@code true} if {@code mod} includes the
 * {@code public} modifier; {@code false} otherwise.
 */
 public static boolean isPublic(int mod) {
    return (mod & PUBLIC) != 0;
 }
 
/**
 * Return {@code true} if the integer argument includes the
 * {@code private} modifier, {@code false} otherwise.
 *
 * @param   mod a set of modifiers
 * @return {@code true} if {@code mod} includes the
 * {@code private} modifier; {@code false} otherwise.
 */
 public static boolean isPrivate(int mod) {
    return (mod & PRIVATE) != 0;
 }
 
 ...

可以发现,这些方法大同小异,都是有一个mod的位向量与修饰符的值进行与运算。

位向量

这里就值得说道一下位向量。每一个Method都会维护一个位向量modifiers:

...
private int                 modifiers;
...
Method(Class<?> declaringClass,
       String name,
       Class<?>[] parameterTypes,
       Class<?> returnType,
       Class<?>[] checkedExceptions,
       int modifiers,
       int slot,
       String signature,
       byte[] annotations,
       byte[] parameterAnnotations,
       byte[] annotationDefault) {
    this.clazz = declaringClass;
    this.name = name;
    this.parameterTypes = parameterTypes;
    this.returnType = returnType;
    this.exceptionTypes = checkedExceptions;
    this.modifiers = modifiers;// 在构造Method的时候进行设置
    this.slot = slot;
    this.signature = signature;
    this.annotations = annotations;
    this.parameterAnnotations = parameterAnnotations;
    this.annotationDefault = annotationDefault;
}
...
// 并且提供了一个方法获取位向量
@Override
public int getModifiers() {
    return modifiers;
}

疑问: 那么这个位向量有什么作用呢?为什么判断是否具有什么修饰符都需要用位向量来判断呢?另外为什么那些修饰符所表示的值所表示的二进制都是2的幂次方呢?

我们设想一下,假如让我们来设计这个模型,或者怎么设计呢?

小A: 在类的编译加载之后,对方法具有的修饰符用字符串数组存储呗?
小B: 字符串存储太费空间了,咱们也不用定义这样奇怪的值,用1、2、3这种简单的数字表示,用整型的数组呗?
小C: 但是我看这里好像就是一些修饰符组合,看看哪些修饰符占着坑位

瞬间,大家都明白了。没错,字符串数组的方式确实也可以,而且不需要翻译就可以表示,缺点就是费空间。整型的数组虽然节省了空间,但是需要进行翻译表示。而位向量的方式,则是比整型的数组更节省空间。只需要用一个数字就可以表示多种修饰符的组合。

于是就可以拿着位向量与对应的修饰符的值进行&(与)运算,从而得出是否是含有这个修饰符。

这里的位向量的值是在类加载之后,根据JVM的规范计算得出,或者说是由本地方法当中C/C++计算得出。

那么做一下测试吧,就拿Object当中的getClass()方法为例:

/**
 * Returns the runtime class of this {@code Object}. The returned
 * {@code Class} object is the object that is locked by {@code
 * static synchronized} methods of the represented class.
 *
 * <p><b>The actual result type is {@code Class<? extends |X|>}
 * where {@code |X|} is the erasure of the static type of the
 * expression on which {@code getClass} is called.</b> For
 * example, no cast is required in this code fragment:</p>
 *
 * <p>
 * {@code Number n = 0;                             }<br>
 * {@code Class<? extends Number> c = n.getClass(); }
 * </p>
 *
 * @return The {@code Class} object that represents the runtime
 *         class of this object.
 * @jls 15.8.2 Class Literals
 */
public final native Class<?> getClass();

可以看到该方法有public、final、native三个修饰符。根据这三个的修饰符对应的值可以看出二进制的值为:

000100010001;

000100010001 = 2^8 + 2^4 + 2^0 = 256 + 16 + 1 = 273;
即getClass()这个方法的位向量为273。

那么我们来运行一下吧

可以看到,这里的位向量确实是273。

Modifier.toString()

我们先来看看源码

/**
 * Return a string describing the access modifier flags in
 * the specified modifier. For example:
 * <blockquote><pre>
 *    public final synchronized strictfp
 * </pre></blockquote>
 * The modifier names are returned in an order consistent with the
 * suggested modifier orderings given in sections 8.1.1, 8.3.1, 8.4.3, 8.8.3, and 9.1.1 of
 * <cite>The Java&trade; Language Specification</cite>.
 * The full modifier ordering used by this method is:
 * <blockquote> {@code
 * public protected private abstract static final transient
 * volatile synchronized native strictfp
 * interface } </blockquote>
 * The {@code interface} modifier discussed in this class is
 * not a true modifier in the Java language and it appears after
 * all other modifiers listed by this method.  This method may
 * return a string of modifiers that are not valid modifiers of a
 * Java entity; in other words, no checking is done on the
 * possible validity of the combination of modifiers represented
 * by the input.
 *
 * Note that to perform such checking for a known kind of entity,
 * such as a constructor or method, first AND the argument of
 * {@code toString} with the appropriate mask from a method like
 * {@link #constructorModifiers} or {@link #methodModifiers}.
 *
 * @param   mod a set of modifiers
 * @return  a string representation of the set of modifiers
 * represented by {@code mod}
 */
public static String toString(int mod) {
    StringBuilder sb = new StringBuilder();
    int len;

    if ((mod & PUBLIC) != 0)        sb.append("public ");
    if ((mod & PROTECTED) != 0)     sb.append("protected ");
    if ((mod & PRIVATE) != 0)       sb.append("private ");

    /* Canonical order */
    if ((mod & ABSTRACT) != 0)      sb.append("abstract ");
    if ((mod & STATIC) != 0)        sb.append("static ");
    if ((mod & FINAL) != 0)         sb.append("final ");
    if ((mod & TRANSIENT) != 0)     sb.append("transient ");
    if ((mod & VOLATILE) != 0)      sb.append("volatile ");
    if ((mod & SYNCHRONIZED) != 0)  sb.append("synchronized ");
    if ((mod & NATIVE) != 0)        sb.append("native ");
    if ((mod & STRICT) != 0)        sb.append("strictfp ");
    if ((mod & INTERFACE) != 0)     sb.append("interface ");

    if ((len = sb.length()) > 0)    /* trim trailing space */
        return sb.toString().substring(0, len-1);
    return "";
}

根据上面所说的,我们可以很轻松明白这里的实现,其实都是根据位向量和对应的修饰符进行&与运算得出是否存在,然后进行字符串的拼接。之所以单独列出这些,是因为这个方法也表明我们对于修饰符的一个正确的顺序书写。
当然,这里的顺序是官方推荐的一种顺序,或者说是规范。既然是规范,即便是顺序写反了也不会影响,就像我之前对static和final的顺序哪个在前傻傻分不清一样。

即便我们写错顺序了,在Java的规范中依然是按照规范的顺序进行表示的。这个大概是由12个修饰符在位向量所占的位置顺序所决定的。