百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 优雅编程 > 正文

深入剖析Java继承中的初始化与构造器调用关系

sinye56 2024-10-19 13:15 8 浏览 0 评论

声明:本文为牛旦教育原创,所有权保留,转载请注明来源。

一个对象有两件事:状态和行为。 类中的实例变量表示其对象的状态。 实例方法表示其对象的行为。 类的每个对象都保持自己的状态。 创建类的对象时,将为在类中声明的所有实例变量以及在所有级别的祖先中声明所有实例变量分配内存。 比如Employee类声明了一个name实例变量。 创建Employee类的对象时,将为其name实例变量分配内存。Mananger为Employee子类, 创建Manager类的对象时,将为其超类Employee中存在的名称字段分配内存。 毕竟,manager的状态与employee的状态类似。 manager的行为类似于employee。 我们来看一个例子。 考虑两个类,U和V,如下所示:

public class U {
	private int id;
	protected String name;
}
public class V extends U {
	protected double salary;
	protected String address;
}

图-1描述了创建U类和V类对象时的内存分配。 当创建类U的对象时,仅为在类U中声明的实例变量分配内存。当创建类V的对象时,为类U和类V中的所有实例变量分配内存。

让我们来看看本文的主要讨论主题,即构造函数(构造器或构造方法)。

重点: 构造函数不是类的成员,它们不会被子类继承。 它们用于初始化实例变量。 创建类的对象时,该对象包含类及其所有祖先的实例变量。 要初始化祖先类的实例变量,必须调用祖先类的构造函数

考虑以下两个类,CSuper和CSub,如清单-1和2所示。 清单-3中的CTest类用于创建CSub类的对象。

清单-1.带有无参数构造函数的CSuper类

 // CSuper.java
package com.newday.inheritance;
public class CSuper {
	public CSuper() {
	 System.out.println("Inside CSuper() constructor.");
	}
}

清单-2. 一个CSub类,它继承自CSuper类并具有No-Args构造函数

 // CSub.java
package com.newday.inheritance;
public class CSub extends CSuper {
	public CSub() {
	 System.out.println("Inside CSub() constructor.");
	}
}

清单-3.一个测试类,它演示了当创建类对象时,从类层次结构顶部开始,其所有祖先的构造函数都会被调用

 // CTest.java
package com.newday.inheritance;
public class CTest {
	public static void main(String[] args) {
	 CSub cs = new CSub();
	}
}

输出结果如下:

Inside CSuper() constructor.

Inside CSub() constructor.

CTest类的输出显示首先调用CSuper类的构造函数,然后调用CSub类的构造函数。 实际上,在CSuper类的构造函数之前调用Object类的构造函数。 你无法打印调用Object类的构造函数的信息,因为Object类不是你的类,因此无法修改它。 问题是,"如何调用CSuper类的构造函数?"这个问题的答案基于这样的规则:当创建类的对象时,为所有实例变量分配内存,包括所有祖先类中的实例变量。 所有类的实例变量必须通过调用其构造函数来初始化。 编译器可以帮助你在很大程度上强制执行此规则。 编译器把对直接祖先类的无参(no-args)构造函数的调用,作为第一个语句添加到类的每个构造函数中。 关键字super用于许多上下文中。 它也指一个类的直接祖先。 如果后跟括号,则指引用超类的构造函数。 如果超类构造函数接受参数,则可以在括号内传递参数列表,类似于方法调用。 以下是调用超类构造函数的示例:

	// 调用超类的无参构造器
	super();
	// 调用一个字符串参数的超类构造器
	super("Hello");
	// 调用带两个double型参数的超类构造器
	super(10.5, 89.2);

可以显式调用超类的构造函数,也可以让编译器为您调用no-args构造函数。 编译CSuper和CSub类时,编译器会修改其构造函数的代码,如清单4和5所示。

清单20-23. 编译器注入一个super()来调用直接祖先的无参构造函数

 // CSuper.java
package com.newday.inheritance;
public class CSuper {
	public CSuper() {
	 super(); // 编译器注入
	 System.out.println("Inside CSuper() constructor.");
	}
}

