深度思考Java成员变量的初始化

2023-05-16

         写Java代码的时候很少去关注成员变量的声明和初始化顺序,今天借此机会抛出一些问题:语言的设计者们为什么会这样设计?比如说很常见的一个问题:abstract(抽象)类不能用final进行修饰。这个问题比较好理解:因为一个类一旦被修饰成了final,那么意味着这个类是不能被继承的,而abstract(抽象)类又不能被实例化。如果一个抽象类可以是final类型的,那么这个类又不能被继承也不能被实例化,就没有存在的意义。从语言的角度来讲一个类既然是抽象类,那么它就是为了继承,所以给它标识为final是没有意义的。语言的设计者们当然不可能让这么大的一个bug产生。对于开发者而言抽象类不能修饰final可能就是一种约定俗成的规定,并没有特殊意义。我们完全可以往前想一点:为什么这么设计?

       下面我所展示的一些代码实例也同样会采用我上面的一些思考方法。有一些是一些”契约“,并没有特别的缘由,可能用别的方法也是合理的。下面的代码会讲到初始化的一些策略,从实际的执行结果中得出一些结论。


代码一

public class Test1 {
    {
        a = 1;
        //System.out.println(a);//这里会抛错。
    }
    private int a=2;//这里初始化一次,上面的动态块中也对a进行了赋值,这个时候a=?,为什么可以对a进行赋值,而不可以对a进行输出
    public static void main(String[] args){
        Test1 test1 = new Test1();
        System.out.println(test1.a);
    }
}

看看上面的代码一,第一个问题就是这段代码能否编译通过。结果是能编译通过。这里说明一个问题就是变量的声明和赋值是两步操作?(这句话我先保留一半,在上面

的代码中有一行代码我注释掉了,这里会抛错,对于这个问题我也没有想明白为什么。)

第一个问题解决了。那下一个问题很显然最后输出的结果是什么?答案是“2”,这里可能会有些诧异。从直观上来讲就是说明在赋值的过程中是完全按照代码的前后顺序进

行。

代码二

public class Test2 {
    
    {
        a = 4;
    }
    
    private final int a;//这里我并没有对a做初始化。
    
    public static void main(String[] args){
        Test2 test2 = new Test2();
        System.out.println(test2.a);
    }
}
       “代码二”只是在“代码一”的基础上对成员变量a多修饰了一个final, 另外我并没有立即初始化。第一个问题就是这段代码能不能编译通过,答案是能。在这里展示这段代码是为了后面做铺垫,因为这段代码仍然符合上面的 “契约”

代码三

public class Test3 {
    
    {
        a = 4;
    }
    
    private static int a;
    
    public static void main(String[] args){
        Test3 test3 = new Test3();//注意:这里开始new了一个对象
        System.out.println(test3.a);
    }
}
       代码三在代码一的的基础上对于成员变量a多修饰了一个static。这里同样可以编译通过,最后输出的结果也皆大欢喜为4。 这里要注意的是我是new了一个对象,而不是直接访问静态变量

代码四

public class Test3 {
    {
        a = 4;
        System.out.println(a);//这里不会报错,但是这条语句并不会执行
    }
    private static int a;
    
    public static void main(String[] args){
        System.out.println(a);
    }
}
        代码四在代码三的基础上把new 对象给去掉了,直接输出静态变量a。 这时候就会出现非常诧异的结果0。对,你没有看错,结果是0。如果有兴趣的可以在a=4后面打印一条,会很清晰的发现并没有执行a=4那一条语句。这里先不解释,只看一下现象。

代码五

public class Test3 {
    static{
        a = 4;
        //System.out.println(a);//这里会抛错。
    }
    private static int a;
    public static void main(String[] args){
        System.out.println(a);
    }
}
        代码五和代码四和不同在于采用了 静态初始化,最后的结论很简单,结果为4。这里有一个问题就是如果在a=4之后紧接着使用a就会报错。也就是说 定义在声明之前的静态化块只能对声明变量进行赋值,并不能使用该变量。对于这一条规则我也不是特别理解,因为按照常理在赋值之后进行使用是一种再正常不过的事情,在这里只有记住这样一条规则。

代码六

public class Test6 {
    
    {
        a = 4;
    }
    
    private static final int a;
    
    public static void main(String[] args){
        System.out.println(a);
    }
}
       代码六是在代码一的基础上增加static final的修饰符。回到我们上面三段代码所问的问题,这次的答案是 “否”,也就是说在这里是不能编译通过。在这里我估计有一部分人和我有同样的疑惑: 为什么对于成员变量修饰单独修饰final或者static可以进行单独的初始化,而把两个修饰符合起来的时候就不行了呢?我们把这个问题要反过来问: 如果可以这样进行初始化会产生什么问题,那么就可以知道为什么需要这样设计

       我们看代码三、代码四、代码五和代码六,这里估计会有点绕。上面也说了在代码三中的初始化块是执行了的,而代码四的初始化块没有执行,代码五的静态初始化块也执行了。所以问题归根结底就一条:静态初始化块和普通的初始化块在什么时候执行。结论就是在初始化阶段编译器会收集类中的类变量(区别实例变量)的赋值动作和静态语句块中的语句,而静态的调用并不会触发实例变量的初始化

       这里回到代码六,根据上面所得出的结论。变量a被修鉓成了static final,那么意味着有且仅有一次赋值。我们在访问a的同时,域中的a=4并未执行(根据代码四所得出的结论),这样就违背了final类型有且仅有一次赋值的这样一个约定。所以{a=4;}不管是放在声明的代码前还是声明的代码后都无法编译通过。

代码七

public class Test7 {
    
    static{
        a = 5;
    }
    
    private static final int a;
    
    
    public static void main(String[] args){
        System.out.println(a);
    }
}
      代码七和代码六的不同在于使用静态化的初始化方法,并不会违背final有且仅有一次赋值这样的一个约定。


总结

       上面七段代码大概阐述了一下变量的初始化顺序。大部分结果可以通过一些已有的结论推敲出来,也方便我们进行记忆。很多人可能会问:了解这些有用吗?如果只能看到我上面所写的那七段代码,可能意义并不大,在平常的编码过程中也不太可能会去那么写,即使写错了eclipse也会很明显的把错误指出来。而我得出的结论:从语言设计者的角度来思考Java语言为什么这么设计?如果这样去思考,然后再进行深度挖掘,一定可以得出一些不一样的理解,甚至可以找出java语言的设计不好的地方


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深度思考Java成员变量的初始化 的相关文章

