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

这是一道阿里巴巴的关于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类继承的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值