java静态/动态成员变量、初始化块,父类/子类构造函数执行顺序问题

news/2024/7/3 0:31:27

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

  1. /*
  2. * 几大原则
  3. * 一、静态成员变量(Static)
  4. * 1、静态成员变量为类变量,所有对象共享同一内存空间
  5. * 2、静态成员变量的声明和定义仅在首次加载类时执行一次
  6. * 3、首次加载类时首先对所有静态成员变量根据类型默认赋初值,然后再对有右值的附右值
  7. * 二、静态初始块
  8. * 1、静态初始化块仅在首次加载类时执行一次
  9. * ······多个静态成员变量与静态始化快参照出现顺序先后执行······
  10. * 三、动态成员变量
  11. * 1、动态成员变量定义在每次实例化对象时在构造函数之前执行
  12. * 四、动态初始化块
  13. * 1、动态初始化块在每次实例化对象时在构造函数之前执行
  14. * ······多个动态成员变量与动态初始化块参照出现顺序先后执行······
  15. * 总结:总的来说,在不涉及继承的前提下,当首次加载类时,按照如下顺序执行
  16. * 1、按照出现顺序先后执行静态成员变量定义与静态初始化块
  17. * 2、按照出现顺序先后执行动态成员变量定义与动态初始化块
  18. * 3、执行构造函数
  19. * 再次实例化对象时只执行第2、3步即可
  20. *
  21. * ············成员变量与定义与初始化块先于构造函数执行·········
  22. * 五、当涉及到继承时,按照如下顺序执行
  23. * 1、执行父类的静态成员变量定义与静态初始化块,执行子类的静态成员变量定义与静态初始化块
  24. * 2、执行父类的非静态成员变量定义与动态初始化块,执行父类构造方法
  25. * 3、执行子类的非静态成员变量定义与动态初始化块,执行子类构造方法
  26. * 另:父类构造方法中用到的方法如果已被子类重写,那么在构造子类对象时在调用父类构造函数中使用子类重写的方法
  27. */


1.实例变量和类变量的内存分配

类变量 :使用static修饰的成员变量是类变量,属于该类本身

实例变量:没有使用static修饰的成员变量是实例变量,属于该类的实例

由于同一个JVM内每个类只对应一个Class对象,因此同一个JVM内的一个类的类变量只需一块内存空间

对于实例变量而言,该类每创建一次实例,就需要为实例变量分配一块内存空间,所以,程序中有几个实例,实例变量就需要几块内存空间

2.类变量的初始化时机总是处于实例变量的初始化之前

我们先看下下面三段代码:

1)因为两个实例变量都是在创建变量的时候才开始分配空间,此时num2还没有分配,所以前向引用就会出现编译错误。

1 int num = num2 + 3; //非法前向引用,会报错 2 int num2 = 2

2)因为两个类变量在JVM加载类的时候分配空间,此时num2还没有分配,所以前向引用就出现编译错误。

1 static int num = num2 + 3; //非法前向引用,会报错 2 static int num2 = 2

3)因为类变量num2在JVM加载类的时候空间已经分配好,而num在创建实例的时候才分配空间,此时num2已经分配成功了,所以num前向引用成功。

1 int num = num2 + 3; //正确使用 2 static int num2 = 2;

由上面三段代码块就可以验证得:类变量的初始化时机总是处于实例变量的初始化之前

3.Java对象的初始化方式及其执行顺序

Java对象的初始化方式有三种:1)构造器 2)初始化块 3)定义变量时指定初始化值

如果这三种初始化方式同时出现,也要注意,他们也有一个执行顺序的规定:

1)静态初始化块只在类第一次创建对象的时候运行一次,后面就不会再运行,而类在每次创建对象时,非静态初始化块总是会运行一次。

public class Test{static {System.out.println("执行---静态初始化代码块.");}{System.out.println("执行---非静态初始化代码块.");}public static void main(String[] args) {for (int i = 1; i <= 2; i++) {System.out.println("创建第 " + i + " 个对象"); new Test();System.out.println();}}
}




运行结果:

2)构造器每次创建对象时,构造器必然有执行的机会,此时,非静态初始化块必定也将获得机会并且运行在构造器之前

public class Test{{System.out.println("执行---非静态初始化代码块.");}public Test() {System.out.println("执行---构造器.");}public static void main(String[] args) {for (int i = 1; i <= 2; i++) {System.out.println("创建第 " + i + " 个对象"); new Test();System.out.println();}}
}




运行结果:

3)定义变量时指定的初始化值和初始化块中指定的初始值的执行顺序与他们在源程序中的排列顺序相同。

验证代码一:

public class Test{String i = "定义变量时指定的初始化值";{i = "初始化块中指定的初始值";}public static void main(String[] args) {for (int i = 1; i <= 2; i++) {System.out.println("创建第 " + i + " 个对象"); System.out.println(new Test().i);System.out.println();}}
}




运行结果

验证代码二 :

public class Test{{i = "初始化块中指定的初始值";}String i = "定义变量时指定的初始化值";public static void main(String[] args) {for (int i = 1; i <= 2; i++) {System.out.println("创建第 " + i + " 个对象"); System.out.println(new Test().i);System.out.println();}}
}




运行结果:

4.关于父子实例的内存控制

(一般情况下是不用内部类来验证的,但是都是一样的啦,我偷懒下,所以使用了内部类,大家原谅哈)

1)当子类重写父类方法后,父类表面上只是调用属于自己的被子类重写的方法。

public class Test{class Base {Base() {this.info();}public void info() {System.out.println("Base");}public void getInfo() {info();}}public class Child extends Base{@Overridepublic void info() {System.out.println("Child");}}public static void main(String[] args) {Test test = new Test();Base base = test.new Child();base.info();base.getInfo();}
}




运行结果:

2)上述是属于多态中方法的体现,但是方法有多态,实例变量无多态。

解释下“方法有多态,变量无多态”这句话:意思是,不管怎样,父类表面上只是调用属于自己的被子类重写的方法。而变量不一样,假设父类和子类都有同一个变量名的实例变量,向上转型后,通过父类访问的实例变量得到的值是自身的而非子类的。向下转型后,通过子类访问的实例变量得到的值是自身的而非父类的。

很多书上或教学视频上都讲,创建一个子类对象的时候,Java 会顺着继承结构往上一直找到 Object,然后从 Object 开始往下依次执行构造函数。先执行父类的构造函数,然后在其子类中会创建一个成员变量指向他的父类。其实这个说法是错误的系统并不会真正的去创建父类对象,只是在子类对象中不仅保存了本身的实例变量,还有它父类的全部实例变量。

public class Test{class Base {    //父类int i = 2;}public class Child extends Base{    //子类int i  = 20;}public static void main(String[] args) {Test test = new Test();Child child = test.new Child();Base base = child;System.out.println(" Base.i : " + base.i);System.out.println("Child.i : " + child.i);}
}




运行结果:

5.final修饰符

final变量在编译时就被确定下来了,相当于一个直接量。

1)final修饰的实例变量赋值时机:

  • 定义final实例变量时 指定初始值 
  • 非静态初始化模块中为final实例变量指定的初始值
  • 在构造器中为final实例变量指定初始值   

2)final修饰的类变量赋值时机: 

  • 定义final类变量时指定初始值
  • 静态初始化模块中为final实例变量指定的初始值

转载于:https://my.oschina.net/Chaos777/blog/306492


http://lihuaxi.xjx100.cn/news/246287.html

相关文章

Ubuntu中使用SQLite

SQLite&#xff0c;是一款轻型的数据库&#xff0c;是遵守ACID的关系型数据库管理系统&#xff0c;它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的&#xff0c;而且目前已经在很多嵌入式产品中使用了它&#xff0c;它占用资源非常的…

VC6迁移到VS2008几个问题——良好的代码,从我做起,从现在开始。

最近。有一个项目开发&#xff0c;需要使用一次项目的代码。只有当项目VC6下编译通过的&#xff0c;在VS2008下不一定编译通过&#xff0c;能编译通过也不一定能链接成功。以下总结一下我在一个VC6项目移植到VS2008中遇到的一些问题以及解决的方法。 1 类型转换 1&#xff09;…

F# 4.5提供Spans、Match!等特性

F# 4.5预览版现已发布&#xff0c;其中提供了一系列新特性&#xff0c;包括对.NET Core 2.1的新原生类型Span\u0026lt;T\u0026gt;的支持、新关键字Match!等。\\类型Span意在实现底层代码指针操作的安全性和可预测性&#xff0c;这可使得很多情况下不必再分配内存&#xff0c;进…

C++关键字decltype

1. decltype简介 decltype与auto关键字一样&#xff0c;用于进行编译时类型推导&#xff0c;不过它与auto还是有一些区别的。decltype的类型推导并不是像auto一样是从变量声明的初始化表达式获得变量的类型&#xff0c;而是总是以一个普通表达式作为参数&#xff0c;返回该表达…

求几亿个数中不重复元素的个数

题目&#xff1a; 有2.5亿个无符号整数&#xff08;但在文件里面&#xff09;&#xff0c;要求找出这2.5亿个数字里面&#xff0c;不重复的数字的个数&#xff08;那些只出现一次的数字的数目&#xff09;&#xff1b;另外&#xff0c;可用的内存限定为600M&#xff1b;要求算法…

android sqlite操作(2)

以下只是我个人的浅见,大神请忽略~ 这一篇说一下sqlite的相关操作,其实安卓提供了相当多的操作sqlite的方法,这里我介绍下我常用的方法。 (1)创建一个数据库文件,这个很简单 1 File dbPathFile new File(dbPath); 2 if(!dbPathFile.exists()) 3 try { 4 dbPathFil…

C++关键字const与constexpr

1. const 1.1. 修饰一般常量 一般常量是指简单类型的常量。这种常量在定义时&#xff0c;修饰符const可以用在类型说明符前&#xff0c;也可以用在类型说明符后。 例如&#xff1a; int const x 2; const int y 2; const std::string& name "csdn"; 1.2…

RNN,LSTM,GRU简单图解:

一篇经典的讲解RNN的&#xff0c;大部分网络图都来源于此&#xff1a;http://colah.github.io/posts/2015-08-Understanding-LSTMs/ 每一层每一时刻的输入输出&#xff1a;https://www.cnblogs.com/lovychen/p/9368390.html 带有权重标识的图&#xff1a;