在同一个类中访问另一个对象的私有字段

108
class Person 
{
   private BankAccount account;

   Person(BankAccount account)
   {
      this.account = account;
   }

   public Person someMethod(Person person)
   {
     //Why accessing private field is possible?

     BankAccount a = person.account;
   }
}

请暂时忽略设计方面的内容。我知道OOP规定私有对象是属于类的。我的问题是,为什么OOP设计中私有字段具有类级别的访问权限而不是对象级别的访问权限


7
我认为OP认为传递给“someMethod”的“Person”对象是一个独立的对象,因此该方法不应该访问它的私有成员,即使它在“Person”类内部。 - Inisheer
1
有些编程语言不会这样做(例如Newspeak)。你可能不太可能得到一个好的答案来解释为什么。你会得到从规定的结果逆向推导出的答案。 - Tom Hawtin - tackline
“someMethod”不是有效的方法。它没有返回任何内容,必须为“void”。 - Nicolas Barbulesco
1
如果不是这样的话,我认为编写复制构造函数和赋值运算符将会非常困难。 - rozina
在Scala中,您可以指定private[this]。我猜Java永远不会添加这个Scala特性。然而,我之所以遇到这个问题,是因为我的IDE出现了故障,告诉我实例A无法访问实例B的类私有字段,即使它们是同一个类。我保存后,灯从红色变成绿色,没有经过黄色。 - Alonso del Arte
9个回答

81

我也有点好奇这个问题的答案。

我在这里找到了一个最令人满意的回答,是来自 Artemix 的另一篇帖子(我将 AClass 重命名为 Person 类):为什么使用类级别的访问修饰符而不是对象级别的?

private 修改器强制实现封装原则。

它的思想是“外部世界”不应该对 Person 内部过程进行更改,因为 Person 的实现可能随着时间的推移而发生变化(你必须更改整个外部世界以修复实现差异——这几乎是不可能的)。

当 Person 实例访问其他 Person 实例的内部时,您可以确信两个实例总是知道 Person 的实现细节。如果 Person 内部的逻辑处理发生更改,您只需更改 Person 的代码。

编辑:

投票Artemix的答案。我只是把它复制粘贴过来。


6
这可能是原因。但这是一个不好的想法。这会鼓励不良做法。在类“Person”中访问Person字段的开发人员,不需要知道整个类的实现。好的做法是使用访问器,而无需知道访问器执行哪些操作。 - Nicolas Barbulesco
19
我认为回答中给出的原因是合理的。例如,如果您想在Java类中实现equals(Object)方法以检查Person对象与另一个Person实例的相等性,则可能希望使外部世界能够检查这两个实例是否相等,但您可能不希望使用公共访问器方法将用于检查相等性的类私有字段全部暴露给外部世界。具有类级访问权限的private字段使得可以实现此类方法而无需实现此类公共方法。 - Malte Skoruppa
1
@MalteSkoruppa - 这是一个很好的例子(实现方法equals)。 - Nicolas Barbulesco
@MalteSkoruppa - 然而,实现equals方法可以通过调用私有访问器来完成。 - Nicolas Barbulesco
@NicolasBarbulesco 当然可以,但关键是,无论您使用私有访问器还是直接访问私有字段来实现方法,private 都必须授予类级别的访问权限。我同意使用访问器通常是一个好习惯,但在这种情况下,这主要是一个上下文和个人风格的问题。请注意,Joshua Bloch 在《Effective Java》(第 8 条)和 Tal Cohen 在 这篇 Dr. Dobbs 文章 中,在讨论如何实现 equals 时,在他们的代码清单中直接访问私有字段。 - Malte Skoruppa

27

很好的问题。似乎对象级别的访问修饰符会更进一步地强制执行封装原则。

但实际情况是相反的。让我们举个例子。假设您想在构造函数中深度复制一个对象,如果您不能访问该对象的私有成员,则唯一可能的方法是为所有私有成员添加一些公共访问器。这将使您的对象对系统中所有其他部分都裸露

