本文主要记录JAVA中对象的初始化过程,包括实例变量的初始化和类变量的初始化以及 final 关键字对初始化的影响。另外,还讨论了由于继承原因,探讨了引用变量的编译时类型和运行时类型

一,实例变量的初始化

一共有三种方式对实例变量进行初始化:

①定义实例变量时指定初始值

②非静态初始化块中对实例变量进行初始化

③构造器中对实例变量进行初始化

当new对象 初始化时,①②要先于③执行。而①②的顺序则按照它们在源代码中定义的顺序来执行。

当实例变量使用了final关键字修饰时,如果是在定义该final实例变量时直接指定初始值进行的初始化(第①种方式),则:该变量的初始值在编译时就被确定下来,那么该final变量就类似于“宏变量”,相当于JAVA中的直接常量。

 public class Test {
public static void main(String[] args) {
final String str1 = "HelloWorld";
final String str2 = "Hello" + "World";
System.out.println(str1 == str2);//true final String str3 = "Hello" + String.valueOf("World");
System.out.println(str1 == str3);//false
}
}

第8行输出false,是因为:第7行中str3需要通过valueOf方法调用之后才能确定。而不是在编译时确定。

再来看一个示例:

 public class Test {

     final String str1 = "HelloWorld";
final String str2 = "Hello" + "World";
final String str3;
final String str4;
{
str3 = "HelloWorld";
}
{
System.out.println(str1 == str2);//true
System.out.println(str1 == str3);//true
// System.out.println(str1 == str4);//compile error
}
public Test() {
str4 = "HelloWorld";
System.out.println(str1 == str4);//true
} public static void main(String[] args) {
new Test();
}
}

把第13行的注释去掉,会报编译错误“The blank final field str4 may not have been initialized”

因为变量str4是在构造器中进行初始化的。而前面提到:①定义实例变量时直接指定初始值(str1 和 str2的初始化)、 ②非静态初始化块中对实例变量进行初始化(str3的初始化)要先于 ③构造器中对实例变量进行初始化。

另外,对于final修饰的实例变量必须显示地对它进行初始化,而不是通过构造器(<clinit>)对之进行默认初始化。

 public class Test {
final String str1;//compile error---没有显示的使用①②③中的方式进行初始化
String str2;
}

str2可以通过构造器对之进行默认的初始化,初始化为null。而对于final修饰的变量 str1,必须显示地使用 上面提到的三种方式进行初始化。如下面的这个Test.java(一共有22行的这个Test类)

 public class Test {
final String str1 = "Hello";//定义实例变量时指定初始值 final String str2;//非静态初始化块中对实例变量进行初始化
final String str3;//构造器中对实例变量进行初始化 {
str2 = "Hello";
}
public Test() {
str3 = "Hello";
} public void show(){
System.out.println(str1 + str1 == "HelloHello");//true
System.out.println(str2 + str2 == "HelloHello");//false
System.out.println(str3 + str3 == "HelloHello");//false
}
public static void main(String[] args) {
new Test().show();
}
}

由于str1采用的是第①种方式进行的初始化,故在执行15行: str1+str1 连接操作时,str1其实相当于“宏变量”

而str2 和 str3 并不是“宏变量”,故16-17行输出false

在非静态初始化代码块中初始化变量和在构造器中初始化变量的一点小区别:因为构造器是可以重写的,比如你把某个实例变量放在无参的构造器中进行初始化,但是在 new 对象时却调用的是有参数的构造器,那就得注意该实例变量有没有正确得到初始化了。

而放在非静态初始化代码块中初始化变量时,不管是调用 有参的构造器还是无参的构造器,非静态初始化代码块都会执行。

二,类变量的初始化

类变量一共有两个地方对之进行初始化:

❶定义类变量时指定初始值

❷静态初始化代码块中进行初始化

不管new多少个对象,类变量的初始化只执行一次。

三,继承对初始化的影响

主要是理解编译时类型和运行时类型的不同,从这个不同中可以看出 this 关键字 和 super 关键字的一些本质区别。

 class Fruit{
String color = "unknow";
public Fruit getThis(){
return this;
}
public void info(){
System.out.println("fruit's method");
}
} public class Apple extends Fruit{ String color = "red";//与父类同名的实例变量 @Override
public void info() {
System.out.println("apple's method");
} public void accessFruitInfo(){
super.info();
}
public Fruit getSuper(){
return super.getThis();
} //for test purpose
public static void main(String[] args) {
Apple a = new Apple();
Fruit f = a.getSuper(); //Fruit f2 = a.getThis();
//System.out.println(f == f2);//true System.out.println(a == f);//true
System.out.println(a.color);//red
System.out.println(f.color);//unknow a.info();//"apple's method"
f.info();//"apple's method" a.accessFruitInfo();//"fruit's method"
}
}

值得注意的地方有以下几个:

⒈ 第35行 引用变量 a 和 f 都指向内存中的同一个对象,36-37行调用它们的属性时,a.color是red,而f.color是unknow

因为,f变量的声明类型(编译时类型)为Fruit,当访问属性时是由声明该变量的类型来决定的。

⒉ 第39-40行,a.info() 和 f.info()都输出“apple's method”

因为,f 变量的运行时类型为Apple,info()是Apple重载的父类的一个方法。调用方法时由变量的运行时类型来决定。

⒊ 关于 this 关键字

当在29行new一个Apple对象,在30行调用 getSuper()方法时,最终是执行到第4行的 return this

this 的解释是:返回调用本方法的对象。它返回的类型是Fruit类型(见getThis方法的返回值类型),但实际上是Apple对象导致的getThis方法的调用。故,这里的this的声明类型是Fruit,而运行时类型是Apple

⒋ 关于 super 关键字

super 与 this 是有区别的。this可以用来代表“当前对象”,可用 return 返回。而对于super而言,没有 return super;这样的语句。

super 主要是为了:在子类中访问父类中的属性 或者 在子类中 调用父类中的方法 而引入的一个关键字。比如第24行。

⒌ 在父类的构造器中不要去调用被子类覆盖的方法(Override),或者说在构造父类对象时,不要依赖于子类覆盖了父类的那些方法。这样很可能会导致初始化的失败(没有正确地初始化对象)

因为:前面第1点和第2点谈到了,对象(变量 )有 声明时类型(编译时类型)和运行时类型。而方法的调用取决于运行时类型。

当new子类对象时,会首先去初始化父类的属性,而此时对象的运行时类型是子类,因此父类的属性的赋值若依赖于子类中重载的方法,会导致父类属性得不到正确的初始化值。示例如下:

     class Fruit{
String color; public Fruit() {
color = this.getColor();//父类color属性初始化依赖于重载的方法getColor
// color = getColor();
}
public String getColor(){
return "unkonw";
} @Override
public String toString() {
return color;
}
} public class Apple extends Fruit{ @Override
public String getColor() {
return "color: " + color;
} // public Apple() {
// color = "red";
// } public static void main(String[] args) {
System.out.println(new Apple());//color: null
}
}

Fruit类的color属性 没有正确地被初始化为"unknow",而是为 null

主要是因为第5行 this.getColor()调用的是Apple类的getColor方法,而此时Apple类的color属性是直接从Fruit类继承的。

四,参考资料

《疯狂JAVA突破程序员基本功16课》第二章

《Effective Java》第二版第17条

JAVA基础之对象的初始化的更多相关文章

  1. 解析Java类和对象的初始化过程

    类的初始化和对象初始化是 JVM 管理的类型生命周期中非常重要的两个环节,Google 了一遍网络,有关类装载机制的文章倒是不少,然而类初始化和对象初始化的文章并不多,特别是从字节码和 JVM 层次来 ...

  2. JAVA基础知识之JVM-——类初始化

    我们通常说的类初始化,其实要分为三个阶段,类加载,连接,和初始化.他们大致完成以下功能.类加载将class文件载入内存,类连接进行内存分配,初始化进行变量赋值. 类的加载,连接和初始化 java.la ...

  3. java基础(二) -对象和类

    Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 对象:对象是类的一个实例(对象不是找个女朋友),有状态和行为.例如,一条狗是一 ...

  4. JAVA基础|从Class.forName初始化数据库到SPI破坏双亲委托机制

    代码托管在:https://github.com/fabe2ry/classloaderDemo 初始化数据库 如果你写过操作数据库的程序的话,可能会注意,有的代码会在程序的开头,有Class.for ...

  5. [转载]解析 Java 类和对象的初始化过程

    原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-clobj-init/index.html 由一个单态模式引出的问题谈起 类的初始化和对象初始化 ...

  6. AJPFX总结Java 类与对象的初始化

    面试的时候,经常会遇到这样的笔试题:给你两个类的代码,它们之间是继承的关系,每个类里只有构造器方法和静态块,它们只包含一些简单的输出字符串到控制台的代码,然后让我们写出正确的输出结果.这实际上是在考察 ...

  7. java基础之——类的初始化顺序

    由浅入深,首先,我们来看一下,一个类初始化有关的都有些啥米: 静态成员变量.静态代码块.普通成员变量.普通代码块.构造器.(成员方法?貌似跟初始化没有啥关系) 现在我们来看看她们的初始化顺序, 从性质 ...

  8. java基础(一)对象

    对象的创建 Test test = new Test(); Test test = new Test("a"); 其实,对象被创建出来时,对象就是一个对象的引用,这个引用在内存中为 ...

  9. Java基础03-12_对象比较

    对象比较 如果说现在有两个数字要判断是否相等,可以使用"=="完成 如果是字符串要判断是否相等使用"equals()" 但是如果说现在有一个自定义的类,要想判断 ...

随机推荐

  1. 泛函编程(19)-泛函库设计-Parallelism In Action

    上节我们讨论了并行运算组件库的基础设计,实现了并行运算最基本的功能:创建新的线程并提交一个任务异步执行.并行运算类型的基本表达形式如下: import java.util.concurrent._ o ...

  2. [iOS] 使用xib做为应用程序入口 with Code

    [iOS] 使用xib做为应用程序入口 with Code 前言 开发iOS APP的时候,使用storyboard能够快速并且直觉的建立用户界面.但在多人团队开发的情景中,因为storyboard是 ...

  3. express新旧语法对比

    备个份, 原文: http://stackoverflow.com/questions/25550819/error-most-middleware-like-bodyparser-is-no-lon ...

  4. linux常识以及常用命令和参数

    linux,it人士众所周知,一款稳定.强大.开源的系统,1973年,unix正式诞生,ritchie等人用c语言写出第一个unix内核,之后经过不后人不断的改进,形成现在linux的各个版本,其中比 ...

  5. ES6新特性(函数默认参数,箭头函数)

    ES6新特性之 函数参数的默认值写法 和 箭头函数. 1.函数参数的默认值 ES5中不能直接为函数的参数指定默认值,只能通过以下的变通方式:   从上面的代码可以看出存在一个问题,当传入的参数为0或者 ...

  6. Step by step configuration of Outgoing Emails from SharePoint to Microsoft Online

    First of all your SharePoint server should be added to Microsoft online safe sender list, so that Sh ...

  7. English Training Material - 02

    OUTSIDE CORRESPONDENCE AND CONTACT   Jared: I'm glad you could come – and you're in for a treat. Thi ...

  8. 【读书笔记】iOS-写代码注意事项

    一,我是尽早和经常编译的强烈支持者.通常,在写完每个方法或有点难度的代码后,都要尝试进行构建.这是一个好习惯,因为如果在上次成功编译以来添加的代码量很小,那么可以非常容易地缩小编译错误范围.这个方法还 ...

  9. 【原】iOS动态性(一):动态添加属性的方法——关联(e.g. 向Category添加属性)

    想到要如何为所有的对象增加实例变量吗?我们知道,使用Category可以很方便地为现有的类增加方法,但却无法直接增加实例变量.不过从Mac OS X v10.6开始,系统提供了Associative ...

  10. Objective-C之Category的使用

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...