我们在JavaSE时阶段时写java程序,第一步编写类(.java文件)、第二步编译类(.class文件,要么在硬盘上要么在内存中)、第三步运行类(执行.class文件),此时字节码文件加载到JVM内存中,所以此时有类的加载过程。类在内存中生命的周期是加载–>使用–>卸载。
1.类什么时候加载?
①当我们主动使用这个类时(比较隐秘);
②当我们通过类加载对象,主动加载某个类的时;
一般我们程序员不需要过分的关注!
2.类的加载过程
①load:把字节码数据“读取”到内存中,用到输入流(二进制数据)读取即可
②link:连接
a:校验
识别是不是字节码文件(.class),用十六进制表示,据Java虚拟机规范的定义,一个Class文件有严格的规范。
如:魔数的校验(cafe)
b:准备
如:在方法区创建一个Class对象,来保存这个类型的相关的信息并且会在方法区把类的静态变量的内存分配好,赋值默认值,如果类中静态常量,那么会分配静态常量的内存,并且显示赋值。
c:解析
把字节码文件中的符号·引用替换为直接地址引用
如:把字节码中出现的String,String是符号引用,但是要把它替换为String类型的Class对象的地址(直接获取地址)
③初始化
执行()类初始化,它的代码由:a.静态变量的显示赋值语句 b.静态代码块的语句
小结:大多情况下,类的加载①③③【校验、准备、解析】是连贯完成,是区分不出来的,特殊情况,③(类初始化)会延迟!所有的Java类型,被加载到内存中,都是以“Class对象”的形式存在的,即要获取某个类型的详细信息,或是我们要使用某个类型的前提条件:需要得到它的“Class对象”,才能进行反射以及其它的操作。
3.什么时候类【发生】初始化?
a.main方法的类要先初始化
即要执行main方法之前,先完成main所有的类的初始化,才能执行main方法;
public class InitTest {
static{
System.out.println("(1)mian所在类的静态代码块-类初始化测试");
}
public static void main(String[] args) {
System.out.println("(2)mian-类初始化测试");
}
}
*打印结果:
(1)mian所在类的静态代码块-类初始化测试
(2)mian-类初始化测试
b.第一次使用某个类型就是new它的对象
package coding0110lin_Class;
public class InitTest {
static{
System.out.println("(1)mian所在类的静态代码块-类初始化测试");
}
public static void main(String[] args) {
System.out.println("(2)mian-类初始化测试");
//第一次使用MyClass,就是new它的对象
new MyClass();
}
}
class MyClass{
//无参构造器
public MyClass() {
System.out.println("C.MyClass的无构造器");
}
{
System.out.println("B.MyClass的非静态代码块");
}
static{
System.out.println("A.MyClass的静态代码块--类初始化第一个完成");
}
}
*打印结果:
(1)mian所在类的静态代码块-类初始化测试
(2)mian-类初始化测试
A.MyClass的静态代码块–类初始化第一个完成
B.MyClass的非静态代码块
C.MyClass的无构造器
c.调用某个类的静态成员(类变量和方法)
package coding0110lin_Class;
public class InitTest02 {
public static void main(String[] args) {
//调用一个类的静态方法或使用它的静态变量
StaticTest.staticMethod();
System.out.println(StaticTest.num);
/*StaticTest的静态代码块
66*/
}
}
class StaticTest{
public static int num = 66;
static {
System.out.println("StaticTest的静态代码块");
}
public static void staticMethod() {
System.out.println("StaticTest的静态方法");
}
}
*打印结果:
//调用静态方法–StaticTest.staticMethod();
StaticTest的静态代码块
StaticTest的静态方法//调用静态变量–System.out.println(StaticTest.num);
StaticTest的静态代码块
66
d.子类初始化时,发现它的父类还没有初始化,即先初始化父类
public class InitTest03 {
public static void main(String[] args) {
//使用子类创建对象
Son son =new Son();
}
}
class Father {
static {
System.out.println("Father的静态代码块");
}
}
class Son extends Father {
static {
System.out.println("Son的静态代码块");
}
}
*打印结果:
Father的静态代码块
Son的静态代码块
e.通过反射操作这个类时,如果这个类没有初始化,也会导致该类先初始化
package coding0110lin_Class;
public class InitTest03 {
public static void main(String[] args) throws ClassNotFoundException {
//使用子类创建对象
//Son son =new Son();
//使用反射操作类,也会导致类初始化
Class.forName("coding0110lin_Class.Son");
}
}
class Father {
static {
System.out.println("Father的静态代码块");
}
}
class Son extends Father {
static {
System.out.println("Son的静态代码块");
}
}
*打印结果:
Father的静态代码块
Son的静态代码块
4.重点-什么时候类初始化【延迟】?
类初始化延迟即发生类加载,不进行类初始化
a.当我们仅仅使用的是一个类的静态的常量时,不会导致类的初始化
静态常量在【link】的【b.准备】阶段就已经赋值,不需要到达【初始化】步骤,则会延迟进行。
public class InitTest04{
public static void main(String[] args) {
//使用类的静态的常量,不会导致类初始化,只是发生类加载
System.out.println(InitConstantTest.AGE);
}
}
class InitConstantTest {
static{
System.out.println("InitConstantTest的静态代码块");
}
public static final int AGE = 18;
}
*打印结果:
18
b.当我们使用某个类的静态变量时,只有声明它的类才会初始化
即当我们通过子类调用父类变量时,只会导致父类的初始化,不会导致子类初始化。
public class InitTest05 {
public static void main(String[] args) {
//通过Zi 类去访问Fu的静态变量,Zi类无声明不会初始化
System.out.println(Zi.daxia);
}
}
class Fu{
public static int daxia =18;
static{
System.out.println("Fu类的静态代码块");
}
}
class Zi extends Fu{
static{
System.out.println("zi类的静态代码块");
}
}
*打印结果:
Fu类的静态代码块
18
c.用某个类型声明数组并创建数组对象时,不会导致这个类初始化
public class InitTest05 {
public static void main(String[] args) {
//使用Zi创建数组对象,这是一种新的类型,并非子类的对象
Zi[] array = new Zi[5];
}
}
class Fu{
public static int daxia =18;
static{
System.out.println("Fu类的静态代码块");
}
}
class Zi extends Fu{
static{
System.out.println("zi类的静态代码块");
}
}
无结果
5.初始化后完成–>拥有类加载结果(一个Class对象)
⑴【必记】如何获取Class对象?
java.lang.Object
java.lang.ClassClass
类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(
boolean、
byte、
char、
short、
int、
long、
float和
double)和关键字
void也表示为
Class 对象。
①类型名.class
要求编译期间已知类型
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import javax.lang.model.element.Element;
import org.junit.Test;
public class ClassTest {
@Test
public void test01() {
// 1.某个类的Class对象
Class clazz = String.class;//获取String类型被加载到内存后生成的Class对象
System.out.println(clazz);// class java.lang.String
Class clazz2 = ClassTest.class;//获取ClassTest类型被加载到内存后生成的Class对象
System.out.println(clazz2); //class coding0110lin_Class.ClassTest
//2.某个接口的Class对象
Class clazz3 = Comparable.class;
System.out.println(clazz3); // interface java.lang.Comparable
Class clazz4 = Serializable.class;
System.out.println(clazz4); //interface java.io.Serializable
//3.获取枚举类型的对象
Class clazz5 = ElementType.class;
System.out.println(clazz5); // class java.lang.annotation.ElementType
Class clazz6 = RetentionPolicy.class;
System.out.println(clazz6); // class java.lang.annotation.RetentionPolicy
//4.获取注解类型的Class对象
Class clazz7 = Override.class;
System.out.println(clazz7); // interface java.lang.Override
//5.获取数组类型的Class对象
Class clazz8 = int[].class;
System.out.println(clazz8); // class [I
Class clazz9 = String[].class;
System.out.println(clazz9); // class [Ljava.lang.String;
// 6.获取基本类型的Class对象
Class clazz10 = int.class;
System.out.println(clazz10); // int
//7.无返回值
Class clazz11 =void.class;
System.out.println(clazz11); //void
}
}
每一种类型在内存中的Class对象是唯一的!
②对象.getClass():获得某一个对象的运行时类型
Java是强类型语言,注意元素的类型!获取对象的运行时类型。
@Test
public void test02(){
Class clazz1 = String.class;
Class clazz2 = "coding0110lin".getClass();
//同一种类型的Class对象是同一个
System.out.println(clazz1 == clazz2); //true
//不是同一种类型
Class clazz3 = Object.class;
System.out.println(clazz1 == clazz3); //false
//一个字符串类型一个事字符串数组类型
Class clazz4 = String[].class;
System.out.println(clazz1 == clazz4); // false
}
③【首先考虑】Class.forName(“类型全名称”)
可以获取编译期间未知的类型,具有解耦合的优点!
所有的框架都有一个配置文件,文件中有指定参数,直接获取就行!
@Test
public void test03() throws ClassNotFoundException{
//1.已知类型
Class clazz1 = String.class;
Class clazz2 = "coding0110lin".getClass();
Class clazz3 = Class.forName("java.lang.String");
System.out.println(clazz1 == clazz2); //true
System.out.println(clazz2 == clazz3); //true
//2.未知类型
Class clazz4 =Class.forName("com.mysql.jdbc.Driver");
System.out.println(clazz4);
}
④ClassLoader对象.loadClass(“类型全名称”):获取类加载器的对象
可以用系统类加载对象或自定义加载器对象加载指定路径下的类型。
@Test
public void test04() throws ClassNotFoundException {
//获取ClassTest类的类加载对象
Class c = ClassTest.class;
ClassLoader Loader = c.getClassLoader();
Class<?> clazz1 = Loader.loadClass("coding0110lin_Class.ClassTest");
System.out.println(clazz1); //class coding0110lin_Class.ClassTest
Class clazz2 = ClassTest.class;
System.out.println(clazz2); //class coding0110lin_Class.ClassTest
System.out.println(clazz1 == clazz2); //true
}
⑵哪些类型具有Class对象?
所有的java的数据类型都具有Class对象!
总结
上面总结了类的加载过程(加载、连接、初始化),对于类初始化的发生、延迟两种情况进行了分析说明,在类加载过程结束后,我们会的到一个类加载结果,即是一个Class对象,对获取Class对象常用四种情况又进行了小结(即类在内存中如何使用),旨在深刻理解类在内存的生命周期各个阶段的情况,这对反射应用的理解大有裨益。
☝上述分享来源个人总结,如果分享对您有帮忙,希望您积极转载;如果您有不同的见解,希望您积极留言,让我们一起探讨,您的鼓励将是我前进道路上一份助力,非常感谢!我会不定时更新相关技术动态,同时我也会不断完善自己,提升技术,希望与君同成长同进步!
☞本人博客:https://coding0110lin.blog.csdn.net/ 欢迎转载,一起技术交流吧!