关于java继承(深入解析父类属性的抽取与构造函数的作用)

在这里插入图片描述

目录

  • 前言
  • 基础
    • 继承
    • 作用
  • 理论分析
    • 父类属性的抽取
    • 构造函数
      • 调用父类构造函数会不会创建一个父类的对象?
      • 生命周期角度
      • 谁的属性用谁的构造函数初始化
  • 示例解析
    • 代码
    • 代码调试展示
    • 构造函数初始化成员变量
  • 总结

前言

    在Java中,继承是一项至关重要的特性,它允许创建具有层次结构的类,从而更有效地组织和管理代码。通过继承,子类可以获取父类的属性和方法,实现代码重用和扩展。
    本文主要想从属性和构造函数的角度分析Java的继承机制。

基础

继承

先说下继承的内容:
    继承定义了类如何相互关联,共享特性。继承的工作方式是定义父类和子类,或叫做基类和派生类,其中子类继承了父类的所有特性。子类不但继承了父类的所有特性,还可以定义新的特性。

    如果子类继承于父类
第一、子类拥有父类非private的属性和功能
第二、子类具有自己的属性和功能,即子类可以扩展父类没有的属性和功能
第三、子类还可以以自己的方式实现父类的功能(方法重写)。

    对象的继承代表了一种is-a的关系,如果两个对象A和B,可以描述为‘B是A’,则表明B可以继承A。‘猫是哺乳动物’,就说明了猫与哺乳动物之间继承与被继承的关系。实际上,继承者还可以理解为是对被继承者的特殊化,因为它除了具备被继承者的特性外,还具备自己独有的个性。

作用

    本文主要讲解父类属性的抽取和构造函数的作用,所以先介绍一下它们:
1、父类属性的作用:
    父类属性是指在父类中定义的属性,它们可以被所有子类共享和继承。通过抽取公共属性到父类中,可以减少子类中的重复代码,提高代码的可维护性和可读性。

2、父类构造函数的作用:
    父类构造函数用于初始化父类中的属性。当创建子类对象时,父类的构造函数会被自动调用,以确保父类属性被正确初始化。在子类构造函数中,可以使用super关键字来显式调用父类的构造函数。如果子类构造函数中没有显式调用父类构造函数,则会自动调用父类的无参构造函数(如果存在的话)。

理论分析

父类属性的抽取

    当大多数子类都拥有某些属性,而只有少数子类没有这些属性时,是否应该将这些属性抽取到父类中?
    这是一个设计决策的问题。可以从以下几方面去考虑

1. 代码重用和一致性:
    将这些属性抽取到父类中可以实现代码重用,因为所有(或大多数)子类都将继承这些属性。这也有助于保持一致性,因为所有相关的子类都将以相同的方式访问和修改这些属性。
2. 类的复杂性和可维护性:
    如果父类包含了许多子类并不需要的属性,它可能会变得过于复杂,这可能会增加维护和理解的难度。然而,如果大多数子类都需要这些属性,并且它们对于理解类的行为很重要,那么将它们放在父类中可能会使类结构更清晰。
3. 灵活性和可扩展性:
    将属性放在父类中可能会限制灵活性,因为所有子类都将继承这些属性,即使某些子类并不需要它们。如果未来需要添加新的子类,并且这些子类不需要这些属性,那么将它们放在父类中可能会增加不必要的复杂性。另一方面,如果未来需要为大多数子类添加新的属性,并且这些属性与现有属性相关,那么将它们放在父类中可能会更容易实现。
4. 业务需求的稳定性:
    如果业务需求相对稳定,并且大多数子类都将长期需要这些属性,那么将它们放在父类中可能是一个好选择。如果业务需求经常变化,或者未来可能会有更多的子类不需要这些属性,那么将它们单独定义在需要它们的子类中可能更灵活。
5. 接口和组合的使用:
    作为替代方案,可以考虑使用接口来定义这些属性,并通过组合而不是继承来将这些属性添加到需要的子类中。这可以提供更大的灵活性,因为子类可以选择实现哪些接口,并且可以在不修改父类的情况下添加或删除属性。

    也就是说这不是写死的内容,是一个可以动态调整的方案,如果为了满足大多数(比如1000个子类里有999个都有相同的属性)可以向上抽,但要知道为此付出的代价是什么,抽取到父类后,即使少数不需要这些属性的子类也会继承这些属性,导致一定的内存开销。这种开销在大多数情况下是可以接受的,特别是当这些属性不是非常大或复杂的数据结构时。如果不抽取到父类,而是在每个需要的子类中分别定义这些属性,修改时可能只需要修改特定的子类,但会导致代码重复和难以维护。也就是需要去考虑到底是复用的代价高还是不复用未来需要修改的时候直接去改付出的代价更高。
    是否将这些属性抽取到父类并没有一个绝对的答案,而是需要根据具体的应用场景、性能要求、代码复用与可维护性需求等多个因素进行权衡和决策。