因此,封装并不意味着对所有其他部分关闭。它意味着有选择地开放给哪些人。


2
这个答案需要被投票支持!其他的答案只是重申了“规则”,但只有这个答案真正揭示了规则背后的原因并抓住了重点。 - mzoz
5
但是,让一个对象负责提供自身的副本不是更好吗?这样,如果你需要一个对象的深层副本,无论是同一类的另一个对象还是不同类的对象,都可以使用相同的机制,例如 o.deepCopy() - dirtside

17

参见Java语言规范第6.6.1节“确定可访问性”

其中提到:

否则,如果成员或构造函数被声明为private,则只有当它在包含该成员或构造函数的顶层类(§7.6)的主体内部发生时,才允许访问。

点击上面的链接获取更多详细信息。 因此,答案是:因为James Gosling和Java的其他作者决定这样做。


1
OP的问题并不特定于Java,而是关于许多面向对象编程语言采用这种设计选择的哲学。这个答案只是重申了事实,没有解释为什么要做出这样的决定。 - wlnirvana

6
这样做是因为你在 class Person 中 - 一个类可以探索它自己类型的类。当你想要编写拷贝构造函数时,这将真正有帮助。
class A
{
   private:
      int x;
      int y;
   public:
      A(int a, int b) x(a), y(b) {}
      A(A a) { x = a.x; y = y.x; }
};

如果我们想要为我们的大数类编写operator+operator-,则可以这样做。

这与依赖注入类似。您可以注入一些其他类的对象,在其中您无法访问私有变量。 - Nageswaran
如果您正在尝试从类B的对象构建类A,并且B有私有组件,那么要么A需要被声明为友元,要么只能看到公共部分[如果A是从B派生的,则可能受保护]。 - Mats Petersson
在Java和.NET中没有友元的概念。在这种情况下,应该如何处理? - Nageswaran

1

关于Java中为什么私有可见性的语义是类级别而不是对象级别,以下是我的个人看法。

我认为这里的关键是方便。事实上,如果私有可见性是对象级别的,就必须将方法公开给其他类(例如在同一个包中),这在OP所示的情况下会强制执行。

事实上,我既无法构思也找不到任何例子,表明与对象级别的可见性相比,类级别的可见性(如Java提供的)会产生任何问题。

话虽如此,具有更细粒度的可见性策略系统的编程语言可以同时支持对象级别和类级别的可见性。

例如Eiffel,提供了选择性导出:您可以将任何类特征导出到您选择的任何类中,从{NONE}(对象级别)到{ANY}(相当于public,也是默认值),到{PERSON}(类级别,参见OP的示例),到特定的类组{PERSON,BANK}。

值得一提的是,在Eiffel语言中,你不需要将属性设为私有并编写getter方法来防止其他类对其进行赋值。在Eiffel中,默认情况下只能以只读模式访问公共属性,因此你不需要一个getter方法来返回它们的值。
当然,你仍然需要一个setter方法来设置属性,但是你可以通过将其定义为该属性的“assigner”来隐藏它。如果你愿意,这允许你使用更方便的赋值运算符而不是调用setter方法。

0
因为private 访问修饰符只在内部可见。该方法仍然在类中

我的问题是为什么它被设计成“在类内”,而不是“仅在对象内”? - Nageswaran
因为这就是Java的设计方式。 - darijan
为什么Java会被设计成这样? - Nageswaran
Java 没有成功,是 Java 的创造者成功了。为什么?因为他们很厉害! - darijan
1
Java的创造者不会没有理由地这样做,肯定有一些原因;我想知道这个原因。 - Nageswaran

0

private字段在声明该字段的类/对象中是可访问的。它对于位于其外部的其他类/对象是私有的。


-1

这里我们首先要理解的是,我们必须遵循面向对象编程原则,所以封装就是将数据包装在一个包(即类)中,然后将所有数据表示为对象并易于访问。因此,如果我们将字段设置为非私有,则会单独访问它,这会导致不良实践。


-2

这与那个问题没有任何关系。 - ihebiheb

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