深入java虚拟机学习 -- 类的加载机制(三)

简介: 类的初始化时机在上篇文章中讲到了类的六种主动使用方式,反射是其中的一种(Class.forName(“com.jack.test”)),这里需要注意一点:当调用ClasLoader类的loadClass方法对类进行加载的时候,并不是对类的主动调用,不会导致类的初始化。

类的初始化时机

在上篇文章中讲到了类的六种主动使用方式,反射是其中的一种(Class.forName(“com.jack.test”)),这里需要注意一点:当调用ClasLoader类的loadClass方法对类进行加载的时候,并不是对类的主动调用,不会导致类的初始化。

那么接下来我继续给大家2个例子,让我们来看看他们的执行结果分别是什么样的,看看你能猜对吗?

public class Test2
{
    public static void main(String[] args)
    {
        System.out.println(FinalTest2.x);
    }
}

class FinalTest2{
    public static final int x=6/2;

    static {
        System.out.println("I am a final x");
    }
}

public class Test2
{
    public static void main(String[] args)
    {
        System.out.println(FinalTest2.x);
    }
}

class FinalTest2{

    public static final int x=new Random().nextInt();

    static {
        System.out.println("I am a final x");
    }
}

是不是看到结果很意外呢,2份代码看起来几乎是一模一样的,而且都是static修饰的,为什么和上篇文章讲的不一样了呢,为什么第一个demo里面的静态块没有执行呢? 下面让我们带着这一系列问题来解决下。

讲解

如果大家细心的话可以看到多了一个final修饰符,是的,结果的造成就是它在起作用。

public static final int x=6/2; 这行代码,java虚拟机在编译期就可以知道x的值是什么,因此在编译期就已经把3放到了常量池,所以在main方法中调用的时候不会触发类的初始化  即此时的x为编译期的常量
public static final int x=new Random().nextInt();  这行代码中的x在编译期不能确定具体的值,需要等到运行的时候才能确定x的值,所以在运行时会触发类的初始化,即此时的x为编译器的变量

接口和父类

前面讲的类主动加载的7种方式,都是再说单个类的情况,下面我们来介绍下接口和父类

当Java虚拟机初始化一个类的时候,要求他的所有父类都已经被初始化,但是如果此类实现的有接口,则:

  1. 在初始化一个类的时候,并不会先初始化它所实现的接口
  2. 在初始化一个接口的时候,并不会先初始化它的父接口

 因此,一个父接口并不会因为他的子接口或者实现类的初始化而初始化。只有当程序首次使用他的静态变量时,才会导致该接口的初始化。

只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以认为是对类或接口的主动使用。这句话是什么意思呢?下面让我们看下这个Demo

public class Test3
{
    public static void main(String[] args)
    {
        System.out.println(Child.x);
    }
}

class Parent{
    public static int x=3;
    static {
        System.out.println("this is a parent");
    }
}

class Child extends Parent{

    static {
        System.out.println("this is a Child");
    }
}

public class Test3
{
    public static void main(String[] args)
    {
        System.out.println(Child.x);
    }
}

class Parent{
    public static int x=3;
    static {
        System.out.println("this is a parent");
    }
}

class Child extends Parent{
    public static int x=3;
    static {
        System.out.println("this is a Child");
    }
}

上面两个例子同样是相差一行代码,结果却差别很大,一个Child发生了初始化,另一个没有。

双亲委派模型

类加载器用来把类加载到Java虚拟机中,从JDK1.2版本开始,类的加载过程采用双亲委派模型机制,这种机制能更好的保证Java平台的安全,在这种机制中,除了java虚拟机自带的根加载器以外(根加载器由c++实现,没有父加载器),其余的类加载器有且只有一个父加载器。当Java程序请求加载Loader1加载一个类的时候,loader1首先会委托自己的父加载器去加载这个类,若父加载器还有父加载器的话,依次类推,如果父加载器能加在,则有父加载器完成加载,否则才会有loader1本身加载。

需要注意的事,这里的加载器之间的父子关系实际上指的是加载器对象之间的包装关系,并不是类之间的继承关系。一对父子加载器可能是同一个加载器类的2个实例,也可能不是,只是在子加载器对象中包装了一个父加载器对象。

