Java 对象里为什么要用 get set?

2022 年 8 月 11 日
 dxatgp02

int max = obj.getMax();

int max = obj.max;

第二种写法不是更简单,更好理解?

19360 次点击
所在节点    Java
185 条回复
BigDogWang
2022 年 8 月 12 日
@guoqiao 这理解能力就不要来喷了好吗
yaphets666
2022 年 8 月 12 日
@guoqiao 我喜欢国外,但不喜欢 py
guoqiao
2022 年 8 月 12 日
@BigDogWang 那么请说说你的理解.
guoqiao
2022 年 8 月 12 日
@yaphets666 我只是提供一个判断问题的角度, 当然不是说适合每个人.
nuk
2022 年 8 月 12 日
最简单的
int max = obj.getMax();
写成
int max = obj.max;
没啥问题

但是
Max max = obj.getMax();
写成
Max max = obj.max;
会爆炸。。。
mmdsun
2022 年 8 月 12 日
因为 Java 字段没办法多态,必须使用函数
BigDogWang
2022 年 8 月 12 日
@guoqiao 我说的很清楚了,java 没有 property ,所以为了避免后续要添加访问控制时修改麻烦,就直接默认加上 get set 了。其他语言有这些特性的,谁还手写 get set 啊
DefoliationM
2022 年 8 月 12 日
并行的时候 get,set 不会数据竞争吗
liuhan907
2022 年 8 月 12 日
@guoqiao 既然题目在问 Java ,你例子也该用 Java 写,你用 py 举例来证明 Java 的 getter/setter 没必要岂不是很滑稽?
sutra
2022 年 8 月 12 日
neptuno
2022 年 8 月 12 日
这也能被说成是教条主义,学了个词就到处用
FrankHB
2022 年 8 月 13 日
@guoqiao 算是这整层楼里差不多唯一孺子可教的。

反过来像一些什么面向对象吹继承封装多态的……项目组里见到这种小朋友建议趁早出清。

@Oktfolio 是这层楼里唯一值得当反面教材批评的。
和其他人不同,此君提到了 TC39 的“丑陋”proposal 。

我补充一下,TC39 的人是知道这并非必要的:github.com/tc39/proposal-class-fields/blob/main/PRIVATE_SYNTAX_FAQ.md#why-is-encapsulation-a-goal-of-this-proposal
为何?因为 ES 本就烂用户集中营,搞不定 js 这种缝合怪,又引进外来种解决用不好的问题,然后 TC39 的品味向这种糟粕趋同了而已。

关键是那么一点:你要类的访问控制,是为了实现封装,而不是 TC39 这里还需要去解释封装是一个实现所谓私有字段的理由——这种典型的倒果为因和妥协才是真正的丑陋。
(顺便说明挂面向对象小朋友的原因之一:封装是普遍需求,跟是否面向对象无关。)
至于为啥要封装?理论上的理由不少,不过“为了更好地实现易用和不易误用的抽象”就够一般工业界用户堵嘴了。

为何要类的字段?因为抄的 C++/Java 之类的不支持一等环境(first-class environment)的静态语言,这类语言中环境(基本就实现为某种内部符号表)不对用户可见,更别想在运行时改,于是所有配置都得靠预先声明。字段声明带访问控制也就是这种限制下比较容易的做法。
对 ES 这样 class 能近似模拟 environment 的强力语言来说,不去发挥长处反而向无法简单扩展的弱鸡限制看齐,这也是耻辱。要知道 v8 之类的高效的实现为了这种程度的动态性已经吃了大亏了。你加了字段又不能把这里的开销塞回去不实现了,结果就是纯纯增加实现和使用的复杂度。多了个啥……字段连正经的糖都不是,半吊子。
不过这里倒不是 TC39 特别拉胯。Scheme 在 R6RS 前后也对一些太偏 syntax 而非 procedure 的 record syntax 有争议。虽然也是一地鸡毛,比 ES 党人高的是,他们好歹知道 syntax 和 procedure 的区别,ES 这种没 hygienic macro 的语言(=所谓的 syntax )自然也不配有自动考虑到这种深度的用户了。

清楚这个以后,就知道所谓少语法糖根本不着边际,而是允许这种语法糖本来就代表不同的思路。所谓 getter/setter 根本不是所谓 property 的模拟。
前者本质是函数,允许被调用同时其中蕴含副作用。
而后者本来“什么都不是”,就是个对象引用,或者更准确地说是符号求值——本质上里面有的是个 name resolution ,语义是拿标识符确定在环境中引用的对象作为求值结果。如果建模出环境写成小步语义,就会很清楚地看到符号求值原则上是纯的,意味着编译器之类的实现可以相当静态地确定这种操作的结果而轻易做出不少优化(只要环境能被确定——这在典型地静态语言中是完全“免费”的)。
而纯函数在函数语法中通常不被支持(除非是 PFP 语言不允许非纯函数,甚至用 η-equivalence 把两种语法当作一种,比如 Haskell ),如果支持要么就扩展到处 __attribute__((pure)) 之类。不算这种扩展,引入任意的所谓 property ,就把 property 折叠为了函数调用的糖,损失了一种原生的纯求值语法,妥妥地削弱语言的抽象能力。(顺便另一侧考虑,这也是我黑 Haskell 的原因之一。)
对支持 hygienic macro 的语言,这类设计通称 identifier macro ( C 这种不 hygienic 的叫 object-like macro ),会让 macro expander 的算法和实现以及对象语言程序的兼容出现更多蛋疼的问题。具体就不展开了。

因为见识的语言太稀薄就歪到所谓面向对象的道道上的用户真该反思自己真没想象力了。

基于同样的一些理由,一些 C++ 用户反对 property ( MSVC 扩展有)。虽然 C++已经是复杂到“怎样都好”了,不过能少点鸡肋不如的特性还是少点好。
FrankHB
2022 年 8 月 13 日
@FrankHB 俩 typo:
class 能近似模拟 environment → object 能近似模拟 environment
record syntax → record grammar ,反正这里的 syntax 不是下文的那种
FrankHB
2022 年 8 月 13 日
@CodeCodeStudy “Java 的成员变量没有多态”——这位童鞋也还是表扬一下吧。
这个算是意外地比较接近核心的说法。
C++ 中尽管没原生支持所谓的 property ,但可以用带有用户定义非 explicit 转换函数的 proxy object 模拟实现。(所以原生 property 多余,这是另一部分 C++ 用户反对的理由。)
转换函数的这种隐式机制是强制(coercion) ,是一种经典的特设多态(ad-hoc polymorphism)。这个意义上,原生的 property 也能视为同类的玩意儿。
这种多态经常是“不好”的,是常见栽赃出所谓“弱类型”的理由之一。不过就破坏求值算法的可预测性和增加复杂性来讲,这里的确是不好(即便对 proxy object 作为表达式的求值可以确定性地分解为符号求值和模拟的函数调用)。
和另一种特设多态——重载一样,这阻碍了直接以名称引用时的一些操作。&个重载函数会直接搞出个非一等的 overload set 还要在特定的上下文中消歧义,而要引用一个 property 的名称区分是不是已经转换的值相比就更加没事找事——更何况万一这玩意还不止 get 和 set 还有更多私货转换呢?(要转换不那么欠扁,除非是 lvalue-to-rvalue conversion 这种完全能不看类型声明就确定转换方向的,那就可以直接当成另外的非特设的子类型多态了)。一个表面上的语法糖搞出了杂七杂八的语义包袱,还有用户会当简单,真是呵呵哒。
guoqiao
2022 年 8 月 13 日
@liuhan907
@BigDogWang

所以说到底, 你俩的观点就是: "因为 Java 的语法就是这样的, 所以这样做是对的".
类似的观点有: "你生在这个国家, 那你就必须爱这个国家, 你说的问题都是国情决定的, 你说国外没用."

楼主问的语言确实是 Java, 但是这是一个通用的编程语言设计问题, 他的问题也可以解读成"Java 为什么要这样做? 是不是合理 ? 有没有更好的设计 ?"

我来说说我真正反感的地方:

我记得早期写 Java/C# 的时候, 把字段设置成 private 只是一个推荐做法.
如果你明确知道自己的字段直接访问没问题(比如只是当作数据载体用), 那么你应该允许我这么做. 哪怕将来确实有较小的概率需要加上访问控制, 也不过是一个简单的重构.

到了 2022 年, 当你在 IDE 里写 C# 或者 Java, 如果 class 的字段没有封装起来, IDE 会拼命的提示你, 这样做不行. 在 V2EX 这样的编程论坛里, 加上 getter/setter 被认为是天经地义的, 质疑的声音会被嘲笑. 这像极了疫情三年来出生的小孩, 他们以为人类出门就是必须带口罩的.

重申一下我的观点:

- 从语言设计层面, 这个问题有更优雅的解决方案.
- 即使是限定在 Java 现有的语法里, 要求所有字段都封装起来, 是矫枉过正, 得不偿失的.
Slurp
2022 年 8 月 13 日
问题在于,你可以更改 get 和 set 的实现,但不能更改字段的实现。

并且更重要的一点,由于 Java 没有不可空类型。你可以在 get 时检查空值。

这种约定为其他许多 JVM 语言提供了坚固的基石。Kotlin 在 Java 世界遵循 getter 和 setter 规范。而在 Kotlin 自身,它将 setter 和 getter 视作 property 。Kotlin 的空安全是极为重要的一点。对于 Kotlin 自身代码,可以在编译时检查,而对于 Java 调用或反射调用,则需要运行时检查,因此必须有一个 getter 。

getter 、setter 绝不是教条主义,只是缺乏一个语言层面的语法糖。
Slurp
2022 年 8 月 13 日
@guoqiao Python 一堆下划线也好意思说?…… 看到一堆 __init__ 就想吐,更何况连基本的编译时限制都没有。严重怀疑你下一步是不是要说:「什么类型检查、什么可访问性修饰符,全部都能 Hook ,都不存在,都是程序员的规定而已」。

脚本语言不需要是因为不需要太考虑兼容性和类型…… 但,如果你写的是库,就让别人天天访问 private ,任意传空值?

天天「确实需要」的时候,难怪 Python 库总是动不动在 Minor Version 改 API 。

因为小错误酿成大错的例子并不罕见,之前 B 站因为一小段 Lua 造成整个服务器瘫痪,就说明了无类型检查的弊端。

Java 因为设计问题,空值处理很垃圾;但 setter / getter 很好地补全了这一问题。这是 setter 和 getter 有用的又一例证。不过……看你的观点,可能会说「既然非空类型有利于预防程序错误,那我们要求所有人都必须写 type hint ,不写的不过面试,好不好啊?」

更搞笑的是,还扯上移民了……
karloku
2022 年 8 月 16 日
@guoqiao

getter/setter 本质上的区别是直接暴露了对象的成员变量, 还是通过逻辑隐藏了成员的访问.

通过 ```@property``` 定义的 getter/setter 可以语法上如成员变量一样进行访问这是 python 提供的的便利之处. java 无法实现这样的语法所以只能用 ```#getXXX()``` / ```#setXXX()``` 进行定义
zagfai
2023 年 3 月 15 日
@devswork #89 但问题来了,为什么要防止别人改?都是一份代码,不想被改约定不被改就可以了。
devswork
2023 年 3 月 15 日
@zagfai #179 因为防止别人改成“乱”的值(非法的值),所以需要参数校验( setter 中检查判断)。比如像这样的类有成千上万个,这么大的量,就没法跟别人约定不让改了吧,一个一个去说不现实,甚至自己在回过头看看半年前定义的约束,你都不一定记得了,如果是多人开发呢,如果你写的类是一个依赖,别人会 import 进来来使用你写的这个类呢,这些情景都是没法“约定”的。如果我并不想对外暴露 gender 性别是什么类型定义的(整数、字符串),我只给 setter 、getter 用来设置和获取结果就可以了,对于调用者来说,这就是 private ,并且只能设置合法的值、获取合法的值,减少很多 if 和心智负担,完全不需要考虑 gender 的细节。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://v2ex.ih06.com/t/872064

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX