编程语言
首页 > 编程语言> > java类型信息简介

java类型信息简介

作者:互联网

Java运行时类型信息

java运行时类型信息让我们可以在程序运行时发现和使用类型信息,主要分两种方式:

  1. 传统的RTTI(Run-Time Type Information),假定我们在编译期已知道了所有类型
  2. 反射机制,它允许我们在运行时发现使用类型的信息
    反射机制:针对所有正确的java类,不管是本地已知的,还是从网络、数据库读入的类,我们可以通过类型信息+反射机制像普通类一样使用这些类,这些类对象由JVM在运行时创建)

运行时类型信息的作用

简单来讲,通过运行时类型信息,我们可得到另一种获取java对象并使用它的方式(普通的获取java对象如new关键字或序列化的方式)。这种获取类对象和使用类的方式主要在框架常用,如Spring框架的依赖注入ioc和面向切面编程aop技术,他们如何获取项目中类并实例化成对象和使用,这里的底层技术支持便是类型信息和反射技术。
简单的ioc实现:https://blog.csdn.net/minaki_/article/details/88858657

Class对象

手动编写的类被编译后会产生一个Class对象,它就是用来保存类型信息对的象,通过class对象我们可以获取类的构造方法、成员变量、成员方法等信息,Class对象保存在同名.class的文件中(字节码文件),并且在类第一次使用其的静态成员引用时,Class对象才会被动态加载到JVM中的(类的实例对象创建时依据Class对象中型信息完成的,构造方法是类的隐式的静态方法)。

Class对象加载过程

在类第一次使用其的静态成员引用时,类加载器首先会检查这个类的Class对象是否已被加载,如果没有,默认的类加载器就会先根据类名查找.class文件,读取该类字节码,java全机制机制会检测该字节码,确保其安全,然后Class对象会动态加载到内存中,此时就可用来创建这个类的所有实例对象。

  1. 加载loading:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
  2. 链接linking(验证verification、准备preparation、解析resolution):验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用。可以实现自己的类加载机制,用于实现更高级的特性
  3. 初始化initialization:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
  4. 使用using:
  5. 卸载unloading

static final 属于编译期静态常量,在编译阶段通过常量传播优化的方式将Initable类的常量static final存储到了一个称为NotInitialization类的常量池中,在以后对Initable类常量static final的引用实际都转化为对NotInitialization类对自身常量池的引用,所以在编译期后,对编译期常量的引用都将在NotInitialization类的常量池获取,这也就是引用编译期静态常量不会触发Initable类初始化的重要原因

如何获得类型信息(class对象)

如上所述,要获得类型信息,我们只需要获取Class对象便可,这里介绍3种方式:

  1. Class.forName(类全限定名),会初始化类,需要捕获一个名称为ClassNotFoundException的异常。
  2. Object.getClass()是从顶级类Object继承而来的,会初始化类。
  3. Class字面常量,如Integer.class,安全,而且在编译期就会受到检验,所以更高效。字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型该方式在类加载第一阶段加载loading获取Class对象,并不会执行初始化initialization阶段,所以并不会去初始化类的静态初始化器和静态初始化成员变量

基础类型的Class对象

// 两则等价
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;

泛化的Class对象引用

Class引用添加泛型约束仅仅是为了提供编译期类型的检查从而避免将错误延续到运行时期。简单总结thinking in java的知识点


//编译无法通过,因为Integer的Class对象并非是Number的Class对象的子类
Class<Number> numberClass=Integer.class;
// 使用Class<?>要优于Class,两者虽然等价,但是使用Class<?>可以告诉编译器并非碰巧或疏忽
Class<?> intClass = int.class;
// 编译通过,泛化指向范围
Class<? extends Number> intClass = Integer.class;
intClass = double.class;

RTTI之类型转换

public class ClassCast {
 public void cast(){
     Animal animal= new Dog();
     //强制转换
     Dog dog = (Dog) animal;
	 //这两句等同于Dog dog = (Dog) animal;
	 Class<Dog> dogType = Dog.class;
	 Dog dog1 = dogType.cast(animal)
 }
}
interface Animal{ }
class Dog implements  Animal{ }

类型强转是利用RTTI进行判断类型是否正确从而确保强制转换的完成,如果类型转换失败,将会抛出ClassCastException异常。除了强制转换外,在Java SE5中新增一种使用Class.cast方式:

public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
         throw new ClassCastException(cannotCastMsg(obj));
     return (T) obj;
  }

想要更安全的类型转换可以使用以下两个方法

//判断对象objA类型是不是objeB的类型或其子类
objA instanceof objB
//判断对象obj是不是class类型信息对应的类或其子类
class.inInstance(obj)

反射:运行时的类型信息

反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,这种动态获取的信息以及动态调用对象的方法的功能称为java反射机制,Class类与java.lang.reflect类库对反射技术进行了支持,该类库包含Field、Method以及Constructor类,这些类型信息对象由JVM在运行时创建,用以表示类里对应的成员,其中可用Constructor创建新的对象,用get和set方法读写与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法
回到开头,运行时的类型信息分两种:RTTI和反射机制,对于RTTI编译器在编译时打开和检查.class文件(我们可以用普通的方式调用对象所有方法);而对于反射机制,.class文件在编译时是不可获得的,所以在运行时打开和检查.class文件。

方法总结

java反射机制常用方法api介绍
https://www.cnblogs.com/wanshiming/p/9180606.html

java反射机制实践ioc简单实现
https://blog.csdn.net/minaki_/article/details/88858657

参考:《thinking in java》

标签:java,类型信息,对象,简介,class,Class,加载
来源: https://blog.csdn.net/minaki_/article/details/100005926