Java为什么将软件包访问设置为默认访问?


26

我之所以问这个问题,是因为我相信他们这样做是有充分的理由的,而且根据我到目前为止的行业经验,大多数人都没有正确使用它。但是,如果我的理论是正确的,那么我不确定为什么它们包含私有访问修饰符...?

我相信,如果正确使用默认访问权限,则可以在保持封装的同时增强可测试性。并且这也使私有访问修饰符变得多余。

通过将唯一的包用于需要隐藏在世界其他地方的方法,可以使用默认访问修饰符来提供相同的效果,并且在不影响可测试性的情况下做到这一点,因为测试文件夹中的包具有相同的功能。能够访问源文件夹中声明的所有默认方法。

我相信这就是Java将包访问权限用作“默认”的原因。但我不确定为什么它们还包括私有访问权,我确定有一个有效的用例...



与先前讨论的单元测试专用方法有关;您如何测试私有方法。回答; 您不应该
Richard Tingle 2013年

Answers:


25

我想他们对普通程序员会做什么有个好主意。通常,程序员是指不是真的擅长编程的人,但是无论如何都会找到工作,因为没有足够的优秀人才,而且他们很昂贵。

如果他们使“公共”访问成为默认访问,那么大多数程序员将永远不会费心使用其他任何东西。结果是到处都是很多意大利面条代码。(因为从技术上讲,如果允许您从任何地方调用任何东西,为什么还要麻烦地在方法中封装某些逻辑?)

将“私有”设置为默认访问权限只会稍微好一点。大多数初学者都会简单地发明(并在他们的博客上分享)一个经验法则,即“您需要在任何地方编写'public'”,然后他们会抱怨为什么Java如此糟糕以至于它迫使他们在任何地方编写“ public”。而且它们还会产生很多意大利面条代码。

程序包访问权限使程序员可以在编写一个程序包中的代码时使用草率的编程技术,但是在制作更多程序包时,他们必须重新考虑它。这是良好的商业惯例和丑陋的现实之间的折衷。像:如果您坚持,写一些意大利面条代码,但是请把丑陋的东西留在包装内;至少在包之间创建了一些更好的接口。

可能还有其他原因,但我不会低估这一原因。


14

默认访问权限不会使私有访问修饰符变得多余。

语言设计师对此的立场反映在官方教程《控制对类成员的访问》中,这一点很清楚(为方便起见,引号中的相关陈述用粗体表示):

选择访问级别的提示:

如果其他程序员使用您的类,则要确保不会发生由于滥用而引起的错误。访问级别可以帮助您做到这一点。

  • 使用对特定成员有意义的最严格的访问级别。除非有充分的理由,否则请使用private。
  • 避免使用除常量之外的公共字段。(本教程中的许多示例都使用公共字段。这可能有助于简洁地说明一些要点,但不建议在生产代码中使用。)公共字段倾向于将您链接到特定的实现,并限制了更改代码的灵活性。

您呼吁可测试性作为完全放弃私有修饰符的理由是错误的,例如,“ TDD新手”中的答案就证明了这一点。我现在应该避免使用私有方法吗?

当然,您可以使用私有方法,也可以测试它们。

某种方法可以使私有方法运行,在这种情况下,您可以用这种方法进行测试,或者没有办法使私有方法运行,在这种情况下:为什么您要尝试对其进行测试,只是删除该死的东西...


在另一个官方教程“ 创建和使用软件包”中解释了语言设计人员在软件包级别访问的目的和用途上的立场,它与删除私有修饰符的想法没有共同之处(为方便起见,引号中的相关陈述用粗体表示) :

出于以下几个原因,应将这些类和接口捆绑在包中:

  • 您和其他程序员可以轻松确定这些类型是否相关...
  • 您可以允许包内的类型彼此不受限制地访问,但仍然限制包外的类型的访问 ...

<rant“我想我已经听到了足够的抱怨声。猜测是时候该大声说清楚了……“>

私有方法有益于单元测试。

下面的注释假定您熟悉代码覆盖率。如果没有,花点时间学习,因为它对那些对单元测试和所有测试感兴趣的人非常有用。

好吧,所以我有了私有方法和单元测试,覆盖率分析告诉我还有差距,我的私有方法没有包含在测试中。现在...

从私有化中我会得到什么

由于method是私有的,因此唯一的方法是研究代码以了解如何通过非私有API使用它。通常,此类研究表明存在差距的原因是测试中缺少特定的使用场景。

    void nonPrivateMethod(boolean condition) {
        if (condition) {
            privateMethod();
        }
        // other code...
    }

    // unit tests don't invoke nonPrivateMethod(true)
    //   => privateMethod isn't covered.

为了完整起见,造成这种覆盖差距的其他(较不频繁)原因可能是规范/设计中的错误。为了简单起见,我不会在这里深入研究这些内容。足以说,如果您弱化访问限制“只是为了使方法可测试”,您将错过机会了解这些错误的存在。

很好,为了弥补差距,我为缺失的场景添加了单元测试,重复进行覆盖率分析并验证差距是否消失。我现在有什么?我已经获得了针对非专用API的特定用法的新单元测试。

  1. 新测试可确保此用法的预期行为不会在没有通知的情况下发生更改,因为如果更改,则测试将失败。

  2. 外部读者可以研究该测试并了解其使用方式和行为方式(此处,外部读者包括我将来的自我,因为在完成测试后一个月或两个月,我往往会忘记代码)。

  3. 新测试可以重构(我可以重构私有方法吗?您敢打赌!)无论我做什么privateMethod,我都会一直想测试nonPrivateMethod(true)。不管我做什么privateMethod,都不需要修改测试,因为方法不是直接调用的。

不错?你打赌

我应该从弱化访问限制中放松些什么

现在想象一下,而不是上面的内容,我只是削弱了访问限制。我跳过了对使用该方法的代码的研究,直接进行了调用my的测试exPrivateMethod。大?不!

  1. 是否可以针对上述非私有API的特定用法进行测试?否:以前没有测试,nonPrivateMethod(true)现在没有这种测试。

  2. 外部读者是否有机会更好地了解课堂使用情况?否。“-嘿,这里测试的方法的目的是什么?-算了,它仅供内部使用。-糟糕。”

  3. 可以容忍重构吗?没办法:无论我进行什么更改exPrivateMethod,都可能会破坏测试。重命名,合并到其他方法中,更改参数并测试将停止编译。头痛?你打赌!

总结起来,坚持使用私有方法可以为我带来有用,可靠的单元测试增强。相比之下,弱化“可测试性”的访问限制只会给我带来晦涩难懂的测试代码,此外,它还存在被任何较小的重构破坏的永久风险。坦白地说,我得到的东西看起来像技术性债务

</ rant>


7
我从来没有发现测试私有方法引人注目的论点。由于与类的公共API无关的原因,您始终可以将代码重构为方法,并且能够独立测试这些方法而无需涉及任何其他代码,这是一件非常好的事情。那就是您重构的原因,对吗?获得一块独立的功能。该功能也应该是可独立测试的。
罗伯特·哈维

@RobertHarvey的回答随着问题的解决而扩大。“私有方法有益于单元测试……”
gnat 2013年

我明白您在说私有方法和代码覆盖率,但是我并不是真正提倡将这些方法公开,以便您可以对其进行测试。 我提倡有一种独立测试它们的方法,即使它们是私有的。 如果需要,您仍然可以使用涉及私有方法的公共测试。而且我可以进行私人考试。
罗伯特·哈维

@RobertHarvey我明白了。猜猜它归结为编码风格。以我通常会生成的私有方法的数量以及重构它们的速度,对它们进行测试只是我无法承受的奢侈。非私有API是另一回事,我倾向于是相当勉强和缓慢的在修改它
蚊蚋

也许您比我更擅长编写第一次起作用的方法。对我来说,我需要测试该方法以确保它可以在继续进行之前首先工作,而不得不通过取消其他一系列方法进行间接测试将是笨拙且笨拙的。
罗伯特·哈维

9

最可能的原因是:它与历史有关。Java的祖先Oak仅有三个访问级别:私有,受保护和公共。

除了Oak中的private等同于Java中的private软件包。您可以阅读《Oak语言规范》(强调我的)的第4.10节:

默认情况下,类(包括构造函数)中的所有变量和方法都是私有的。私有变量和方法只能由类中声明的方法访问,而不能由其子类或任何其他类(同一包中的除外)访问。

因此,就您而言,Java最初并不存在专用访问权限。但是,当一个包中有多个类时,如我们现在所知,没有私有类将导致名称冲突的噩梦(例如,java.until.concurrent具有接近60个类),这可能就是为什么它们介绍了它。

但是,Oak和Java之间没有更改默认的程序包访问(最初称为私有)语义。


5

我相信,如果正确使用默认访问权限,则可以在保持封装的同时增强可测试性。并且这也使私有访问修饰符变得多余。

在某些情况下,人们想要两个单独的类,而这两个类之间的联系要比公开公开所有内容都紧密。这在C ++中具有类似“朋友”的想法,其中另一个被声明为另一个“朋友”的类可以访问其私有成员。

如果您查看诸如BigInteger之类的内部信息,则会发现许多对字段和方法(列表中所有蓝色三角形的内容)的程序包默认保护。这使得其他的类java.math中包有它们的内脏为特定的操作(你可以找到所谓的这些方法更优化的访问的BigDecimal - BigDecimal的是由一个BigInteger支持,节省了重新实现的BigInteger 再次,这些都是包装水平ISN”这是一个保护问题,因为java.math是密封包装,不能向其添加任何其他类。

另一方面,确实有许多事情确实是私有的。大多数包装没有密封。如果没有私有字段,您的同事可以在该包中放入另一个类,然后访问(不再)私有字段并破坏封装。

值得注意的是,在构建保护范围时,并没有想到要进行单元测试。直到1997年才构思出JUnit(用于Java的XUnit测试框架)(有关该故事的故事可以在http://www.martinfowler.com/bliki/Xunit.html上阅读)。JDK的Alpha和Beta版本是在1995年,而JDK 1.0是在1996年,尽管直到JDK 1.0.2才真正确定保护级别(在此之前您可以拥有private protected保护级别)。

有人会认为默认值不应该是程序包级别,而应该是私有的。其他人则认为不应有默认保护,而应明确说明所有内容-此方法的一些追随者将编写如下代码:

public class Foo {
    /* package */ int bar = 42;
    ...
}

注意那里的评论。

缺省情况下,包级别保护是默认的真正原因可能丢失了Java的设计说明(我一直在挖掘,找不到任何为什么文章,很多人解释了其中的区别,但没有人说“这是原因”)。

所以我会猜测。仅此而已-一个猜测。

首先,人们仍在尝试弄清楚语言设计。从那时起,语言就从Java的错误和成功中学到了东西。可能由于没有为所有字段定义某些内容而将其列为错误。

  • 设计师偶尔会发现需要C ++的“朋友”关系。
  • 设计人员希望使用的明确声明publicprivate因为这些声明对访问的影响最大。

因此,私有不是默认值,其他所有东西都落实到位。默认范围保留为默认范围。它可能是创建的第一个作用域,并且为了与早期代码进行严格的向后兼容,因此保留了这种方式。

如我所说,这些全都是猜测


啊,如果仅可以将朋友功能应用于单个成员,那么您可以说void someFunction()只能在X类中看到……这实际上会加强封装!
newlogic 2013年

哦,等等,您可以使用C ++做到这一点,我们需要使用Java!
newlogic 2013年

2
@ user1037729使得两个类之间的联系更加紧密-具有该方法的类现在需要知道作为其朋友的其他类。这倾向于违背Java提供的设计哲学。可以与朋友解决但不能解决程序包级别保护的问题不是那么大。

2

封装是说“您不必考虑这一点”的一种方式。

                 Access Levels
Modifier    Class   Package  Subclass   World
public        Y        Y        Y        Y
protected     Y        Y        Y        N
no modifier   Y        Y        N        N
private       Y        N        N        N

将它们放在非技术术语中。

  1. 公开:每个人都必须考虑一下。
  2. 受保护:默认值和子类必须了解它。
  3. 默认情况下,如果您使用的是包装,您会知道的(它就在眼前)也可能会让您对此有所了解。
  4. 私人的,如果您正在上这堂课,您只需要知道这一点。

我不认为这是一个私人类或保护类这样的东西..
科瑞图加伊

@KorayTugay:看看docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html。内部类是私有的。阅读最后一段。
jmoreno

0

我不认为他们专注于测试,只是因为那时的测试并不十分普遍。

我认为他们想要实现的是封装级别的封装。如您所知,类可以具有内部方法和字段,并且只能通过公共成员公开其中的一些方法和字段。以相同的方式,包可以具有内部类,方法和字段,并且只公开其中的一些。如果以这种方式考虑,则包在一组类,方法和字段中具有内部实现,而在另一组(可能是部分重叠的)类,方法和字段中具有公共接口。

我认识的最有经验的编码人员会这样思考。他们采用封装并将其应用于比该类更高的抽象级别:包或组件(如果您愿意)。在我看来,考虑到Java仍然保持良好的状态,Java的设计师们的设计已经成熟了。


没错,自动化测试在那儿不是很普遍。但这是一个不成熟的设计。
Tom Hawtin-大头钉
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.