Java 代码不写getter, setter, 转化为 public, 有什么弊端?

2017/1/5

描述

一个字段被认为是私有的, 那么外部就不能访问它( 最多提供getter )

如果一个私有的字段提供了getter和setter, 那么它就被认为是公开的, 那就前后矛盾了, 应该使用 public

  • 目的是什么

1. 一直不重视这个问题的「why」, 而只关注 how, 导致东西理解不透彻
2. anyway, 我甚至想弄清楚 what, 以刷新三观

相关代码

public class Bicycle {
        
    private int cadence;
    private int gear;
    // 比如speed被认为是公开的
    // 所以改成 public 的
    // 然后移除对应的 getter setter
    private int speed;
        
    public Bicycle(int startCadence, int startSpeed, int startGear) {
        gear = startGear;
        cadence = startCadence;
        speed = startSpeed;
    }
        
    public int getCadence() {
        return cadence;
    }
        
    public void setCadence(int newValue) {
        cadence = newValue;
    }
        
    public int getGear() {
        return gear;
    }
        
    public void setGear(int newValue) {
        gear = newValue;
    }
        
    public int getSpeed() {
        return speed;
    }
        
    public void applyBrake(int decrement) {
        speed -= decrement;
    }
        
    public void speedUp(int increment) {
        speed += increment;
    }
}

上下文环境

  • 产品版本: Java 8

尝试解决

我自己的理解

  • 如果定义为 public 的, 那么日后要改这个字段就不容易, 而函数的「可修改性」就很强 ( 姑且称 method 为函数 )

  • 但是真的要为以后想那么多吗( 代码是需要不断重构的, 预先空想可能是 over-design )

update:
一个python的例子, 怎么说呢, 总感觉python的这种写法舒服点( 虽然定义的时候, 代码没有比java少多少, 但是调用的时候舒服点 )

In [1]: class MyClass(object):
   ...:     def __init__(self, name, age, gender):
   ...:         self._name = name
   ...:         self._age = age
   ...:         self._gender = gender
   ...:     @property
   ...:     def name(self):
   ...:         return self._name
   ...:     @name.setter
   ...:     def name(self, value):
   ...:         self._name = value.strip()
   ...:         

In [2]: c = MyClass('hezhiming', 22, 'male')

In [3]: c
Out[3]: <__main__.MyClass at 0x7febf5e59c10>

In [4]: c.name
Out[4]: 'hezhiming'

In [5]: c.name = '         hezhiming         '

In [6]: c.name
Out[6]: 'hezhiming'
阅读 10.8k
9 个回答

呃,通过方法去操作其实有很多事情可以干,尤其在配合了OO思想的情况下——类型是一个重要信息。

另外,这是一种标准,Java Bean就是这么一回事,许多框架、第三方库(最著名的就是Spring)的“正常运作”也依赖于此。你可以搜索一下“Java 帝国之Java bean”相关的文章。

强烈反对关于Setter写逻辑之类的答案。这样的代码极具误导性,有经验的人都知道不可以把逻辑写到"bean"层(一般对应Model层),起码要写到Service层(按照常见的三层式架构)。故此,这个情况下的Setter并不是显得那么重要了。综上,答的关于Setter里写逻辑的,都是不正确的。

最后评价一下那个说存在即合理的人,希望他能去好好理解一下“存在即合理”究竟是什么意思。


正式回应一下踩我的那些人————其中就不乏泛着说Setter里写逻辑的人,也有靠着“歪理邪说”来说服别人的人。

说个故事吧,我一年前刚来社区的时候。曾看见过一个分数比较高的大神签名版上写着垃圾SF!,我注意到了他的声望记录(那时声望记录和GitHub的提交记录有点相似,不过不是记录1年这么长),几乎所有问题都被踩了一遍,很显然——这是恶意的

在混迹社区一年多以来,我偶尔也会碰到这样的情况。不过手段并没有这么恶劣,同样,我承认我还是很菜的,对于有些问题吃不准,指不定就是误人子弟,挨踩就挨踩吧!

而今这个情况,是令我无法忍受的——错了就是错了,还要去误导别人。如果别人面试因为这种如此初级的题失利而被筛掉,那是多么的可惜啊!

