子类会继承私有字段吗?

287

这是一个面试问题。

子类会继承私有字段吗?

我回答“不会”,因为我们不能使用“常规的OOP方法”访问它们。但是面试官认为它们被继承了,因为我们可以间接或使用反射访问这些字段,并且它们仍然存在于对象中。

当我回来后,在javadoc中找到了以下引用:

超类中的私有成员

子类不会继承其父类的私有成员。

你知道面试官观点的任何论据吗?


40
我曾经处于类似的情况,我意识到我甚至不想为一个面试官连Java基础都不如我的公司工作。 :) - biziclop
56
即使面试官知道你是对的,有时候他还是会不同意你。一个好的面试官会尽力了解你的技术知识以外的更多内容。 - Andy Thomas
4
Java 语言规范是否也写得很糟糕?请参考 RD01 的答案:https://dev59.com/CG445IYBdhLWcg3wwcyg#4716335。 - OscarRyz
10
我不愿意与故意撒谎来测试我的反应的人一起工作,Andy Thomas-Cramer。 - biziclop
4
嗯,我认为我们首先应该弄清楚Java中“继承”的含义。子类没有私有字段和子类有私有字段但无法访问它是不同的,哪一个指的是Java中继承的确切含义? - MengT
显示剩余14条评论
21个回答

265
大部分关于继承的问题/答案的混乱都围绕着继承的定义。显然,如@DigitalRoss所解释的,子类的一个对象必须包含其超类的私有字段。正如他所说,没有访问私有成员并不意味着其不存在。

然而。这与类的继承概念不同。就像在Java世界中一样,如果存在语义问题,则仲裁者是Java语言规范(目前是第三版)。

正如JLS(https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.2)所述:

类声明为private的成员不会被该类的子类继承。只有声明为protected或public的类成员才会被声明在与该类不同的包中的子类继承。
这回答了面试官提出的确切问题:"子是否继承私有字段"。(由我强调)

答案是不会。他们不会。子类的对象包含其超类的私有字段。子类本身不知道其超类的私有字段。

它是一个晦涩难懂的语义吗?是。它是一个有用的面试问题吗?可能不是。但是JLS为Java世界建立了定义,并且在这种情况下,它做到了明确。

3
@digital 为什么叹气?我知道你相信自己是正确的。我不反对你认为对象继承是大多数程序员所学习和思考的内容。但是JLS定义直接适用于原始问题。这是语义学,但JLS决定了定义,而不是你或我。 - robert_x44
4
为了解决这一切,一个方法是承认在Java世界中,“继承”这个词被用于描述派生类和父类之间的关系有两种非常不同的方式。是的,JSL是权威的。是的,它意味着你可以使用“继承”这个不幸的词。但显然子类会改变其父类的私有字段(因为现在没有一个单词来描述这种情况)。 - DigitalRoss
1
@digital 它们位于类的对象中,而不是类本身。Simula将它们称为连接对象。当子类的对象被创建时,它由“前缀对象”组成。超类对象是一个前缀对象,它本身可以包含其他前缀对象。我认为说JLS有“明显糟糕的措辞”是傲慢的。至于我们使用什么词,当然是继承。使用略微模糊的术语并没有错。这种情况经常发生。但这并不意味着没有精确的定义。 - robert_x44
1
@digital 我们可以肯定地认为这个词被用在不同的方式上。 :) 我们也可能会认同,一个依赖于模糊术语的面试问题可能不是一个好问题。 - robert_x44
4
有没有Java/Oracle关于“子类对象包含其超类的私有字段”的参考文献?我同意这一点,但是找不到任何官方文件证明。 - MengT
显示剩余9条评论

91

是的

需要意识到,虽然有两个类,但只有一个对象。

所以,是的,它继承了私有字段。它们可能是适当的对象功能所必需的,虽然父类的对象不是派生类的对象,但派生类的实例绝大部分都是父类的实例。没有所有字段就不能这样做。

不能直接访问它们,但它们确实被继承了。它们必须继承。

这是个好问题!


更新:

嗯,“不”

好吧,我想我们都学到了些什么。由于JLS采用了精确的“未继承”措辞,回答“不”是正确的。由于子类无法访问或修改私有字段,因此可以说它们并未被继承。但实际上只有一个对象,它确实包含了私有字段,因此如果有人错误地理解JLS和教程中的措辞,将很难理解OOP、Java对象以及真正发生的事情。

