Java中包私有类的优缺点是什么?

75

我最近在学习Java,了解到package-private类的概念,如果我们不指定任何访问修饰符,这是默认的。但后来我意识到:

  1. 我很少看到使用package-private类。这是出于什么原因呢?例如,它有严重缺陷、冗余或仅仅是因为我读得不够多?是否有强有力的理由支持/反对其使用?

  2. 如果在大多数情况下它真的没有用处,为什么会成为默认值?

  3. 在实际工作中什么情况下应该使用package-private呢?即,在什么情况下它变得不可替代?

换句话说,使用默认的package-private修饰符的主要优缺点是什么?

8个回答

64
简而言之,它是“private”的一种略微更广泛的形式。
假设您已经熟悉了“public”和“private”的区别,以及如果这些方法和变量仅在该类内部使用,通常是将它们设置为“private”的良好实践。
作为其扩展,如果您考虑以模块化方式创建软件,则可以考虑为您的“模块”提供公共接口,其中将有多个类在其中协作。 在此情况下,如果将由消费者调用,则使方法“public”非常合理;如果它们是类内部的,则为“private”;如果它们用于在此模块中在类之间进行调用,则为“package private”,即它是您的模块的实现细节(由公共调用者看到),但跨越了几个类。
实际上很少使用这种方法,因为包系统对于这种事情并不是很有用。 您必须将给定模块的所有类都放入完全相同的包中,对于任何非平凡的内容都会变得有些笨重。 因此,这个想法很好-使一种方法仅对“附近”的几个类可访问,作为稍宽的“private”-但是定义该类集合的限制意味着它很少使用/有用。

1
我能理解为什么有人会这样说 - protected 修饰符与 package private 相同,只是子类也被邀请参加派对。但如果本来不存在子类关系,则为了访问而创建子类关系是错误的。例如,将 EmailSender 类作为 DomainObjectFilter 类的子类或反之通常没有任何意义。在这种情况下,该方法通常只是被设置为 public(可能附带一条注释说明它并不是 概念上 的公共方法...)。 - Andrzej Doyle
1
"将给定模块的所有类都倒入到完全相同的包中--不太方便。" -- 这是正确的,但我甚至在非常复杂的情况下也使用了这种方法,例如在一个包中有20-30个类。当然,任何更大的情况都需要进行重构。 - Victor Sorokin
你不能将源文件组织成目录层次结构,然后在构建期间将它们全部转储到同一个目录/包中进行编译吗?这样可以为您提供组织结构和包私有访问的好处,对吧? - jmrah
1
@jrahhali,你可以这样做,但我怀疑你会找到一个愿意支持的集成开发环境。除非你编写一个知道你的意思的IDE扩展,否则会很烦人。 - froginvasion
为了使用包私有可见性修饰符并使用基于层次结构的修饰符来使用模块,您可以使用实验性项目https://github.com/odrotbohm/moduliths,在这种情况下它的表现很好。 - Lubo

18

默认访问权限(package-private)的一个好处是,可以将其用于授权让单元测试类访问你原本认为应该私有的方法。当然,缺点是包中的其他类可能会在不应该的情况下调用它。


6
除了封装之外,使用包私有类的主要优点之一是它们不会出现在项目的javadoc中。因此,如果您使用一些辅助类来帮助公共类实现客户端需要的功能,这些辅助类没有其他用途,那么将它们设置为包私有是有意义的,因为您希望保持库的使用方便性。

例如,您可以查看我开发的一个库。 javadoc 仅包含5个接口和12个类,而源代码则有更多内容。但是隐藏的大多数内容都是提供不了任何增值服务给客户端的内部层(通常所有的抽象基类都被隐藏)。

JDK中也有许多这样的例子。


3

包私有访问级别比protected更加限制:通过简单地对类进行子类化,仍然可以访问受保护的属性和方法。受保护的成员(或可能)旨在继承,而包私有成员则不是。

包私有成员通常用于多个包内类可以访问实现特定属性或(实用)方法的情况。

好的例子是String的包私有构造函数和StringBuilder.value字符数组:

/*
* Package private constructor which shares value array for speed.
* this constructor is always expected to be called with share==true.
* a separate constructor is needed because we already have a public
* String(char[]) constructor that makes a copy of the given char[].
*/
String(char[] value, boolean share) {
    // assert share : "unshared not supported";
    this.value = value;
}

因此,java.lang包中的类可以在不影响安全性的情况下,如果已经存在于char[]中,高效地创建新的Strings。您无法从应用程序中执行此操作,因为如果可以的话,您将可以访问(引用)不可变的String的内部字符数组(不算反射!)。

StringBuilder(或实现来自AbstractStringBuilder)中持有当前值的字符数组char[] value和访问器方法char[] getValue()也是包私有的,因此String的各种实用程序方法,如contentEquals(StringBuffer sb)contentEquals(CharSequence cs),可以利用它以提高效率和更快的比较,而不会将内部字符数组暴露给“世界”。


2
在“为什么会成为默认值”的问题上,这里的“默认值”只是指缺少其他限定符。我猜他们本可以发明另一个关键字("package"已经被使用了),但他们没有这样做。
在现实世界中,我使用默认访问权限来处理实用类和抽象类,以防止人们从其他包中调用或使用它们。假设你有一个接口和两个具体实现,它们都扩展自某个抽象类。你将两个具体类声明为final,因为你不一定希望人们对它们进行子类化(参见Effective Java)。出于同样的原因,您也不希望人们来操纵您的抽象类。如果您对抽象类使用默认访问权限,那么人们只有在将其类放在您的包中时才会看到它。虽然这并不是百分之百可靠,但我认为这是默认访问权限的合理用途/示例。尽管如此,它不能像私有访问权限一样防止细节泄漏,即不能保证任何内容,这意味着它并不是特别有用的约定。
另一个原因是人们往往会从Javadoc中排除具有默认访问权限的类。

他并没有误解“默认”这个术语,他想知道为什么它是默认的访问级别。一个默认的原因是某些东西经常被使用。还有其他原因,他正在询问这些原因。 - B T
1
我不确定你是对的,但我很乐意更新响应以便更清晰明了。 - jtoberon

1

1 - 取决于架构-通常,如果您只是为自己编写代码并且在小型项目上使用它,那么您可能不会使用它。 在大型项目中,确保您可以控制某些方法的调用位置和方式可能会有所帮助。

2 - 默认(即非公共/受保护/私有)与私有不同-它是第四种状态。请参见Java访问控制

3 - 当您编写不希望第三方依赖于您如何实现底层代码的库时,它可以使生活更轻松-您只需将API本身公开即可。


@BZ 默认不是包私有的吗? - zw324
1
@BZ说:如果一个类没有修饰符(默认情况,也称为包私有)。感谢答案,术语是琐碎的 :) - zw324
看看权限表 - private 和没有修饰符之间有区别。没有修饰符的任何内容都可以在包中读取 - 所以不是真正的私有。这与私有类相比是一个巨大的区别。 - BZ.
2
@BZ - "默认"和"包私有"这两个术语经常被交替使用。看起来你没有注意到子瑶正在使用一个由两个单词组成的复合术语,而不仅仅是"私有"。 - Andrzej Doyle
@BZ 是的,我注意到了,这是我说“包私有”的原因。猜想我忘了连字符:) 编辑:好的,已添加连字符。 - zw324
默认是_package-private_。然而,它与_private_不同。 - Fabian Barney

-1
“Package Private”是在有多个包的情况下使用的,它意味着同一包中的其他类可以像“public”一样访问该类或类成员,而其他包中的类则无法访问,就像“private like them.”

-2
请注意,当您谈论类时,只有两个选项:
1.公共类 2.包私有类
“私有类”的概念是没有意义的。(为什么要创建一个在任何地方都不使用的类?!)
因此,如果您有一个用于中间操作的类,不需要向API用户公开,则应将其声明为“包私有”。
此外,当您在同一源文件中定义多个类时,只允许一个类是公共的(其名称与.java文件名匹配)。如果在同一文件中定义了任何其他类,则必须是“包私有”。

3
这个回答最多算是具有误导性。静态嵌套类是一个私有类的概念可以被运用的例子。另请参阅:https://dev59.com/GXVD5IYBdhLWcg3wJIIK#34551507。 - Synoli
这个答案并不差。它只是应该讨论“顶层类”,而不是所有类。它不适用于任何类型的内部类,但是当考虑顶层类时,这是完全正确的。 - Keith Irwin

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