Java 返回可变引用对象的相关问题
1.问题
/**
* 输出: Mon Apr 26 10:54:10 CST 2010
* Mon Apr 26 10:54:10 CST 2010
*/
public static void main(String[] args){
Example test = new Example(new Date());
Date d = test.getDate();
double tenYearsInMillisSeconds = 10 * 365.25 * 24 * 3600 * 1000;
d.setTime((long) (d.getTime() - tenYearsInMillisSeconds));
System.out.println(d);
System.out.println(test.getDate());
}
public class Example {
private Date date;
public Example(Date date){
this.date = date;
}
public Date getDate() {
return date;
}
}
Date
类破坏了Example
的封装,导致修改实例 d
时影响了 test
的值,原因是Date
类生成的对象是可变的。
2.对象与对象变量
Date birthday = new Date();
Date deadline = birthday;
这两个变量引用同一个对象(请参见图 4-4 )。
但一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
在 Java 中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new
操作符的返回值也是一个引用。
Date birthday = new Date();
可以理解为new Date()
构造了一个 Date
类型的对象, 并且它的值是对新创建对象的引用。这个引用存储在变量 birthday
中。
Java 对象变量与 C++ 的引用并不同
可以将 Java 的对象变量看作 C++ 的对象指针。例如,
Date birthday; // Java
实际上,等同于
Date* birthday; // C++
所有的 Java 对象都存储在堆中。 当一个对象包含另一个对象变量时, 这个变量依然
包含着指向另一个堆对象的指针。
3.更改器方法与访问器方法
上文还是没有解释清楚为什么Date
类的对象是可变对象,原因在这。
假设在上文中Example
类中使用Java中与Date
类相近的LocalDate
类便不会出现上述情况,测试可以自己去尝试。
原因在于假设使用LocalDate
类中的plusDays
方法来修改对象变量,它会生成一个新的LocalDate
对象,然后把这个新对象赋值给调用者,原来的对象不做任何改动。
此类只访问对象而不修改对象的方法有时称为访问器方法(accessor method)
而像Date
类中的setTime
方法会使得原对象的状态发生改变,此类称为更改器方法(mutator method)
4.解决方法
如果需要返回一个可变数据域的拷贝,就应该使用 clone。这样会创建一个当前对象的副本,而不会对当前对象造成影响。
有关 clone 的进一步讲解 Java 浅拷贝和深拷贝
public class Example {
private Date date;
public Example(Date date){
this.date = date;
}
public Date getDate() {
return (Date) date.clone();
}
}
5.不可变类
5.1什么是不可变类
不可变类指当类被实例化后,该类的成员变量均不可被改变。
如JDK内部自带的很多不可变类Interger
、Long
、 Boolean
和String
等。
5.2优缺点
- 优点:1.线程安全 2.易于构造、使用和测试 3.可以被自由地共享
- 缺点:对于每一个不同的值都需要对应一个单独的对象
5.3如何实现不可变类
- Class需要用
final
修饰,保证类不能被继承 - 所有成员变量需要
private
修饰,保证成员变量不能直接被访问 - 类中不允许提供
setter
方法,保证成员变量不会被改变 - 在
getter
方法中不能返回对象本身,返回对象的拷贝