Java封装

我们总是说如果我们只是将变量private定义并定义getter setter来访问这些变量,那么数据将被封装。 我的问题是,如果我们可以通过getter和setter访问变量(数据),那么数据是如何隐藏或安全的?

我搜索了大量的解释但没有找到任何东西。 每个人都在他们的博客和post中说它是一种数据隐藏技术,但没有解释/阐述它。

期待在stackoverflow论坛上获得适当,满意的解释。

我理解你的问题的方式是,虽然我们将变量声明为private ,因为可以使用getter和setter访问这些变量,但这些变量不是私有的。 那么,这样做的意义是什么?

好吧,当使用getter和setter时,您可以限制对private变量的访问。

 private int x; public int getInt(String password){ if(password.equals("RealPassword")){ return x; } } 

对于制定者来说也是如此。 希望这可以帮助!

封装不仅仅是为类定义访问器和增变器方法。 面向对象编程的更广泛概念在于最小化类之间的相互依赖性,并且通常通过信息隐藏来实现。

封装之美是在不影响用户的情况下改变事物的力量

在像Java这样的面向对象编程语言中,您可以通过使用可访问性修饰符隐藏细节来实现封装(公共,受保护,私有,加上没有修饰符,这意味着包私有)。 通过这些级别的可访问性,您可以控制封装级别,级别限制越少,发生更改的代价越高,类与其他依赖类(即用户类,子类)的耦合程度越高。

因此, 目标不是隐藏数据本身 ,而是隐藏有关如何操作此数据的实现细节。

我们的想法是提供一个公共接口,通过它您可以访问这些数据。 您可以稍后更改数据的内部表示,而不会影响类的公共接口。 相反,通过暴露数据本身,您会破坏封装,从而改变操作数据的方式而不影响其用户的能力。 您可以使用数据本身创建依赖关系,而不是使用类的公共接口创建依赖关系。 当“变化”终于找到你时,你会创造一个完美的鸡尾酒。

您可能希望封装对字段的访问权限有几个原因。 Joshua Bloch在他的“有效Java”一书中,第14项:最小化课程和成员的可访问性,提到了几个令人信服的理由,我在这里引用:

  • 您可以限制可以存储在字段中的值(即性别必须为F或M)。
  • 您可以在修改字段时执行操作(触发事件,validation等)。
  • 您可以通过同步方法来提供线程安全性。
  • 您可以切换到新的数据表示(即计算字段,不同数据类型)

但是,封装不仅仅是隐藏字段。 在Java中,您可以隐藏整个类,这样就隐藏了整个API的实现细节。 例如,在Arrays.asList()方法中Arrays.asList() 。 它返回一个List实现,但你不关心哪个实现,只要它满足List接口,对吧? 可以在将来更改实现,而不会影响方法的用户。

封装之美

现在,在我看来,要真正理解封装,首先必须理解抽象。

例如,考虑一下汽车概念的抽象层次。 汽车内部实施复杂。 它们有几个子系统,如传动系统,制动系统,燃料系统等。

然而,我们已经简化了它的抽象,我们通过它们的抽象的公共接口与世界上的所有汽车进行交互。 我们知道所有的汽车都有一个方向盘,通过它我们控制方向,他们有一个踏板,当你按下它加速汽车和控制速度,另一个,当你按它,你让它停止,你有一个齿轮如果你前进或后退,你可以控制它。 这些特征构成了汽车抽象的公共界面。 早上你可以驾驶一辆轿车,然后下车并在下午驾驶一辆SUV就好像它是一样的。

但是,我们很少有人知道如何在引擎盖下实现所有这些function的细节。 想想汽车没有液压定向系统的时间。 有一天,汽车制造商发明了它,并决定将它从那里放入汽车。 尽管如此,这并没有改变用户与他们交互的方式。 最多,用户在定向系统的使用方面经历了改进。 这样的改变是可能的,因为汽车的内部实现是封装的。 可以安全地完成更改,而不会影响其公共接口。

