java对象初始化面试问题总结

来源:互联网 发布:华大基因 待遇 算法 编辑:程序博客网 时间:2024/05/29 18:37

这是一道阿里巴巴的关于Java对象初始化的面试题,但是需要面试者对Java中对象初始化有一个透彻的认识,首先这道题对我有所启发,所以我将记录下来,大家相互学习。
代码如下:

public class InitializeDemo {    private static int k = 1;    private static InitializeDemo t1 = new InitializeDemo("t1");    private static InitializeDemo t2 = new InitializeDemo("t2");    private static int i = print("i");    private static int n = 99;    static {        print("静态块");    }    private int j = print("j");    {        print("构造块");    }    public InitializeDemo(String str) {        System.out.println((k++) + ":" + str + "   i=" + i + "    n=" + n);        ++i;        ++n;    }    public static int print(String str) {        System.out.println((k++) + ":" + str + "   i=" + i + "    n=" + n);        ++n;        return ++i;    }    public static void main(String args[]) {        new InitializeDemo("init");    }}

结果如下:

1:j   i=0    n=0  2:构造块   i=1    n=1  3:t1   i=2    n=2  4:j   i=3    n=3  5:构造块   i=4    n=4  6:t2   i=5    n=5  7:i   i=6    n=6  8:静态块   i=7    n=99  9:j   i=8    n=100  10:构造块   i=9    n=101  11:init   i=10    n=102 

那么为什么会出现上面的结果呢。首先我们要写了解对象的初始化过程是按照什么顺序来进行的。
这里的核心理念有:

1.静态属性和静态代码块都是在类加载的时候初始化和执行,两者的优先级别是一致的,且高于非静态成员,执行按照编码顺序。  2.非静态属性和匿名构造器在所有的构造方法之前执行,两者的优先级别一致,执行按照编码顺序。  3.以上执行完毕后执行构造方法中的代码。  

总结一句话就是:(静态属性=静态代码块)> (非静态属性 = 构造块)> 构造方法。

然后我们来逐步分析代码执行过程。

1.运行main方法的时候,JVM会调用ClassLoader类加载器来加载InitializeDemo类,那么一起源于这次加载。  2.上面有五个静态属性,所以会按顺序逐一初始化这五个静态属性。  3.private static int k = 1; 此时将k初始化为14.private static InitializeDemo t1 = new InitializeDemo("t1");创建InitializeDemo对象,这里我的理解是创建对象,初始化非静态属性和构造代码块。因此先执行private int j = print("j");,打印出j,然后执行构造块,最后执行构造方法。  5.private static InitializeDemo t2 = new InitializeDemo("t2");同步骤46.private static int i = print("i");打印i。  7.private static int n = 99;直到这一步,n才被赋值为99,之前是从默认的0开始++的。  8.静态属性初始化完毕,代码走到静态块,打印出静态块,此时n=999.静态属性和静态块执行完毕,然后执行main方法中的代码new InitializeDemo("init");  10.main方法中创建对象,先初始化非静态属性,private int j = print("j");打印j,然后执行构造块,最后执行构造方法。

这里没有提到基类,如果遇到,加上一点,基类静态优先于衍生类静态执行;

继承对于初始化的影响

这里主要是理解编译时类型和运行时类型的不同,从这个不同中可以看出 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类继承的。