构造函数

    在Java中,当创建一个子类对象时,首先需要调用父类的构造函数来初始化从父类继承来的部分。这是因为子类继承了父类的属性和方法,而这些属性和方法(特别是非静态的实例属性和方法)是依赖于对象的实例来存在的。因此,在子类对象被创建之前,必须先确保其父类部分也被正确初始化。

调用父类构造函数会不会创建一个父类的对象?

    调用父类的构造函数主要是用于初始化子类对象中父类部分的属性和方法。对象的创建是通过构造函数来完成的,但构造函数本身并不等同于对象。构造函数是一个特殊的方法,用于在对象创建时初始化对象的状态。

    当创建一个子类对象时,以下步骤会发生:

  1. 分配内存空间给新的子类对象。
  2. 调用父类的构造函数来初始化子类对象中父类部分的属性和方法。这一步是隐式进行的,除非显式地在子类的构造函数中使用了super关键字来调用父类的另一个构造函数
  3. 执行子类构造函数中的其余代码,以初始化子类特有的属性和方法。
    重要的是要理解,尽管子类对象在内存中是独立的,但它包含了从父类继承来的属性和方法。这些属性和方法是子类对象的一部分,但它们是通过父类的构造函数来初始化的。

    即使子类没有显式地调用父类的构造函数(通过super),Java编译器也会自动插入一个对父类无参构造函数的调用(如果父类有一个可访问的无参构造函数的话)。如果父类没有无参的构造函数,或者无参构造函数不是公开的,那么子类必须显式地调用一个存在的、可访问的父类构造函数,否则编译器会报错。

    总结来说:继承允许一个类(子类)获得另一个类(父类)的属性和方法。但这并不意味着在内存中会为每个子类对象都创建一个对应的父类对象。相反,子类对象在其内部结构中包含了从父类继承的属性和方法,这些属性和方法是通过子类对象的引用来访问的。
    当创建一个子类对象时,Java虚拟机会为该对象分配内存,并根据子类的定义(包括从父类继承的部分)来初始化该对象的内部状态。这个过程中,父类的构造函数被调用以确保从父类继承的属性和方法被正确初始化。但是,这个初始化过程是在子类对象的上下文中进行的,而不是在独立的父类对象上进行的。

生命周期角度

    上面的问题如果结合生命周期来说

    当创建一个子类对象时,Java虚拟机会加载并链接相关的类(包括父类和子类),以确保它们准备好在运行时被使用(类加载过程)。这个过程中,父类的.class文件会被读取,并且父类中定义的属性和方法会被加入到类的内部表示中(通常称为类的元数据或类对象)。然后,当子类对象被创建时,它会包含指向这些从父类继承的属性和方法的引用。

    这些引用允许子类对象在需要时调用父类的方法或访问父类的属性。实际上,当子类对象调用一个从父类继承的方法时,Java虚拟机会使用动态绑定(也称为运行时多态性)来找到并调用正确的父类方法实现。

    也就是说子类对象“共享”父类的属性和方法,这里共享主要是指子类对象在运行时能够访问和使用这些从父类继承的属性和方法,而这些属性和方法的定义是存储在父类的.class文件中的。这种“共享”是通过Java的类加载机制、运行时环境和动态绑定来实现的。

    在对象创建时,内存空间被分配,但对象还没有被完全初始化。对象初始化是通过调用构造函数来完成的,这包括调用父类的构造函数来初始化从父类继承的部分。

父类生命周期:
创建父类对象 ---- 初始化父类对象 ---- 使用父类对象 ---- 回收父类对象

子类生命周期:
创建子类对象 ---- (调用父类构造函数初始化从父类继承的部分) ---- 初始化子类特有的部分 ---- 使用子类对象 ---- 回收子类对象

    子类对象的创建和初始化过程涉及到了对父类构造函数的调用,但这并不表示创建了一个独立的父类对象。这个过程是在为子类对象设置其从父类继承的属性和方法。

谁的属性用谁的构造函数初始化

    父类属性的初始化:父类中定义的属性应该由父类的构造函数来初始化。当创建子类对象时,如果子类构造函数没有显式调用父类的其他构造函数(通过super()),那么它会自动调用父类的无参构造函数(如果存在)。这样,父类构造函数就会执行,从而初始化父类定义的属性。
    子类属性的初始化:子类中可以定义自己的属性,这些属性应该由子类的构造函数来初始化。子类构造函数在执行时,可以首先调用父类的构造函数(通过super()),以确保父类部分被正确初始化。然后,子类构造函数可以继续初始化子类自己定义的属性。
    继承与属性初始化:当子类继承父类时,子类对象将包含父类定义的属性(除非它们是私有的,在那种情况下,子类无法直接访问它们,但它们仍然存在于子类对象中)。然而,这些父类属性的初始化是由父类的构造函数来完成的,而不是子类的构造函数。子类构造函数只负责初始化子类自己定义的属性,并通过调用父类构造函数来确保父类部分的正确初始化。

示例解析

代码

//父类
public abstract class Parent {//所有子类共有的属性protected String commonProperty;//构造方法public Parent(String commonProperty) {this.commonProperty = commonProperty;}//大多数子类共有的属性,一部分子类没有protected int sharedByMostProperty;public void setSharedByMostProperty(int sharedByMostProperty){this.sharedByMostProperty=sharedByMostProperty;}public int getSharedByMostProperty(){return sharedByMostProperty;}//抽象方法abstract  void doSomething();//普通方法public void showProperties(){System.out.println("所有子类共有的属性"+commonProperty);System.out.println("大部分子类共有的属性"+sharedByMostProperty);}}
//子类A有所有子类的属性,也拥有大部分子类都有的属性
public class ChildA extends Parent{//子类A拥有所有子类的属性,也拥有大部分子类都有的属性public ChildA(String commonProperty,int sharedByMostProperty){super(commonProperty); 可以通过父类的setter设置,也可以直接在这里赋值this.sharedByMostProperty=sharedByMostProperty;}@Overridevoid doSomething() {System.out.println("子类A实现抽象方法");}}
//子类B
public class ChildB extends Parent{// 子类B特有的属性private double uniqueToChildB;
//给父类共有属性赋值,和自己独有属性赋值public ChildB(String commonProperty,double uniqueToChildB){super(commonProperty);this.uniqueToChildB=uniqueToChildB;}@Overridevoid doSomething() {System.out.println("子类B实现抽象方法");}//子类独有的方法public void doSomethingElse(){System.out.println("子类独有的方法");/*System.out.println("从父类继承的大部分子类共有的属性: " + this.sharedByMostProperty);*/}
}
//客户端
public class Client {public static void main(String[] args) throws InterruptedException {//创建子类A的实例Parent childA=new ChildA("Common Value", 42);//childAchildA.doSomething();childA.showProperties();//创建子类B的实例,调用子类独有方法,必须创建子类型变量ChildB childB=new ChildB("Common Value", 3.14);childB.doSomething();//ChildB 的实例仍然有一个 sharedByMostProperty 属性,只是它的值将是默认初始值childB.showProperties();childB.doSomethingElse();}
}

这个例子我想说明的问题是:
当创建一个子类对象时,该对象内部已经包含了父类部分的所有属性和方法(除非被子类覆盖或隐藏)。这意味着子类对象可以直接调用这些继承自父类的方法,而无需创建额外的父类对象。

    当子类调用从父类继承下来的方法(没有重写),执行的是父类中的方法实现。这个过程是通过子类对象内部的引用(或称为“指针”)来完成的,它指向父类方法的具体实现。子类调用继承自父类的方法时,实际上是在操作子类对象本身,而不是创建一个新的父类对象。子类对象只是借用了父类的方法来实现某些功能,是不会实例化父类的。

    这一点是面向对象编程中继承机制的核心之一,它允许代码的重用和扩展,同时避免了不必要的对象创建和内存开销。
##字节码展示
在这里插入图片描述
在上面的字节码文件中可以看到