现在,我们认为汽车制造商决定将燃油盖放在汽车下方,而不是在其中一侧。 你去购买这些新车中的一辆,当你用完汽油时,你会去加油站,而你却找不到加油盖。 突然你意识到它在汽车下方,但你无法用气泵软管到达它。 现在,我们打破了公共接口合同,因此,整个世界都破裂了,它因为事情没有按照预期的方式运转而崩溃。 这样的改变会花费数百万美元。 我们需要改变世界上所有的燃气泵。 当我们打破封装时,我们必须付出代价。

因此,正如您所看到的,封装的目标是最小化相互依赖性并促进变更。 您可以通过最小化实现细节的曝光来最大化封装。 只能通过其公共接口访问类的状态。

我真的建议你阅读Alan Snyder撰写的一篇论文,称为面向对象编程语言中的封装和inheritance 。 此链接指向ACM上的原始论文,但我相信您将能够通过Google找到PDF副本。

数据是安全的,因为您可以在getter / setter中执行其他逻辑,并且无法更改变量的值。 想象一下,您的代码无法使用null变量,因此在您的setter中,您可以检查空值并指定一个默认值!= null。 所以你的代码仍在工作,无论是否有人试图将你的变量设置为null。

我的问题是,如果我们可以通过getter和setter访问变量(数据),那么数据是如何隐藏或安全的?

例如,您可以在getters / setter下封装逻辑

 public void setAge(int age){ if(age < 0){ this.age = 0; }else{ this.age = age; } } 

继续Jigar的回答。 在封装中有一些东西。

  1. 合同管理:如果你把它public ,你实际上就是让任何人把它改成他们想要的东西。 您无法通过添加约束来保护它。 您的setter可以确保以适当的方式修改数据。

  2. 可变性:你并不总是需要一个二传手。 如果有一个属性,您希望在对象的生命周期内保持不可变。 你只是将它设为私有而且没有设置它。 它可能会通过构造函数设置。 然后你的getter将只返回属性(如果它是不可变的)或属性的副本(如果属性是可变的)。

通常,getter和setter对字段的封装为更改提供了更大的灵活性。

如果直接访问字段,则会遇到“愚蠢的字段”。 字段只能写入和读取。 访问字段时无法执行任何其他操作。

使用方法时,您可以在设置/读取值时执行任何操作。 正如Markus和Jigar所说,validation是可能的。 此外,您可以决定某一天,该值是从另一个值派生的,或者如果值更改,则必须执行某些操作。

数据是如何隐藏或安全的

使用getter和setter,数据既不隐藏也不安全。 它只是让你有可能使它安全。 隐藏的是实现而不是数据。

如果存在访问器和/或变更器,则数据validation是关于封装如何提供安全性的问题的主要答案。 其他人已经使用在mutator中设置默认值的故障安全示例提到了这一点。 您回答说您更喜欢抛出exception,这很好,但是在您使用它时确定您有错误数据并不会改变您有错误数据的事实 。 因此, 修改数据之前捕获exception是不是最好,也就是说,在mutator中做到这一点? 这样,除非mutator已经validation它是有效的,否则永远不会修改实际数据,因此在数据不良的情况下保留原始数据。

我自己还只是一个学生,但是当我第一次遇到封装时,我的确和你一样,所以我花了一些时间来搞清楚它。

考虑到线程,我喜欢这个解释。 如果你公开你的字段,当某个Thread改变其中一个字段时你的实例怎么知道? 唯一的方法是使用Encapsulation,或者更简单的把get getter和setter添加到该字段,因此你总是知道并且可以检查/响应字段更新。

封装使代码更容易被其他人重用。 使用封装的另一个关键原因是接口不能声明字段,但它们可以声明方法,这可以引用字段!

方法在开头用动词正确命名。 如:getName(),setName(),isDying()。 哪个可以帮助阅读代码!