【Java】【基础类】详解Class类
一、简介
Class类是干什么用的?为什么需要Class这个类
在Java中,为了让程序在运行的时候可以发现一个对象的类型和使用一个类的信息(包括方法和字段属性等),那么怎么发现对象的类型和使用类的信息呢,主要通过两个方式:
第一个就是在编译时就已经知道了所有对象的类型(传统的RTTI,运行时类型识别),需要指定具体的对象类型,从而获取该类的信息(方法、字段);
第二个就是利用反射机制,在运行时允许发现和使用类的信息,不需要知道对象的具体类型,通过反射获取一个类的Class对象,从而获取其类的信息(方法、字段);
那么获取这些类的信息呢,其实就在Class类中有对应的方法进行获取,Class类存在java.lang包中。实际上每个类都有一个Class对象(也就是我们手动编写的类经过编译后JVM会动态给我们的类创建一个Class对象保存在该类的.class字节码文件中),Class对象保存了该类的相关类型信息;通过Class类你可以发现和使用运行时类的相关信息(通过反射技术)。
那么获取Class对象的方法有哪些(在运行时):
- 我们所有类都继承了Object类,Object类里面有Native方法:getClass(),通过实例对象调用getClass()方法
- 所有的数据类型(包括基本类型和引用类型)都有一个静态的class属性,可以直接调用。
- 通过Class类的静态方法Class.forName(String className),通过传入完全限类名称获取该类的Class对象。
二、Class类结构图
(1)实现了Type接口:Type接口在java.lang.reflect包下面,它是Java编程语言中所有类型的公共超接口,这些类型包括原生态类型(Object类型),原始类型(就是基本类型,int,char等等)、参数化类型(其实就是泛型,例如list<T>)、数组类型(int[] )、类变量(引用类型)等,该接口里面有一个默认方法default String getTypeName(),返回该类型的名字。
(2)实现了GenericDeclaration接口,它是所有实体类声明类型变量的一个公共接口,含有一个抽象方法:public TypeVariable<?>[] getTypeParameters();返回泛型声明类型的所有变量存在TypeVariable数组中。
(3)实现了AnnotatedElement接口,该接口代表这在虚拟机中运行的程序所带的注解元素,这个接口可以反射读取注解,该接口中的方法返回的注解都是不可变的和可序列化的,里面存在四个默认方法:
-
- T[] getDeclaredAnnotationsByType(Class):根据传进来的Class类型元素,返回里面包含的所有声明的注解并保存在任何继承了Annotation的类型数组中,如果没有则返回长度为0的数组。忽略继承的注解。
- T[] getAnnotationsByType(Class):该方法里面就是调用上面getDeclaredAnnotationsByType方法实现的,不同的是,该方法会在结果返回数组长度为0或者传进来Class类型是通过继承来的,它还会帮你找父类的注解,并返回
- T[] getDeclaredAnnotation(Class):获取当前Class类型的注解,忽略继承的注解
- boolean isAnnotationPresent(Class):判断是否在当前Class类型元素中是否含有注解。
其他的抽象方法就不列了,反正该接口主要是根据传进来的Class类型元素,获取该元素的注解信息的。
(4)实现了Serialized接口,支持本地序列化和反序列化
(5)可以看到Class类是带泛型的,Class<T>,这样的好处是可以在获取Class对象的时候指定具体的类型,即使在运行期间泛型被擦除,但也能保证在编译期能够正确使用Class对象的类型了,例如Class<Integer> fClass=Float.class;在编译就会出错,无法通过的。注意的是反不具备继承关系,如Class<Number> fClass=Float.class;也是不会通过的,虽然Float是Number的子类。使用通配符?是可以的,Class<?> fClass=Float.class;?表示任意类型的泛型。要使用继承的话,只能这样使用Class<? Extends Number> fClass=Float.class;设置了通配符?的下限,当然也可以设置下限:Class<? Supper Number>
三、Class类的源码注释
/**
1.Class的实例对象代表这运行程序中的所有类或接口,一个类或接口只对应一个Class实例,其中enum枚举也是类类型,annotation是一个接口类型,同样也包括java原生类型(也就是基本类型,如boolean,byte,int,short,int,long,float,double,char等)
* Instances of the class {@code Class} represent classes and
* interfaces in a running Java application. An enum is a kind of
* class and an annotation is a kind of interface. Every array also
* belongs to a class that is reflected as a {@code Class} object
* that is shared by all arrays with the same element type and number
* of dimensions. The primitive Java types ({@code boolean},
* {@code byte}, {@code char}, {@code short},
* {@code int}, {@code long}, {@code float}, and
* {@code double}), and the keyword {@code void} are also
* represented as {@code Class} objects.
*
2.Class类是没有public构造器的,相反,当一个类被JVM加载的时候,同时Class类是被JVM使用类加载器调用defineClass方法自动加载创建该Class类的,
* <p> {@code Class} has no public constructor. Instead {@code Class}
* objects are constructed automatically by the Java Virtual Machine as classes
* are loaded and by calls to the {@code defineClass} method in the class
* loader.
*
3.下面是使用的例子:获取Class对象的name属性
* <p> The following example uses a {@code Class} object to print the
* class name of an object:
*
* <blockquote><pre>
* void printClassName(Object obj) {
* System.out.println("The class of " + obj +
* " is " + obj.getClass().getName());
* }
* </pre></blockquote>
*
* <p> It is also possible to get the {@code Class} object for a named
* type (or for void) using a class literal. See Section 15.8.2 of
* <cite>The Java™ Language Specification</cite>.
* For example:
*
* <blockquote>
* {@code System.out.println("The name of class Foo is: "+Foo.class.getName());}
* </blockquote>
总结一下:
- Class类对象是JVM动态加载到实体类或接口的.class文件中的,而且无论实体类创建多少个实例对象其对应的Class对象只有一个。
- Class对象保存有该实体类的相关类型信息,在运行时可以通过相应的方法即可获得(主要是利用发射技术获取)
- 由于Class类对象只能由JVM自动动态加载,因此它的构造器是私有,可以往下看它的构造器。
三、Class类的构造器
因为Class类是不允许实例化的,只能由JVM自动加载创建,所以它的构造器是私有的:
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
//初始化类加载器
classLoader = loader;
}
四、Class类的主要字段属性
(1)私有属性classLoader被JVM初始化并非是private构造器:
// Initialized in JVM not by private constructor
// This field is filtered from reflection access, i.e. getDeclaredField
// will throw NoSuchFieldException
private final ClassLoader classLoader;
(2)缓存注解数据属性:
// Annotations cache
@SuppressWarnings("UnusedDeclaration")
private volatile transient AnnotationData annotationData;
(3)缓存类的构造器属性:
private volatile transient Constructor<T> cachedConstructor;
(4)缓存类的实例属性:
private volatile transient Class<?> newInstanceCallerCache;
五、Class类的常用方法
(1)forName(String ClassName)静态方法,获取一个类的Class对象,在类加载器的加载阶段就获取到了。
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
(2)getConstructor方法,反射返回一个Constructor类对象,包括子类的构造器
public Constructor<T> getConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return getConstructor0(parameterTypes, Member.PUBLIC);
}
(3)getDeclaredConstructor方法,也是反射返回一个Constructor类对象,不包括继承的子类构造器
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
return getConstructor0(parameterTypes, Member.DECLARED);
}
(4)getFiled方法,反射返回一个Field类对象,仅包括public声明的字段属性。
public Field getField(String name)
throws NoSuchFieldException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Field field = getField0(name);
if (field == null) {
throw new NoSuchFieldException(name);
}
return field;
}
(5)getDeclaredFiled方法,反射返回一个Field类对象,包括所有声明的字段属性
public Field getDeclaredField(String name)
throws NoSuchFieldException, SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
Field field = searchFields(privateGetDeclaredFields(false), name);
if (field == null) {
throw new NoSuchFieldException(name);
}
return field;
}
(6)getMethod方法,反射返回的是一个Method类对象,返回的是公共成员方法
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Method method = getMethod0(name, parameterTypes, true);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
(7)getDeclaredMethod方法,反射返回一个Method类对象,返回的是所有声明的成员方法。
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
(8)isInstance方法,是一个Native方法,与instaceOf关键字产生的效果是一样的。
public native boolean isInstance(Object obj);
(9)newInstance()方法,创建指定类型T的一个实例,主要是反射出来其构造器,再调用构造器创建新实例对象。
public T newInstance()
throws InstantiationException, IllegalAccessException
{
if (System.getSecurityManager() != 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 = {};
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)) {
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}
// Run constructor,执行构造方法
try {
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
Constructor类里面存在创建实例对象的newInstance方法(真正创建实例的方法):
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
(10)cast强制类型转换方法
强制将传进来的Object对象转换成被指定的T类型对象
/**
* Casts an object to the class or interface represented
* by this {@code Class} object.
*
* @param obj the object to be cast
* @return the object after casting, or null if obj is null
*
* @throws ClassCastException if the object is not
* null and is not assignable to the type T.
*
* @since 1.5
*/
@SuppressWarnings("unchecked")
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}
六、Class对象的加载
由于Class对象是由JVM加载的,那么类的JVM加载时机是什么呢 ?在JVM虚拟机规范中只有五种情况触发JVM类加载器加载到内存中并对类进行初始化的,总结一下:
- 创建类的实例(New方式)访问某个类或接口的静态变量(不包括编译期常量),或者给该静态变量进行赋值,或者调用该类的静态方法等,Class类调用内部的forName(类名)静态方法也会加载该类的.class文件到内存中。
- 使用反射包(java.lang.reflect)的方法反射调用类的信息,如果类还没有初始化,则会先初始化。
- 该类被标记为启动类(main类),JVM启动的时候就会加载该类,或者直接使用命令java.exe运行某个主类(包括main方法的类),会将该类的Class对象加载到内存中
- 初始化某个子类的时候,其父类也会跟着初始化。
- 使用JDK7动态类型语言调用时(在java.lang.invoke包下)
因此Java程序在它们开始运行之前并非被完全加载到内存中,其各个部分都是按需加载的,所以在使用该类的时候(通过上面的五种情况)步骤如下:
(1)类加载器首先会检查这个类的Class对象是否已经被加载到内存中(类的实例对象创建时是根据Class对象中的类型信息完成的),
(2)如果没有被加载,则默认的类加载器就会根据这个类的类名去查找对应的.class文件(因为编译后的Class对象被保存在同名的.class字节码文件中),加载这个.class文件,
(3)在这个类的.class字节码文件被加载时,它必须接受相关验证以确保其文件没有被破坏并且不包含不良的java代码(这是java的安全机制检测),
(4)完全没问题后就会被加载到内存中,此时相当于Class对象也就被加载到内存中的(因为.class字节码文件保存有Class对象),同时也可以被用来创建这个类的所有实例对象了。
类的加载过程:
- 加载阶段,查找并加载类的.class字节码二进制数据,同时在java堆中创建一个java.lang.Class类的对象(该对象在编译期就存在.class文件中,一个类的实例对象只能对应一个Class对象,所以在共享堆中创建一份)。
- 连接阶段,验证字节码的安全性和完整性,分为三块内容:验证、准备、解析。
- )验证,包括文件格式、元数据、字节码和符号引用等验证
- )准备,为类的静态域分配内存,并将其初始化为默认值
- )解析,把类中的符号引用转换为直接引用
- 初始化阶段,若有超类则进行初始化,并为类的静态变量赋予正确的初始值。
在完成初始化这个阶段后,类就被加载到内存里面了(Class对象在加载阶段就已经加载了),此时可以对 类进行各种必要的操作了(如new对象,第哦啊用静态成员等),在这个阶段,才真正开始执行类中定义的Java代码。
至于获取Class对象引用上面已经有说了,三种方式:
- 我们所有类都继承了Object类,Object类里面有Native方法:getClass(),通过实例对象调用getClass()方法,会触发类加载器的初始化阶段
- 所有的数据类型类(包括基本类型和引用类型)都有一个静态的class属性(Class字面常量),可以直接调用,在编译期就会被检查,效率比较高,但是并不会初始化该类的初始化,只在类加载器的加载阶段就完成了。
- 通过Class类的静态方法Class.forName(String className),通过传入完全限定类名称获取该类的Class对象,会触发类加载器的初始化阶段。同时会自动初始化该类,不过这会抛ClassNotFoundException异常,因为forName方法在编译期是无法检测到其传递的字符串对应的类是否存在,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。
七、总结
Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。
(1)、如何得到Class的对象呢?有三种方法可以的获取:
1、调用Object类的getClass()方法来得到Class对象,这也是最常见的产生Class对象的方法。例如:
MyObject x;
Class c1 = x.getClass();
2、使用Class类的中静态forName()方法获得与字符串对应的Class对象。例如:
Class c2=Class.forName("MyObject"),Employee必须是接口或者类的名字。
3、获取Class类型对象的第三个方法非常简单。如果T是一个Java类型,那么T.class就代表了匹配的类对象。例如
Class cl1 = Manager.class;
Class cl2 = int.class;
Class cl3 = Double[].class;
注意:Class对象实际上描述的只是类型,而这类型未必是类或者接口。例如上面的int.class是一个Class类型的对象。由于历史原因,数组类型的getName方法会返回奇怪的名字。
(2)、Class类的常用方法
1、getName()
一个Class对象描述了一个特定类的属性,Class类中最常用的方法getName以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
2、newInstance()
Class还有一个有用的方法可以为类创建一个实例,这个方法叫做newInstance()。例如:
x.getClass.newInstance(),创建了一个同x一样类型的新实例。newInstance()方法调用默认构造器(无参数构造器)初始化新建对象。
3、getClassLoader()
返回该类的类加载器。
4、getComponentType()
返回表示数组组件类型的 Class。
5、getSuperclass()
返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。
6、isArray()
判定此 Class 对象是否表示一个数组类。
(3)、Class的一些使用技巧
1、forName和newInstance结合起来使用,可以根据存储在字符串中的类名创建对象。例如
Object obj = Class.forName(s).newInstance();
2、虚拟机为每种类型管理一个独一无二的Class对象。因此可以使用==操作符来比较类对象。例如:
if(e.getClass() == Employee.class)...
- 点赞
- 收藏
- 关注作者
评论(0)