Java源码分析——Class类、ClassLoader类解析(二) 类的识别、Modifier类、

2020-09-29 17:45发布

Java源码分析——Class类、ClassLoader类解析(二) 类的识别、Modifier类、TypeVariable、GenericDeclaration接口


    在类的加载与实例化的时候,如何识别类、接口、注解以及数组是个值得思考的问题,不仅是这些常用的引用类,还包括类、接口等的public、private、defalut、static等修饰符,以及识别一个泛型类或者接口。
    在java中,所有的类型判断都是java本身定义了许多native本地的方法来从jvm中判断目标类的类型,来判断它是非引用类型还是类、接口或者其它。先从简单的获取类的完全限定名开始,当我们获取到Class类的对象时,打印出来会是对应类的完全限定名:

System.out.println(Kt.class);
//输出为:class test.Kt

类(广泛意义)的种类识别

    事实上这里重写了Object类的toString方法,重写的toString方法里面有两个native方法,判断是否属于类或者接口还是其它是通过native方法来判断的,下面两个是用来用来判断是类还是接口或者是非引用类型的自动装箱类型:

public String toString() {
        return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
            + getName();
    }

    判断其类型以及修饰符的native方法都是is+判断的类型或者修饰符,下文若遇见则不再阐述,其中isPrimitive()判断的装箱类型有如下9类:

boolean.class = Boolean.TYPE;
char.class = Character.TYPE;
byte.class = Byte.TYPE;
short.class = Short.TYPE;
int.class = Integer.TYPE;
long.class = Long.TYPE;
float.class = Float.TYPE;
double.class = Double.TYPE;
void.class = Void.TYPE;

Modifier类

    因为类或者接口的修饰符数量不定,在java中专门定义了Modifier修饰类,来帮忙判断修饰符,该类直接继承自Object类。通过该类判断的原理是有这几步,对类修饰符来说:

  1. 在Modifier修饰类里定义每个修饰符都有特定的的数字序列,在jvm会有同样有一样的数字序列定义;

  2. jvm在加载该类时,会遍历每个修饰符,将每个修饰符连续都做与操作,得到一个数字序列;

  3. 将需要判断的修饰符通过与操作合并为一个数字序列;

  4. 取出jvm中的数字序列与3式中合并的数字序列做出与操作,留下只与需要判断的修饰符有关的数字序列,也就是得到2式中的序列;

  5. 让剩余的数字序列与每个修饰符特定的序列做与操作,大于0则表明含有这个修饰符,否则则没有。

    可以看得出来,其实3式,4式可以去掉,只需要1、2、5步就行了,通过上面的方法,下面用类修饰符的判定来作为例子。在Modifier类中,待判定的修饰符序列如下:

 private static final int CLASS_MODIFIERS =
        Modifier.PUBLIC         | Modifier.PROTECTED    | Modifier.PRIVATE |
        Modifier.ABSTRACT       | Modifier.STATIC       | Modifier.FINAL   |
        Modifier.STRICT;

    可以看到,集合了所有的类修饰符,这里就有个疑问了,为什么可以让所有的类别进行或操作后,再让其与每个修饰符序列进行与操作后就能得出是否含有该修饰符呢?其实转换成二进制序列的每个位都代表着一个修饰符,也就是每个修饰符都占据着二进制的一个位。这是什么意思呢?假如public的二进制序列是01,private的二进制序列是10,那么它们进行或操作后就是11,这个二进制序列是在jvm中进行的,判断有无对应的修饰符,将或操作后的二进制序列与要判断的修饰符序列做与操作就可以了,01&11=01>0,所以含有public修饰符,验证代码如下:

public abstract class Cat {
}

public class Test {
    public static void main(String args[]) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        System.out.println("类的总修饰符二进制序列");
        System.out.println(Integer.toBinaryString(Modifier.classModifiers()));
        System.out.println("Cat类在jvm中的二进制修饰符序列");
        System.out.println("类的修饰符"+Integer.toBinaryString(Cat.class.getModifiers()));
        System.out.println("Cat类在jvm中的二进制修饰符序列与类的总修饰符序列取与操作");
        int bin=Cat.class.getModifiers()&Modifier.classModifiers();
        System.out.println(Integer.toBinaryString(bin));
        System.out.println("打印");
        System.out.println(Modifier.toString(bin));
        System.out.println("public 序列与abstract序列取与操作");
        System.out.println(Integer.toBinaryString(Modifier.ABSTRACT|Modifier.PUBLIC));
    }
}

在这里插入图片描述

    可以看到,公共抽象类在jvm中的修饰符序列和public 序列与abstract序列取与操作后的二进制序列是相等的,证明了上述说法是正确的。其中。Modifier的toString操作实现了每个修饰符的判定操作:

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 "";
    }

    不仅类修饰符是这样的,构造方法修饰符、方法修饰符、接口修饰符以及属性修饰符都是基于这样的原理。