最后,我想告诉 all:

  1. 我不会因为被踩了而退出社区,哪怕是恶意踩。因为我还要去回答更多的问题,防止更多的新手被“可怕的初学者”误导。同样,为了维护社区的质量,我对极具误导性的回答也决不会放过。

  2. 如果不信我的话,请在面试的时候告诉面试官你经常在Java Bean的Setter里写逻辑吧!

  3. 刚才我仔细的扫了一遍全部的答案,幸好有@kevinz 这样的用户在,我为他顶了一票,防止“可怕的初学者”将他摁下去。

  4. 回答+1分,跟风回答也才就1分。被踩就是2分。而且这样的答案越来越多,社区的质量就越来越差,届时,就算你有1W的声望,在你聊天 or 交流的时候骄傲的说了出来,别人也只会嗤之以鼻:那个社区的质量并不高,都是灌水跟风的。

每种语言的哲学不一样。Java 讲的是完全的面向对象,在编码时提倡代码高度的灵活性与可扩展性。而像Go语言提倡的又是代码极致的简洁性,所以只需要像下面的方式定义struct就ok。

type S struct {
    A string
    B int
}

但是在Java中你还是应该遵守规范为javabean定义get/set方法,因为你遵守规范才来享受规范为你带来的好处,比如你接入第3方库的时候,要使用reflect的方式来操作javabean时他们大多数都是采用get/set方法来实现的。如果你的javabean此时没有get/set方法那显然你是无法使用该库的。


个人经验感受,代码的简洁性比代码的灵活性与可扩展性要重要。所以java中也出现了像lombok这种工具包可以很好的简化代码。

上面的人说了这么多,都说漏了一点,方法具有多态性,属性没有多态性,此话怎么理解?比如Person为父类,Man为子类,父类和子类都有Name属性(虽然这很罕见),父类和子类中的name都为public。请看如下代码:

Person p = new Man();
p.name = "person";
Print(p.name);
Print(p.getName());

答案会输出
person
null

这个问题问得好!为什么我们不直接把这个私有字段变成public,而要用麻烦的setter和getter去做呢?原因有几个:

永远不要相信客户的输入

我们来假设这个类:

class Bicycle {
    public int speed;
    
    // ...
}

public class BicycleTest{
    public static void main(String[] args){
        Bicycle b = new Bicycle();
        b.speed = 300000; //什么?自行车这么快?不科学啊?自行车车速不超过120吧。
        // 可是作为程序员的我怎么办呢?只能在文档里告诉用这个类的人:超过120罚款?
        // WTF!写这个类的程序员该死。
        b.speed = -30; // 这个?负的速度,高中物理?我勒个去。
    }
}

我们如果改成这个,是不是好点:

class Bicycle {
    private int speed;
    
    public int setSpeed(int speed) {
        if(speed > 120){
            this.speed = 120;
        } else if(speed < 0){
            this.speed = 0;
        } else{
            this.speed = speed;
        }
    }
    public int getSpeed() {
        return speed;
    }
}

public class BicycleTest{
    public static void main(String[] args){
        Bicycle b = new Bicycle();
        b.speed = 300000; 
        System.out.println(b.getSpeed()); // 显示120
    }
}

便于修改

假如说,现在要求speed是0-40之间。如果没有getter和setter,所有的代码要重写一下,没有1个钟头时间基本不可能完成。有了这个setter的话,简单,改一下setter的逻辑,1分钟不完工,这程序员可以开了。

public int setSpeed(int speed) {
        if(speed > 40){
            this.speed = 40;
        } else if(speed < 0){
            this.speed = 0;
        } else{
            this.speed = speed;
        }
    }

我的理解是利弊是相对的,对于一个“小程序”来说,没有什么弊端,因为你和其它维护的人能清晰看到和把控参数值的变化,这种时候,因为你能完全把控程序的每个角落,public甚至“利大于弊”,因为没有了setter&getter,减少了不少代码量。但当程序“大“到一定规模程度的时候,是不是应该要考虑程序的可维护性呢,比如a.moneya.getMoney(),突然有一天加入了一个a.action因子来影响money,如何保证每一个money的调用者都能知道因子影响规则,显然直接a.money就不那么可靠了。从写程序的角度来说,应该多写方法,这是我大学计算机程序课老师教我的,数据很枯燥,唯方法能让你形象地知道程序在干什么。从面向对象的角度来说,对象应该提供操作对象的方法,所以,还是方法,setter和getter就是体现了这一思想。从Java特性来说,setter和getter体现了封装特性的思想,就上面的例子,当另外一个money使用者需要调用money时,调用者本不需要知道还有一个action因子在那里,他只要getMoney()拿到正确的money就可以了,变量私有,而提供操作变量的公共方法。这时,利大于弊。才疏学浅,一家之言,理解不妥之处接受批评指正。


补充,对于setter&getter内逻辑问题,我觉得是一个”应不应该的问题“,而不是一个”能不能的问题“,对于后者,我会说能,为什么不能!而对于前者,得权衡利弊,当你程序逻辑、风险把控很好时,能方便到你,写啊!那些优秀的框架不那么写,肯定也是利弊考量(扩展等),逻辑放在逻辑处理层,也是正常啊。

首先method的翻译是方法。


然后我来理解一下你的困惑是

但是真的要为以后想那么多吗

而其实你不这么做的原因主要还是因为这样做很麻烦吧。

但是实际上,你会拥有那些必须使用getter setter的域(字段,属性),比如只读的域,所以1⃣️,为了整体代码的一致性,还是全部getter setter的好。
2⃣️,使用ide的帮助其实并不用花太多功夫就可以完成。
3⃣️,这基本上算是一个规范性的东西,完全不必要思考需不需要这样做。
4⃣️,有些情况的确可以不需要getter setter的,如果你有自信这里的数据都很简单就直接public。


关于另外一个问题

如果一个私有的字段提供了getter和setter, 那么它就被认为是公开的, 那就前后矛盾了, 应该使用 public

目的是什么

首先一个私有字段可能只有getter和setter的一个,这自然算不上公开。如果setter getter都有,也不是你想象的那么简单。因为你自己写的getter setter没任何处理逻辑,但你考虑这个

private String name;

public void setName(String name) {
    this.name = name.trim();
}

public String getName() {
    return name;
}

如果你有像这个例子一样的需求呢,在存姓名的时候需要先去除掉多余的空格。(顺便我不敢保证上面代码语法没问题,好久没写java了。


关于开放字段调用会更舒服的问题,其实题主你提到的python的开放字段,并不是开放字段对不对。实际存储字段是_name,而getter和setter是name,这和java仅仅是语法的不同,本质是一样的。

我再放两段代码来展示一下getter和setter的好处。

private int userId;

public User getUser();

public void setUser(user);

再比如

private Map data;

public User getUser();

public int getStatus();
class GoodDog {
    private int size;
    
    public int getSize() {
       return size;
    }

    public void setSize(int s){
       size = s;
    }
}

这里就有一个封装和控制的问题。假设你直接访问属性 goodDog .size;突然有一天,你可能需要对每个尺寸,或者某个尺寸过滤掉一些东西,怎么办?那么你就只好在出现goodDog .size的地方到处添加过滤机制。如果你使用getSize()方法,那么我就在这个方法里面过滤一下就OK了。 其实总体思想就是一个面向对象的观点来做事情,你要什么,就给我讲,我到屋里给你拿出来,但是你却不可以直接进屋去拿,万一你不熟悉我家里的情况,把我家搞乱了咋个办啊。

如果goodDog .size=12这样直接设置的话不安全,万一size的值是小于10的话
是不是在每个属性设置的地方都要进行验证,为了不让这种事情发生,Java就统一的在set方法的时候改变值,控制范围,这样就不要以后需求变化的时候满世界的找那些地方使用了goodDog .size

有参方法和无参方法要看方法的具体用途 set方法和get方法是为了体现面向对象编程的封装思想 把成员变量设为private 只能通过特定方法修改和访问 保证了程序的安全性.

还有上面的回答,像java bean或者hibernate这些,它去属性不是取你定义的属性size,而是取你的getSize,然后去掉get方法S小写,得到size。

再或者还是不理解就记住一点:存在即合理,总有一天,在写某一段代码的时候,你会恍然大悟。

简单场景可以使用public,setter和getter主要是对外界封闭作用,可以在setter和getter里面加一些统一的处理,重构也方便。

我补充一点,隐藏可见性,settergetter方法不一定都是直接存取值,也可以加入一些处理逻辑。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