每天学点java—对象和类的学习心得(1)

时间:2021-10-23 13:58:46

  大三上学期的时候就接触java了,但是感觉自己学习的都是非常基础的东西,所以这次希望通过再一次的复习能够回顾,同时也有所提高。(哈哈,毕竟古语云:“温故而知新”)
  废话不多说,这是第一次写博文,有点激动。只是希望把自己看书学习之中注意到的一些觉得可以稍微记录下来的内容加以探讨,有需要还可以再翻出来看看,也是极好的。
  我看的书是《JAVA核心技术卷1 第8版》。今天就看了“对象与类”这章的一部分内容。看的过程中有时还是会有所疑问的,所以喜欢写一些代码去验证我的想法,接下来的一些心得中也会主要以代码形式作为说明吧。

  不要写返回引用可变对象的访问器

  大家都知道java是面向对象的语言,其中有个很重要的概念就封装。对数据的封装可以更好的实现数据隐藏,更好保证对象中数据的安全。
之前我的编程过程中都会这样写代码,把所有的非静态类成员变量都设置为私有的(private),然后给每个私有域写一个域访问器(get方法)和域更改器(set方法)。比如下面这样:

class IDCard{
private int id;
//...省略若干
}

class Person{
private int age;
private IDCard idCard;
public Person(){...}

//域更改器和域访问器
public int getAge() {return age;}
public IDCard getIdCard() {return idCard;}
public void setAge(int age) {this.age = age;}
public void setIdCard(IDCard idCard) {this.idCard = idCard;}
}

今天看书就发现当一个私有域为可变对象(一个类对象,而不是基本类型变量)的时候,这样写代码就会破坏了对象的封装性。而导致问题的产生就出现在getIdCard()方法中,它直接返回的是私有域成员对象idCard的引用。这样也就是说可以通过该方法获取到idCard这个私有域成员对象,并且现在我们可以使用这个引用改变这个私有域成员的状态了。

Person person = new Person();
IDCard idCard = person.getIdCard();
//这里改变idCard的状态实际就直接改变persion中idCard对象的状态
idCard.setId(...);

就像上面这行代码,在通过getName()方法就获取到了persion实例私有成员idCard 的引用,然后就可以直接通过访问idCard实例的方法改变它的状态了。这显然是破坏了Person类的封装性的,这样看起来好像Person类中的setIdCard()方法就没什么必要了。
所以书上推荐的解决方法就是不直接返回该私有域对象的引用,而是先对该对象进行克隆,再返回该克隆的对象的引用?如下所示:

public IDCard getIdCard() {return idCard.clone();}

然而事情并没有那么简单,因为Object类中的clone()是protected,受保护限制的。所以我们需要把IDCard类实现Cloneable接口,并且以public的方式重写clone()方法。(详情可看Cloneable接口说明)

class IDCard implements Cloneable{
//...省略
/**
* 调用Object类的默认浅拷贝克隆方法
* 调用super.clone()方法要抛出CloneNotSupportedException异常
*/

@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

然后我们再使用该类的时候就可以这样子:

Person person = new Person();
IDCard idCard = person.getIdCard();
//这里改变idCard的状态并不会改变persion中idCard对象的状态
idCard.setId(...);
//通过Person的域更改器来更新idCard的状态
person.setIdCard(idCard);

  虽然这样子看起来比之前多了一行代码,感觉好像是绕了个弯,但是这样才能正真确保了类中数据的封装性吧。然后我还有一点感到很奇怪,我们用eclipse中自动生成get(),set()方法的功能时,其中get方法都是直接返回私有域的引用的!!!所以我又产生了一个疑问,或许上面讲的真的是多此一举?eclipse还没注意到这个问题?还是说后面java版本升级了,能智能识别在get函数中返回的就是克隆对象?哈哈哈,是不是脑洞有点大啊。
反正我以后会注意一下这个问题的了,我觉得有时候可能实际情况就需要那样的写法,虽然会破坏一定的封装性。所以一切还是要看实际情况而定吧。
  这里就又涉及到了类对象中的clone()函数,然后我又想到了深拷贝,浅拷贝的问题,这个我之后会在研究研究。


2015/08/03更新:昨晚没注意到clone方法是protected的问题。今天更新了一下用实现Cloneable的方式来解决。
同时我也大概了解了为什么Eclipse里自动生成get()方式时是直接返回该域对象的引用了,毕竟对象的克隆最终还是由用户需求来决定的。