再谈super、static、final

news2024/9/22 10:05:50

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

从一道面试题说起

public class FuTest {

    public static void main(String[] args) {
        // 猜猜打印的内容
        Zi zi = new Zi();
    }

    static class Fu {
        int a = 10;

        public void printA() {
            System.out.println("Fu PrintA:" + a);
        }

        public Fu() {
            printA();
        }
    }

    static class Zi extends Fu {
        int a = 20;

        @Override
        public void printA() {
            System.out.println("Zi PrintA:" + a);
        }

        public Zi() {
            printA();
        }
    }
}

我想大部分人应该会猜错。在我解释之前,希望大家把代码复制到本地,断点调试一下,这很重要。

img

当我们断点跟踪时,会发现程序运行的大致顺序是:

  • 初始化Fu的int a
  • 调用Fu的构造方法,执行printA()
  • 调用Zi的printA():打印 zi.a = 0(因为zi的a还没初始化,默认0)
  • 初始化Zi的int a
  • 调用Zi的构造方法,执行printA()
  • 调用Zi的printA():打印zi.a = 20

我们知道,子类实例化时会隐式调用父类构造器进行初始化工作,如果把这个过程显式化,就是这样:

img

Zi的构造器中,加不加super()都会调用父类构造器进行初始化,并且如果显式调用super(),则必须放在第一行。

img

要想解决上面这个面试题,有两个难点要搞清楚:

  • 字段的初始化时机
  • 方法重写

字段的初始化时机

为了更全面地认识字段的初始化时机,我们改一下上面的程序:

public class FuTest {

    public static void main(String[] args) {
        Zi zi = new Zi();
    }

    static class Fu {
        // 新增static变量
        static int FU_STATIC_A = 10;
        int a = 10;

        public void printA() {
            System.out.println("Fu PrintA:" + a);
        }

        public Fu() {
            printA();
        }
    }

    static class Zi extends Fu {
        // 新增static变量
        static int ZI_STATIC_A = 20;
        int a = 20;

        @Override
        public void printA() {
            System.out.println("Zi PrintA:" + a);
        }

        public Zi() {
            // 为了方便观察,显式调用super()
            super();
            printA();
        }
    }
}

重新断点调试,会发现执行顺序是:

  • main方法执行 Zi zi = new Zi()

  • 初始化FU_STATIC_A

  • 初始化ZI_STATIC_A

  • 执行Zi构造器

    • 初始化Fu
      • 初始化Fu的int a
      • 调用Fu的构造器
      • 调用printA(),实际调用Zi的printA()
    • 初始化Zi的int a
    • 调用Zi的构造器
    • 调用printA(),实际调用Zi的printA()

总得来说,分为几个阶段:

  • 类加载

    • 先加载父类
      • 初始化static修饰的字段
    • 后加载子类
      • 初始化static修饰的字段
  • 对象初始化

    • 先初始化父“对象”
      • 初始化父“对象”普通字段
      • 调用父“对象”构造器
    • 再初始化子对象
      • 初始化子对象普通字段
      • 调用子对象构造器

类加载阶段所做的事情,大家在学习JVM时都接触过:

img

类加载的最后阶段,会进行初始化,也就是static相关的一切操作(因为static的操作都是伴随着类加载进行,所以我们说 static是属于类的)。

public class FuTest {

    public static void main(String[] args) {
        // 0:发现要new Zi,而此时内存中没有Zi这个类,而Zi又继承了Fu,所以会先加载 Fu、再加载 Zi(注意,此时只是类加载!)
        // 5:【类加载并初始化】完毕,开始【对象创建和初始化】
        Zi zi = new Zi();
    }

    static class Fu {
        // 类加载1:加载Fu,给Fu的静态字段默认初始化
        static int FU_STATIC_A = 10;

        static {
            // 类加载2:调用static代码块,给Fu静态字段初始化
            FU_STATIC_A = 11;
        }

        // 对象初始化7:初始化fu普通字段
        int a = 10;

        public void printA() {
            System.out.println("Fu PrintA:" + a);
        }

        public Fu() {
            // 对象初始化8:调用fu构造器
            printA();
        }
    }

    static class Zi extends Fu {
        // 类加载3:加载Zi,给Zi的静态字段默认初始化
        static int ZI_STATIC_A = 20;

        static {
            // 类加载4:调用static代码块,给Zi静态字段初始化
            ZI_STATIC_A = 21;
        }

        // 对象初始化9:初始化zi普通字段
        int a = 20;

        @Override
        public void printA() {
            System.out.println("Zi PrintA:" + a);
        }

        public Zi() {
            // 对象初始化6:优先初始化父对象
            super();
            // 对象初始化9:zi构造器执行完毕
            printA();
        }
    }
}

方法重写

最后再来解释一下为什么调用Fu构造器时,最终调用的是Zi的printA(),而不是Fu的printA()。其实就是上一篇讲到的 虚方法表。因为Zi重写了Fu的printA(),那么通过Zi类实例invoke方法时,就会直接调用Zi类重写的方法。而方法打印的字段,一定是调用者this所在的字段(方法执行时,会根据this找到目标对象并处理)!

