为什么外部Java类可以访问内部类的私有成员?

203

我发现外部类可以访问内部类的私有实例变量。这是如何实现的?以下是演示此功能的示例代码:

class ABC{
    class XYZ{
        private int x=10;
    }

    public static void main(String... args){
        ABC.XYZ xx = new ABC().new XYZ();
        System.out.println("Hello :: "+xx.x); ///Why is this allowed??
    }
}

为什么允许这种行为?


这个问题困扰了我很长时间,直到我看到评论...解释了为什么我无法在我的机器上访问xx.x。 - Wang Sheng
4
这些评论让我感到困惑,我在Java 8中运行了上面的代码,它能够编译和运行。我可以访问 xx.x。 - leon
1
Harish,请问你能否取消接受已接受的答案(该答案并未回答你所提出的问题),而是接受下面Martin Andersson的答案,该答案非常详细地回答了你的问题? - temporary_user_name
1
就我个人而言,这种语法非常可怕:new ABC().new XYZ() - Josh M.
请更改已接受的答案 - 它并没有回答这里提出的问题。 - Moritz Wolff
10个回答

82

内部类仅是一种将真正属于原始外部类的某些功能干净地分离出来的方法。当您有以下 2 个要求时,它们可以用作:

  1. 外部类中某些功能最清晰的实现方式是在单独的类中实现。
  2. 尽管它在一个单独的类中,但该功能与外部类的工作方式非常紧密相关。

鉴于这些要求,内部类可以完全访问其外部类。由于它们基本上是外部类的成员,因此它们可以访问外部类的方法和属性,包括私有方法和私有属性。


240
本回答解释了为什么嵌套类可以访问其外部类的私有成员,但问题是为什么外部类可以访问嵌套类的私有成员。 - Andrew
17
只需在“给定这些要求,内部类可以完全访问它们的外部类”后面加上“反之亦然”,问题就得到了回答。 - anthropomo
15
这个并不是解决这个问题的正确答案,这里才是:https://dev59.com/tmIj5IYBdhLWcg3w8JNZ?lq=1 - Colin Su
4
不,它不需要。即使外部类没有访问内部类的私有成员变量,这两个要求也是完全可行的。 - O. R. Mapper
一个特别有用的例子是建造者模式,https://dev59.com/nXRC5IYBdhLWcg3wVvjL#1953567。父类只需要一个构造函数,接受一个建造者并访问它的所有成员变量。否则,您将需要在父类中拥有一个包含所有私有成员变量的私有构造函数。 - vikky.rk
显示剩余4条评论

68

如果你想要隐藏内部类的私有成员,可以定义一个只有公共成员的接口,然后创建一个实现这个接口的匿名内部类。以下是示例:

class ABC{
    private interface MyInterface{
         void printInt();
    }

    private static MyInterface mMember = new MyInterface(){
        private int x=10;

        public void printInt(){
            System.out.println(String.valueOf(x));
        }
    };

    public static void main(String... args){
        System.out.println("Hello :: "+mMember.x); ///not allowed
        mMember.printInt(); // allowed
    }
}

这是一段非常棒的代码片段。正是我所需要的。谢谢! - kevinarpe
请提供可运行的代码。此外,请说明为什么私有变量无法访问。 - androidyue
8
但是... 这个内部类是匿名的,你无法创建该内部类的多个实例,或在任何变量声明中使用该内部类等。 - O. R. Mapper
@O.R. Mapper,这就是为什么即使x在这里是公共的,也不允许像mMember.x那样使用。 - Ram

62

内部类(用于访问控制)被视为包含类的一部分。这意味着可以完全访问所有私有内容。

实现此功能的方式是使用合成的包保护方法:内部类将编译为同一包中的单独类(ABC$XYZ)。JVM 不直接支持这种级别的隔离,因此在字节码级别,ABC$XYZ 将具有包保护方法,外部类使用这些方法来访问私有方法/字段。


20

我认为这段代码可以很好地回答我的问题。 - shen

10
Thilo为你的第一个问题“这怎么可能?”添加了一个很好的答案。我想稍微详细解释一下第二个问题:为什么允许这种行为?
首先,让我们明确一点,这种行为不仅限于内部类,内部类根据定义是非静态嵌套类型。这种行为适用于所有嵌套类型,包括嵌套的枚举和接口,它们必须是静态的,并且不能有封闭实例。换句话说:嵌套代码可以完全访问封闭代码,反之亦然。
那么,为什么会这样呢?我认为通过一个例子可以更好地说明这一点。
想象一下你的身体和大脑。如果你向手臂注射海洛因,你的大脑就会产生高潮感。如果你的大脑的杏仁核区域看到他认为对你个人安全构成威胁的东西,比如一只黄蜂,它会让你的身体转过身来,毫不犹豫地逃向山丘。
所以,大脑是身体的固有部分 - 同样地,身体也是大脑的一部分。在这两个紧密相关的实体之间使用访问控制会削弱它们之间的关系。如果您确实需要访问控制,则需要将类更加分离为真正独立的单元。在那之前,它们是同一个单元。一个进一步研究的驱动示例是看一下Java中通常如何实现Iterator
从封闭代码到嵌套代码的无限访问使得向嵌套私有类型的字段和方法添加访问修饰符变得相当无用。这样做只会增加混乱,并可能给Java编程语言的新手提供错误的安全感。

1
这真的应该成为被接受的答案。非常清晰和详细。相反,被接受的答案甚至没有回答问题。 - temporary_user_name
1
在我看来,这仍然没有回答为什么我们不能轻松地向内部类添加私有字段,而外部类无法直接访问。除非我错了,否则这破坏了内部类的主要用例之一——创建短暂的“类似结构体”的不可变类型。值得一提的是,C#很高兴支持这一点:https://repl.it/repls/VengefulCheeryInverse - Josh M.
1
如果你不将嵌套类暴露给外部世界,通常可以无限访问嵌套代码,这使得向嵌套类型的字段和方法添加访问修饰符变得相当无用。此外,如果你对嵌套类的字段和方法使用默认访问权限,则它们可以被你的整个包访问,这很可能不是你想要的。 - Mark Rotteveel
谢谢马克。我去掉了“大部分”并加上了“私人”。如果你有任何关于如何改进答案的想法,请告诉我 ❤️ - Martin Andersson
谢谢马克。我去掉了“大部分”并加上了“私人”。如果你有任何更好的建议,请告诉我 ❤️ - undefined

5

内部类在工厂模式中是一个很重要的应用场景,这是我个人的看法。封装类可以准备内部类的实例而不受访问限制,并将该实例传递给外部世界,在那里私有访问将被尊重。

abyx相反,声明类为静态并不会改变对封装类的访问限制,如下所示。此外,同一封装类中的静态类之间的访问限制也是有效的。我感到很惊讶...

class MyPrivates {
    static class Inner1 { private int test1 = 2; }
    static class Inner2 { private int test2 = new Inner1().test1; }

    public static void main(String[] args) {
        System.out.println("Inner : "+new Inner2().test2);
    }
}

1
很好的评论,但没有答案。 - ceving
@cerving 这实际上是唯一一个让我看到这个本来很奇怪的设计决策有实际应用的答案。问题是为什么要这样决定,这是一个很好的推理 - 展示了你可能希望外部类在内部类中访问的内容与你希望其他不相关的类访问的内容之间的区别。 - et_l

3

访问限制是基于每个类进行的。在一个类中声明的方法无法无法访问所有实例/类成员。内部类也可以自由访问外部类的成员,而外部类也可以自由访问内部类的成员。

通过将一个类放在另一个类中,使其与实现紧密关联,而任何实现的一部分都应该能够访问其他部分。


3
内部类的逻辑是,如果在外部类中创建内部类,那是因为它们需要共享一些东西,因此让它们能够比“常规”类更加灵活是有意义的。
如果在您的情况下,类之间无法看到彼此的内部工作(这基本上意味着内部类可以简单地成为常规类),则可以将内部类声明为static class XYZ。使用static将意味着它们不会共享状态(例如,new ABC().new XYZ()将不起作用,您需要使用new ABC.XYZ())。
但是,如果是这种情况,您应该考虑XYZ是否真的应该是一个内部类,也许它应该有自己的文件。有时候创建静态内部类是有意义的(例如,如果您需要一个实现外部类正在使用的接口的小类,并且在其他地方没有用处)。但是大约有一半的时间它应该被制作成外部类。

2
外部类可以像访问静态内部类的公共成员一样访问其私有成员,因此这与静态无关。您说“类能够看到彼此的内部工作毫无意义”,但这并不一定是这样-如果仅内部类能够看到外部类的内部工作,而反之则不行,那么这就是有意义的。 - O. R. Mapper

-1

内部类被视为外部类的属性。因此,无论内部类实例变量是否为私有,外部类都可以像访问其它私有属性(变量)一样轻松地访问。

class Outer{

private int a;

class Inner{
private int b=0;
}

void outMethod(){
a = new Inner().b;
}
}

-2
因为您的 main() 方法在 ABC 类中,该类可以访问其自己的内部类。

2
问题不在于 ABC 类的成员是否可以访问嵌套在 ABC 类中的类,而在于为什么它们可以访问 Java 中嵌套在 ABC 类中的 私有 成员。 - O. R. Mapper
我在问题被提出的当天就回答了它。两年后有人编辑了这个问题,三年后才有了负评。我相信那个编辑者改变了问题的措辞,而且改变得太多了。 - aberrant80
@aberrant80,修订历史记录是公开的,甚至原始问题也很明显地在询问“为什么”。 - nog642

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