40.Java之Class、Type详谈
Object 是一个类。每个类都继承自 Object,所有对象(包括数组)都实现了这个类的方法。从以下版本开始:JDK1.0Class 是一个类(不是class)。该类没有公共构造函数,相反,类对象由Java虚拟机自动构建,用于表示JVM运行时类或接口的信息。Class类的构造函数被设计为私有的,这意味着我们不能通过new的方式来创建Class对象,只有JVM才能创建该类的实例。从以下版本开始:J
40.1 官方介绍
- Object:
Object 是一个类。 每个类都继承自 Object,所有对象(包括数组)都实现了这个类的方法。
从以下版本开始:JDK1.0
- Class:
Class 是一个类(不是class)。该类没有公共构造函数,相反,类对象由Java虚拟机自动构建,用于表示JVM运行时类或接口的信息。Class类的构造函数被设计为私有的,这意味着我们不能通过new的方式来创建Class对象,只有JVM才能创建该类的实例。
从以下版本开始:JDK1.0
- Type:
Type 是一个接口。Type是Java编程语言中所有类型的通用超级接口,这些包括:
原始类型(raw types):包括类和接口。
参数化类型(parameterized types):也叫泛型,例如List<String>是一个参数化类型。
数组类型(array types):数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括 基本数据类型和引用数据类型。
类型变量(type variables):也叫泛型变量,即泛型中的变量,例如:T、K、V等变量,可以表示任何类。
基本数据类型(primitive types):byte、short、boolean、char、int、long、float、double 八大基本数据类型。
从以下版本开始:JDK1.5
40.2 Class类
类对象: 在Java中,类是是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例。作为概念层次的类,其本身也具有某些共同的特性,如都具有类名称、由类加载器去加载,都具有包,具有父类,属性和方法等。于是,Java中有专门定义了一个类,Class,去描述其他类所具有的这些特性,因此,从此角度去看,类本身也都是属于Class类的对象。
当一个类在被加载的时候虚拟机就会自动的生成一个这个类的一个Class类型的“类对象”,每个类都对应着一个这样的类对象。
Java运行时系统一直对所有的对象进行所谓的运行时类型标识,记录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
我们都知道所有的java类都是继承了object这个类,在object这个类中有一个方法:getclass() 这个方法是用来取得该类已经被实例化了的对象的该类的引用,这个引用就是指向的是Class类的对象。
1. 关于类对象什么时候被加载
所有的类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类,使用new操作符创建类的新对象也会被当作对类的静态成员的引用。
因此Java在开始运行前并不会去加载类,其各个部分是在必须时才加载的。
类加载器首先检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并不包含不良Java代码。
一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象
2. 获取Class类对象
以下这些方法都是获取已加载的值,即某个类对应的Class对象已经在堆中生成以后,我们通过不同方式获取对这个Class对象的引用。
而通过类装载器中的 defineClass 方法生成才是真正将字节码加载到虚拟机的方法,会在堆中生成新的一个Class对象。
第一种:Class类的forName函数
Class obj1 = Class.forName("java.lang.Integer");
第二种办法,使用对象的getClass()函数
Integer integer = new Integer(100);
Class obj2 = integer.getClass();
// 获取该类的父类的类型
Class obj2Parent = obj2.getSuperclass();
第三种办法,使用类字面常量
Class obj3 = Integer.class;
注意:使用类字面常量获取类对象不会引起静态初始化
3. 使用Class类的对象来生成目标类的实例
生成的是object类的实例,需要类型转换
Class objClass1 = Class.forName("java.lang.String");
String s = (String) objClass1.newInstance();
利用泛型,直接生成目标实例
Class<String> objClass2 = String.class;
String s = objClass2.newInstance();
4. 使用类字面常量获取类对象为什么不会引起静态初始化
类加载过程:
- 加载:类加载过程的一个阶段,通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象;
- 链接:验证字节码的安全性和完整性,准备阶段正式为静态域分配存储空间,注意此时只是分配静态成员变量的存储空间,不包含实例成员变量,如果必要的话,解析这个类创建的对其他类的所有引用;
- 初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。
所以当我们获取字面常量的Class引用时,触发的应该是加载阶段,因为在这个阶段Class对象已创建完成,获取其引用并不困难,而无需触发类的最后阶段初始化。
5. 关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化
- 使用new关键字实例化对象时、读取或者设置一个类的静态字段(不包含编译期常量)以及调用静态方法的时候,必须触发类加载的初始化过程(类加载过程最终阶段);
- 使用反射包(java.lang.reflect)的方法对类进行反射调用时,如果类还没有被初始化,则需先进行初始化,这点对反射很重要;
- 当初始化一个类的时候,如果其父类还没进行初始化则需先触发其父类的初始化。
- 当Java虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类;
- 当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle 实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应类没有初始化时,必须触发其初始化(这是1.7的新增的动态语言支持,其关键特征是它的类型检查的主体过程是在运行期而不是编译期进行的)
6. Class类和Object类的关系
这很像一个先有鸡还是先有蛋的问题,众所周知:
一切类都继承自己Object,Class也是类,所以Class也会继承自Object
一切类都是Class的实例(类对象),Object也是类,所以Object也会是Class类的一个实例
那么到底Object和Class执行的先后顺序是怎样的呢?
事实是:这些相互依赖的核心类型完全可以在“混沌”中一口气都初始化好,然后对象系统的状态才叫做完成了“bootstrap”,后面就可以按照Java对象系统的一般规则去运行。JVM、JavaScript、Python、Ruby等的运行时都有这样的bootstrap过程。
在“混沌”(boostrap过程)里,JVM可以为对象系统中最重要的一些核心类型先分配好内存空间,让它们进入已分配空间但尚未完全初始化状态。此时这些对象虽然已经分配了空间,但因为状态还不完整所以尚不可使用。
然后,通过这些分配好的空间把这些核心类型之间的引用关系串好。到此为止所有动作都由JVM完成,尚未执行任何Java字节码。然后这些核心类型就进入了完全初始化状态,对象系统就可以开始自我运行下去,也就是可以开始执行Java字节码来进一步完成Java系统的初始化了。
7. Class类常用方法
getGenericInterfaces():Type[]
用于获取由该实体直接实现的接口的类型。
该实体可以是类或接口。
该方法返回由该实体直接实现的接口类型的数组。
40.3 Type接口
在泛型还没出现之前,描述类的类型是Class,随着语言的优化,泛型的出现,而Class又无法描述泛型,故而出现了其余四种对于涉及泛型的描述。
- 原始类型(Class):基本数据类型和不涉及泛型的所有引用类型(类、接口、数组)均是Class类型。比如
String s
、String s1[]
、List list
等对应的类型均是Class类型。 - 参数化类型(ParameterizedType):带有泛型的类型,也就是带有参数化类或者接口对应的类型,例如
List<String> s1
、Map<String,Integer> map
等对应的类型均是ParameterizedType类型 - 数组类型(GenericArrayType):带有泛型的数组类型,也就是带有参数化类或者接口所表示的数组对应的类型,例如
List<String> s[]
、Map<String,Integer> map[]
等对应的类型均是GenericArrayType类型,而List s[]
、Map map[]
是Class类型 - 类型变量(TypeVariable):泛型本身的类型,例如
HashMap<K,V>
中的 K,V - 通配符类型(WildcardType) :通配符对应的类型,例如
List<? extends Object>
中?
对应的类型
Type其实是和泛型一起出现的,可以说Type就是为了支持泛型。
- 泛型出现之前,我们可以通过Class来确认一个对象的类型,比如ClassA cc,那么cc的类型就是ClassA;
- 泛型出现之后,显然不能通过Class唯一确认一个对象的类型,比如List<T> A,A的Class是List,但是A的类型显然不仅仅是List,它是由Class类型的List + TypeVariables的 T 联合确认的一个Type。
-
ParameterizedType:参数化类型常用方法
-
getRawType(): Type
该方法的作用是 返回当前的ParameterizedType的类型。如一个List,返回的是List的Type,即返回当前参数化类型本身的Type。 -
getOwnerType(): Type
返回ParameterizedType类型所在的类的Type。如Map.Entry<String, Object>这个参数化类型返回的是Map(因为Map.Entry这个类型所在的类是Map)的类型。 -
getActualTypeArguments(): Type[]
该方法返回参数化类型<>中的实际参数类型, 如 Map<String,Person> map 这个 ParameterizedType 返回的是 String 类,Person 类的全限定类名的 Type Array。注意: 该方法只返回最外层的<>中的类型,无论该<>内有多少个<>。
getBounds(): Type[]
返回当前类型的上边界,如果没有指定上边界,则默认为Object。getName(): String
返回当前类型的类名getGenericDeclaration():Type
返回当前类型所在的类的Type
-
getGenericComponentType(): Type
返回组成泛型数组的实际参数化类型,如 List[] 则返回 List。注意:无论从左向右有几个[ ]并列,这个方法仅仅脱去最右边的[ ]之后剩下的内容就作为这个方法的返回值。
getLowerBounds():Type[]
得到下边界的type数组getUpperBounds(): Type[]
得到上边界的type数组
更多推荐
所有评论(0)