img

final的作用

final的作用主要3个:

  • final class,不允许extends
  • final method,不允许override
  • final field,不允许change

其实final本质上就做一件事:把任何动态的统统变成静态的,把不确定的变成确定的。以final method为例,当一个方法被final修饰,那么子类就不允许重写了,所以obj.method()调用时就是确定的。

img

比如Person也可以调用wait(),但此时查虚方法表只能查到Object原始的wait(),最终是往Object的wait()去了。

final和static实战

实际开发中,final和static组合使用的场景居多:

class XxxService {
    // 当我们需要一个 静态常量 时,可以这样写
    private static final int a = 1;
    
    // 省略...
}

public final class ConnectionUtils {
    
    private ConnectionUtils() {}

    // 全局只要一个tl对象,而且final不允许改变
    private static final ThreadLocal<Connection> tl = new ThreadLocal<>();

    private static final BasicDataSource DATA_SOURCE = new BasicDataSource();

    // 对于static final修饰的DATA_SOURCE,希望做一些较为复杂的赋值工作,可以挪到静态代码块
    static {
        DATA_SOURCE.setDriverClassName("com.mysql.jdbc.Driver");
        DATA_SOURCE.setUrl("jdbc:mysql://localhost:3306/demo");
        DATA_SOURCE.setUsername("root");
        DATA_SOURCE.setPassword("123456");
    }
}

final和static单独使用的场景,无非就是 final表示“不能更改”,static表示“属于类”。

public final class EnumUtil { // 工具类,没必要继承(当然,这玩意可写可不写)
    
}

public void method() {
    final long userId = 1L; // 不希望这个值被后面的语句覆盖(也是可写可不写)
    // ...
    
}

// 如果你有需求,不希望子类覆盖某个方法,要么用private,要么用final,取决于你要不要暴露这个方法

另外,static有个比较特别的用法,用来修饰内部类。一般来说,static是无法修饰class的:

img

但却可以修饰内部类:

public class UserDTO {
    
    private String name;
    private Department department;
    
    // 比如对于一个Response的TO,内部有个字段需要一个TO表示,且只会在你这个接口里使用,就没必要定义为公共类
    static class Department {
        private String name;
    }
    
}

静态内部类的好处是,外部调用者在new的时候无需实例化外部类:

img

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602
进群,大家一起学习,一起进步,一起对抗互联网寒冬

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

app小程序定制开发的优势|企业软件网站建设

app小程序定制开发的优势|企业软件网站建设 小程序定制开发是目前互联网行业中备受关注的领域之一。随着智能手机的普及和移动互联网的迅猛发展&#xff0c;越来越多的企业和个人开始重视小程序的潜力&#xff0c;并积极寻求定制开发的服务。那么&#xff0c;为什么小程序定制开…

光谱图像常见评价指标

光谱图像常见评价指标 SAM&#xff08;Spectral Angle Mapper&#xff09;RMSE——Root Mean Square ErrorPSNRSSIMMSSIMEGARS SAM&#xff08;Spectral Angle Mapper&#xff09; ​ SAM算法是由Kruse等[146]在1993年提出&#xff0c;把图像中的每个像元的光谱视为一个高维向…

为什么越来越多人选择学习Python?

今天我要和大家聊聊一个很热门的话题&#xff1a;为什么那么多人学习Python&#xff1f; 最近小编发现一个有趣的现象&#xff0c;高中生们居然在学校课程里学Python&#xff0c;这不仅给我们这些已经毕业多年的人当头一棒&#xff0c;更是彻底颠覆了传统观念。现在的高中生竟…

国产化区块链平台-FISCO BCOS 区块链

目录 FISCO BCOS 版本信息 系统概述 关键特性 组件服务 开发运维工具 FISCO BCOS作为一种企业级区块链平台&#xff0c;为企业和组织提供了高性能、隐私保护和可定制的区块链解决方案。其强大的架构和丰富的功能使得企业能够在安全可信的环境中开展区块链应用&#xff0…

【Linux】C文件系统详解(四)——磁盘的物理和抽象结构

文章目录 磁盘结构磁盘物理结构磁盘的具体物理结构磁盘结构的逻辑抽象 文件系统BootBlockSuperBlockGroupDescriptorTableinode tableDataBlocksinodeBitmapblockBitmaplinux中的inode 和文件名如何理解文件的增删查改删 补充细节1.如果文件误删了,我们该怎么办?2.inode确定分…

SMART PLC数值积分器功能块(矩形+梯形积分法完整源代码)

PLC的数值积分器算法也可以参考下面文章链接: PLC算法系列之数值积分器(Integrator)-CSDN博客文章浏览阅读1.5k次,点赞3次,收藏3次。数值积分和微分在工程上的重要意义不用多说,闭环控制的PID控制器就是积分和微分信号的应用。流量累加也会用到。有关积分运算在流量累加上…

充电桩负载测试需要检测哪些项目

充电桩负载测试在进行充电桩负载测试时&#xff0c;需要检测以下几个项目&#xff1a; 充电速度&#xff1a;测试充电桩的充电速度&#xff0c;包括直流充电桩的最大输出功率和交流充电桩的充电功率&#xff0c;以确定其是否符合标准要求。充电效率&#xff1a;测试充电桩的充电…

横向扩展统一存储备份解决方案的特点与优势

Infortrend 使企业能够实现高效和可靠的数据备份&#xff0c;确保业务不间断的运行&#xff0c;保护有价值的业务信息。用户可以依靠我们的存储解决方案实现恢复时间目标&#xff08;RTO&#xff09;和恢复点目标&#xff08;RPO&#xff09;&#xff0c;用于广泛的备份应用场景…

【网络安全】国家专利局专利办理系统存在信息泄漏风险

今天在办理专利的时候&#xff0c;发现该系统存在严重的信息泄漏问题。 废话少说&#xff0c;贴图为证。 每一个都可以点开&#xff0c;查看身份证、港澳通信证扫描件&#xff0c;很清晰。 本人没找到可以反馈的渠道&#xff0c;微博被限流。 发此贴只为警醒相关主管部门和运…

JAXB:用XmlElement注解复杂类型的Java属性,来产生多层嵌套的xml元素

例如&#xff0c;下面这段请求的xml代码&#xff0c;在元素body下面又多了一层&#xff0c;嵌套了4个元素&#xff1a; <?xml version"1.0" encoding"UTF-8"?><request><reqtype>04</reqtype><secret>test</secret>…

2005B 2.4W AB类音频功率放大器应用领域

2005B 2.4W AB类音频功率放大器应用领域&#xff1a;1、便携式DVD&#xff1b;2、笔记本电脑&#xff1b;3、插卡音箱 / USB音箱&#xff1b;4、液晶电视 / 液晶显示器等等。 2005B是一颗单通道AB类音频功率放大器。在5V 电源供电&#xff0c;THDN10%&#xff0c;4欧姆负载上可…

QQ同步通讯录,详细操作方法来了!

腾讯QQ是一款功能丰富的即时通信软件&#xff0c;能够让用户随时随地与好友保持联系&#xff0c;不受时间和地域限制&#xff0c;受到了广大用户的喜爱和信赖。 为了能够快速添加QQ好友&#xff0c;我们可以通过开启通讯录来实现。那么&#xff0c;qq同步通讯录如何操作呢&…

YOLOv8改进 | 2023 | InnerIoU、InnerSIoU、InnerWIoU、FocusIoU等损失函数

论文地址&#xff1a;官方Inner-IoU论文地址点击即可跳转 官方代码地址&#xff1a;官方代码地址-官方只放出了两种结合方式CIoU、SIoU 本位改进地址&#xff1a; 文末提供完整代码块-包括InnerEIoU、InnerCIoU、InnerDIoU等七种结合方式和其Focus变种 一、本文介绍 本文给…

53. 最大子数组和 : 图解从 O(n) 的常规理解到 O(n) 的分治做法

题目描述 这是 LeetCode 上的 「53. 最大子数组和」 &#xff0c;难度为 「中等」。 Tag : 「前缀和」、「区间求和问题」、「线性 DP」、「分治」 给你一个整数数组 nums&#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#…

NC Cloud uploadChunk文件上传漏洞复现

简介 NC Cloud是指用友公司推出的大型企业数字化平台。支持公有云、混合云、专属云的灵活部署模式。该产品uploadChunk文件存在任意文件上传漏洞。 漏洞复现 FOFA语法&#xff1a; app"用友-NC-Cloud" 访问页面如下所示&#xff1a; POC&#xff1a;/ncchr/pm/fb/…

酷柚易汛ERP - 客户等级操作指南

1、应用场景 客户等级用于整个系统对客户进行分级&#xff0c;同一商品可设定多个不同价格&#xff0c;当然这个价格是以客户等级进行区分。 注意&#xff1a;系统中默认设定五种等级&#xff0c;默认等级是不允许删除和禁用&#xff0c;新增的客户等级是可以删除和禁用。

【文末送书】十大排序算法及C++代码实现

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

球幕投影有哪些常见的物理表现形式?

近年来&#xff0c;投影技术不断发展完善&#xff0c;给内容的表达方式带来了突破&#xff0c;使其展示形式不再局限于平面&#xff0c;即使在弧面、球面等异形幕墙上&#xff0c;也能呈现出令人惊叹的视觉画面。其中球幕投影备受关注&#xff0c;它以半球形屏幕将图像投影到球…

SAP ABAP给指定用户增加SAP ALL权限

下面的例子是给指定用户增加SAP ALL的权限ABAP代码&#xff0c;增加指定权限对像的没研究&#xff0c;只能自己看了。这应该是SAP权限的无限破解了吧。 例子中SAP*,是当前系统中有SAP_ALL权限的一个用户&#xff0c;用来参考使用的&#xff0c;根据实际系统用的最大权限用户&a…

webpack快速上手之搭建cesium三维地球环境

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热衷分享有趣实用的文章&#xff0c;希望大家多多支持&#xff0c;一起进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 目录 Cesium介绍 初始化一个Cesium地球 ​编辑 Webpack的使用 Webpac…