更新更新:

这里涉及到一个基本的模糊性:究竟讨论的是什么?对象?还是在某种意义上谈论类本身?当描述类而不是对象时,可以允许很大范围的内容。因此,子类不继承私有字段,但是作为子类实例的对象肯定包含私有字段。


2
@Ma99uS。当然它们会被重复使用。这就是继承的全部意义所在。如果没有它们,派生类型就不能成为父类型的示例。面向对象编程将毫无意义。多态类型 将停止工作。理解只有一个对象且你是父类型实例的概念对于理解面向对象编程至关重要。您必须克服此问题才能真正理解它。 - DigitalRoss
2
不确定父类的例子是否很好,因为一个字段可以在父类仍然存在并且具有该字段的情况下被继承。如果继承是这样工作的,那么我就可以在我父亲还活着并且他仍然拥有同样的钱的情况下继承他的钱。我的孩子们将分别拥有他的钱和我的钱。 - Peter Lawrey
2
@Peter Lawrey,我并不是在争论什么,但这是我的想法。父类有一辆“汽车”,他把它放在一个孩子没有钥匙的“私人”储物柜里。你确实继承了这辆“汽车”,但对你来说它是无用的。因此,实际上,你并没有从继承中受益。 - Nishant
7
Java语言规范明确说明它们没有被继承。没有任何特殊情况,它们就是不会被继承。在Java的环境下,任何其他定义都是错误的。 - biziclop
2
我同意你的观点,只需要确认一下:超类的私有方法也包含在子类对象中,对吗? - MengT
显示剩余13条评论

23

非私有字段不会被继承...这就是为什么Protected被发明出来的原因。这是按设计需求实现的。我想这证明了protected修饰符的存在。


现在进入具体情境。如果您指的是从派生类创建的对象中是否存在继承,那么是的,它存在。

如果您的意思是派生类是否可以使用它,那么不行。

现在谈到函数式编程,超类的私有字段不能以有意义的方式被子类继承。对于子类而言,超类的私有字段与任何其他类的私有字段相同。

从功能上讲,它并没有被继承。但是理论上,它被继承了。


好的,刚刚查看了Java教程内容,他们引用了以下内容:

 

父类中的私有成员

    

子类不会继承其父类的私有成员。但是,如果父类具有公共或受保护的方法来访问其私有字段,则子类也可以使用这些方法。

参考:http://download.oracle.com/javase/tutorial/java/IandI/subclasses.html

我同意,该字段存在。但是,子类对该私有字段没有任何特权。对于子类而言,私有字段与任何其他类的私有字段相同。

我认为这纯粹是观点问题。您可以将论据塑造成任何一方。最好两种方式都能证明。

 


2
这不是正确的。你不能访问它们,这是正确的。但是它们必须像我解释的那样被继承。 - DigitalRoss
1
非常好的回答!!!对于“我认为这纯粹是观点问题。”和“证明了受保护修饰符的存在是有道理的。”给予+1。 - Ravi

17

这取决于您对“继承”的定义。子类是否仍然在内存中具有相应的字段?肯定是有的。它是否可以直接访问它们?不行。这只是定义的微妙之处;关键是要理解实际发生了什么。


没错。但是我认为在这样基础的问题中,应该有通用的答案。 - Stan Kurilin
我认为这是关于Java继承的定义。 - OscarRyz
否则就取决于你对“field”的定义。定义一个整数型“foo”字段就像租一个整数大小的存储柜并在上面贴上“foo”标签一样。如果该字段声明为私有,派生类将会继承一个未标记的整数大小的存储柜。是否继承了这个“字段”取决于是否将那个未标记的存储柜称为“字段”。 - supercat

12

我将用代码演示这个概念。子类实际上继承了超类的私有变量。唯一的问题是,除非您在超类中提供公共的getter和setter来访问私有变量,否则它们对子对象不可访问。

考虑Dump包中的两个类。Child继承Parent。

如果我没记错的话,在内存中,子对象由两个区域组成。一个是仅属于父类部分,另一个是仅属于子类部分。子类只能通过父类中的公共方法访问其父类代码中的私有部分。

