(3)Class类的深度解析

在上一个小节,介绍了如何获取一个类的class信息,获取class的修饰符,以及获取类中的成员(字段,方法,构造函数),这一章节,将对部分class中提供的方法进行深度解析。

1.Class.getName()和Class.getCanonicalName()以及Class.getSimpleName()有什么区别?

这里先给出一个例子,先观察这个例子的输出结果,就可以看出区别:

/**
 * @Project: jdk
 * @description:   Class.getName()和Class.getCanonicalName()以及Class.getSimpleName()的测试
 * @author: sunkang
 * @create: 2018-10-04 23:25
 * @ModificationHistory who      when       What
 **/
interface Person{
    void eat();
}
public class TestClassName {
    //这个是一个幂名内部类的实现的一个对象
   static Person person = new Person() {
        @Override
        public void eat() {
            System.out.println("eat something");
        }
    };
   class innerClass {

   }
    public static void main(String[] args) {
       //对于普通类
        Class test= TestClassName.class;
        System.out.println("普通类getName :"+test.getName());
        System.out.println("普通类getCanonicalName :"+test.getCanonicalName());
        System.out.println("普通类getSimpleName :"+test.getSimpleName());

        //对于数组
        Class arrayClass = new String[]{}.getClass();
        System.out.println("数组getName :"+arrayClass.getName()); //[Ljava.lang.String;
        System.out.println("数组getCanonicalName :"+arrayClass.getCanonicalName()); //java.lang.String[]
        System.out.println("数组getSimpleName :"+arrayClass.getSimpleName());//String[]

        //对于内部类
        Class innerClass= innerClass.class;
        System.out.println("内部类getName :"+innerClass.getName()); //这个是外部类+$+外部类
        System.out.println("内部类getCanonicalName :"+innerClass.getCanonicalName()); //外部类+.+外部类
        System.out.println("内部类getSimpleName :"+innerClass.getSimpleName());

        //对于匿名内部类
        Class clazz = person.getClass();
        System.out.println("匿名内部类getName:"+clazz.getName());  //返回com.java.reflect.classes.TestClass$1
        System.out.println("匿名内部类getCanonicalName :"+clazz.getCanonicalName()); //返回null
        System.out.println("匿名内部类getSimpleName :"+clazz.getSimpleName());  //返回空
    }
}

输出结果如下:

普通类getName :com.java.reflect.classes.TestClassName
普通类getCanonicalName :com.java.reflect.classes.TestClassName
普通类getSimpleName :TestClassName
数组getName :[Ljava.lang.String;
数组getCanonicalName :java.lang.String[]
数组getSimpleName :String[]
内部类getName :com.java.reflect.classes.TestClassName$innerClass
内部类getCanonicalName :com.java.reflect.classes.TestClassName.innerClass
内部类getSimpleName :innerClass
匿名内部类getName:com.java.reflect.classes.TestClassName$1
匿名内部类getCanonicalName :null
匿名内部类getSimpleName :

可以发现Class.getSimpleName是得到类的简单信息,重点区分Class.getName()和Class.getCanonicalName()的区别,在普通类中这两个其实是一样的,但是在数组,内部类或者匿名类中输出的结果是不一样的,从输出结构可以看出这两者的区别,特别是匿名类的时候,getName的结果为com.java.reflect.classes.TestClassName$1而getCanonicalName为null

可以查看getCanonicalName的源码可以发现:

    public String getCanonicalName() {
        if (isArray()) { //先判断数组
           //得到数组的单元的getCanonicalName的名称
            String canonicalName = getComponentType().getCanonicalName();
            if (canonicalName != null)
                return canonicalName + "[]"; //最后加上[]
            else
                return null;
        }
        if (isLocalOrAnonymousClass())//如果是匿名内部类,返回null
            return null;
        Class<?> enclosingClass = getEnclosingClass();//这里这剩下内部类和普通类,得到内部类的外部类
        if (enclosingClass == null) { // top level class  //这里说明是普通类
            return getName();//是普通类,getCanonicalName和getName是一样的
        } else {
            String enclosingName = enclosingClass.getCanonicalName(); //外部类的名称信息
            if (enclosingName == null)
                return null;
            return enclosingName + "." + getSimpleName();//如果是内部类,那么信息为外部类的信息+ .+内部类的简单的名称
        }
    }

2.class.getFileds和class.getDeclaredFields的深度解析

先看看下面的例子:

/**
 * @Project: jdk
 * @description:   fileds字段测试
 * @author: sunkang
 * @create: 2018-10-05 14:45
 * @ModificationHistory who      when       What
 **/
public class TestFields  extends  FiledConstants   {
    public  int age;

    public static  String addr;

    private   String email;

    public static void main(String[] args) {
        Class  clazz  =TestFields.class;
        //得到public的字段,包括继承的父类的,接口的,实现逻辑是先加载本地的,然后加载接口的(采用了递归),然后加载父类的(采用了递归)
        Field[]   fields= clazz.getFields();
        for( Field  field : fields){
            System.out.println(" publicField :"+field.toGenericString());
        }
        System.out.println("-----------------------------------");
        //得到本地声明的字段
        Field[]   declaredFields =  clazz.getDeclaredFields();
        for(Field field : declaredFields){
            System.out.println("declaredField :"+field.toGenericString());
        }
    }
}
class FiledConstants implements  interfaceConstants{
    public  int age;

    public  String  name;

    private String  phoneNumber;
}
interface interfaceConstants {
      int age = 22;
}

输出结果为: 可以发现字段并不能覆盖,age字段存在不同的类中都是可以获取到的。

publicField :public int com.java.reflect.classes.TestFields.age
 publicField :public static java.lang.String com.java.reflect.classes.TestFields.addr
 publicField :public int com.java.reflect.classes.FiledConstants.age
 publicField :public java.lang.String com.java.reflect.classes.FiledConstants.name
 publicField :public static final int com.java.reflect.classes.interfaceConstants.age
-----------------------------------
declaredField :public int com.java.reflect.classes.TestFields.age
declaredField :public static java.lang.String com.java.reflect.classes.TestFields.addr
declaredField :private java.lang.String com.java.reflect.classes.TestFields.email

思考一下: class.getFileds是如何获取父类和以及接口的公开的字段的,怎么实现的?实现过程中运用到了哪些思想?

带着这个问题我们先看看class.getFileds这个方法:源码这样写的

   @CallerSensitive
    public Field[] getFields() throws SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyFields(privateGetPublicFields(null));//copyFileds运用了深克隆
    }
private Field[] privateGetPublicFields(Set<Class<?>> traversedInterfaces) {
        checkInitted();
        Field[] res;
        ReflectionData<T> rd = reflectionData(); //ReflectionData对象缓存了方法,字段,构造函数等信息
        if (rd != null) {//先判断缓存的对象是否存在
            res = rd.publicFields;  
            if (res != null) return res;//在判断缓存对象的publicFields是否有值,有直接返回
        }

        // No cached value available; compute value recursively.
        // Traverse in correct order for getField().
        List<Field> fields = new ArrayList<>();
        if (traversedInterfaces == null) {
            traversedInterfaces = new HashSet<>();
        }

        // Local fields  先加载本地的字段
        Field[] tmp = privateGetDeclaredFields(true);//得到公共的字段
        addAll(fields, tmp);  //放入fields集合中

        // Direct superinterfaces, recursively
        for (Class<?> c : getInterfaces()) { //得到接口的类
            if (!traversedInterfaces.contains(c)) { //对重复接口进行删除
                traversedInterfaces.add(c); //
                addAll(fields, c.privateGetPublicFields(traversedInterfaces)); //利用递归加入字段
            }
        }

        // Direct superclass, recursively
        if (!isInterface()) { //判断该类是否是一个接口,如果不是一个接口,走下面的判断
            Class<?> c = getSuperclass(); //得到父类
            if (c != null) {
                addAll(fields, c.privateGetPublicFields(traversedInterfaces)); //递归得到父类的字段信息
            }
        }

        res = new Field[fields.size()];
        fields.toArray(res);  //转换为数组
        if (rd != null) {
            rd.publicFields = res;  //对共有的字段进行缓存
        }
        return res;
    }