若有一个类加载器能成功加载Sample类,那么这个类加载器被成为定义类加载器,所有能成功返回Clas对象的引用的类加载器(包括定义类加载器)都被称为初始类加载器。

双亲委派模型的有点是能够提高软件系统的安全性。在此机制下,用户自定义的类加载器不可能加载应该由父加载器加载的可靠类,从而防止不可靠甚至恶意的代码代替由父加载器加载的可靠代码。例如:java.lang.Object类总是由根加载器加载,其他任何用户自定义的类加载器都不可能加载该类。

运行时包

由同一类加载器加载的属于相同包的类组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是否相同。只有属于同一个运行时包的类才能相互访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类去访问核心类库的包可见成员。

假设用户自定义了一个类:java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库java.lang.*由不同的加载器加载, 所以他们属于不同的运行时包,因此java.lang.Spy不能访问核心类库java.lang包中的可见成员。

我们下一篇来着重说下如何来实现一个自定义的类加载器

 

开开心心编码,快快乐乐生活。
目录
相关文章
|
13天前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
31 1
|
13天前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
26 0
java基础(13)String类
|
2天前
|
Java
Java Object 类详解
在 Java 中,`Object` 类是所有类的根类,每个 Java 类都直接或间接继承自 `Object`。作为所有类的超类,`Object` 定义了若干基本方法,如 `equals`、`hashCode`、`toString` 等,这些方法在所有对象中均可使用。通过重写这些方法,可以实现基于内容的比较、生成有意义的字符串表示以及确保哈希码的一致性。此外,`Object` 还提供了 `clone`、`getClass`、`notify`、`notifyAll` 和 `wait` 等方法,支持对象克隆、反射机制及线程同步。理解和重写这些方法有助于提升 Java 代码的可读性和可维护性。
|
7天前
|
Java API
Java的日期类都是怎么用的
【10月更文挑战第1天】本文介绍了 Java 中处理日期和时间的三个主要类:`java.util.Date`、`java.util.Calendar` 和 `java.time` 包下的新 API。`Date` 类用于表示精确到毫秒的瞬间,可通过时间戳创建或获取当前日期;`Calendar` 抽象类提供丰富的日期操作方法,如获取年月日及时区转换;`java.time` 包中的 `LocalDate`、`LocalTime`、`LocalDateTime` 和 `ZonedDateTime` 等类则提供了更为现代和灵活的日期时间处理方式,支持时区和复杂的时间计算。
26 14
|
14天前
|
存储 Java
Java编程中的对象和类
【8月更文挑战第55天】在Java的世界中,“对象”与“类”是构建一切的基础。就像乐高积木一样,类定义了形状和结构,而对象则是根据这些设计拼装出来的具体作品。本篇文章将通过一个简单的例子,展示如何从零开始创建一个类,并利用它来制作我们的第一个Java对象。准备好让你的编程之旅起飞了吗?让我们一起来探索这个神奇的过程!
26 10
|
11天前
|
安全 Java 编译器
java访问类字段
java访问类字段
|
13天前
|
Java
java的class类
java的class类
19 5
|
14天前
|
Java 数据处理
Java Scanner 类详解
`Scanner` 类是 Java 中 `java.util` 包提供的强大工具,用于从多种输入源(如键盘、文件、字符串)读取数据。本文详细介绍如何创建 `Scanner` 对象并使用其常用方法(如 `next()`, `nextInt()`, `nextLine()` 等)。通过示例代码展示如何从标准输入、字符串及文件中读取数据,并进行输入验证。使用时需注意关闭 `Scanner` 以释放资源,并确保输入类型匹配,避免异常。掌握 `Scanner` 可显著提升程序的数据处理能力。
|
19天前
|
安全 Java
Java StringBuffer 和 StringBuilder 类详解
在 Java 中,`StringBuffer` 和 `StringBuilder` 用于操作可变字符串,支持拼接、插入、删除等功能。两者的主要区别在于线程安全性和性能:`StringBuffer` 线程安全但较慢,适用于多线程环境;`StringBuilder` 非线程安全但更快,适合单线程环境。选择合适的类取决于具体的应用场景和性能需求。通常,在不需要线程安全的情况下,推荐使用 `StringBuilder` 以获得更好的性能。
|
14天前
|
Java 索引
java基础扫盲-String类常用的方法
java基础扫盲-String类常用的方法
下一篇
无影云桌面