TypeVariable与GenericDeclaration接口

    在java中TypeVariable这个接口是用来干嘛的呢?如字面意义,类型变量,也就是来描述狭义上的泛型的接口,该接口继承自GenericDeclaration接口,在官方文档中,GenericDeclaration接口是声明泛型的所有实体的公共接口。而GenericDeclaration接口又继承自AnnotatedElement接口,该接口是获取被注解的类、方法、字段的。在GenericDeclaration接口里定义了一个行为:

public interface GenericDeclaration extends AnnotatedElement {
    public TypeVariable<?>[] getTypeParameters();
}

    这个行为旨在获取其对应类的所有的类型变量的。GenericDeclaration接口其直接实现子类:java.lang.reflect子包中的:Class,Method,Constructor,所以,这三个对应的类上、方法上、构造器上可以声明类型变量,GenericDeclaration的直接实现子类没有Field类,所以属性上面不能定义类型变量,但是可以实现泛型。如返回返回类上的类型变量,代码如下:

public class Test<T extends Cat&Catch,V> {
    public static void main(String args[]) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        TypeVariable<?>[] typeVariable=Test.class.getTypeParameters();
        for (TypeVariable d:typeVariable) {
            System.out.println(d.getName());
        }
    }
}

//结果为返回了两个类型变量:T  V

    上述的行为是在GenericDeclRepository仓库中实现的:

public TypeVariable<?>[] getTypeParameters() {
        TypeVariable[] var1 = this.typeParams;
        if (var1 == null) {
            FormalTypeParameter[] var2 = ((Signature)this.getTree()).getFormalTypeParameters();
            var1 = new TypeVariable[var2.length];

            for(int var3 = 0; var3 < var2.length; ++var3) {
                Reifier var4 = this.getReifier();
                var2[var3].accept(var4);
                var1[var3] = (TypeVariable)var4.getResult();
            }
            this.typeParams = var1;
        }
        return (TypeVariable[])var1.clone();
    }

    接下来讨论TypeVariable接口的行为:

public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
   //获得每个类型变量的上限,也就是返回每个类型继承了什么类或者接口
    Type[] getBounds();
   //返回对当前类的描述
    D getGenericDeclaration();
    //获得这个类型变量在声明时候的名称,即上面测试代码的T与V
    String getName();
  	//返回注解的上限,作用与getBounds差不多
     AnnotatedType[] getAnnotatedBounds();
}

    其中TypeVariable接口的D类型变量是继承自GenericDeclaration接口的。TypeVariable接口的直接实现类是TypeVariableImpl,在里面实现了这几种方法。切记,类型变量没有下限,也就是不能使用super关键字。因为如果使用super,那么类型变量是其super类的子类,传进来的不一定有着共同的方法,子类的范围往往比父类大,这就违反了泛型具有共性的特征。继续用上次的用例来描述下TypeVariable接口的几个方法:

public class Test<T extends Cat&Catch,V> {
    public static void main(String args[]) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        TypeVariable<?>[] typeVariable=Test.class.getTypeParameters();
        for (TypeVariable d:typeVariable) {
            int size=d.getBounds().length;
            System.out.println("类型变量的上限类,接口名:");
            for (int i=0;i<size;i++){
               System.out.println(d.getBounds()[i]);
            }
        }
    }
}

在这里插入图片描述
    从结果看,程序打印出了两个类型变量的上限类或者接口的完全限定名,如果没有继承,默认是Object类。

    在Class类中,提供了一个方法用来打印关于类的所有的信息:

public String toGenericString() {
        if (isPrimitive()) {
            return toString();
        } else {
            StringBuilder sb = new StringBuilder();

            // Class modifiers are a superset of interface modifiers
            int modifiers = getModifiers() & Modifier.classModifiers();
            if (modifiers != 0) {
                sb.append(Modifier.toString(modifiers));
                sb.append(' ');
            }
            if (isAnnotation()) {
                sb.append('@');
            }
            if (isInterface()) { // Note: all annotation types are interfaces
                sb.append("interface");
            } else {
                if (isEnum())
                    sb.append("enum");
                else
                    sb.append("class");
            }
            sb.append(' ');
            sb.append(getName());
            //找类型变量
            TypeVariable<?>[] typeparms = getTypeParameters();
            if (typeparms.length > 0) {
                boolean first = true;
                sb.append('<');
                for(TypeVariable<?> typeparm: typeparms) {
                    if (!first)
                        sb.append(',');
                    sb.append(typeparm.getTypeName());
                    first = false;
                }
                sb.append('>');
            }
            return sb.toString();
        }
    }

    继续用Test类测试:

public final class Test<T extends Cat&Catch,V> {
    public static void main(String args[]) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        System.out.println(Test.class.toGenericString());
    }
}

//结果为:public final class test.Test<T,V>

    可见,把一个类的全部修饰符以及类型变量都打印出来了,而其它接口、构造函数、方法等成分的属性识别都是与类的识别原理一样,实现也一样,只不过面对的对象不同而已。

作者:suye233

链接:https://blog.csdn.net/hackersuye/article/details/83418293

来源:CSDN
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。