一:关于类的加载机制,先看几个面试代码
- -----------------------------------------------1------------------------------------------------
class YeYe{
static {
System.out.println("YeYe静态代码块");
}
}
class Father extends YeYe{
public static String strFather="HelloJVM_Father";
static{
System.out.println("Father静态代码块");
}
}
class Son extends Father{
public static String strSon="HelloJVM_Son";
static{
System.out.println("Son静态代码块");
}
}
public class day2 {
public static void main(String[] args) {
System.out.println(Son.strFather);
}
}
控制台输出结果
YeYe静态代码块
Father静态代码块
HelloJVM_Father
- -----------------------------------------------2------------------------------------------------
class YeYe1{
static {
System.out.println("YeYe静态代码块");
}
}
class Father1 extends YeYe1{
public final static String strFather="HelloJVM_Father";
static{
System.out.println("Father静态代码块");
}
}
class Son1 extends Father1{
public static String strSon="HelloJVM_Son";
static{
System.out.println("Son静态代码块");
}
}
public class day3 {
public static void main(String[] args) {
System.out.println(Son1.strFather);
}
}
控制台输出结果
HelloJVM_Father
- -----------------------------------------------3------------------------------------------------
class Father2{
public static String strFather="HelloJVM_Father";
static{
System.out.println("Father静态代码块");
}
}
class Son2 extends Father2{
public static String strSon="HelloJVM_Son";
static{
System.out.println("Son静态代码块");
}
}
public class InitativeUseTest2 {
public static void main(String[] args) {
System.out.println(Son2.strSon);
}
}
控制台输出结果
Father静态代码块
Son静态代码块
HelloJVM_Son
- -----------------------------------------------4------------------------------------------------
class Test{
static {
System.out.println("static 静态代码块");
}
// public static final String str= UUID.randomUUID().toString();
public static final double str=Math.random(); //编译期不确定
}
public class FinalUUidTest {
public static void main(String[] args) {
System.out.println(Test.str);
}
}
解释:其实final不是重点,重点是编译器把结果放入常量池!当一个常量的值并非编译期可以确定的,那么这个值就不会被放到调用类的常量池中,这时在程序运行时,会导致主动使用这个常量所在的类,所以这个类会被初始化
控制台输出结果
static 静态代码块
0.8328688978347876
- -----------------------------------------------5------------------------------------------------
public class ClassAndObjectLnitialize {
public static void main(String[] args) {
System.out.println("输出的打印语句");
}
public ClassAndObjectLnitialize(){
System.out.println("构造方法");
System.out.println("我是熊孩子我的智商=" + ZhiShang +",情商=" + QingShang);
}
{
System.out.println("普通代码块");
}
int ZhiShang = 250;
static int QingShang = 666;
static
{
System.out.println("静态代码块");
}
}
控制台输出结果
静态代码块
输出的打印语句
- -----------------------------------------------6------------------------------------------------
public class ClassAndObjectLnitialize {
public static void main(String[] args) {
new ClassAndObjectLnitialize();
System.out.println("输出的打印语句");
}
public ClassAndObjectLnitialize(){
System.out.println("构造方法");
System.out.println("我是熊孩子我的智商=" + ZhiShang +",情商=" + QingShang);
}
{
System.out.println("普通代码块");
}
int ZhiShang = 250;
static int QingShang = 666;
static
{
System.out.println("静态代码块");
}
}
控制台输出结果
静态代码块
普通代码块
构造方法
我是熊孩子我的智商=250,情商=666
输出的打印语句
- -----------------------------------------------7------------------------------------------------
class Father6{
public static int a = 1;
static {
System.out.println("父类粑粑静态代码块");
}
}
class Son6{
public static int b = 2;
static {
System.out.println("子类熊孩子静态代码块");
}
}
public class OverallTest {
static {
System.out.println("Main方法静态代码块");
}
public static void main(String[] args) {
Father6 father6;
System.out.println("======");
father6=new Father6();
System.out.println("======");
System.out.println(Father6.a);
System.out.println("======");
System.out.println(Son6.b);
}
}
控制台输出结果
Main方法静态代码块
======
父类粑粑静态代码块
======
1
======
子类熊孩子静态代码块
2
- -----------------------------------------------8------------------------------------------------
class Demo{
static {
System.out.println("static 静态代码块");
}
}
public class day4 {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader classLoader=day4.class.getClassLoader();
System.out.println(classLoader.getResource(""));
//1、使用ClassLoader.loadClass()来加载类,不会执行初始化块
classLoader.loadClass("com.zdp.demo.practice.test_20191115.Demo");
//2、使用Class.forName()来加载类,默认会执行初始化块
Class.forName("com.zdp.demo.practice.test_20191115.Demo");
//3、使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块
Class.forName("com.zdp.demo.practice.test_20191115.Demo",false,classLoader);
}
}
二:总结
1:(类的加载,连接与初始化)
1、加载:查找并加载类的二级制数据到java虚拟机中
2、连接:验证:确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值,但是到达初始化之前类变量都没有初始化为真正的初始值(如果是被 final 修饰的类变量,则直接会被初始成用户想要的值。)
把类中的符号引用转换为直接引用,就是在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用的过程3、初始化:为类的静态变量赋予正确的初始值
类从磁盘上加载到内存中要经历五个阶段:加载、连接(验证,准备,解析)初始化、使用、卸载
2:(Java程序对类的使用方式可分为两种)
(1)主动使用
(2)被动使用
所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才能初始化他们
- 主动使用
- (1)创建类的实例
- (2)访问某个类或接口的静态变量 getstatic(助记符),或者对该静态变量赋值 putstatic
- (3)调用类的静态方法 invokestatic
- (4)反射(Class.forName(“com.test.Test”))
- (5)初始化一个类的子类
- (6)Java虚拟机启动时被标明启动类的类以及包含Main方法的类
- (7)JDK1.7开始提供的动态语言支持(了解)
- 被动使用
除了上面七种情况外,其他使用java类的方式都被看做是对类的被动使用,都不会导致类的初始化
3:初始化类构造器和初始化对象构造器
始化入口方法。当进入类加载的初始化阶段后,JVM 会寻找整个 main 方法入口,从而初始化 main 方法所在的整个类。当需要对一个类进行初始化时,会首先初始化类构造器(),之后初始化对象构造器()。
初始化类构造器:
JVM 会按顺序收集类变量的赋值语句、静态代码块,最终组成类构造器由 JVM 执行。
初始化对象构造器:
JVM 会按照收集成员变量的赋值语句、普通代码块,最后收集构造方法,将它们组成对象构造器,最终由 JVM 执行。值得特别注意的是,如果没有监测或者收集到构造函数的代码,则将不会执行对象初始化方法。对象初始化方法一般在实例化类对象的时候执行。