Java之父类构造器

当创建任何Java对象时,程序总会依次调用每个父类非静态初始化块/父类构造器执行初始化,最后调用本类的非静态初始化块/构造器执行初始化。
一  隐式调用和显式调用

当调用某个类的构造器来创建Java对象时,系统总会调用父类的非静态初始化块进行初始化。这个调用是隐式执行,而且父类的静态初始化块总是会被执行。接着会调用父类的一个或多个构造器执行初始化,这个调用既可以通过super进行显式调用,也可以是隐式调用。
当所有父类的非静态初始化块/构造器依次调用完成后,系统调用本类的非静态初始化块/构造器执行初始化,最后返回本类的实例。
(Java程序里所有类的最终父类都是java.lang.Object类)

我们来看下面的例子:
public class Creater {
    {
        System.out.println("Creater的非静态初始化块");
    }
    public Creater(){
        System.out.println("Creater无参构造");
    }
    public Creater(String name){
        this();
        System.out.println("Creater带有参数的name构造器"+name);
    }
}
/**
 * 子类
 */
public class Animal extends Creater {
    {
        System.out.println("Animal的非静态初始化块");
    }
    public Animal(String name){
        super(name);
        System.out.println("Animal的有参构造器"+name);
    }
    public Animal(String name,int age){
        this(name);
        System.out.println("Animal带两个参数的构造器"+name+age);
    }
}
/**
 * 继承Animal的子类
 */
public class Wolf extends Animal {
    {
        System.out.println("Wolf的非静态初始化块");
    }
    public Wolf() {
        super("灰太狼",3);

        System.out.println("Wolf的无参构造器");

    }
    public Wolf(double weight){
       this();

        System.out.println("Wolf带参的构造器"+weight);

    }
}
调用:

public class Test {
    public static  void main(String[] args){
     new Wolf(2.5);
    }
}


输出结果:
Creater的非静态初始化块    
Creater无参构造                      (总父类初始化)
Creater带有参数的name构造器灰太狼
Animal的非静态初始化块
Animal的有参构造器灰太狼<span style="white-space:pre">	</span>(wolf的父类初始化)
Animal带两个参数的构造器灰太狼3
Wolf的非静态初始化块
Wolf的无参构造器<span style="white-space:pre">		</span>(wolf初始化)
Wolf带参的构造器2.5
只要在程序中创建Java对象,系统总是先调用最顶层的父类初始化操作,包括初始化块和构造器,然后依次向下调用所有父类的初始化操作,最终执行本类的初始化操作返回本类的实例。
至于调用父类的哪个构造器执行初始化,则分为如下几种情况:
   1子类构造器执行体的第一行代码使用super显式调用父类的构造器,系统将根据super调用里传入的实参列表来确定调用父类的哪个构造器。
2子类构造器执行体的第一行代码使用this显式调用本类中重载的构造器,系统将根据this调用里传入的实参列表来确定本类的另一个构造器(执行本类中另一个构造器时即进入第一种情况)
3子类构造器执行提中既没有super调用,也没有this调用,系统将会在执行子类的构造器之前,隐式调用父类无参数构造器。

super调用用于显式调用父类的构造器,this调用用于显式调用本类中另一个重载的构造器。super和this调用都只能在构造器中使用,而super和this调用都必须作为构造器的第一行代码,因此构造器中的super调用和this调用最多只能使用其中之一,而且最多只能调用一次。    
<think>好的,我现在需要回答用户关于Java加载器的问题。首先,我要确保自己正确理解加载器的概念和作用。加载器是Java运行时环境的一部分,负责动态加载到JVM中。不过具体细节可能有点模糊,我需要回忆一下相关的知识。 首先,加载器的主要作用是将.class文件加载到内存中,并生成对应的Class对象。这样Java程序才能使用这些。不过加载器不仅仅是加载这么简单,它还有不同的层次结构,比如启动加载器、扩展加载器、应用程序加载器,可能还有自定义加载器。每个加载器有不同的加载路径和优先级,这涉及到双亲委派模型。这里可能需要详细解释双亲委派机制,以及它的作用,比如避免重复加载,保证核心库的安全。 然后,用户可能想知道加载器的工作流程。比如,当一个需要被加载时,加载器首先会委托给父类加载器,只有当父类加载器无法完成时,自己才会尝试加载。这样的机制可以防止用户自定义的覆盖核心库,确保安全。但有时候,比如在Tomcat这样的应用服务器中,可能需要破坏双亲委派模型,使用自定义的加载器来实现不同的模块加载,这时候需要解释为什么会有这样的设计。 另外,加载器的种需要分清楚。启动加载器(Bootstrap ClassLoader)是用本地代码实现的,负责加载JVM自身需要的,比如rt.jar。扩展加载器(Extension ClassLoader)负责加载jre/lib/ext目录下的jar包。应用程序加载器(Application ClassLoader)也叫系统加载器,负责加载用户路径(classpath)上的。自定义加载器则由用户继承ClassLoader来实现,可以指定特定的加载路径,比如从网络加载或者加密的class文件。 可能还需要提到加载的过程:加载、连接(验证、准备、解析)、初始化。加载阶段由加载器完成,连接阶段包括验证字节码的正确性,准备阶段分配内存并设置初始值,解析阶段将符号引用转为直接引用,初始化阶段执行构造器<clinit>方法。 另外,加载器的隔离作用。不同的加载器加载的,即使包名和名相同,也会被视为不同的,这样可以在同一个JVM中运行多个互不干扰的应用,比如应用服务器中的不同Web应用。 但可能用户更关注的是加载器的实际应用场景,比如热部署、模块化、OSGi、Tomcat中的加载器结构。这时候需要举一些例子,说明自定义加载器如何实现这些功能。例如,热部署可以通过自定义加载器重新加载修改后的,而无需重启JVM。 不过,我需要注意不要混淆概念。比如双亲委派模型的具体实现方式,可能用户会问如何打破这个模型,这时候需要说明重写loadClass方法或者findClass方法的不同。默认情况下,加载器遵循双亲委派,但自定义加载器可以重写这些方法改变行为。 还要注意可能的问题,比如不同的加载器加载同一个导致的型转换异常,这是因为JVM认为它们是不同的。这时候需要强调加载器在命名空间中的作用。 总结下来,回答的结构应该是先定义加载器,然后分说明不同型的加载器,接着讲解双亲委派模型,再描述加载的过程,最后举例应用场景和自定义加载器的作用。同时,要使用用户易懂的语言,避免过于技术化的术语,必要时举例说明。 需要检查是否有错误,比如启动加载器是否真的由C++实现,是否无法在Java代码中直接引用。扩展加载器的父类是否是启动加载器,而应用程序加载器的父类是扩展加载器。双亲委派模型中的“双亲”是否指的是父类加载器,而不是父类继承关系。 另外,在加载过程中,连接阶段的三个步骤是否正确:验证、准备、解析。验证包括文件格式、元数据、字节码验证等;准备阶段为静态变量分配内存并设置默认值;解析阶段将符号引用解析为直接引用。 还有初始化阶段是执行静态代码块和静态变量的赋值操作,按顺序执行。 最后,确保回答结构清晰,分点说明,帮助用户逐步理解Java加载器的概念、作用、型、机制和应用。</think>Java加载器(ClassLoader)是JVM的核心组件之一,负责**动态加载到内存**并生成对应的`Class`对象。以下是逐步解析: --- ### 一、加载器的作用 1. **加载字节码**:将`.class`文件(本地、网络或其他来源)的二进制数据读入内存。 2. **定义结构**:在方法区创建`Class`对象,作为程序访问元数据的入口。 3. **隔离命名空间**:不同加载器加载的被视为独立命名空间,避免冲突。 4. **安全控制**:通过双亲委派机制保障核心库不被篡改。 --- ### 二、加载器的型 #### 1. 启动加载器(Bootstrap ClassLoader) - **实现**:由C/C++编写,嵌入JVM内核。 - **职责**:加载`JAVA_HOME/lib`目录的核心库(如`rt.jar`)。 - **特点**:Java代码中无法直接引用(返回`null`)。 #### 2. 扩展加载器(Extension ClassLoader) - **实现**:`sun.misc.Launcher$ExtClassLoader`。 - **职责**:加载`JAVA_HOME/lib/ext`目录的扩展。 - **父加载器**:启动加载器。 #### 3. 应用程序加载器(Application ClassLoader) - **实现**:`sun.misc.Launcher$AppClassLoader`。 - **职责**:加载用户路径(`classpath`)下的。 - **父加载器**:扩展加载器。 - **默认加载器**:Java程序的入口通常由它加载。 #### 4. 自定义加载器 - **实现**:继承`ClassLoader`并重写`findClass()`。 - **用途**: - 从非标准来源加载(如网络、加密文件)。 - 实现热部署、模块化(如Tomcat、OSGi)。 --- ### 三、双亲委派模型(Parent Delegation Model) #### 1. 工作流程 当一个加载器收到加载请求时: 1. **委派父类**:先将请求向上委派给父类加载器。 2. **父类处理**:若父类能加载,直接返回结果。 3. **自行加载**:若父类无法加载,才由自己处理。 #### 2. 核心目的 - **避免重复加载**:确保只被加载一次。 - **保护核心库**:防止用户自定义覆盖JDK核心(如`java.lang.String`)。 #### 3. 打破双亲委派 某些场景需破坏该模型(如Tomcat): - **重写`loadClass()`**:自定义加载逻辑。 - **线程上下文加载器**:解决SPI(服务提供接口)加载问题。 --- ### 四、加载过程 #### 1. 加载(Loading) - 查找`.class`文件并读入字节码。 - 在方法区生成`Class`对象。 #### 2. 连接(Linking) - **验证**:检查字节码是否符合JVM规范。 - **准备**:为静态变量分配内存并赋初始值(如`int`默认为0)。 - **解析**:将符号引用转换为直接引用。 #### 3. 初始化(Initialization) - 执行静态代码块(`<clinit>`方法)。 - 为静态变量赋实际值。 --- ### 五、典型应用场景 1. **热部署**:通过自定义加载器重新加载修改后的。 2. **模块化**:OSGi使用加载器实现动态模块化。 3. **容器隔离**:Tomcat为每个Web应用分配独立加载器。 4. **加密保护**:加载加密的`.class`文件,防止反编译。 --- ### 六、示例:自定义加载器 ```java public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] classData = loadClassData(name); // 从自定义路径读取字节码 return defineClass(name, classData, 0, classData.length); } } ``` --- ### 总结 Java加载器通过**层次化结构**和**双亲委派机制**,保障了加载的安全性、隔离性与灵活性。理解其原理有助于解决冲突、实现热更新等高级需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值