Java中,子类是否继承超类的私有成员?

3

我已经阅读了这篇文章:

子类继承私有字段吗?

但是我仍然感到困惑...

我只是在谈论继承,而不是访问。我知道它们在类外部不可见。

但是子类的对象是否拥有超类中这些私有成员的自己的副本?

例如...

class Base {
    private int i;
}

class Derived extends Base {
    int j;
}

现在,

Base b = new Base();
Derived d = new Derived();

int的大小为4

现在,

b的大小是4,d的大小是8吗?

还是说d的大小也只有4?

当然,我说的是堆上的对象,而不是引用。

更新:我刚刚在Kathy Sierra和Bert的SCJP书中读到...它说它们没有被继承...我发布这个更新,因为仍然有一群人说是的...


private 成员不会被继承,但存在于父类(超类)中实例化的对象中。你的问题实际上是关于遮蔽的(我想是吧?你似乎在谈论对象的大小,这在 Java 中不适用)。请参见:https://dev59.com/s3A75IYBdhLWcg3wDUco - Brian Roach
你能提供确切的文本说明它们没有被继承吗?我认为在这一点上术语上存在混淆。根据Java规范,子类不会“继承”其超类的私有成员,但这是使用非常严格的继承概念对Java规范有用的。该子类的实例肯定会有自己的这些成员的实例。因此,在你问题中使用的意义上,它们是被继承的。 - Nimrand
@Nimrand,具体文本如下:“当成员被声明为私有时,子类无法继承它。”在第1章“声明和访问控制”中的“私有成员”一节。 - Sam
5个回答

6

是的,子类的实例将拥有父类的一个私有域的拷贝。

但是这些域不会被子类看到,所以唯一访问它们的方式是通过父类的方法。


4
由于JVM规范,class文件是这样构建的:
ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

super_class字段包含:

`对于一个类,super_class项的值必须为零或者是常量池表中有效索引。如果super_class项的值不为零,则该索引处的常量池条目必须是表示此类文件定义类的直接超类的CONSTANT_Class_info(§4.4.1)结构。它的直接超类及其任何超类都不能在其ClassFile结构的access_flags项中设置ACC_FINAL标志。

如果super_class项的值为零,则此类文件必须表示Object类,即没有直接超类的唯一类或接口。

对于一个接口,super_class项的值必须永远是常量池表中的有效索引。该索引处的常量池条目必须是表示类Object的CONSTANT_Class_info结构`。

fields []部分更有趣:

fields表中的每个值必须是field_info(§4.5)结构,给出此类或接口中字段的完整描述。字段表仅包括由此类或接口声明的那些字段。它不包括表示从超类或超接口继承的字段的项。

因此,编译后的类不包含继承的字段。另一方面,当创建对象时,private超级字段在内存中。为什么?让我们想象一下这个例子:

 classs A {
      int a;

      A(int a) {
          this.a = a;
      }

      void methodA() {
          System.out.println("A is: " + a);
      }
 }

 classs B extends A {
      int b;

      B(int b) {
          super(10);
          this.b = b;
      }

      void methodB() {
          super.methodA();
          System.out.println("B is: " + b);
      }
 }

输出应该是:A是:10 \n B是...

说实话,这个详细的解释有点复杂了,不过提供了有关Java内部工作原理的重要信息,对于解释中的类头结构给予赞赏。 - Igor Čordaš

1

一个对象的大小包括一些开销,因此既不是Base也不是Derived占用更多空间。

进行快速测试,看起来Base的大小约为20字节,Derived的大小约为28字节。请注意,这些结果可能在不同的JVM上有所不同。

public static void main(String[] args) throws InterruptedException {
    int size = 500000;
    double mem;

    mem = Runtime.getRuntime().freeMemory();
    Base[] base = new Base[size];
    for (int i = 0; i < size; i++) {
        base[i] = new Base();
    }
    System.out.println((mem - Runtime.getRuntime().freeMemory()) / size);

    System.gc();
    Thread.sleep(100);
    System.gc();

    mem = Runtime.getRuntime().freeMemory();
    Derived[] derived = new Derived[size];
    for (int i = 0; i < size; i++) {
        derived[i] = new Derived();
    }
    System.out.println((mem - Runtime.getRuntime().freeMemory()) / size);
}

0

不,私有字段被设计为只能在其声明的类中访问。它们要想在类外部(包括派生类)可访问的唯一方式是通过公共方法。

如果您想使基类中的成员被派生类继承,请使用"protected"而不是private。代码应该如下:

class Base {
  protected int i;
}

class Derived extends Base {
  int j;
}

"Protected" 访问修饰符允许成员被继承并分配自己的访问修饰符。


您说得完全正确,但我们讨论的是继承它们而不是访问它们。 - Sam
在这种情况下,如果您想将基类成员继承到派生类中,请将这些成员在基类中声明为“protected”。 - winux

0

是的,派生类会有基类的私有字段。如果派生类定义了公共的getter和setter,您将能够使用该字段从派生类中使用它。

那么b的大小是4,d的大小是8吗?还是d的大小也只有4?

这是Java,不是C。您无法轻松地估计堆中对象的大小。它将比仅为4或8个字节更大。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接