可以这样想。Borat的父亲Boltok有一个保险柜,里面有10万美元。他不想分享他的“私有”保险柜。所以,他没有提供保险柜的钥匙。Borat继承了这个保险柜。但是,如果他甚至无法打开它,那还有什么用呢?如果他的父亲提供了钥匙就好了。

父类 -

package Dump;

public class Parent {

    private String reallyHidden;
    private String notReallyHidden;

    public String getNotReallyHidden() {
        return notReallyHidden;
    }

    public void setNotReallyHidden(String notReallyHidden) {
        this.notReallyHidden = notReallyHidden;
    }

}//Parent

孩子 -

package Dump;

public class Child extends Parent {

    private String childOnly;

    public String getChildOnly() {
        return childOnly;
    }

    public void setChildOnly(String childOnly) {
        this.childOnly = childOnly;
    }

    public static void main(String [] args){

        System.out.println("Testing...");
        Child c1 = new Child();
        c1.setChildOnly("childOnly");
        c1.setNotReallyHidden("notReallyHidden");

        //Attempting to access parent's reallyHidden
            c1.reallyHidden;//Does not even compile

    }//main

}//Child

11
不,它们不会继承它。
其他类间接使用它并不意味着继承,而是封装的概念。
例如:
class Some { 
   private int count; 
   public void increment() { 
      count++;
   }
   public String toString() { 
       return Integer.toString( count );
   }
}

class UseIt { 
    void useIt() { 
        Some s = new Some();
        s.increment();
        s.increment();
        s.increment();
        int v = Integer.parseInt( s.toString() );
        // hey, can you say you inherit it?
     }
}

您还可以通过反射在UseIt内获取count的值。这并不意味着您继承了它。

更新

即使该值存在,子类也不会继承它。

例如,一个定义为:

class SomeOther extends Some { 
    private int count = 1000;
    @Override
    public void increment() { 
        super.increment();
        count *= 10000;
    }
}

class UseIt { 
    public static void main( String ... args ) { 
        s = new SomeOther();
        s.increment();
        s.increment();
        s.increment();
        v = Integer.parseInt( s.toString() );
        // what is the value of v?           
     }
}

这种情况与第一个例子完全相同。属性count被隐藏,没有被子类继承。但是,正如DigitalRoss所指出的那样,该值仍然存在,但不是通过继承手段而来。
可以这样说,如果你的父亲很富有并给你一张信用卡,你仍然可以用他的钱买东西,但这并不意味着你已经继承了所有的钱,对吧? 其他更新 虽然非常有趣,但了解为什么该属性存在也很重要。
坦白地说,我没有确切的术语来描述它,但是JVM以及它的工作方式也会加载“未被继承”的父定义。
我们实际上可以更改父级,子类仍将工作。
例如:在这里
//A.java
class A {
   private int i;
   public String toString() { return ""+ i; }
}
// B.java
class B extends A {}
// Main.java
class Main {
   public static void main( String [] args ) {
      System.out.println( new B().toString() );
    }
}
// Compile all the files
javac A.java B.java Main.java
// Run Main
java Main
// Outout is 0 as expected as B is using the A 'toString' definition
0

// Change A.java
class A {
   public String toString() {
      return "Nothing here";
   }
}
// Recompile ONLY A.java
javac A.java
java Main
// B wasn't modified and yet it shows a different behaviour, this is not due to 
// inheritance but the way Java loads the class
Output: Nothing here

我猜这个确切的术语可以在这里找到:Java虚拟机规范


1
它们必须被继承,否则多态类型就毫无意义。请看我的解释。的确你不能随意更改它们,但它们是必要的。它们必须存在。 - DigitalRoss
你的代码中没有继承(extends/implements)关键字,所以这不是一个继承的例子。 - fmucar
1
如果它们在那里,它们是怎么到那里的?因为子类定义了它们吗?不是。因为它们被继承了? - DigitalRoss
1
关于 封装继承 的优劣的观点非常棒,我想这个回答应该得到更多的赞同。 - Eric
感谢您的示例,它意味着子类B引用了其父类的方法和属性。这就是为什么我们不必重新编译子类B,JVM会自动允许子类B看到我提到的父类A中所有“引用”的更改。 - Long Tran
显示剩余2条评论

6
好的,我的回答是 - 子类不会继承父类的私有成员,但是只有通过公共getter或setter方法或原始类的适当方法才能访问子类或子类对象。一般的做法是将成员声明为私有的,并使用公共的getter和setter方法来访问它们。那么如果只继承getter和setter方法,而这些方法所处理的私有成员不可用于对象,那还有什么意义呢?在这里,“继承”指的是直接在子类中可用以由子类新引入的方法进行操作。

