由一道面试题所想到的--Java实例变量初始化

时间:2015-10-07 16:08:38      阅读:23      评论:0      收藏:0      [点我收藏+]

标签:java   面试题   初始化

该题目源自微信公众号(程序员的那些事)的推送:携程 Java 工程师的一道面向对象面试题

题目是这样的:求下面程序的输出:

public class Base
{
private String baseName = "base";
public Base()
{
callName();
}
public void callName()
{
System. out. println(baseName);
}
static class Sub extends Base
{
private String baseName = "sub";
public void callName()
{
System. out. println (baseName) ;
}
}
public static void main(String[] args)
{
Base b = new Sub();
}
}

很显然一开始我也做错了,原因很简单,这道题目考察什么我大概知道,可是之前在学习类的加载机制时对类的初始化过程还比较了解,但实例变量的初始化过程比较模糊。这里还牵扯的难点有:继承的时候子类的同名属性不会覆盖父类,会将父类的属性隐藏;在父类的构造函数里面调用虚函数引起多态的变态代码。

接下来查看了一下深入Java虚拟机(Bill Venners著) 等才对该问题有了清楚的认识。下面我会尽量简单而清晰的将其分析的透彻明了。

前面我在JAVA类加载和初始化 中简单的讲解了类的加载链接初始化过程,有兴趣的可以看看,里面也有一道变态的题目。

类的实例变量初始化的过程 
一旦一个类被加载连接初始化,他就可以随时被使用了,程序可以访问他的静态字段,调用静态方法,或者创建它的实例。在Java程序中类可以被明确或者隐含地实例化,有四种途径:明确使用new操作符;调用Class或者Constructor对象的newInstance()方法;调用任何现有对象的clone()方法;或者通过objectInputStream类的getObject()方法反序列化。

虚拟机创建一个新的实例时,都需要在堆中为保存对象的实例分配内存。所有在对象的类中和它的超类中声明的变量(包括隐藏的实例变量)都要分配内存。一旦虚拟机为新的对象准备好堆内存,它立即把实例变量初始化为默认的初始值。这一点很类似于类变量在链接的准备阶段赋予默认初始值是一样样的。

一旦虚拟机完成了为新的对象分配内存和为实例变量初始化为默赋予正确认的初始值后,接下来就会为实例变量的初始值。即调用对象的实例初始化方法,在java的class文件中称之为< init >()方法,类似于类初始化的< clinit >()方法。

一个< init >()方法可能包含三种代码:

  • 调用另一个< init >()方法
  • 实现对任何实例变量的初始化
  • 构造方法体的代码

实际上,一般有下面的情况:

  • 构造函数明确的调用了同一个类中的另一个构造方法,即调用了this(),它对应的< init >()方法由两部分构成: 
    一个同类的< init >(…)方法的调用 
    实现了对应构造方法的方法体的字节码

  • 不是以this()开始的,也不是Object,由三部分组成: 
    超类的< init >()方法调用 
    任意实例变量初始化方法的字节码 
    实现了对应构造方法的方法体的字节码

什么意思?简单理解就是说< init >()就是从class文件字节码角度的构造函数,一般由Java代码里面的几部分构成。

超类的< init >()方法调用———————>对应super() 
任意实例变量初始化方法的字节码————>对应定义变量时的赋值代码 
实现了对应构造方法的方法体的字节码——>构造函数里面的代码

注:Java保证了一个对象被初始化前其父类也必须被初始化。有下面机制来保证:Java强制要求任何类的构造函数中的第一句必须是调用父类构造函数或者是类中定义的其他构造函数。如果没有构造函数,系统添加默认的无参构造函数,如果我们的构造函数中没有显示的调用父类的构造函数,那么编译器自动生成一个父类的无参构造函数。

举个栗子:

class B {
private int b = 10;
public B(){
b = 100;
}
}

编译成class文件后,使用命令 javap -c B.class 反编译 

很显然可以看到初始化< init >()分为三部分

// 第一部分:父类的<init>()方法
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
// 第二部分:实例变量初始化,也就是定义变量时的赋值
4: aload_0
5: bipush 10
7: putfield #2 // Field b:I
// 第三部分:构造函数方法体
10: aload_0
11: bipush 100
13: putfield #2 // Field b:I
16: return

学习到这里总结一下前面学习的内容就是: 
Java实例变量在初始化时的顺序是父类的初始化代码(xxx—>xxx—>xxx)—>定义变量时直接赋值—>构造函数代码块。

回头看开始的问题: 
直到Base类和Sub类完成了初始化过程(类初始化完成) 
在初始化Sub对象前,首先在堆区开辟内存并将子类中的baseName和父类中的baseName(已被隐藏)均赋为null 
接下来执行对象的初始化过程,由于Sub类的构造函数没有写,初始化代码包含三部分:

  1. super();调用Base类的init<>()

    1. 调用super()也就是Object类的init<>()
    2. baseName = “base”;这里是父类的baseName赋值。
    3. 父类构造函数里面的:调用callName()由于该函数是在Sub类的里面调用的,所以当前的this其实是子类,由于多态调用子类Sub的callName方法此时子类的baseName变量还未赋值还是null!
  2. baseName = “sub”;这里是子类的baseName赋值。

  3. 空(构造函数什么都没有)

