使用java中的多态在父类中打印时,为什么子私有字段为空?

时间:2018-02-22 07:47:44

标签: java inheritance polymorphism

据了解,子类getS中的BB方法会覆盖父类AA中的相同方法。然而,尽管两个类都初始化了由s返回的字段getS,但它打印为null。为什么会这样?

这是代码:

public class AA {
    public String getS() {
        return s;
    }
    private String s = "hello1";
    public AA() {
        System.out.println(service() + getS());
    }
    public static String service() {
        return "A service ";
    }
}

public class BB extends AA {
    private String s = "hello2";
    @Override
    public String getS() {
        return s;
    }
    public static String service() {
        return "B service ";
    }
}

public class CC {
    public static void main(String[] args) {
        BB b =new BB(); //prints "A service null"
    }
}

4 个答案:

答案 0 :(得分:4)

当我们致电new SomeClass()

  1. new运算符首先创建SomeClass的对象,但该对象的所有字段都设置为默认值(0,' \ 0',false,null)。

  2. 创建对象后,将对初始化对象执行构造函数的代码(将其字段设置为正确的值,并可能执行其他一些操作)。

  3. 但是如果类具有父类,则在我们开始使用可能依赖于那些字段状态的继承方法之前,构造函数首先(隐式或显式)调用其super()构造函数以确保所有继承的字段都已正确初始化。

    但方法是多态的(只要它们不是privatestaticfinal)。这意味着当在超类中时,我们调用被覆盖的方法,"最新版本"代码将被执行(因为多态性使用this实例的实际类型 - 由new关键字返回 - 来定位应从中开始搜索应该执行的代码的类 - 这称为迟到或{ {3}})。

    因此,您在getS()超类中调用AA方法但在BB实例上调用(因为这是new BB创建的),来自BB类的重写代码被执行了。问题是此代码使用s类中声明的BB,但s尚未初始化。让我们看看BB调用的new BB()的默认构造函数如何:

    BB(){
        super(); //calls AA()
        s = "hello2"; // by default `s` holds `null` value 
                      // all initialization is moved to constructors 
                      // after superconstructor call
    }
    

    所以

    • 执行s = "hello2";之前s(BB类)持有null
    • 但在s初始化为"hello2"之前super()被调用(内部调用getS()
    • 由于getS()BB类的多态代码,因此该方法将被执行
    • 但该代码(来自BB)使用的BB s尚未初始化,因此它保留null,这就是您在控制台中看到的内容。

    因此,在可以覆盖的构造函数方法中,被认为是不好的做法(在大多数情况下)。我们应该限制自己调用非多态的方法,这意味着private staticfinal

答案 1 :(得分:2)

Here您可以看到创建对象时初始化的顺序。该答案基于Java Specs

让我复制粘贴

对象初始化

  
      
  1. 通常,只要创建新对象,就会初始化对象   通过评估类实例创建表达式。这继续下去   如下:

  2.   
  3. 将构造函数的参数分配给新创建的参数   此构造函数调用的变量。

  4.   
  5. 如果此构造函数以显式构造函数调用开头   (§8.8.7.1)同一类中的另一个构造函数(使用此),   然后评估参数并处理构造函数调用   递归地使用这五个相同的步骤。如果那个构造函数   调用突然完成,然后此过程完成   突然出于同样的原因;否则,继续步骤5.

  6.   
  7. 此构造函数不以显式构造函数开头   在同一个类中调用另一个构造函数(使用此方法)。如果   这个构造函数用于Object以外的类,然后是这个   构造函数将以a的显式或隐式调用开始   超类构造函数(使用超级)。评估参数和   使用递归进行超类构造函数调用的过程   这五个步骤相同。如果该构造函数调用完成   突然,这个程序突然完成同样的事情   原因。否则,请继续执行步骤4.

  8.   
  9. 执行实例初始值设定项和实例变量初始值设定项   对于此类,分配实例变量的值   初始化器到相应的实例变量中   从左到右的顺序,它们在源中以文本形式出现   该类的代码。如果执行任何这些初始化程序   导致异常,然后不再处理初始化器   并且此过程突然以相同的异常完成。   否则,请继续步骤5.

  10.   
  11. 执行此构造函数的其余部分。如果执行   突然完成,然后这个程序突然完成   同样的道理。否则,此过程正常完成。

  12.   

执行新的BB()时,将调用默认构造函数

 public BB() {
      super();
 }

super()在您的代码中声明为

public AA() {
    System.out.println(service() + getS());
}

service()是一个静态方法,无法覆盖,因此调用A.service()方法,并将其结果与调用覆盖方法的结果B.getS()

连接起来

此时(参见5),成员B.s尚未初始化,其值为null

这些是获得

的原因
A service null

答案 2 :(得分:1)

这是因为类层次结构。运行时

 System.out.println(service() + getS());

它调用重写的getS()方法。因为它打印了在类BB中声明的属性(它不在类AA中访问s属性,因为它是私有的)。

此时(在AA类中运行构造函数时)BB类中的属性未初始化。因此它打印为null。

答案 3 :(得分:1)

原因是BB没有构造函数本身。 new BB()调用 public AA(),然后执行以下语句:

System.out.println(service() + getS());

然后在 A中调用,所以:

  • 服务:A

  • 的方法
  • getS():返回s(null)的默认值,

调用构造函数意味着尚未创建对象,然后属性的初始化仍然为null。