清单5. 编译器注入一个super()来调用直接祖先的无参构造函数

 // CSub.java
package com.newday.inheritance;
public class CSub extends CSuper {
	public CSub() {
	 super(); // 编译器注入
	 System.out.println("Inside CSub() constructor.");
	}
}

? 提示:

关键字super指的是类的直接祖先。你可以使用super关键字作为构造函数中的第一个语句来调用超类构造函数。

你还可以将no-args构造函数或超类的任何其他构造函数显式调用为类构造函数中的第一个语句。 仅当你没有显式添加一个时,编译器才会注入无参(no-args)构造函数调用。让我们尝试改进Employee和Manager类。 让我们在Employee类中添加一个构造函数,该类接受雇员的名字作为参数。 然后调用新类Employee2,如清单6所示。

清单6. Employee2类,它是原始Employee类的修改版本并具有接受字符串参数的构造方法

 // Employee2.java
package com.newday.inheritance;
public class Employee2 {
 private String name = "Unknown";
 public Employee2(String name) {
 this.name = name;
 }
 public void setName(String name) {
 this.name = name;
 }
 public String getName() {
 return name;
 }
}

我们叫新的Manager类为Manager2,它继承自Employee2类。

	// Manager2.java
	package com.newday.inheritance;
	// 不会编译
	public class Manager2 extends Employee2 {
	 // No code for now
	}

前面的Manager2类代码无法编译。 它会生成类似以下编译时错误:

Error(4,23): constructor Employee2() not found in class com.newday.inheritance.Employee2

尚未为Manager2类添加任何构造函数。 因此,编译器将为它添加一个no-args构造函数。 它还将尝试注入一个super()调用作为no-args构造函数中的第一个语句,它将调用Employee2类的no-args构造函数。 但是,Employee2类没有no-args构造函数。 这就是你收到上一个错误的原因。 在被编译器修改后的Manager2类的代码看起来如下所示。你可能会注意到super()调用无效,因为Employee2类没有no-args构造函数。

	// 编译器注入无参构造函数并调用super()后的Manager2类代码
package com.newday.inheritance;
	// 不会编译
public class Manager2 extends Employee2 {
	// 由编译器注入如下构造器
	public Manager2() {
	 // 由编译器注入的调用Employee2类不存在的无参构造器
	 super();
	}
}

那么,你如何修复Manager2类? 有很多方法可以解决它。 一些可以修复Manager2类的方法如下。

A)可以向Employee2类添加no-args构造函数,就像这样:

public class Employee2 {
	// A no-args constructor
	public Employee2() {
	}
	/* 所有其它代码保持不变 */
}

在向Employee2类添加no-args构造函数之后,Manager2类的代码将编译正常。

B)可向Manager2类添加一个no-args构造函数,并显式调用带有一个String参数的Employee2类的构造函数,如下所示:

public class Manager2 extends Employee2 {
	public Manager2() {
	 // 显示调用Employee2类的构造器
	 super("Unknown");
	}
}

C)可向Manager2类添加构造函数,该类接受String参数并将参数值传递给Employee2类构造函数。 这样,您可以通过将Manager2的name作为参数传递给Employee2构造函数来创建Manager2。

public class Manager2 extends Employee2 {
	public Manager2(String name) {
	 // 显示调用Employee2构造器
	 super(name);
	}
}

通常,第三种实现用于使用了Manager的name来创建Manager2类对象方式。 请注意,Manager2类无权访问Employee2类的name实例变量。 但仍然可以使用super关键字从Manager2类初始化Employee2类中的name实例变量,并调用Employee2类的构造函数。 清单20-26包含将要编译的Manager2类的完整代码。 清单20-27包含测试Manager2类的代码,其输出结果它按预期工作。

? 提示:

每个类必须直接或间接地从其构造函数中调用其超类的构造函数。 如果超类没有no-args构造函数,则必须显式调用超类的任何其他构造函数,如清单-7所示。

清单-7。具有接受字符串参数的构造函数的Manager2类并显式调用Employee2类的构造函数

 // Manager2.java
package com.newday.inheritance;
public class Manager2 extends Employee2 {
	public Manager2(String name) {
	 super(name);
	}
}

清单-8.用于测试Manager2类的测试类

 // Manager2Test.java
package com.newday.inheritance;
public class Manager2Test {
	public static void main(String[] args) {
	 Manager2 mgr = new Manager2("John T");
	 String name = mgr.getName();
	 System.out.println("Manager name: " + name);
	}
}

输出结果:

Manager name: John T

我需要讨论一些关于从子类中使用超类的构造函数的规则。考虑以下类X和Y的定义,它们位于两个不同的包中:

	// X.java
package com.newday.inheritance.pkg1;
	public class X {
	 // X() has package-level access
	 X() {
	 }
}
	// Y.java
package com.newday.inheritance.pkg2;
import com.newday.inheritance.pkg1.X;
	public class Y extends X {
	 public Y() {
	 }
}

Y类的代码无法编译。 它生成编译时错误,如下所示:

Error(7): X() is not public in com.newday.inheritance.pkg1.X; cannot be accessed from outside package

该错误表明类X中的no-args构造函数具有包级访问权限。 因此,无法从类Y中访问它,Y位于不同的包中。 之所以收到此错误,因为编译器将修改类Y的定义,如下所示:

	// 编译器修改了类Y的版本
	// Y.java
package com.newday.inheritance.pkg2;
import com.newday.inheritance.pkg1.X;
public class Y extends X {
	public Y() {
	 //编译器注入了调用X()构造器
	 super();
	}
}

类X的no-args构造函数具有包级访问权限。 因此,只能从com.newday.inheritance.pkg1包访问它。 你如何修正Y类? 在这种情况下建议解决方案很棘手。 解决方案取决于创建类X和类Y后面使用的设计。但是,要编译类Y,必须为类X创建一个构造函数,它具有public访问权限或受保护访问权限,以便它可以被从Y类访问。

这是伴随继承使用构造函数的另一个规则。 必须使用super关键字从类的构造函数内部显式或隐式调用超类构造函数。 但是,从类中访问超类构造函数是由超类构造函数的访问级别控制的。 有时,类的构造函数的访问级别的结果可能是根本无法访问它。 考虑以下名为NoSubclassingAllowed的类的定义:

	public class NoSubclassingAllowed {
	private NoSubclassingAllowed() {
	}
	// 其它相关你代码
	}

NoSubclassingAllowed类已显式声明了私有构造函数。 无法从包括子类在内的任何地方访问私有构造函数。 对于存在的子类,子类必须能够至少调用其超类的一个构造函数。 这得出结论,NoSubclassingAllowed类不能被任何其他类继承。 这是禁用类继承的方法之一。 以下代码将无法编译,它会尝试子类化NoSubclassingAllowed类,该类没有可访问的构造函数:

	// 不会编译,没有可访问的构造函数
public class LetUsTryInVain extends NoSubclassingAllowed {
}

你可能注意到的一件事是没有人可以创建NoSubclassingAllowed类的对象,因为它的构造函数不能从外部访问。 像这样的类提供了创建其对象并将其返回给调用者的方法。 这也是一种控制和封装类的对象创建的方法。最常见的就是单例模式。

可以查看(掌握好Java中this关键字和方法重载的精要),你可以使用this关键字从同一个类的另一个构造函数中调用类的构造函数,并且调用必须是构造函数体中的第一个语句。 当你查看规则以调用同一个类的另一个构造函数和超类的构造函数时,您会发现两者都声明该调用必须是构造函数体内的第一个语句。 这两个规则的结果是,从一个构造函数中,你可以使用this()来调用同一个类的另一个构造函数,或使用super()来调用超类的构造函数,但不能同时调用它们。 此规则还确保始终只调用一次超类的构造函数

相信你已能完全掌握由于继承而引出的实例变量的初始化和构造函数的调用问题了。

就写到这里了,相信一定对有帮助。分享出去吧。

相关推荐

程序员:JDK的安装与配置(完整版)_jdk的安装方法

对于Java程序员来说,jdk是必不陌生的一个词。但怎么安装配置jdk,对新手来说确实头疼的一件事情。我这里以jdk10为例,详细的说明讲解了jdk的安装和配置,如果有不明白的小伙伴可以评论区留言哦下...

