java不可变类
不可变类的意思是创建该类的实例后,该类的属性是不可改变的。java中的8种包装类型和java.lang.String都是不可变类。如果需要创建自定义的不可变类,需要遵守如下规则:
1,类属性使用private和final修饰符来修饰。
2,提供带参的构造方法,用于初始化类中的属性。
3,为该类属性只提供get方法,不提供set方法,因为final修饰的属性是不许允被修改的。
4,如果有必要,重写Object类中的equals()和hashCode()方法。
下面我们来定义一个不可变类。代码如下:
package fly.zxy.CollectionJava;
public class Address {
//使用privae final修饰属性
private final String detail;
private final String postCode;
public Address(){
this.detail = "";
this.postCode = "";
}
//提供带参的构造函数来初始化属性
public Address(String detail, String postCode){
this.detail = detail;
this.postCode = postCode;
}
//重写equals方法
@Override
public boolean equals(Object obj) {
if(this == obj){
return true;
}
if(obj!=null && obj instanceof Address){
Address a = (Address) obj;
if( this.getDetail().equals(a.getDetail()) && this.getPostCode().equals(a.getPostCode()) && this.hashCode()==obj.hashCode() ){
return true;
}
}
return false;
}
@Override
public int hashCode() {
return this.getDetail().hashCode() + this.getPostCode().hashCode();
}
public static void main(String [] args){
Address ar1 = new Address("创新城","20011");
Address ar2 = new Address("创新城","20001");
System.out.println(ar1.equals(ar2));
}
//只提供get方法
public String getDetail() {
return detail;
}
public String getPostCode() {
return postCode;
}
}
对于上面的Address类,当我们程序创建了该类的实例后,是无法修改detail和postCode属性的。与不可变类对应的是可变类,我们大部分时候创建的类都是可变的。特别是JavaBean,我们提供了get和set方法来获取和改变属性。
当使用fianl修饰引用类型变量时,表示该引用类型变量不能被重写赋值(引用不能被改变),但是引用所指向的对象内容是可以被改变的。这就产生了一个问题:当创建不可变类时如果它包含的引用类型属性是可变的,那么其属性对象本身的值依然是可变的,那么这个可变类其实是失败的。注意上面的Address类的属性类型都是String类型,而String是不可变类。
下面程序定义了一个不可变类Person,但因为Person包含一个引用Name类型属性name,Name是可变类,所以导致Person变成了不可变类。代码如下:
package fly.zxy.CollectionJava.finalClass;
public class Person {
private final Name name;
private final int age;
public Person(Name name, int age){
this.name = name;
this.age = age;
}
public static void main(String [] args){
Name n1 = new Name("zhangxue","yuan");
Person p1 = new Person(n1, 25);
//=>p1:zhangxueyuan
System.out.println("p1:"+p1);
//通过n1更改 firstName属性的值为 "AB"
n1.setFirstName("AB");
//=>p1:AByuan
System.out.println("p1:"+p1);
//通过getName()获取的Name对象来更改firstName属性值为"CD"
Name n2 = p1.getName();
n2.setFirstName("CD");
//=>p1:CDyuan
System.out.println("p1:"+p1);
}
public String toString(){
return this.name.getFirstName()+this.name.getLastName();
}
public Name getName() {
return name;
}
public int getAge() {
return age;
}
}
class Name{
private String firstName;
private String lastName;
public Name(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
从上面的例子中我们可以发现Person的name对象的firstName被修改了。这破坏了设计Person类的初衷。
为了保持Person的不可变,必须保护好name属性,让外界无法访问到name属性所指向的Name对象。我们可以这样更改Person类,代码如下:
package fly.zxy.CollectionJava.finalClass;
public class Person {
private final Name name;
private final int age;
public Person(Name name, int age){
//相当于创建一个 name 对象的副本,然后在赋给this.name
this.name = new Name(name.getFirstName(), name.getLastName());
this.age = age;
}
public static void main(String [] args){
Name n1 = new Name("zhangxue","yuan");
Person p1 = new Person(n1, 25);
//=>p1:zhangxueyuan
System.out.println("p1:"+p1);
//通过n1更改 firstName属性的值为 "AB"
n1.setFirstName("AB");
//=>p1:AByuan
System.out.println("p1:"+p1);
//通过getName()获取的Name对象来更改firstName属性值为"CD"
Name n2 = p1.getName();
n2.setFirstName("CD");
//=>p1:CDyuan
System.out.println("p1:"+p1);
}
public String toString(){
return this.name.getFirstName()+this.name.getLastName();
}
public Name getName() {
//创建一个name对象的副本并返还
return new Name(name.getFirstName(), name.getLastName());
}
public int getAge() {
return age;
}
}
class Name{
private String firstName;
private String lastName;
public Name(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}