    在子类ChildB的构造函数(Ljava/lang/String;D)V中,有一条
INVOKESPECIAL org/example/test/sharedByMostProperty/Parent. (Ljava/lang/String;)V指令。
    这条指令表示ChildB的构造函数在初始化自己之前,会先调用父类Parent的构造函数来初始化父类部分。
    传递给父类构造函数的参数(在这里是一个String类型的参数)表明ChildB至少有一个与父类构造函数相对应的属性或参数,这通常是父类中定义的属性。

代码调试展示

    当子类调用从父类继承下来的方法(没有重写),执行的是父类中的方法实现。这里也显示并没有创建父类对象
在这里插入图片描述
    客户端代码调试,只有子类对象,没有父类的,即便调用的是从父类继承下来的方法(未重写)
在这里插入图片描述

构造函数初始化成员变量

    当创建一个对象时,Java虚拟机会为对象的实例变量分配内存空间,并根据变量的类型自动为它们赋予初始值。这些初始值是在对象内存空间分配时自动设置的,与构造函数无关。
在这里插入图片描述

    接下来,父类构造函数被调用,这一步是确保父类部分被正确初始化的关键。
    如果构造函数中为某个属性赋值了,那么这个赋值操作会覆盖该属性在内存分配时自动赋予的初始值。换句话说,构造函数中的赋值是先有了初值(由JVM自动赋予),然后又被构造函数中的代码赋予了新的、理想的值。
(子类将参数值通过父类构造函数传递给父类)
在这里插入图片描述

上面的过程总结:
构造函数调用之前,实例变量已经被JVM赋予了初值。
构造函数中的赋值操作会覆盖这些初值。
如果构造函数中没有为某个属性赋值,则该属性保留其初值。

总结

    本文主要想从属性和构造函数的角度去解析继承关系中子类与父类的关系。希望本文能为读者在Java面向对象编程的道路上提供一点点帮助,也请大家不吝赐教,分享您的见解与经验,共同学习进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/455826.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解23种设计模式——第二部分:结构型模式

目录 3 结构型模式 3.1 代理模式 3.2 适配器模式 3.2.1 默认适配器模式 3.2.2 对象适配器模式 3.2.3 类适配器模式 3.2.4 适配器模式总结 3.3 桥梁模式 3.4 装饰模式 3.4 门面模式 3.5 组合模式 3.6 享元模式 3.7 结构型模式总结 接上一篇:详解23种设计…

openrtp 音视频时间戳问题

解决音视频发送的rtp问题 openrtp增加了音频aac的发送,地址 OpenRTP Gitee开源地址 同时使用两个rtp ,来发送音频和视频 使用以下音频rtp,是可以发送和接收的,音频端口在视频端口上2 v0 o- 0 0 IN IP4 127.0.0.1 sMy Stream cI…

Windows通过netsh控制安全中心防火墙和网络保护策略

Windows通过netsh控制安全中心防火墙和网络保护策略 1. 工具简介 【1】. Windows安全中心 【2】. netsh工具 netsh(Network Shell) 是一个Windows系统本身提供的功能强大的网络配置命令行工具。 2. 开启/关闭防火墙策略 在设置端口(禁用/启用)前&am…

使用 CDN 后 Apache 的日志记录客户真实 IP

经常搭建网站服务器的都知道,在给站点使用了 CDN 后 Web 应用的日志记录里就会只记录 CDN 节点 IP 了,这就没法看到真实客户请求 IP,对于日志分析、运维日常维护来说就有点儿麻烦了,今天明月结合在五洛云服务器上搭建的Apache环境…

多ip访问多网站

多IP访问多网站 1.预配操作 [rootlocalhost ~]# mount /dev/sr0 /mnt mount: /mnt: WARNING: source write-protected, mounted read-only. [rootlocalhost ~]# systemctl stop firewalld ----------关闭防火墙 [rootlocalhost ~]# setenforce 0 -------关闭selinux2.安装n…

【论文阅读】ESRGAN

学习资料 论文题目:增强型超分辨率生成对抗网络(ESRGAN: Enhanced Super-Resolution Generative Adversarial Networks)论文地址:[1809.00219] ESRGAN:增强型超分辨率生成对抗网络代码:xinntao / ESRGAN:ECCV18 研讨会 - 增强的 SRGAN。Champion PIRM Challenge 关于感知…

【机器学习】VQ-VAE(Vector Quantized Variational Autoencoder)

VQ-VAE(Vector Quantized Variational Autoencoder)是一种生成模型,它结合了变分自编码器(Variational Autoencoder, VAE)和向量量化(Vector Quantization)技术。VQ-VAE的主要目的在于通过离散潜…

【动态规划】子序列问题(上)

1. 最长递增子序列 300. 最长递增子序列 和子数组不同的是,子数组要求是连续的,子序列只要下标是递增的就可以,这里严格递增的意思是不能有相等的元素,必须一直递增 状态表示:以 i 位置为结尾的所有的子序列中最长递…

Android GPU Inspector分析帧数据快速入门

使用 谷歌官方工具Android GPU Inspector (AGI) 可以对Android 应用进行深入和全面的系统性能分析和帧性能分析 。AGI 是一个非常强大的分析工具,尤其是在需要诊断 GPU 性能问题和优化应用时,可以帮助你精准找到性能瓶颈。本文介绍如何使用该工具对帧数据…

梳理一下spring中,与message相关的知识点

本次梳理的相关知识点包括jms,amqp(rabbitmq),sping-messaging,spring-integration,springcloud-stream,这些都是与消息message相关的内容,它们有什么区别与联系呢? 相关的要点与相互关系都整理…

物联网消息队列Emqx日志配置及日志追踪以及Centos7上的rc.local开机不执行、git提交的小问题

一、物联网消息队列Emqx日志配置及日志追踪 EMQX支持将日志输出到控制台或者日志文件,或者同时使用两者。使用 Docker 部署 EMQX,默认只能通过 docker logs 命令查看 EMQX 日志。EMQX 的默认日志级别为 warning,默认在单日志文件超过10MB(log…

word压缩大小怎么弄?快来试试这几种压缩word方法!

word压缩大小怎么弄?在处理Word文档时,如果遇到体积过大的情况,无疑会带来一系列麻烦,大型Word文档不仅占据大量存储空间,而且在传输过程中会耗费更多时间,想象一下,当你急需将一份重要的文档发…

Perl打印9x9乘法口诀

本章教程主要介绍如何用Perl打印9x9乘法口诀。 一、程序代码 1、写法① use strict; # 启用严格模式,帮助捕捉变量声明等错误 use warnings; # 启用警告,帮助发现潜在问题# 遍历 1 到 9 的数字 for my $i (1..9) {# 对于每个 $i,遍历 1…

【设计模式系列】观察者模式

一、什么是观察者模式 观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这种模式也被称为发布-订阅模式&…

【AscendC算子开发】笔记2 算子高级开发和调试调优

算子调试 Tensor也可以通过特定的printf方法来打印,见上图。 gdb调试见上图。 为什么gdb调试无法成功,因为run.sh里面有两行export,如果直接通过.XX运行的话需要配置一下。 npu域也支持调试,可以使用上述的方法。 内存检测工…

AI自动生成PPT哪个软件好?智能生成PPT不再熬夜做课件

大概这世上,都是职场牛马对“PPT”这三个字母的头痛反应最大吧! 是的,就连各个年级段的老师也是很头痛——愁着怎样能在排版整齐的情况下,将必考知识点都呈现在PPT每一张幻灯片页面里...... 近期打听到用人工智能生成ppt课件&am…

ProtoBuf 的含义和安装

ProtoBuf 是什么 Protocol Buffers 是 Google 的⼀种语⾔⽆关、平台⽆关、可扩展的序列化结构数据的⽅法,它可⽤ 于(数据)通信协议、数据存储等。 Protocol Buffers 类⽐于、 XML,是⼀种灵活,⾼效,⾃动化机…

Java项目-基于springboot框架的智慧外贸系统项目实战(附源码+文档)

作者:计算机学长阿伟 开发技术:SpringBoot、SSM、Vue、MySQL、ElementUI等,“文末源码”。 开发运行环境 开发语言:Java数据库:MySQL技术:SpringBoot、Vue、Mybaits Plus、ELementUI工具:IDEA/…

2024年最新苹果iOS证书申请创建App详细图文流程

iOS 证书设置指南: 对于开发者来说,在没有Mac电脑或对Xcode等开发工具不熟悉的情况下,如何快速完成IOS证书制作和IPA文件提交至开发者中心一直是一个难题。但是现在,有了初雪云提供的极简工具,您可以轻松实现这两个任…

Tomcat隐藏版本号和报错信息

为了避免漏洞扫描的时候造成版本泄露&#xff0c;可以在conf/server.xml配置文件中的<Host>配置项中添加如下配置: <Valve className"org.apache.catalina.valves.ErrorReportValve" showReport"false" showServerInfo"false" /> …