接下来先看ReflectionData这个类缓存了哪些东西 ?

 private static class ReflectionData<T> {
        volatile Field[] declaredFields;  //声明的字段  (本类的)
        volatile Field[] publicFields;  // 公开的字段(包括继承和接口的)
        volatile Method[] declaredMethods;  //同上
        volatile Method[] publicMethods;//同上
        volatile Constructor<T>[] declaredConstructors;
        volatile Constructor<T>[] publicConstructors;
        // Intermediate results for getFields and getMethods
        volatile Field[] declaredPublicFields;  // 得到一个类中的中间的结果,声明并且是公开的 
        volatile Method[] declaredPublicMethods;
        volatile Class<?>[] interfaces;

        // Value of classRedefinedCount when we created this ReflectionData instance
        final int redefinedCount;

        ReflectionData(int redefinedCount) {
            this.redefinedCount = redefinedCount;
        }
    }

先看看reflectionData()方法是干嘛的?,这里的ReflectionData是一个软引用,也就是当jvm内存不足的时候会发生内存的回收,所以这个缓存是在jvm内存不足的时候发生的回收的,这个做法是可以借鉴的。

 // Lazily create and cache ReflectionData
    private ReflectionData<T> reflectionData() {
        SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
        int classRedefinedCount = this.classRedefinedCount;
        ReflectionData<T> rd;
        if (useCaches &&   //默认useCaches 为true
            reflectionData != null &&
            (rd = reflectionData.get()) != null &&
            rd.redefinedCount == classRedefinedCount) {
            return rd; //返回缓存对象
        }
        // else no SoftReference or cleared SoftReference or stale ReflectionData
        // -> create and replace new instance
        return newReflectionData(reflectionData, classRedefinedCount); //创建新的缓存对象
    }

其他的细节,可以自己参考源码

总结: 可以借鉴的点

  • 递归思想: class.getFileds先得到本地的字段信息,然后去得到该类的接口和父类信心,利用递归思想,得到每一层的接口的字段或者是父类的字段
  • 缓存: ReflectionData是一个方法级别的缓存,当同时调用class.getFileds两遍的时候,第二遍会直接返回缓存的对象,并且这个缓存是软引用,当jvm内存不足的时候会发生回收,这里不用强引用,用软引用说明是可以回收的缓存
  • 深克隆 :copyFields()方法运用了深度克隆,杜绝了这个类的字段操作对其他的字段的影响

2.class.getMethods()和 class.getDeclaredMethods()以及 class.getConstructors()和class.getDeclaredConstructors()的深度解析

类比字段的获取信息,基本这两个基本是一样的,但是方法获取的时候,需要
排除子类覆盖父类方法的对应父类方法
排除子类实现了接口方法的对应接口方法

3.class.newInstance()深度解析

还是上一节的例子:

public class ClassTrouble {
    public static void main(String... args) {
    try {
        Class<?> c = Class.forName("com.java.reflect.classes.Cls");
        c.newInstance();  // InstantiationException
    } catch (InstantiationException x) {
        x.printStackTrace();
    } catch (IllegalAccessException x) {
        x.printStackTrace();
    } catch (ClassNotFoundException x) {
        x.printStackTrace();
    }
    }
}
class Cls  {
    private Cls() {}
}

输出结果为:

java.lang.IllegalAccessException: Class com.java.reflect.classes.ClassTrouble can not access a member of class com.java.reflect.classes.Cls with modifiers "private"
    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
    at java.lang.Class.newInstance(Class.java:436)
    at com.java.reflect.classes.ClassTrouble.main(ClassTrouble.java:10)

来进一步来了解class.newInstance(),首先先看到源码newInstance部分:

  @CallerSensitive
    public T newInstance()
        throws InstantiationException, IllegalAccessException
    {
        if (System.getSecurityManager() != null) {   //这里是安全管理器不用管,一般默认为null
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
        }

        // NOTE: the following code may not be strictly correct under
        // the current Java memory model.

        // Constructor lookup
        if (cachedConstructor == null) {
            if (this == Class.class) {
                throw new IllegalAccessException(
                    "Can not call newInstance() on the Class for java.lang.Class"
                );
            }
            try {
                Class<?>[] empty = {};//构造一个空class数组,模拟一个无参的构造函数的参数
                final Constructor<T> c = getConstructor0(empty, Member.DECLARED);//先得到一个无参的构造函数,这里可以是私有的,也可以是共有的无参构造函数
                // Disable accessibility checks on the constructor
                // since we have to do the security check here anyway
                // (the stack depth is wrong for the Constructor's
                // security check to work)
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                        public Void run() {
                                c.setAccessible(true);  //设置构造函数可以访问
                                return null;
                            }
                        });
                cachedConstructor = c; //替换过去
            } catch (NoSuchMethodException e) {
                throw (InstantiationException)
                    new InstantiationException(getName()).initCause(e);
            }
        }
        Constructor<T> tmpConstructor = cachedConstructor;
        // Security check (same as in java.lang.reflect.Constructor)
        int modifiers = tmpConstructor.getModifiers();//得到修饰符
        if (!Reflection.quickCheckMemberAccess(this, modifiers)) {//this代表class对象,modifiers 为构造函数的修饰符,在上个例子中,类是可以访问的,构造函数是私有的,所以这里调用之后是false
            Class<?> caller = Reflection.getCallerClass();//得到上级的调用类
            if (newInstanceCallerCache != caller) { 
                Reflection.ensureMemberAccess(caller, this, null, modifiers);//严格确保成员是否可以访问,类的访问权限为protect ,构造函数为私有,不可以访问,这里会抛出异常
                newInstanceCallerCache = caller;
            }
        }
        // Run constructor
        try {
            return tmpConstructor.newInstance((Object[])null); //实质上是调用了构造函数的newInstance的方法
        } catch (InvocationTargetException e) {
            Unsafe.getUnsafe().throwException(e.getTargetException());
            // Not reached
            return null;
        }
    }

先看看 Reflection.ensureMemberAccess的方法

    public static void ensureMemberAccess(Class<?> var0, Class<?> var1, Object var2, int var3) throws IllegalAccessException {
        if (var0 != null && var1 != null) {
            if (!verifyMemberAccess(var0, var1, var2, var3)) { //这里验证失败会抛出异常,与测试结果的异常是一样的,verifyMemberAccess这里的验证逻辑可以自己去看一下,基本遵循了java的访问权限的控制
                throw new IllegalAccessException("Class " + var0.getName() + " can not access a member of class " + var1.getName() + " with modifiers \"" + Modifier.toString(var3) + "\"");
            }
        } else {
            throw new InternalError();
        }
    }

总结:
class.newInstance()调用过程是先得到无参的构造函数,然后验证该构造函数是否有访问权限(访问权限参考下图),如果没有则抛出异常,本质上是调用Construtor.newInstance()方法。

image.png

4. class对象获取注解的案例

需要注意一点的class.getDeclaredAnnotations()和class.getAnnotations()输出结果是一样的,是返回指定类上面的所有注解

/**
 * @Project: jdk
 * @description:  注解的测试
 * @author: sunkang
 * @create: 2018-10-05 17:00
 * @ModificationHistory who      when       What
 **/

@Test1(name = "test1")
public class TestAnnotations  extends ParentAnnotations  {
    public static void main(String[] args) {
        Class annoClass = TestAnnotations.class;
        //1. 得到指定类的所有注解
        Annotation[]   declaredAnnotations = annoClass.getDeclaredAnnotations();//得到这个类上的直接注解,不包含继承的注解
        for(Annotation annotation : declaredAnnotations ){
            System.out.println(annotation.annotationType()); //对应注解的类型信息
        }

        Annotation[]   annotations = annoClass.getAnnotations();//与getDeclaredAnnotations()方式相同
        for(Annotation annotation : annotations ){
            System.out.println(annotation.annotationType());
        }
        //2. 得到指定的注解
       if(annoClass.isAnnotationPresent(Test1.class)){ //判断指定类是否存在注解
           Test1 anotation = (Test1) annoClass.getAnnotation(Test1.class);  //得到指定的注解
           System.out.println(anotation.name());
       }
    }
}
@Test2
class ParentAnnotations{

}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface  Test1{
    String name() default  "";
}

@Retention(RetentionPolicy.RUNTIME) //说明在运行期间生效
@Target({ElementType.TYPE,ElementType.METHOD})//target 表示指定在哪些目标,比如类,接口,枚举上面,或者是方法上面
@interface  Test2{

}

测试结果如下:

interface com.java.reflect.classes.Test1
interface com.java.reflect.classes.Test1
test1
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352