本文主要记录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. 发布ASP.NET Core程序到Linux生产环境

    原文翻译:Publish to a Linux Production Environment 作者:Sourabh Shirhatti 在这篇文章里我们将介绍如何在 Ubuntu 14.04 Serv ...

  2. ADO.NET 实体类和数据访问类

    SQL数据库字符串注入攻击:需要使用cmd.Parameters这个集合占位符: @key 代表这个位置用这个占位符占住了 Parameters这个集合中将此占位符所代表的数据补全 cmd.Param ...

  3. HTML Jquery

    在<网页制作Dreamweaver(悬浮动态分层导航)>中,运用到了jQuery的技术,轻松实现了菜单的下拉.显示.隐藏的效果,不必再用样式表一点点地修改,省去了很多麻烦,那么jQuery ...

  4. Vue计算属性

    github地址:https://github.com/lily1010/vue_learn/tree/master/lesson06 一 计算属性定位 当一些数据需要根据其它数据变化时,这时候就需要 ...

  5. ABAP 搜索帮助

    当选择屏幕上的一个字段所参考的数据元素没有建立搜索帮助时,可以手工建立一个: 1.在se11创建一个搜索帮助ZAUTEST,需要输入: (1)描述: (2)选择方法:即搜索帮助显示字段所在的透明表: ...

  6. 简析android消息模型

    android总结系列 一.消息系统构成要素和基本原理 l  消息队列 l  发送消息 l  消息读取 l  消息分发 l  消息循环线程 消息系统必须要依赖一个消息循环线程来轮询自己的消息队列,如果 ...

  7. JSP page指令详解

    JSP指令用来设置整个JSP页面相关的属性,如网页的编码方式和脚本语言. 语法格式如下: <%@ directive attribute="value" %> 指令可以 ...

  8. iOS 清理缓存功能实现第一种方法

    添加一个提示框效果导入第三方MBProgressHUD #import "MBProgressHUD+MJ.h" /** * 清理缓存第一种方法 */ -(void)clearCa ...

  9. IOS之UI -- UITableView -- 1 -- 相关初识

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

  10. MAC OS Finder 中快速定位指定路径

    在看一些 tip 文章的时候,时不时会有需要进到某某目录替换文件之类的步骤.如果碰上这个目录层次够多,一层一层的点击既麻烦又容易出错,有什么快捷的办法呢? 快捷键:Shift + Command + ...