JDK进阶(2):Java类型信息-Class对象

RRTI的概念

RTTI(Run-Time Type Identification)运行时类型识别,其作用是在运行时识别一个对象的类型和类的信息。这里分两种情况:

  1. 传统的RRTI,它假定我们在编译期已知道了所有类型,在没有反射机制创建和使用类对象时,一般都是编译期已确定其类型,如new对象时该类必须已定义好
  2. 另外一种是反射机制,它允许我们在运行时发现和使用类型的信息。

Class 类

  1. 在 Java 中用来表示运行时类型信息的对应类就是 Class 类,Class 类也是一个实实在在的类,存在于JDK 的java.lang包中。class是关键字,Class 是一个类

  2. Class 是 Java 中类的类型抽象。Java 程序中的各个类,属于同一类事物,可以用一个类来描述,这个类就是 Class。比如创建一个 Person 类,那么,JVM 就会创建一个 Person 对应 Class 类的 Class 对象,该 Class 对象保存了 Person 类相关的类型信息。如 Person 类的名称、类的属性、类所属的包名、字段名称列表、方法的列表等类型信息。

  3. Java 中每个类都有一个 Class 对象,当我们编写并且编译一个新创建的类就会产生一个对应 Class 对象并且这个 Class 对象会被保存在同名 .class文件里,编译后的字节码文件保存的就是 Class 对象。每个Java类,无论创建多少个实例对象,在JVM中都只有一个 Class 对象,即在内存中每个类有且只有一个相对应的 Class 对象(.class只有一份)

Class对象的加载及其获取方式

Class对象的加载

  1. Class 是由Jvm创建的,内部只有一个私有的构造函数。
  • 只会被JVM加载一次,也就是只有一份
  • 编译后Class对象被保存在同名的.class文件中,所以JVM的类加载器根据类名查找.class文件
  • Java安全机制检测。类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码
  • 类的实例对象创建是依据在内存中的 Class 对象(class字节码)类型信息完成的
  1. 简单看一个例子:static语句,会在类第一次被加载时执行
  • new 一个对象,构造函数将被调用,属于静态方法的引用,Class 对象肯定会被加载。因为实例对象的创建依据其Class对象。
  • forName 方法是 Class类的一个 static 成员方法,表明调用 Class.forName()方法获取Class对象的引用。

class Candy {
    static {
        System.out.println("Loading Candy");
    }
}

class Gum {
    static {
        System.out.println("Loading Gum");
    }
}

class Cookie {
    static {
        System.out.println("Loading Cookie");
    }
}

/**
 * static语句,会在类第一次被加载时执行
 */
public static void test() {
    print("inside main");
    
    new Candy();
    print("After creating Candy");
    
    try {
        Class.forName("com.learning.optimize.jdk.reflect.clazz.Gum");
    } catch (ClassNotFoundException e) {
        print("Couldn't find Gum");
    }
    
    print("After Class.forName(\"com.learning.optimize.jdk.reflect.Gum\")");
    new Cookie();
    print("After creating Cookie");
    
    // 字面常量的方式获取Class对象,不会初始化该类
    Class<Gum> gumClass = Gum.class;
}

输出:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("com.learning.optimize.jdk.reflect.Gum")
Loading Cookie
After creating Cookie

Class获取方式

  1. Class 对象的加载方法,加载是同一份
  • Class.forName(“类名”),例如:Class.forName("java.lang.Date")
  • Class字面常量,类名.class,例如:Date.class
  • 对象.getClass(),例如:New Date().getClass()
String str1 = "abc";
 
Class cls1 = str1.getClass();
Class cls2 = String.class;
Class cls3 = Class.forName("java.lang.String");

// 三种方式加载的代码是同一份字节码
System.out.println(cls1 == cls2);//true
System.out.println(cls1 == cls3);//true

Class字面常量与基本类型

  1. 相对其他两种方法更加简单,更安全,无需初始化该类。(这个很重要)
// 字面常量的方式获取Class对象,不会初始化该类。(没有打印输出)
Class<Gum> gumClass = Gum.class;

class Gum {
    static {
        System.out.println("Loading Gum");
    }
}
    
  1. Class字面常量方法可以应用于普通的类、接口、数组以及基本数据类型等
  2. 基本类型(包装类)与void
  • void:void.classVoid.TYPE
  • 基本类型、数组类型的判断:isPrimitive()isArray()方法
  • 基本类型的包装类:常量type代表包装类型的字节码,如Integer.TYPE
System.out.println(String.class.isPrimitive()); // false
System.out.println(int.class.isPrimitive());    // true
System.out.println(int[].class.isPrimitive());  // false
System.out.println(int[].class.isArray());      // true,数组类型

// 注意基本类型的包装类
System.out.println(int.class == Integer.class); // false
System.out.println(int.class == Integer.TYPE);  // true(常量type代表包装类型的字节码)

理解泛型化的Class对象引用

  1. 由于Class的引用总数指向某个类的Class对象,利用Class对象可以创建实例类,这也就足以说明Class对象的引用指向的对象确切的类型。
  2. 在Java5 引入泛型后,使用我们可以利用泛型来表示Class对象更具体的类型,即使在运行期间会被擦除,但编译期足以确保我们使用正确的对象类型
static void test6() {
    //没有泛型
    Class intClass = int.class;

    //带泛型的Class对象
    Class<Integer> integerClass = int.class;

    integerClass = Integer.class;

    //没有泛型的约束,可以随意赋值
    intClass = double.class;

    //编译期错误,无法编译通过
    //integerClass = double.class
}

instanceof 关键字与 isInstance方法

  1. instanceof:表明对象是不是某个特定的类型实例
  2. isInstance:Class类中的一个Native方法,用于判断对象类型
static void test(Object x) {
    print("Testing x of type " + x.getClass());
    print("x instanceof A " + (x instanceof A));
    print("x instanceof B " + (x instanceof B));
    print("A.isInstance(x) " + A.class.isInstance(x));
    print("B.isInstance(x) " + B.class.isInstance(x));
    print("x.getClass() == A.class " + (x.getClass() == A.class));
    print("x.getClass() == B.class " + (x.getClass() == B.class));
    print("x.getClass().equals(A.class)) " + (x.getClass().equals(A.class)));
    print("x.getClass().equals(B.class)) " + (x.getClass().equals(B.class)));
}
输出:两个方法的结果是一致的,使用不一样而已
x instanceof A true
x instanceof B false // 父类不一定是子类的某个类型
A.isInstance(x) true
B.isInstance(x) false
x.getClass() == A.class true
x.getClass() == B.class false
x.getClass().equals(A.class)) true
x.getClass().equals(B.class)) false
------------------------------------------------
Testing x of type class com.learning.optimize.jdk.reflect.clazz.B
x instanceof A true
x instanceof B true
A.isInstance(x) true
B.isInstance(x) true
x.getClass() == A.class false
x.getClass() == B.class true
x.getClass().equals(A.class)) false
x.getClass().equals(B.class)) true

获取父类 Class

  1. 方法
方法返回值方法名称方法说明
Class<? super T>getSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。
TypegetGenericSuperclass()返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
  1. 实例
class Cookie<T> {
    static {
        System.out.println("Loading Cookie");
    }
}

class FruitsCookie extends Cookie<Fruits> {

}

class Fruits {

}
static void test6() {
    Class<FruitsCookie> clazz = FruitsCookie.class;
    // getSuperclass(): 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class
    // Object 类、一个接口、一个基本类型或 void,则返回 null。
    Class<? super FruitsCookie> superclass = clazz.getSuperclass();
    print(superclass.getName());

    // getGenericSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
    Type genericSuperclass = clazz.getGenericSuperclass();
    print(genericSuperclass instanceof ParameterizedType);
    // 获取父类的信息
    print(genericSuperclass.getTypeName());

    // 封装了父类及其注解(JDK1.8 新增)
    AnnotatedType annotatedSuperclass = clazz.getAnnotatedSuperclass();
    print(annotatedSuperclass instanceof AnnotatedParameterizedType);

    // 父类的信息
    Type type = annotatedSuperclass.getType();
    print(type instanceof ParameterizedType);
    print(type.getTypeName());
}
输出:
com.learning.optimize.jdk.clazz.Cookie
true
com.learning.optimize.jdk.clazz.Cookie<com.learning.optimize.jdk.clazz.Fruits>
true
true
com.learning.optimize.jdk.clazz.Cookie<com.learning.optimize.jdk.clazz.Fruits>
true
com.learning.optimize.jdk.clazz.Fruits
  1. ParameterizedType 参数化类型
  • getActualTypeArguments():获取泛型的类型, 如 Map<String,Person> map 这个 ParameterizedType 返回的是 String 类,Person 类的全限定类名的 Type Array
  • getRawType():返回的是当前这个 ParameterizedType 的类型
  • getOwnerType():返回 Type 对象,表示此类型是其成员之一的类型。例如,如果此类型为 O.I,则返回 O 的表示形式。
static void test7() {
    Class<FruitsCookie> clazz = FruitsCookie.class;

    // getGenericSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
    Type genericSuperclass = clazz.getGenericSuperclass();
    print(genericSuperclass instanceof ParameterizedType);
    ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;

    // 获取泛型的类型: 如 Map<String,Person> map 这个 ParameterizedType 返回的是 String 类,Person 类的全限定类名的 Type Array
    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
    Type actualTypeArgument = actualTypeArguments[0];
    System.out.println(actualTypeArgument.getTypeName());

    // 返回的是当前这个 ParameterizedType 的类型
    Type rawType = parameterizedType.getRawType();

    // Type getOwnerType()返回 Type 对象,表示此类型是其成员之一的类型。例如,如果此类型为 O<T>.I<S>,则返回 O<T> 的表示形式。
    // 如果此类型为顶层类型,则返回 null。
    Type ownerType = parameterizedType.getOwnerType();

}

参考

  1. 源码地址:Java 进阶
  2. java进阶(二):反射的基础Class类
  3. java Type 详解
    Fork me on Gitee
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值