请将以下文件保存为ParentClass.java并尝试运行:

public class ParentClass {
  private int x;

  public int getX() {
    return x;
  }

  public void setX(int x) {
    this.x = x;
  }
}

class SubClass extends ParentClass {
  private int y;

  public int getY() {
    return y;
  }

  public void setY(int y) {
    this.y = y;
  }

  public void setXofParent(int x) {
    setX(x); 
  }
}

class Main {
  public static void main(String[] args) {
    SubClass s = new SubClass();
    s.setX(10);
    s.setY(12);
    System.out.println("X is :"+s.getX());
    System.out.println("Y is :"+s.getY());
    s.setXofParent(13);
    System.out.println("Now X is :"+s.getX());
  }
}

Output:
X is :10
Y is :12
Now X is :13

如果我们试图在SubClass的方法中使用ParentClass的私有变量x,那么它不会直接可以被修改(即没有被继承)。但是可以通过原始类的setX()方法(例如setXofParent()方法)在SubClass中进行修改,或者可以使用ChildClass对象通过setX()方法或setXofParent()方法进行修改,这最终调用了setX()。因此,setX()和getX()就像是访问ParentClass的私有成员x的门户一样。
另一个简单的例子是Clock超类具有hours和mins作为私有成员以及适当的getter和setter方法作为公共方法。然后将DigitalClock作为Clock的子类。如果DigitalClock的对象不包含hours和mins成员,则会出现问题。

2
根据Oracle文档 - 子类不会继承其父类的私有成员。但是,如果超类具有用于访问其私有字段的公共或受保护方法,则子类也可以使用这些方法。 - dganesh2002

5

好的,这是一个非常有趣的问题。经过我的研究,我得出了一个结论:父类的私有成员确实可以在子类对象中使用(但不可访问)。为了证明这一点,我写了一个包含父类和子类的示例代码,并将子类对象写入到一个文本文件中,然后读取了文件中名为“bhavesh”的私有成员,从而证明它确实在子类中可用,但由于访问修饰符而不可访问。

import java.io.Serializable;
public class ParentClass implements Serializable {
public ParentClass() {

}

public int a=32131,b,c;

private int bhavesh=5555,rr,weq,refw;
}

import java.io.*;
import java.io.Serializable;
public class ChildClass extends ParentClass{
public ChildClass() {
super();
}

public static void main(String[] args) {
ChildClass childObj = new ChildClass();
ObjectOutputStream oos;
try {
        oos = new ObjectOutputStream(new FileOutputStream("C:\\MyData1.txt"));
        oos.writeObject(childObj); //Writing child class object and not parent class object
        System.out.println("Writing complete !");
    } catch (IOException e) {
    }


}
}

打开MyData1.txt文件,查找名为“bhavesh”的私有成员。请告诉我你们的想法。


3
似乎子类确实继承了私有字段,因为这些字段在子类的内部运作中被使用(从哲学角度讲)。子类在其构造函数中调用超类构造函数。如果超类构造函数在其构造函数中初始化了这些字段,则显然子类通过调用超类构造函数继承了超类的私有字段。这只是一个例子。但是,如果没有访问器方法,子类就无法访问超类的私有字段(这就像不能弹出iPhone的后面板以取出电池来重置手机一样...但电池仍在那里)。
PS 我遇到的众多继承定义之一: "继承——一种编程技术,允许派生类扩展基类的功能,继承它的所有状态(强调是我的)和行为。"
即使子类无法访问私有字段,这些私有字段仍然是超类的继承状态。

3
例如,
class Person {
    private String name;

    public String getName () {
        return this.name;
    }

    Person(String name) {
        this.name = name;
    }
}

public class Student extends Person {

    Student(String name) {
        super(name);
    }
    
    public String getStudentName() {
        return this.getName(); // works
        // "return this.name;" doesn't work, and the error is "The field Person.name is not visible"

    }
}


public class Main {
    public static void main(String[] args) {
        Student s = new Student("Bill");

        String name = s.getName(); // works
        // "String name = s.name;" doesn't work, and the error is "The field Person.name is not visible"

        System.out.println(name);
    }
}

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