主要参考了以下三个链接,按参考比重由大到小排列,都是很好的博文,
强烈推荐第一篇,我学习Class的思路都是按照第一篇文章来的,
我这篇文章也主要是通过第一篇文章总结出来的,一些例子和原话直接截图原文了
为了自己巩固记忆,梳理总结多篇文章的知识点,特发此文
https://blog.csdn.net/mcryeasy/article/details/52344729
https://blog.csdn.net/chenge_j/article/details/72676467
https://blog.csdn.net/weixin_44650929/article/details/88680228
一.概念
1.从某种意义上来说,java有两种对象,一种是实例对象,一种是Class对象
2.每个类的运行时的类型信息就是用Class对象表示的
3.实例对象是根据Class对象创建的
4.多态就是基于RTTI(运行时类型识别Run-Time Type Identification)实现的,而RTTI是java使用Class对象执行的
5.每个类都有一个Class对象,每编译一个新类就产生一个Class对象
6.基本类型,数组,关键字void都有Class对象
7.Class对象对应着java.lang.Class类
8.如果说类是对 对象的抽象和集合,那么Class就是对 类的抽象和集合
9.Class类没有公共构造方法,Class对象是在类加载的时候由java虚拟机及通过调用类加载器中的defineClass方法自动构造的,因此不能显式声明一个Class对象
10.Class对象和其他对象一样,我们可以获取并操作它的引用
11.一旦类被加载到内存上,不论是怎样创建的class对象(new,还是调用静态成员,还是Class.forName),它们返回的都是指向同一个java堆地址上的Class引用,jvm不会创建两个相同类型的Class对象,即,内存上,一个类的Class对象是唯一的
但是严格来说:
(下面这个图片是链接中的原文,但是我还没有接触过类加载器,没有亲眼见过这种情况)
二.一个类被加载到内存并供使用需要经历以下三个阶段
1.加载
由类加载器(ClassLoader)执行
类加载器首先检查这个类的Class对象是否已被加载,如果尚未加载,默认的类加载器就会通过一个类的全限定名来获取其定义的二进制字节流(Class字节码)
字节码被加载时,会被验证确保其没有被破坏,不含不良java代码,
然后根据字节码在java堆中生成一个代表这个类的java.lang.Class对象
一旦某个类的Class对象被载入内存,就可以用它来创建这个类的所有对象
2.链接
在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,
为静态域分配存储空间并设置类变量的初始值(默认的零值),
如果必需的话,将常量池中的符号引用转化为直接引用
符号引用参见:https://www.cnblogs.com/shinubi/articles/6116993.html
3.初始化
此阶段才真正开始执行类中定义的java程序代码.
执行该类的静态初始器和静态初始块,
如果该类有父类,则优先对其父类进行初始化
需要注意的是:
所有的类都是在第一次使用时,动态加载到JVM中的(懒加载)
因此java程序在它开始运行之前并非被完全加载,各个类都是在必需时才加载
这与许多传统语言不通,动态加载使能的行为,在c++等静态加载语言中是很难实现的
静态代码块 仅在 类被第一次加载时才会执行
类被加载的两种情况:
当程序首次引用对类的静态成员(静态方法或者非 常量静态域),会加载这个类,
或者首次使用new创建类对象的时候也会被当做对类的静态成员的引用,会加载这个类
(静态成员,参考链接:https://blog.csdn.net/qq_41431457/article/details/85337939)
三.获取Class对象的三种方式and观察类被加载的情况
1.Class.forName("类的全限定名")
Class.forName("全限定名")是Class类的静态成员,forName的时候如果发现类还没有被加载,JVM就会调用类加载器去加载这个类,并返回加载后的Class对象.如果找不到这个类,会抛出ClassNotFoundException
Class.forName的好处是,不需要为了获取Class引用而持有该类型的对象,只要有全限定名即可.
new也是,如果发现类没有加载过,会立即加载类
比如下面new了两次cat(或者一次new,一次用Class.forName),但是cat中的静态代码块只执行了一次
package com.cloudiip.classtest;
class Dog {
static {
System.out.println("dog---------");
}
}
class Cat {
static {
System.out.println("cat---------");
}
}
public class Test{
@org.junit.Test
public void test(){
System.out.println("main----------");
new Cat();
System.out.println("cat第一次结束----------");
new Dog();
System.out.println("dog第一次结束-------------------------");
try {
Class catClass=Class.forName("com.cloudiip.classtest.Cat");
} catch (ClassNotFoundException e) {
System.out.println("用Class.forName获取cat失败----------");
}
System.out.println("cat第二次结束----------");
}
}
2.实例对象.getClass()
如果已经有该类型的对象,可以通过该对象.getClass()来获取Class引用
这个方法是Object中的,返回的是表示该对象实际类型的Class引用
这个方法肯定不会触发类的第一次加载,因为实例对象都有了,类肯定已经被加载过了
3.类.class
使用类字面常量是一种不需要try,catch的,更简单安全的方式
不仅可以应用于普通的类,也可以应用于接口,数组及基本数据类型
用.class来创建对Class对象的引用时,不会自动地初始化该Class对象(这点和Class.forName,new 对象不同)
类对象的初始化阶段被延迟了,静态成员被首次引用时才执行
需要注意的是:
基本数据类型.class和其包装类的.class不同
Class c1=Cat.class;
Class c2=int.class;
Class c3=Integer.class;
Class c4=Integer.TYPE;
.class的例子见下,Dog.class的时候没有加载类,调用Dog中的静态常量s1也没有加载类,但是调用s2这个非常量静态域的时候才加载类,执行了静态代码块
package com.cloudiip.classtest;
class Dog {
static final String s1 = "Dog_s1";
static String s2 = "Dog_s2";
static {
System.out.println("Loading Dog");
}
}
class Cat {
static String s1 = "Cat_s1";
static {
System.out.println("Loading Cat");
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("----Star Dog----");
Class dog = Dog.class;
System.out.println("------");
System.out.println(Dog.s1);
System.out.println("------");
System.out.println(Dog.s2);
System.out.println("---start Cat---");
Class cat = Class.forName("com.cloudiip.classtest.Cat");
System.out.println("-------");
System.out.println(Cat.s1);
System.out.println("finish main");
}
}
上面Dog中s1这样被static final修饰的字段,被称为 编译时常量,因为在编译期就把结果放进常量池中了,
调用这个字段的时候是不会对Dog类进行初始化的
但是,如果只是将一个域设置成static或者final,还不足以确保这种效果,如同Dog中的s2被调用后,会强制Dog进行类的初始化
使用javap -c -v Dog.class 对Dog的字节码反编译后能看到,由于s1被static final修饰,而s2只有static,因此反编译文件中,s1多了一个ConstantValue,表示这个值被写到了常量池
static final的特例:
下面的staticFinal2,没有加载到常量区,因此调用这个常量的时候会触发该类初始化
四.泛型Class引用
向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查
Class引用表示的就是它指向的对象的确切类型,而该对象就是Class类的一个对象
javaSE5中,允许对Class引用所指向的Class对象的类型进行限定,也就是说可以用Class对象使用泛型语法.通过泛型语法,可以让编译器强制指向额外的类型检查
虽然int.class和Integer.class指向的不是同一个Class对象引用,但是它们是基本类型和包装类的关系,int可以自动包装成Integer,所以编译可以通过
而Double不是Integer,也不能包装成Integer,因此编译无法通过
Class<Integer> c1=int.class; System.out.println(c1); c1=Integer.class; System.out.println(c1); //c1=Double.class; 编译报错 |
泛型中的类型也不能持有其子类的引用
Class<Number> c1=Number.class; System.out.println(c1); //c1=Integer.class; 编译报错 |
但是为了使用泛化的Class放松限制,我们可以使用通配符,它是java泛型的一部分
通配符?------表示任何事物
Class<?> c1=Integer.class; c1=Double.class; |
? 可以与extends结合使用 表示子类
Class<? extends Number> c1=Number.class; System.out.println(c1); c1=Double.class; System.out.println(c1); c1=Integer.class; System.out.println(c1); |
? 可以用super结合使用 表示父类(父类的父类也可以)
Class<? super Integer> c1=Integer.class; System.out.println(c1); c1=Number.class; System.out.println(c1); c1=Object.class; System.out.println(c1); |
五.Class类的方法
1.方法一览
import java.lang.reflect.Field;
interface I1 {
}
interface I2 {
}
class Cell{
public int mCellPublic;
}
class Animal extends Cell{
private int mAnimalPrivate;
protected int mAnimalProtected;
int mAnimalDefault;
public int mAnimalPublic;
private static int sAnimalPrivate;
protected static int sAnimalProtected;
static int sAnimalDefault;
public static int sAnimalPublic;
}
class Dog extends Animal implements I1, I2 {
private int mDogPrivate;
public int mDogPublic;
protected int mDogProtected;
private int mDogDefault;
private static int sDogPrivate;
protected static int sDogProtected;
static int sDogDefault;
public static int sDogPublic;
}
public class Test {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<Dog> dog = Dog.class;
//类名打印
System.out.println(dog.getName()); //com.cry.Dog
System.out.println(dog.getSimpleName()); //Dog
System.out.println(dog.getCanonicalName());//com.cry.Dog
//接口
System.out.println(dog.isInterface()); //false
for (Class iI : dog.getInterfaces()) {
System.out.println(iI);
}
/*
interface com.cry.I1
interface com.cry.I2
*/
//父类
System.out.println(dog.getSuperclass());//class com.cry.Animal
//创建对象
Dog d = dog.newInstance();
//字段
for (Field f : dog.getFields()) {
System.out.println(f.getName());
}
/*
mDogPublic
sDogPublic
mAnimalPublic
sAnimalPublic //父类中的公共的静态变量也能打印出来
mCellPublic //父类的父类的公共字段也打印出来了
*/
System.out.println("---------");
for (Field f : dog.getDeclaredFields()) {
System.out.println(f.getName());
}
/** 只有自己类声明的字段,四种权限都可
mDogPrivate
mDogPublic
mDogProtected
mDogDefault
sDogPrivate
sDogProtected
sDogDefault
sDogPublic
*/
}
}
2.getName、getCanonicalName与getSimpleName的区别
package com.cry; public class Test { private class inner{ } public static void main(String[] args) throws ClassNotFoundException { //普通类 System.out.println(Test.class.getSimpleName()); //Test System.out.println(Test.class.getName()); //com.cry.Test System.out.println(Test.class.getCanonicalName()); //com.cry.Test //内部类 System.out.println(inner.class.getSimpleName()); //inner System.out.println(inner.class.getName()); //com.cry.Test$inner System.out.println(inner.class.getCanonicalName()); //com.cry.Test.inner //数组 System.out.println(args.getClass().getSimpleName()); //String[] System.out.println(args.getClass().getName()); //[Ljava.lang.String; System.out.println(args.getClass().getCanonicalName()); //java.lang.String[] //我们不能用getCanonicalName去加载类对象,必须用getName //Class.forName(inner.class.getCanonicalName()); 报错 Class.forName(inner.class.getName()); } } |
六.源码分析
//前一个Class表示这是一个类的声明,第二个Class是类的名称, <T>表示这是一个泛型类,并实现了四种接口。 public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement { //定义了三个静态变量 private static final int ANNOTATION= 0x00002000; private static final int ENUM = 0x00004000; private static final int SYNTHETIC = 0x00001000;
//定义了一个名为registerNatives()的本地方法,并在静态块中调用: private static native void registerNatives(); static { registerNatives(); }
// 私有构造函数,只能由JVM调用,创建该类实例 private Class(ClassLoader loader) { classLoader = loader; } /*如果Class对象是一个Java类,返回class full_classname,即class 包名.类名; 比如上面例子的List,返回的就是class java.util.List; 如果是接口,将class改成interface; 如果是void类型,则返回void; 如果是基本类型,返回基本类型。*/ public String toString() { return (isInterface() ?"interface " : (isPrimitive() ? "" : "class")) + getName(); } |