所以输出null!

Java实例变量初始化的更多相关文章

  1. Java 类的实例变量初始化的过程 静态块、非静态块、构造函数的加载顺序

    先看一道Java面试题: public class Baset { private String baseName = "base"; // 构造方法 public Baset() ...

  2. java学习之实例变量初始化

    实例变量的初始化方法 第一种:通过构造函数进行初始化. 第二种:通过声明实例字段初始化. 第三种:通过对象代码块初始化. 通过构造函数进行初始化方法 通过构造函数进行对象初始化,必须在类中声明一个带参 ...

  3. java静态类、静态方法、静态代码块,静态变量及实例方法,实例变量初始化顺序及内存管理,机制

    1.当一个类被第一次使用时,它需要被类加载器加载,而加载过程涉及以下两点: (1)在加载一个类时,如果它的父类还未被加载,那么其父类必须先被加载: (2)当类加载到内存之后,按照在代码中的出现顺序执行 ...

  4. java 实例变量的初始化

    1.对于实例变量,该类没创建一次实例,就需要为实例变量分配一块内存空间:2.程序通过Person对象来访问eyeNum类变量时,底层依然会转换为通过Person访问eyeNum类变量:3.当Perso ...

  5. 面试题:java实例变量,局部变量,类变量 背1

    一.实例变量 也叫对象变量.类成员变量:从属于类由类生成对象时,才分配存储空间,各对象间的实例变量互不干扰,能通过对象的引用来访问实例变量.但在Java多线程中,实例变量是多个线程共享资源,要注意同步 ...

  6. oc实例变量初始化方法

    1 使用实例setter方法 默认初始化方法 + setName:xxx setAge:xxx 2 使用实例功能类方法,默认初始化方法 + setName:xxx age:xxx3 使用实例初始化方法 ...

  7. java 实例变量和类变量的区别

    Example4_10.java public class Example4_10 { public static void main(String args[]) { Lader.下底=100; / ...

  8. Java 中变量初始化、子类和父类构造器调用的顺序

    先说结论 变量初始化 -> 父类构造器 -> 子类构造器 贴代码 Animcal.java 父类 public class Animal { private static int inde ...

  9. java 静态变量初始化

    java 静态变量在编译阶段就已经明确位置, 所以静态变量的声明与初始化在编码顺序上可以颠倒.也就是说可以先编写初始化的代码,再编写声明代码.如: public class Test { // 静态变 ...

随机推荐

  1. three.js入门系列之导入拓展类

    先来看一下three.js包的目录结构: 我们使用的时候,可以一次性import所有的功能,也可以按需引入,全依赖three.module.js这个文件对three.js的功能作了模块化处理: 但是, ...

  2. ng-bind 拼接字符标签

  3. C#中PadLeft和PadRight小知识点

    当我们显示字符串数据时,有时候我们需要考虑数据的排列美观. 比如一些人名和一些编号,我们想让他们整齐对齐显示等. C# String类提供了2种操作方法: String.PadLeft(int tot ...

  4. java之接口

    背景 为了防止[多重继承]:(在面向对象的编程语言(例如java)中,指一个类可以同时继承多个父类的行为和特征功能)所引发的"致命方块",出现了接口. 使用  定义 public ...

  5. 安装hadoop 2.2.0

    安装环境为 CentOS 64位系统, 大概分下面几个步奏, 0. 安装JDK1. 配置SSH2. 配置/etc/hosts3. 拷贝hadoop包到没台机器上4. 修改hadoop配置文件5. 关闭 ...

  6. Windows下ruby安装和ri设置

    1. ruby最强大的web框架ruby on rails .必学的.所以可以直接安装RailsInstaller. 地址:http://www.railsinstaller.org/en .安装过程 ...

  7. 51nod 1244 莫比乌斯函数之和 【杜教筛】

    51nod 1244 莫比乌斯函数之和 莫比乌斯函数,由德国数学家和天文学家莫比乌斯提出.梅滕斯(Mertens)首先使用μ(n)(miu(n))作为莫比乌斯函数的记号.具体定义如下: 如果一个数包含 ...

  8. 优化 UWP 中图片的内存占用

    跟图片打交道的 UWP 应用或多或少都会遇到图片带来的性能问题,就算不主要处理图片,做个论坛做个新闻客户端都涉及到大量图片.一个帖子.一篇文章里多半都是些高清大图,这些图片一张即可占用程序 1~2M ...

  9. web安全知识

    参考文章 :  https://www.mudoom.com/php%E5%AE%89%E5%85%A8%E7%BC%96%E7%A0%81/ SQL注入 造成sql注入的原因是因为程序没有过滤用户输 ...

  10. 实用且堪称神器的Chrome插件推荐

    前言 相信很多人都在使用 Chrome 浏览器,其流畅的浏览体验得到了不少用户的偏爱,但流畅只是一方面, Chrome 最大的优势还是其支持众多强大好用的扩展程序(Extensions).最近为了更好 ...