Linux中安装jdk并配置环境变量_linux jdk安装教程及环境变量配置

一、通过连接工具登录到Linux(我这里使用的Centos7.6版本)服务器连接工具有很多我就不一一介绍了今天使用比较常用的XShell工具登录成功如下:二、上传jdk安装包到Linux服务器jdk...

麒麟系统安装JAVA JDK教程_麒麟系统配置jdk

检查检查系统是否自带java在麒麟系统桌面空白处,右键“在终端打开”,打开shell对话框输入:java–version查看是否自带java及版本如图所示,系统自带OpenJDK,要先卸载自带JDK...

学习笔记-Linux JDK - 安装&配置

前提条件#检查是否存在JDKrpm-qa|grepjava#删除现存JDKyum-yremovejava*安装OracleJDK不分系统#进入安装文件目...

Linux新手入门系列:Linux下jdk安装配置

本系列文章是把作者刚接触和学习Linux时候的实操记录分享出来,内容主要包括Linux入门的一些理论概念知识、Web程序、mysql数据库的简单安装部署,希望能够帮到一些初学者,少走一些弯路。注意:L...

测试员必备:Linux下安装JDK 1.8你必须知道的那些事

1.简介在Oracle收购Sun后,Java的一系列产品就被整合到Oracle官网中,打开官网乍眼一看也不知道去哪里下载,还得一个一个的摸索尝试,而且网上大多数都是一些Oracle收购Sun前,或者就...

Linux 下安装JDK17_linux 安装jdk1.8 yum

一、安装环境操作系统:JDK版本:17二、安装步骤第一步:下载安装包下载Linux环境下的jdk1.8,请去官网(https://www.oracle.com/java/technologies/do...

在Ubuntu系统中安装JDK 17并配置环境变量教程

在Ubuntu系统上安装JDK17并配置环境变量是Java开发环境搭建的重要步骤。JDK17是Oracle提供的长期支持版本,广泛用于开发Java应用程序。以下是详细的步骤,帮助你在Ubuntu系...

如何在 Linux 上安装 Java_linux安装java的步骤

在桌面上拥抱Java应用程序,然后在所有桌面上运行它们。--SethKenlon(作者)无论你运行的是哪种操作系统,通常都有几种安装应用程序的方法。有时你可能会在应用程序商店中找到一个应用程序...

Windows和Linux环境下的JDK安装教程

JavaDevelopmentKit(简称JDK),是Java开发的核心工具包,提供了Java应用程序的编译、运行和开发所需的各类工具和类库。它包括了JRE(JavaRuntimeEnviro...

linux安装jdk_linux安装jdk软连接

JDK是啥就不用多介绍了哈,外行的人也不会进来看我的博文。依然记得读大学那会,第一次实验课就是在机房安装jdk,编写HelloWorld程序。时光飞逝啊,一下过了十多年了,挣了不少钱,买了跑车,娶了富...

linux安装jdk,全局配置,不同用户不同jdk

jdk1.8安装包链接:https://pan.baidu.com/s/14qBrh6ZpLK04QS8ogCepwg提取码:09zs上传文件解压tar-zxvfjdk-8u152-linux-...

运维大神教你在linux下安装jdk8_linux安装jdk1.7

1.到官网下载适合自己机器的版本。楼主下载的是jdk-8u66-linux-i586.tar.gzhttp://www.oracle.com/technetwork/java/javase/downl...

window和linux安装JDK1.8_linux 安装jdk1.8.tar

Windows安装JDK1.8的步骤:步骤1:下载JDK打开浏览器,找到JDK下载页面https://d.injdk.cn/download/oraclejdk/8在页面中找到并点击“下载...

最全的linux下安装JavaJDK的教程(图文详解)不会安装你来打我?

默认已经有了linux服务器,且有root账号首先检查一下是否已经安装过java的jdk任意位置输入命令:whichjava像我这个已经安装过了,就会提示在哪个位置,你的肯定是找不到。一般我们在...

取消回复欢迎 发表评论: