V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
dxatgp02
V2EX  ›  Java

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

  •  
  •   dxatgp02 · 2022-08-11 08:50:23 +08:00 · 14945 次点击
    这是一个创建于 617 天前的主题,其中的信息可能已经有所发展或是发生改变。

    int max = obj.getMax();

    int max = obj.max;

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

    183 条回复    2023-03-16 14:57:46 +08:00
    1  2  
    kenvix
        101
    kenvix  
       2022-08-11 11:57:34 +08:00   ❤️ 5
    因为老 Java 没有 [属性] 只有 [字段] 的概念啊。新 Java 有了 record 在一定程度上解决了这个问题。而 Kotlin 和 C#有 [属性] 的概念,则从根本上解决了这个问题
    guoqiao
        102
    guoqiao  
       2022-08-11 11:59:04 +08:00   ❤️ 9
    这个问题很有代表性.

    我刚学编程的时候也疑惑过这个问题(C#/C++). 同样的, 人们给出的也都是封装, 重构, 访问控制之类的理由. 我将信将疑. 但因为当时的我所知甚少, 就姑且信之, 当作是 Good Practice.

    直到后来我转向了 Python. 什么狗屁 getter/setter ? 它连 public/ptotected/private 关键字都没有, 默认字段都是 public 的(尽管你可以用比如 __name 来实现 private, 如果你真的需要).

    Python 也可以用 @property / @property.setter 来实现一样的访问控制, 但同样, 仅仅是在你*确实*需要的时候.

    别看这只是一个小小的语法偏好差异, 但它却启发了我对思维方式的反思:

    - 加上 getter/setter 在某些特定情况下能带来一些好处, 那我们强制要求所有的字段都设成 private, 全部加上 getter/setter, 好不好啊 ?

    - 面向对象有很多好处, 那我们强制要求万物皆对象, 哪怕是 Hello World, 都要定义在一个 Class 里, 好不好啊 ?

    - 设计模式如此精妙, 能解决很多很多常见问题, 那我们把能套上设计模式的地方全部都用上, 要求每个程序员都掌握, 好不好啊 ?

    - 既然戴口罩有利于预防流感, 那我们要求所有人都必须戴口罩, 不戴的不让上路, 好不好啊 ?

    诸如此类.

    我知道很多人会说好, 因为现实生活中有很大一部分人就是习惯别人给自己制定一个一劳永逸一刀切的规范, 然后他无脑跟着执行就行了, so easy.
    但也有另一类人, 不喜欢被束缚, 不喜欢被别人的规范所限制, 喜欢自由自在的感觉, 哪怕这个确实会带来一些弊端和损失.

    编程语言的选择, 以及是否移民的选择, 也可以借助这个问题一并回答:

    如果你是前者, 那么你适合学 Java, 以及生活在国内.
    如果你是后者, 那么你适合学 Python, 以及生活在国外.
    lambdaq
        103
    lambdaq  
       2022-08-11 12:02:56 +08:00
    getter 和 setter 是一种 hook 。
    javaisthebest
        104
    javaisthebest  
       2022-08-11 12:05:15 +08:00
    @dxatgp02

    面向六大原则 开闭原则,对扩展开放对修改关闭。

    知道什么叫做访问修饰符嘛? 结合访问修饰符不就是实现了开闭原则。

    不要包装的东西强行包装? 那请问什么叫做 不需要包装呢?


    你是讨厌 用 getter setter 方法 还是讨厌 java 必须要 getter setter 呢?
    franklinre
        105
    franklinre  
       2022-08-11 12:06:49 +08:00
    比如数据库新增字段 Integer maxQusCount ,新增的记录 maxQusCount 默认值是 0 ,那原来的记录的 maxQusCount 就是 null ,此时就可以统一使用 getMaxQusCount(){this.maxQusCount==null?0:this.maxQusCount}
    jeehal
        106
    jeehal  
       2022-08-11 12:06:57 +08:00
    如果这个类的 max 不用了或者要改
    项目其他地方写了一堆 xxx.max ,难不难受
    guoqiao
        107
    guoqiao  
       2022-08-11 12:07:28 +08:00   ❤️ 6
    #82: "getter 和 setter 作用都不理解的(这不是 Java 特有的),我只能说在红利期程序员这碗饭太好吃了,不过看互联网公司这裁员势头,红利期也差不多过了,你不被优化谁被优化"

    我要特别的替楼主怼一下#82 楼这个回答. 请你不要被他唬住.
    有没有可能, 说这类话的人, 不过是因为他们没有经过自己的思考而人云亦云? 会不会是因为他先学了 Java, 甚至只学了 Java, 从而先入为主, 被禁锢在自己的思维茧房里了 ? 能问出最傻最基础问题的人, 会不会才是那个直面本质, 大胆思考, 甚至推倒重来, 开宗立派的人呢 ?
    soupu626
        108
    soupu626  
       2022-08-11 12:12:13 +08:00
    所以说只是少了一个默认的语法糖,默认生成字段的最简 get/set ,有数据校验之类的加强需求,再来手写就完了,lombok 的 @Getter@Setter 做的不也就是这件事么
    skinny
        109
    skinny  
       2022-08-11 12:13:33 +08:00
    @guoqiao 那就拜托你们这类人务必一直保持对这种基础知识的无知状态,好给别人腾出饭碗呢
    soupu626
        110
    soupu626  
       2022-08-11 12:14:02 +08:00
    另外直接访问字段,然后前后写校验逻辑,为什么不把者些逻辑放到 get/set 里呢,如果校验逻辑改了,是不是要霰弹式修改
    soupu626
        111
    soupu626  
       2022-08-11 12:30:37 +08:00
    @guoqiao 用啥编程语言还上升到思维方式,要不要移民上了。。。。你牛
    dxatgp02
        112
    dxatgp02  
    OP
       2022-08-11 12:35:02 +08:00
    @skinny 别墨鱼划水了,说优化你心不虚吗?
    dog82
        113
    dog82  
       2022-08-11 12:57:40 +08:00
    我觉得 Java 要符合 OO 里的 OCP 原则,所以设计了 get/set
    cenbiq
        114
    cenbiq  
       2022-08-11 13:20:29 +08:00 via iPhone   ❤️ 1
    回复太乱了,说到底就是“getter/setter”==“属性(prop)”,作用就是对“字段(field)”的访问控制。java 这门语言没有“属性”这个概念,它的做法是用方法包装“字段”强行模拟“属性”。其他更先进的面向对象语言则本身支持“属性”。
    ychost
        115
    ychost  
       2022-08-11 13:53:50 +08:00
    主要是没有 get/set 语法糖,比如 c# 就很友好地区分了 property 、attribute 、field
    qiumaoyuan
        116
    qiumaoyuan  
       2022-08-11 13:54:52 +08:00   ❤️ 1
    这个问题很简单,你觉得没用就不用。这是标准答案。等到你要用了,自然就知道为什么用。在网上问,最后的结果你也会变成你自己说的“为用而用”。

    同样的问题包括:为什么要写测试,为什么要用设计模式,为什么要面向对象。
    codingbody
        117
    codingbody  
       2022-08-11 14:00:19 +08:00
    java 虽然是面向对象设计语言,但是很多人在写业务的时候都是面向过程式的,面向数据库的,对象大多数都是只有 getter setter 的数据对象,并且把大量的业务逻辑放在一个叫 service 的庞大的类中处理,这种没有设计的代码,我写吐了。
    codingbody
        118
    codingbody  
       2022-08-11 14:03:13 +08:00
    @yule111222 #14 同意,这种现象大量出现在面向数据库编程的项目中,这种项目中的很多对象,就在一种数据对象,数据结构,没有承担起作为一个类该有的职责。
    realpg
        119
    realpg  
       2022-08-11 14:04:21 +08:00   ❤️ 9
    一堆人说了一大堆好处
    然后我翻了公司历史版本库里的各种 getter setter
    没有人在 getter setter 里写任何所谓的校验逻辑

    查找累计结果 近 18 万个 随机抽选了 50 个,没有一个有逻辑.
    Hstar
        120
    Hstar  
       2022-08-11 14:27:32 +08:00   ❤️ 1
    我个人对这个问题的回答是:这是 Java 这门语言的基调和 Java 创始人的个人风格导致的,充斥着规范、教条、约定俗成组成的条条框框,它假设使用者是个菜鸡,强迫使用者沿着设计师设计好的方式来堆砌代码。

    OOP 思想广泛的来说,使用对象来操作就可以视为 oop ,那么如何操作对象,这就是各个语言充斥着私货的地带了。比如这里提到的这个点,用了 Java 为什么一定要用 Get/Set 来操作对象的属性,我怎么操作你管得着吗,how dare you ?

    但从另一个角度来看,这些私货其实每个语言都有,只是或多或少而已。

    追根溯源,这个问题是 Java 缺失了对象属性的分类导致的,不区分属性、字段,导致不必要通过 Get/Set 来操作的属性也必须这么玩。可以理解为 Java 的时代局限性或作者的个人口味吧,同理还有类的多继承,等等。

    还有想说一句,Java 并不能代表 OOP ,它只是其中一个实现,它的做法不是一定都是对的,都是真理。奉劝楼里的几位井底之蛙 java 程序员多看多想,争取晚点被优化。
    zxfeng
        121
    zxfeng  
       2022-08-11 14:27:56 +08:00
    写过两年 java 写过几年 c#(unity)

    个人搬砖过程中驱动我主动去写 getter/setter (属性)的场景
    1 某个属性只想被其他类读或者写
    2 调试的时候方便;查找调用位置的时候,可以快速通过 ide 找到写或读的地方,比如某个值很少赋值但是有海量的读,通过快捷键找起来写的位置来比较麻烦。虽然有的 ide 也可以对成员读写位置区分,但这样能更快定位
    3 可以在里面做一些操作,比如打印调用来源,检查 null ,写操作监听回调等等

    缺点暂时就想起来一个
    1 比如在属性 get 里用到了循环 /内存分配等影响性能等操作,但是调用属性的地方却很容易当作一个常量的值来频繁调用

    至于设计原则啊模式啥的 没考虑过 可能层次还没有达到
    tonymua
        122
    tonymua  
       2022-08-11 14:49:09 +08:00
    封装,细化属性访问权限。
    theniupa
        123
    theniupa  
       2022-08-11 15:08:54 +08:00
    也没强制用呀,合适的时候用 public 也不是不行,就像我们自己写 jni 为了偷懒,就直接用 public field (🐶)
    Oktfolio
        124
    Oktfolio  
       2022-08-11 15:09:42 +08:00
    @realpg 这个是现在 Java Web 流行的失血模型导致的,CURD 才不会去思考那些,跟风就行了。
    sinnosong1
        125
    sinnosong1  
       2022-08-11 15:19:05 +08:00
    @realpg 我司项目里只有我写过一个数据校验。我还是 C#转过来的。😂
    misaka19000
        126
    misaka19000  
       2022-08-11 15:20:35 +08:00
    @zxfeng #121 好奇怎么会既写 Java 又写 C#的呀,请问下是从 web 开发转行到游戏开发了吗?谢谢!😆
    BigDogWang
        127
    BigDogWang  
       2022-08-11 15:22:20 +08:00
    @guoqiao 我就想问你。一开始你觉得不需要访问控制,然后到处直接访问变量。突然有一天,这个属性需要访问控制了,你改起来蛋疼不。。。不同语言本身就有不同的设计和风格
    sinnosong1
        128
    sinnosong1  
       2022-08-11 15:24:15 +08:00
    @misaka19000 我是 java 后端,之前做 asp.net core ,我公司要做桌面端,我用 WPF 和 Avalonia 写了 Windows 和 Linux 的 2 个版本。会写 C#又会写 java 的应该挺多。。。
    misaka19000
        129
    misaka19000  
       2022-08-11 15:27:39 +08:00
    @sinnosong1 #128 感觉 C#和 Java 不分家啊😂不过近几年 C#好像生态也好起来了,比以前要好了
    Bingchunmoli
        130
    Bingchunmoli  
       2022-08-11 15:35:52 +08:00 via Android
    哎,钓鱼
    realpg
        131
    realpg  
       2022-08-11 15:47:55 +08:00
    @sinnosong1 #125
    我从不写 java 还用过 setter
    当然不是 everywhere
    需要的时候 就把变量私有 必须 setter getter
    setter 里可以监控谁修改了这个东西, 很多时候非常有必要

    翻了全部楼, 都没见人说过这种用途, 感觉很多也就只是吹 JAVA, 根本也没太多生产项目用这些
    zxfeng
        132
    zxfeng  
       2022-08-11 15:49:50 +08:00
    @misaka19000 一直游戏开发,刚开始的时候还没有 unity ,所以用一些开源引擎做,后来 unity 起来就转了
    zxfeng
        133
    zxfeng  
       2022-08-11 15:57:54 +08:00
    @realpg 那啥 虽然我提了这个,但实际中也只是调试中或者监听后做一些轻度的操作。如果包含 io 等重操作或者导致很深的其他调用,写在这里调用的时候如果没有这个意识或者调用方不清楚里面的实现当作简单的赋值还是有一定风险
    cmdOptionKana
        134
    cmdOptionKana  
       2022-08-11 16:01:04 +08:00
    现在有了 record, 可惜有些第三方库还是只认 getXXX, setXXX, 不支持 record, 明明有好东西却不能用的感觉有点烦。
    sinnosong1
        135
    sinnosong1  
       2022-08-11 16:02:49 +08:00
    @realpg 我记得好像是个字符串设置值的时候写过,因为我感觉好像从来没有人这么在 java 写,但是在 C#应该挺多。
    NeroKamin
        136
    NeroKamin  
       2022-08-11 16:21:57 +08:00
    @codingbody #117 说的太好了,感觉现在写业务真的就是这样,一个 DAO 从数据库查数据在 Service 里处理处理就返回给 Controller 就完事儿了
    misaka19000
        137
    misaka19000  
       2022-08-11 16:49:34 +08:00
    @zxfeng #132 哇🤩,居然没有 unity 的时候就开始写游戏了啊,那肯定是元老级的游戏开发工程师了👍
    Chinsung
        138
    Chinsung  
       2022-08-11 17:16:21 +08:00
    你写业务逻辑,操作的全是实体类,确实必要不大,所以基本都是 lombok 生成
    但是很多框架的扩展就是通过标准 setget 来让你方便知道你可以获取哪些东西可以设置哪些东西
    再换句话说,难道全部用 getset 就没有一点好处吗?起码开头前缀统一吧,我.set 就可以提示所有我可以 set 的东西,get 也是一样
    obj.max ,那我想知道他有哪些东西,哪些只读哪些可写,怎么才能比较好的处理呢?
    acctv2
        139
    acctv2  
       2022-08-11 17:42:32 +08:00
    @realpg 但如果你需要加入校验的时候,之前的类没有设计 setter ,你怎么解决呢?
    loryyang
        140
    loryyang  
       2022-08-11 17:43:51 +08:00
    按照实用主义来说,99%没有意义,但是也没啥坏处,现在 getter 、setter 都可以自动生成了,没啥成本
    lux182
        141
    lux182  
       2022-08-11 17:47:17 +08:00
    @dxatgp02 不要着急盖帽子(教条主义)。
    private var;//变量私有
    public get/set ;//变量操作开放
    这样的好处是,
    1.变量作为成员私有,不允许别人随便获取和更改。
    value = instance.var //获取变量 value = instance.getVar()
    instance.var = value //赋值变量 instance.setVar(value)
    2.面向修改关闭,面向扩展开放
    java 面向对象的设计模式的基本原则。
    在大型工程中,设计模式是模块化的基础。大家都遵守统一的设计模式,最终才能快速的合作开发。
    如果依靠程序员的个人能力约束,没有语法层面限制,最终项目的维护是灾难性的
    3.java 一直不是最激进的语言,有时候觉得好像过于啰嗦,但从工程实践来说,它是很放心的语言
    dxatgp02
        142
    dxatgp02  
    OP
       2022-08-11 19:45:57 +08:00
    如果真的思考过就回答下面的问题:
    Q1 一个黑箱的实体对象 set/get 值,是否要自行通过工具类或断言类再验证?这种实体类 public set/get 和 public 字段的区别在什么地方?打断点打日志反编译工具全都上?
    Q2 入参拿到一个 obj 对象后 对象 private 属性+private set() 是否就无法注入且更变 obj 里的字段值?

    数据对象里大量 set/get 只有 this.v = p 或 return this.v 只是感觉这种语法及其奇怪。
    Huelse
        143
    Huelse  
       2022-08-11 20:06:59 +08:00
    最大的作用在于序列化,你不需要注释就知道这个类是干啥的
    xylxAdai
        144
    xylxAdai  
       2022-08-11 20:19:45 +08:00
    没啥用。最烦 get/set 。如果只是单纯的拿变量,建议还是直接 obj.value 吧
    DOLLOR
        145
    DOLLOR  
       2022-08-11 21:20:22 +08:00 via Android
    @BigDogWang
    其他语言(比如 JS )倒是挺方便的,只需把那个属性变成 getter/setter 就行了,其他地方可以继续保留 a=b.c 和 b.c=a 语法不变。
    snowlyg
        146
    snowlyg  
       2022-08-11 22:50:20 +08:00 via Android
    存在即合理
    11232as
        147
    11232as  
       2022-08-11 23:29:10 +08:00
    如果一个对象在自己的整个生命周期可以不对自身状态做任何限制,那写 getter 、setter 确实没啥意义,全部的属性设置成 public 也没啥问题。但稍微大一点的程序里面的对象或多或少都得对属性的访问域作出限制,setter 和 getter 是其中限制访问的一种方法之一。简单讲就是为了封装。
    举个简单的例子,有一个日历对象保存了一个 List<Schedule>,它对外暴露的方法可能就是一个`publlic void addSchedule(String date,ScheduleType type,String description)`,一个`public List<Schedule> getSchedule()`,而且 getter 返回的这个 List<Schedule>还是一个不可变对象——你不能不通过日历对象就去更改代办事项列表,这样可能会跳过很多校验,比方说同一个时间点不能有两个代办事项。
    而且日历对象不能每次都让客户端程序直接生成一个 Schedule 插入到 List 中,Schedule 可能还需要保存用户的信息,日历对象可以通过添加依赖资源来在 add 方法中解决这个问题,而不是需要客户端程序每次重复输入一遍用户的信息。客户端只需要提供时间、类型和描述。
    getter 和 setter 有它存在的意义,但和其他很多好东西一样,一旦滥用就令人生厌。最后,如果你真不喜欢 getter 和 setter ,而又得在 Java 生态里干活,那我推荐学习一下 kotlin ,它在语法层面支持自动的 getter 和 setter 。
    shalk
        148
    shalk  
       2022-08-11 23:53:13 +08:00
    具体情况具体分析,有的时候,你用方法二是可以的。
    有的时候并不是,有一些框架会和 get set 配合。
    因此,能写一,就不写二。因为不好写,就由了 lombok ,或者 插件生成 等等方法解决这个问题。
    msg7086
        149
    msg7086  
       2022-08-12 07:26:48 +08:00
    是不是先明确一下你问的到底是「用 getter/setter vs 字段」还是「写 getter/setter 代码 vs 不写 getter/setter 代码」?

    我随便拿 Ruby 来举个例子。Ruby 里 obj.var = value 实际会调用 setter 方法 obj.var=(),obj.var 实际会调用 getter 方法 obj.var(),而获取 field 如果不走 getter/setter 则只能通过反射。

    所以 Ruby 是一个典型的,用 getter/setter 但是不需要你手写 getter/setter 方法代码的语言。

    现在你这个问题有些歧义,看上去像是在问为什么要用 getter/setter ,但是却又好像在抱怨需要手写 getter/setter 而不是语言自动生成。
    msg7086
        150
    msg7086  
       2022-08-12 08:08:40 +08:00   ❤️ 2
    至于通常用 getter/setter 而不是直接读写 field 的理由是,代码可控性。

    字段是一个数据。读取和写入数据的是代码。那么这个代码,谁有最终控制权?

    如果一个类只包含数据,并且有另一个你能控制的类来操作这个数据,那么这个数据类就不需要 getter/setter 。操作数据的类就充当了 getter/setter 的作用,因为这个类也是属于你全权控制的。
    但是如果是交给第三方调用的类就不行了。当你要修改一个行为的时候,你需要一个一个组去协调,让别人按照你的意思去改他们的调用方法才行。

    又比如你调试 bug 的时候需要监控谁在什么时候修改了你类里面的一个字段。如果有 setter ,你把日志或者断点插进去即可。没有 setter 的话,你去哪里找调用方呢。

    还有一个点,就是 getter/setter 是方法,而方法是可以重载的。子类行为可以和父类不同。比如同样存储数据,父类可以存在字段里,子类可以重写然后存进数据库里。

    顺便一提,上面很多人都说 C#的 property 好用。Property 就是字段+getter+setter 的语法糖,归根结底还是 getter/setter 设计方案。
    coala
        151
    coala  
       2022-08-12 08:45:14 +08:00   ❤️ 1
    目前来说, 如果你不按这套规范写, 用轮子会出很多问题, 转 Json, 序列化, 对象拷贝之类的情况, 所以必须按规范写。

    要知道这些值都是 private 修饰的,不能外部直接访问, 所以有了 getter/setter ,

    应该问为什么非要用 private 修饰? 如果是为了访问控制, 为什么非要访问控制? 安全在哪里?
    BigDogWang
        152
    BigDogWang  
       2022-08-12 08:52:49 +08:00
    @DOLLOR 是的,所以说不同语言不同的设计。我用 java 使用封装就是为了防止后面出现这种情况。后面用 kt 就没有这种顾虑。都是工作经验换来的呀
    Anarchy
        153
    Anarchy  
       2022-08-12 09:19:11 +08:00
    看 kotlin 的特性就行了,kotlin 就是少写了很多 java 的模板代码
    Akitora
        154
    Akitora  
       2022-08-12 09:33:10 +08:00
    getter 和 setter 可以被动态代理,一些 orm 以此实现的懒加载功能
    wolfie
        155
    wolfie  
       2022-08-12 09:49:55 +08:00   ❤️ 1
    @guoqiao
    二极管思维,设计模式不适合套用世界上 100% 的事务设计,所以不改用设计模式。
    只会情绪输出有点自主思考吧。
    karloku
        156
    karloku  
       2022-08-12 10:24:01 +08:00
    好处是让接口更有稳定性.

    用了 getter/setter, 日后无论你怎么改里面的实现方式, 只要保持行为一致, 使用者都不需要更新自己的代码. 如果直接 obj.value 的话, 日后如果要更改内部实现逻辑, 下游用着要炸.
    pierswu
        157
    pierswu  
       2022-08-12 10:58:59 +08:00
    java 诞生于 90 年代,而且也是从 C++发展出来的,由于时代的局限性,没有类似于 js 中对象 property 的特性。
    就算后面想加,也会因为代码兼容性而作罢了,这也是不断的开发新的编程语言的原因。
    guoqiao
        158
    guoqiao  
       2022-08-12 11:54:14 +08:00
    @BigDogWang

    "我就想问你。一开始你觉得不需要访问控制,然后到处直接访问变量。突然有一天,这个属性需要访问控制了,你改起来蛋疼不。。。不同语言本身就有不同的设计和风格"

    不蛋疼, 如下:

    ```
    # V1: no access control
    class Person:
    def __init__(self, name):
    self.name = name

    # V2: make name readonly
    class Person:
    def __init__(self, name):
    self._name = name

    @property
    def name(self):
    return self._name

    @name.setter
    def name(self, value):
    raise AttributeError("name is readonly")


    # no change to interface & usage:
    p = Person("Foo Bar")
    print(p.name)
    # work with V1, Exception for V2:
    p.name = "John Smith"

    ```
    guoqiao
        159
    guoqiao  
       2022-08-12 12:16:53 +08:00
    @karloku 并不会, 如上.
    liuzhihang
        160
    liuzhihang  
       2022-08-12 12:32:34 +08:00 via iPhone
    试着把自己的系统全部改成 public 和 static 你看看大家开发用的怎么样。

    就比如我问我四川朋友,为什么川菜要加辣椒,不加又不是不能吃。或者直接吃碳水不也一样,为啥要各种烹饪。
    BigDogWang
        161
    BigDogWang  
       2022-08-12 14:41:25 +08:00
    @guoqiao 这理解能力就不要来喷了好吗
    yaphets666
        162
    yaphets666  
       2022-08-12 14:45:08 +08:00
    @guoqiao 我喜欢国外,但不喜欢 py
    guoqiao
        163
    guoqiao  
       2022-08-12 14:47:03 +08:00
    @BigDogWang 那么请说说你的理解.
    guoqiao
        164
    guoqiao  
       2022-08-12 14:48:32 +08:00
    @yaphets666 我只是提供一个判断问题的角度, 当然不是说适合每个人.
    nuk
        165
    nuk  
       2022-08-12 15:11:57 +08:00
    最简单的
    int max = obj.getMax();
    写成
    int max = obj.max;
    没啥问题

    但是
    Max max = obj.getMax();
    写成
    Max max = obj.max;
    会爆炸。。。
    mmdsun
        166
    mmdsun  
       2022-08-12 16:23:43 +08:00 via iPhone
    因为 Java 字段没办法多态,必须使用函数
    BigDogWang
        167
    BigDogWang  
       2022-08-12 19:28:47 +08:00
    @guoqiao 我说的很清楚了,java 没有 property ,所以为了避免后续要添加访问控制时修改麻烦,就直接默认加上 get set 了。其他语言有这些特性的,谁还手写 get set 啊
    DefoliationM
        168
    DefoliationM  
       2022-08-12 21:06:40 +08:00
    并行的时候 get,set 不会数据竞争吗
    liuhan907
        169
    liuhan907  
       2022-08-12 21:09:50 +08:00
    @guoqiao 既然题目在问 Java ,你例子也该用 Java 写,你用 py 举例来证明 Java 的 getter/setter 没必要岂不是很滑稽?
    sutra
        170
    sutra  
       2022-08-12 21:44:56 +08:00
    neptuno
        171
    neptuno  
       2022-08-12 22:23:25 +08:00 via iPhone   ❤️ 1
    这也能被说成是教条主义,学了个词就到处用
    FrankHB
        172
    FrankHB  
       2022-08-13 01:02:02 +08:00   ❤️ 1
    @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
        173
    FrankHB  
       2022-08-13 01:07:45 +08:00
    @FrankHB 俩 typo:
    class 能近似模拟 environment → object 能近似模拟 environment
    record syntax → record grammar ,反正这里的 syntax 不是下文的那种
    FrankHB
        174
    FrankHB  
       2022-08-13 01:34:06 +08:00
    @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
        175
    guoqiao  
       2022-08-13 04:52:56 +08:00   ❤️ 1
    @liuhan907
    @BigDogWang

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

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

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

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

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

    重申一下我的观点:

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

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

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

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

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

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

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

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

    更搞笑的是,还扯上移民了……
    karloku
        178
    karloku  
       2022-08-16 17:09:02 +08:00
    @guoqiao

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

    通过 ```@property``` 定义的 getter/setter 可以语法上如成员变量一样进行访问这是 python 提供的的便利之处. java 无法实现这样的语法所以只能用 ```#getXXX()``` / ```#setXXX()``` 进行定义
    zagfai
        179
    zagfai  
       2023-03-15 21:23:32 +08:00
    @devswork #89 但问题来了,为什么要防止别人改?都是一份代码,不想被改约定不被改就可以了。
    devswork
        180
    devswork  
       2023-03-15 22:49:21 +08:00
    @zagfai #179 因为防止别人改成“乱”的值(非法的值),所以需要参数校验( setter 中检查判断)。比如像这样的类有成千上万个,这么大的量,就没法跟别人约定不让改了吧,一个一个去说不现实,甚至自己在回过头看看半年前定义的约束,你都不一定记得了,如果是多人开发呢,如果你写的类是一个依赖,别人会 import 进来来使用你写的这个类呢,这些情景都是没法“约定”的。如果我并不想对外暴露 gender 性别是什么类型定义的(整数、字符串),我只给 setter 、getter 用来设置和获取结果就可以了,对于调用者来说,这就是 private ,并且只能设置合法的值、获取合法的值,减少很多 if 和心智负担,完全不需要考虑 gender 的细节。
    zagfai
        181
    zagfai  
       2023-03-16 03:11:26 +08:00
    @devswork 成千上万个类严格意义上是一坨屎山代码吧,可维护性理论上是很有问题的,所有人都不太会愿意去碰。
    devswork
        182
    devswork  
       2023-03-16 09:13:12 +08:00
    @zagfai #181 按照你的说法,那基本上有点规模的项目,都是屎山。所有类的数据校验交给调用方,一个 gender 只要调用方使用,就自己写一遍 if 判断,就算几十个类,也都背下来。这么做也完全 OK 。
    zagfai
        183
    zagfai  
       2023-03-16 14:57:46 +08:00
    @devswork 数据校验是一码事,这个在对外暴露接口是需要做的,我认可。现代项目基本上都往小型化,微服务去走,按功能划分成很多小队人马去完成,那么我是认为,小队人马( 3 个 5 个)人的内部,是没有必要做太严格的校验的。另外,基础架构开发团队产生的库或框架,那肯定是需要数据验证的,但不认同校验的粒度过细。
    1  2  
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5112 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 1510ms · UTC 03:45 · PVG 11:45 · LAX 20:45 · JFK 23:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.