随机推荐

  • 关于kali gnome-tweaks-tool无法找到命令问题

    输入gnome tweaks即可
  • 在 Ubuntu 中添加新用户并给予 root 权限

    在 Ubuntu 中添加新用户并给予 root 权限的步骤如下 xff1a 打开终端 xff0c 以 root 身份登录 sudo su 使用 adduser 命令添加新用户 adduser username 为新用户设置密码 passwd
  • tensorflow载入数据的三种方式

    Tensorflow数据读取有三种方式 xff1a Preloaded data 预加载数据Feeding Python产生数据 xff0c 再把数据喂给后端 Reading from file 从文件中直接读取 这三种有读取方式有什么区别
  • 网络数据包封装与解封装基本过程

    数据包 应用层 TCP头 xff1a 源端口 43 目的端口 数据包 传输层 IP头 xff1a 源 IP 地址 43 目的 IP 地址 TCP头 xff1a 源端口 43 目的端口 数据包 网络层 Ethernet头 xff1a 源 MA
  • 环形缓冲区

    什么是环形缓冲区 在通信程序中 xff0c 经常使用环形缓冲区作为数据结构来存放通信中发送和接收的数据 环形缓冲区是一个先进先出 的循环缓冲区 xff0c 可以向通信程序提供对缓冲区的互斥访问 环形缓冲区原理 环形缓冲区通常有一个读指针 和
  • win10蓝牙链接上的标准串行com口无法删除

    Bluetooth 链接上的标准串行设备 无法删除解决办法 一 原理 Windows系统每次使用蓝牙与手机 xff08 耳机等蓝牙设备 xff09 配对时 xff0c 就会自动分配一个串口 xff0c 如果该蓝牙设备一直处于保留状态 xff
  • 创新工场两道笔试题0919

    题目1 字符串去重 xff0c 老题目 xff0c 只是要求不能开辟新空间用来复制原字符串 思路 xff1a 使用布尔型的简单hash表可以节省空间 xff0c 用来存储字符是否出现的信息 xff0c 刚开始hash表里面都是false x
  • Dockerfile构建镜像读取环境变量问题

    起初使用如下Dockerfile构建Java镜像 xff1a FROM span class token number 10 170 span 33 245 base openjdk 11 0 6 jdk slim buster MAINT
  • Manjaro-i3的安装

    Manjaro i3的安装 manjaro的安装方法记录在这里 xff0c 其中涉及到一些常用软件的安装 xff0c 此处只介绍Manjaro i3的安装 xff0c 并对一些安装配置中出现的问题列出解决方法 写在前面 之前用Win10的时
  • 解决Error:Configuration with name 'debug' not found.

    在同步开发时 同事添加了某个module 自己同步代码后会出现Error Configuration with name 39 default 39 not found 的错误 这是因为同步时不会自动把远程仓库下载下来需要你自己初始化下载
  • Altium Designer布局布线基本规则设置

    PCB布局布线过程中 xff0c 有如下几项最基本的规则需要设置 1 间距规则 一般情况下设置三个不同的间距规则即可 即整板间距 ALL ALL xff0c 一般密度的板子 6mil间距即可 如果有 BGA 封装 xff0c 或者需要控制阻
  • 使用环形缓冲区ringbuffer实现串口数据接收

    文章目录 1 ringbuffer简单介绍2 ringbuffer的代码实现2 1 ringbuffer数据结构定义2 2 ringbuffer初始化2 3 ringbuffer写数据2 4 ringbuffer读数据 3 在串口中使用ri
  • 在线刷机详细教程(图文)〓诺基亚——NSS修改CODE+NSU升级

    导读 xff1a 在线刷机详细教程 图文 诺基亚 NSS修改CODE 43 NSU升级 上次帮零刷机的时候发生了好多问题 费了好久的时间才搞定 主要是之前都没做好细致的工作 对刷机没有太深的了解 所以我用了一点时间把刷机教程再重新的整理一下
  • angularJS绑定数据时自动转义html字符串

    angular js转换字符串形式的html标签 在保存数据到数据库的时候有些保存一个商品的描述数据 会直接保存这个文件描述中的一些html标签 当在数据库拿这个数据的时候用angular显示会直接字符串 显示下列这样 span class
  • 【K8S 一】使用kubeadm工具快速部署Kubernetes集群(单Master)

    此为安装部署单Master集群 xff0c 如需高可用Master集群 xff0c 请一并参考 K8S 五 使用kubeadm工具快速部署Kubernetes集群 Master高可用集群 目录 安装前配置 依赖包安装 kube proxy开
  • [Scala Shell脚本执行]

    Scala语言来自于Scalable xff08 可伸缩的 xff09 xff0c 既可以写一些小的脚本 xff0c 又可以写一写复杂的服务器端的程序 scala支持源文件解释执行 xff0c jar执行 xff0c 各有利弊 本文主要介绍
  • 全息投影技术

    1 概念 全息投影技术 xff08 front projectedholographic display xff09 也称 虚拟成像 技术是利用干涉和衍射原理记录并再现物体真实的 三维 图像的技术 全息投影技术不仅可以产生立体的空中幻像 x
  • Android Settings源码结构分析与自实现

    最近的项目一直是按照PRD与高清 xff0c 修改系统设置 xff0c 调整布局 间距 颜色 xff0c 涉及到一些流程的更改与自定义控件 xff0c 以及对settings源码结构的研究 在项目相对空闲是 xff0c 做个整理记录 由于项
  • 程序员的职业规划

    在这个节点讲讲我对职业发展路径的理解 xff0c 另外就是理一理脑子里的一些思路 过段时间再看此文又可能是另一番理解 很多路从后往前看的时候总会有遗憾 xff0c 要是当初怎么怎么样就会怎么怎么样 导致这样的原因的第一个原因是人在做决策的时
  • 深度思考Java成员变量的初始化

    写Java代码的时候很少去关注成员变量的声明和初始化顺序 xff0c 今天借此机会抛出一些问题 xff1a 语言的设计者们为什么会这样设计 xff1f 比如说很常见的一个问题 xff1a abstract xff08 抽象 xff09 类不