在 Java 中,公开对象的成员是否一个坏主意?

Posted

技术标签:

【中文标题】在 Java 中,公开对象的成员是否一个坏主意?【英文标题】:In Java, is it ever a bad idea to make an object's members publicly available?在 Java 中,公开对象的成员是不是一个坏主意? 【发布时间】:2010-10-07 23:22:00 【问题描述】:

我的应用程序中有一个数据类。我的应用程序永远不会被用作公共 API,我将是唯一在我的项目中开发代码的人。

我正在尽我所能节省每一盎司的处理器和内存功率。

让我的数据类中的数据成员具有公共/受保护/默认保护以便我不必使用吸气剂是一个坏主意吗?使用吸气剂将需要更多的内存和堆栈的创建等等......我认为这是没有必要的。我可以看到使用 getter 的唯一原因是为了保护/隐私,但如果我是唯一的编码人员并且没有其他人会使用我的 API,那么不使用 getter 是不是一个坏主意?

如果这很愚蠢,请告诉我。

【问题讨论】:

您将失去几个时钟周期。微优化确实无济于事,最终会伤害您,因为作为开发人员,您必须记住更多,它会引入更多错误位置并降低可读性。我建议你不要这样做。 这是一个很好的观点。我想也许我应该用 getter 和 w/o 对代码进行计时,看看它是否真的有什么意义。 另外,你为什么要节省“每一盎司的内存和 CPU 周期”?如果您确实需要使用错误的语言,但我怀疑您没有。 codinghorror.com/blog/archives/000654.html 【参考方案1】:

如果您要替换 getter/setter 以优化性能/内存,那么您采取了错误的方法。这些几乎肯定不会成为您的应用程序运行缓慢或使用过多内存的原因。

优化的主要罪过是在你知道你需要做之前就去做。仅当您有真实信息向您显示最浪费时间/内存的位置时才进行优化,然后再花时间进行优化。这背后的想法是,通过在占用总运行时间 80% 的代码中节省 5% 的时间,比在仅占总运行时间 5% 的代码中节省 20% 的时间,您将获得更多收益。总运行时间。 (同样适用于内存)。

此外,我会按照您的建议小心设计应用程序,因为这意味着某些属性(例如:简单属性)将可以直接访问,而其他属性(更复杂的派生属性或您不想要的属性)暴露底层类型)将有getter/setter。所以你最终会得到混合的访问样式,这将是不太可维护的。

【讨论】:

【参考方案2】:

将成员公开作为优化是一个坏主意。正如其他人所说,它几乎没有影响。但是,如果对象是一个行为很少的简单数据结构,那么可以这样写。只要您这样做是为了简单和可读性,而不是为了性能。

【讨论】:

【参考方案3】:

无论如何,微不足道的 getter 和 setter 很有可能会被内联 - 但是对于公共字段,您将失去合约 (API) 和实现之间的区别。即使这只是 将使用的 API,保持 IMO 松散耦合也是很好的。

【讨论】:

【参考方案4】:

Python 人不使用 getter,他们不会在地狱中燃烧。

Getter/setter 是一种将类的一部分暴露给 Java 的简单内省的方法。 Java Bean 规范依赖于公共 getter 和 setter 来确定哪些属性是重要的。

虽然对于需要/生产/使用 bean 的事物来说是必不可少的,但它不是 OO 编程或 Java 编程的基本特征。它只是 Bean 规范的一部分,并且对于任何想要参与类似 bean 的事情的类都是必需的。

公共属性很好。它们简单、直接、明显。

【讨论】:

Python 确实有一个约定,其中“私有”成员有一个公共前缀“_”。这个想法是,除非您知道自己在做什么,否则不要使用它们。 @Chase Seibert:虽然 _ 意味着“私有”,但我们仍然不会浪费太多时间来编写 getter 和 setter。 公共属性的唯一问题是,当您实现它们时,它们可能是个好主意,但是当您想要更好地控制您的集合/获取时,它们可能会限制您前进。 @Cameron McKay:(1) 希望你已经放入 getter 和 setter 是很少见的。 (2) 除非你在出售你的代码,否则你可以很容易地找到你的类的所有用途并修复它们。 不幸的是,pythonic 的做法很难在 Java 世界中应用。简单的属性很好而且很简单,直到...您需要重构,您需要为分配添加检查,您需要代理...您需要 (...)【参考方案5】:

与任何优化一样,在前后进行衡量,看看是否有任何好处可以证明缺点是合理的。

我认为您会发现它不会对您的代码的性能产生任何明显的影响(但请自己尝试一下)。 JVM 会内联常用的 getter 和 setter。

使用分析器并在您的应用程序中找到真正的热点。没有证据的优化只是猜测。

【讨论】:

【参考方案6】:

这有点异端,但我同意你的观点,如果你知道没有其他人会使用你的课程,你可以跳过那些东西。我不会在可能被重用的代码中这样做,甚至隐藏在 API 后面,但在你的情况下,它似乎相当安全。

【讨论】:

嗯,每个使用 1960 年代两位数日期的人都知道,以后不可能发展成问题...... ...在内存很小的嵌入式应用程序中,2 位日期可能仍然是正确的选择。 问题是你不能总是预测代码什么时候可以被重用。我认为设计你的私人图书馆总是一个好主意,就好像它们要公开一样。这也是一种很好的做法。【参考方案7】:

我认为这个问题的反面是一个更好的问题:

让对象成员公开可用是个好主意吗?

在开发过程中,从使用所需的最低可访问性级别的原则来处理事情通常是一个好主意;换句话说,只公开你需要的尽可能多的数据。如果你普遍应用这个原则,那么它就变成了一种只在需要时共享的做法。

话虽如此 - 为什么要这样做?

【讨论】:

【参考方案8】:

我很确定使用的内存量可以忽略不计。如果您为生产环境构建,JVM 甚至可能会根据运行时的实现优化调用以使其内联。如果您自己开发它,它可能仍然有帮助,因为如果您有一个难以跟踪的错误,您可以在 getter/setter 中设置一个断点,并准确查看您何时更改值。它也不应该是任何代码,因为大多数现代 IDE 都具有自动生成代码的功能。

【讨论】:

这是我讨厌阅读 Java 代码的另一件事……IDE 编写的成百上千行无用的 CRAP 代码。【参考方案9】:

所有过早的优化问题都在前面的答案中讨论过。

我只是觉得值得一提的是,在 Java 中,您可以通过定义一个只有公共成员的类来模仿 C 的结构,如下所示:

public class DataClass 
    public int a;
    public String b;
    public char c;

只有通过引用那些公共成员才能访问(获取和设置)。

这个成语在某些特定场景下是完全可以接受的。

【讨论】:

我更喜欢使用不透明的指针:en.wikipedia.org/wiki/Opaque_pointer【参考方案10】:

Getter 和 setter 不仅仅用于保护/隐私。主要用途是改变你暴露底层数据的方式。

例如,在 asp.net c# 中,控件的可见属性可能具有如下内容:

private bool visible = true;

public bool Visible

    get
    
        return visible && Parent.Visible;
    
    set
    
        visible = value;
    

外部属性(访问器/修改器)与底层变量有不同的含义。此外,如果您在开始时没有上述功能,那么当您有简单的获取/设置时,它就会成为您以后可以轻松添加的东西。

至于性能点,您可以节省每一盎司的处理器和内存。如果这会产生明显的影响,那么您最好还是看看另一种语言。

【讨论】:

【参考方案11】:

我从不希望我有一个公共字段而不是一个属性,但我无法计算我想要一个属性并拥有一个公共字段的次数。最终你最终会想要其中的逻辑。

【讨论】:

完全正确...即使您认为自己不会,为什么不让选项保持打开状态? 您不应该在访问器中包含逻辑。当我想要一个字段而不是一个“属性”(实际上也做一些其他事情的访问器)已经暴露时,我可以数次。【参考方案12】:

考虑一下,一旦您将某些内容放入公共 API,它就会一成不变。你不能改变它的名字,你不能改变它的类型,你不能改变它的存储方式。

把它放在一个方法后面不会导致现代 VM 的性能下降(过去 10 年几乎没有)。

在我看来,您从获得价值的方法中获得的灵活性更为重要。

如果您决定将它们公开,请确保将它们标记为最终的并且它们是不可变的。

公共课点 公共最终整数 x; public final int y; 公共点(最终 int xVal,最终 int yVal) x = xVal; y = yVal;

编辑:

我所说的公共 API 是指任何公开的变量,而不是类本身将成为其他开发人员公开 API 的一部分。通过将其公开,您可能会在以后引发问题(如果它是其他开发人员可以访问的“真实”公共 API,除非您喜欢在发布更新时破坏其他人的代码,否则它实际上是一成不变的)。

【讨论】:

【参考方案13】:

这并不愚蠢,但我认为这也可能不是一个好主意。

我理解这种诱惑...如果没有其他人使用过该代码,那么您无需担心。但一次又一次,事实证明这与实际发生的情况不符......您发现您将在比您想象的更多地方使用它,或者项目变得比预期更大,或者某人只是掌握了它并认为它很有用……本来不应该永久或公开的事物有办法变成那样。

处理器能力和内存真的有那么严格的限制吗?我不知道你的情况(也许是嵌入式应用程序?),但作为一个(过度概括的)规则,你最好在其他地方寻找资源......在你的数据结构、算法或架构中看到更好的改进内存或 CPU。

【讨论】:

希望有人在这里写评论,说明为什么这被否决...似乎相对无害。【参考方案14】:

你永远不知道,总是像阅读你的代码的人知道你住在哪里并且是连环杀手一样编写代码。即使您只为自己开发代码,也请尝试 (imo) 像在团队中开发一样进行。通过这种方式,您可以习惯于构建可读的最佳实践代码。至于性能开销,如果你真的真的需要你的每一点内存和你的 CPU 的每一个周期,也许最好用 C 语言来做。

【讨论】:

【参考方案15】:

恕我直言,对于本质上只是被动地保存数据并且不能做任何更复杂的事情而不从 API 用户的角度完全改变其含义的属性,getter 和 setter 只是不必要的样板。如果该属性是,并且就其性质而言,显然永远都是,只是一个数据持有者,例如与 C 结构类似使用的类,那么只需将愚蠢的事情公开。作为更笼统的说法,如果过度热心地应用最佳实践而不考虑为什么它们是最佳实践,则很容易成为浪费时间甚至是最糟糕的实践。哎呀,即使 goto 偶尔也可以说是合理的。

【讨论】:

【参考方案16】:

使用的潜在缺点

public final 类型 var;

而不是

private final 类型 var;

public type getvar() return var ;

(我亲身经历的)包括:

前景——无论现在看起来多么不可能——表示需要在基类或未来的某个子类中改变。

混合暴露的不可变类型和可变类型字段的潜在尴尬。

麻烦记住哪些类使用public final,哪些使用getvar

向以后可能会看到源代码的其他人解释这种不一致的麻烦。

我一直无法说服自己这是值得的,尽管我已经尝试过了。

【讨论】:

【参考方案17】:

我创建了两个小类,并对其进行测试。

首先我创建了一个对象作为原型。

然后我创建了一个 2,000,000 的数组来存储它们。

然后我运行了一个循环,在那里创建一个新实例并从原型中获取值。

比较每个结果的平均秒数是:

WithGS  Without
1.1323  1.1116

Diff  = 0.0207 secs.

因此,对于这种情况,我认为首先有一个未优化的解决方案会更好,一旦不需要进一步的要求,就继续进行分析。

代码如下:

PersonGetSet.java


public class PersonGetSet 
    private String name;
    private boolean deceased;

    public void setName( String name ) 
        this.name = name;
    
    public String getName() 
        return name;
    

    public void setDeceased( boolean deceased ) 
        this.deceased = deceased;
    
    public boolean isDeceased() 
        return this.deceased;
    

    public static void main( String [] args )  
        PersonGetSet pb = new PersonGetSet();
        pb.setName( "name" );
        pb.setDeceased( true ) ;

        long start = System.currentTimeMillis();
        PersonGetSet [] array = new PersonGetSet[2000000];
        for( int i = 0 ; i < array.length; i++ ) 
            PersonGetSet personGs = new PersonGetSet();
            personGs.setName( pb.getName() );
            personGs.setDeceased( pb.isDeceased() );
            array[i] =  personGs;
        
        System.out.println( "PersonGetSet took " + ( System.currentTimeMillis() - start ) + " ms. " );
    



Person.java


public class Person 
    String name;
    boolean deceased;
    public static void main( String [] args )  
        Person pb = new Person();
        pb.name=  "name" ;
        pb.deceased = true;

        long start = System.currentTimeMillis();
        Person [] array = new Person[2000000];
        for( int i = 0 ; i < array.length; i++ ) 
            Person simplePerson = new Person();
            simplePerson.name=  pb.name;
            simplePerson.deceased = pb.deceased;
            array[i] =  simplePerson;
        
        System.out.println( "Person took " + ( System.currentTimeMillis() - start ) + " ms. " );
    

【讨论】:

我会将类包本地化,将字段设置为最终字段并使用构造函数设置它们。否则,只需将您的类视为数据结构。 为什么每次都将对象存储在一个 2M 的数组中?你在测试 GC 吗? @eljenso: Nahh :PI 真的不知道 VM 可以做多少优化,所以我将所有引用保存在数组中以避免优化器意识到我不使用它们(只是在如果它有超级优化:P)【参考方案18】:

如果您的首要任务是性能,您应该研究您选择的 Java 虚拟机以及它如何执行程序。 Sun 的 Desktop Java 花费大量时间分析和编译 Java 字节码为机器码,这导致程序运行速度可能非常快但会占用大量内存。

如果你发现你想使用基于 HotSpot 的 JVM,你应该研究所有的技巧来帮助它。 getter 和 setter 通常是内联的(即直接在机器代码中替换,而不是调用子程序)并且常量被折叠。小范围的 switch 语句通常转换为跳转表。

通常我发现“head-under-the-arm”方法适用于编写大部分代码,然后您分析正在运行的代码并找到瓶颈。例如,我发现 StringBuffers 在很多事情上都很快,但 deleteCharAt(0) 不是其中之一。重写以对字符串使用简单的迭代而不是在字符串缓冲区中删除,确实很神奇。您也很可能会发现最大的好处在于算法 - 而不是小捷径。

在 C 世界中,人类几乎无法再聪明地超越编译器了。在 Java 世界中,人类可以通过直接编写 Java 来帮助 HotSpot。

【讨论】:

【参考方案19】:

任何堆栈使用都将是高度暂时的,而且无论如何都不确定。无论如何,您可能会发现编译器优化了任何不必要的开销,因此您可能会发现您实际上一无所获。

我同意私有非 API 类使用公共成员不一定是坏事,但你获得的东西太少(如果有的话)我不会打扰。

【讨论】:

【参考方案20】:

从谁编写代码和谁将使用它的角度来看,无论是你还是其他人都没有关系,你可能仍然需要 getter 和 setter - 如果有人在以后使用它一个事实上的公共 API。

但是,如果它真的只是数据并且您不需要与类相关的任何行为,那么一定要让成员公开。

对我来说,类是否有任何行为,或者您是否可能想要更改其数据成员的名称或类型作为真正重要的实现细节。

关于处理器能力和内存 - 你测量过吗?

【讨论】:

【参考方案21】:

如果速度如此关键,那么用 C 或 C++ 编写它可能是值得的。

【讨论】:

不,Java 现在的速度非常快,在 C/C++ 的 epsilon 范围内。 Java 真正低效的地方在于内存使用。 桌面 Java 牺牲内存来换取速度。像 jamvm 或 squawk 这样的小型 JVM 使用的内存要少得多,但不能运行得这么快。【参考方案22】:

设计你永远不需要的功能(getter/setter)与过早的优化一样糟糕。如果这真的只是供您使用,请不要将其公开,然后构建您认为最简单的结构。

只有在您真正需要时才应添加优化和/或吸气剂。

【讨论】:

【参考方案23】:

你需要 getter 吗?也就是说,您的对象是否应该为您工作,而不是您的客户获取数据值并自己执行工作?

OOP 的一个关键方面是告诉一个对象做某事,而不是把这些部分拿出来自己做。例如

double ret = myObj.calculateReturn()

对比

int v = myObj.getValue();
int ov = myObj.getOldValue();
double ret = (v-ov)/ov * 100; // do I work about dividing by zero etc.? 

我经常看到。通常你确实需要 getter(也许还有 setter),但是当我写一个 getter 时,我总是问自己是否应该公开这些数据,或者对象是否应该为我完全隐藏它。

【讨论】:

【参考方案24】:

公开非最终字段总是是个坏主意。

如果您以后想添加事件通知怎么办?

如果您以后想要子类化并更改行为怎么办?

如果您想强制执行一些跨领域约束怎么办?

TofuBeer 显示了一个带有 final 字段的 Point 类——没关系。但是,AWT 中的 Point 类不使用 final,并且在 Java 早期让我很伤心。

我正在编写一些 3D 代码并希望将 Point 子类化,这样每当 x 和 y 发生变化时,我都可以根据需要自动调整 z。因为 Point 的字段是公开的,所以无法知道 x 或 y 何时更改!

【讨论】:

【参考方案25】:

在我们的团队中,我们使用以下规则:如果类字段表示的状态是可变的,则始终将其设为私有并提供修改器;但是,如果它是不可变的(即在类实例化后不会重新分配),则可以将其声明为 public final [type] [field-name] -- 可以安全访问。

请注意,这里的“不可变状态”特指该字段被声明为final。不应与 public [immutable-type] [field-name] 混淆,后者可以执行重新分配,尽管 immutable-type 实例本身是不可变的。

还请注意,即使对于不可变字段,也有很多充分的理由使用访问器。例如,在读取字段时需要执行某些操作(例如安全检查)的情况。

【讨论】:

【参考方案26】:

该代码仅供您自己使用的论点是一个很容易掉入的陷阱 - 大多数代码都是这种情况,公开暴露的位通常非常小。因此,假设大多数代码都是供私人使用的,为什么还要使用 private、protected、final、const (C/C++) - 我知道的反驳问题,但我希望这一点很清楚。

这里遗漏的另一件事是锁定。如果您直接访问成员,您将只能在整个对象或成员级别锁定此代码。并且该锁定将由使用代码控制,而不是对象本身。也许可以,也许不行,因为在所有事情上它都是课程的马。

【讨论】:

【参考方案27】:

如果性能很关键,我建议将字段设为私有(如果仅在同一个类中访问)或包本地(如果它们在另一个类中访问)。如果你给一个内部/嵌套类私有字段,编译器将添加/使用访问器方法(因为 VM 不允许类访问另一个类中的字段,即使 Java 允许)

拥有公共可变字段对我来说是一个设计问题。将性能关键代码拆分到多个包中并不意味着您需要对性能关键代码进行严格编码。

如果您有不可变的字段,那么将字段公开是没有问题的,前提是您预计如果字段改变行为会产生更高的开发成本。事实上,我建议使用公共不可变字段,如果您认为这会使代码更简单/更容易理解。

但是,首要任务仍然是代码的可读性。最简单的代码通常也表现最好。

【讨论】:

【参考方案28】:

一般来说:如果您想手动优化某些东西,您应该考虑架构改进而不是这种微优化。在许多情况下,微优化可以由工具自动完成。

对于 Java:有 ProGuard 工具,可以进行许多优化。但是,如果您使用现代 JVM(例如 HotSpot 或 OpenJDK),则 JVM 可以即时执行这些优化。如果您有一个长时间运行的应用程序,ProGuard 可能会对性能产生轻微影响。

【讨论】:

以上是关于在 Java 中,公开对象的成员是否一个坏主意?的主要内容,如果未能解决你的问题,请参考以下文章

将私有方法公开以对其进行单元测试...好主意吗?

通过唯一的成员 ID 来识别对象是个好主意吗?

我可以在 Java 中公开受保护的成员吗?我想从子类访问它

类的高级概念

java里的静态成员变量是放在了堆内存还是栈内

面向对象的JavaScript:私有成员和公开成员