> 文档中心 > Java可变类型与不可变类型

Java可变类型与不可变类型

 一、可变类型(mutable type)与不可变类型(immutable type)

       已知,基本数据类型都是不可变类型;引用(对象)数据类型既有可变类型,也有不可变类型

首先区分:改变一个变量、改变一个变量的值,二者有何区别?

  • 改变一个变量:将该变量指向另一个存储空间。——修改指向
  • 改变一个变量的值:将该变量当前指向的存储空间中写入一个新的值。——修改存储内容

接着区分:可变类型(mutable type)与不可变类型(immutable type)

  • 不变数据类型:一旦被创建,其值不能改变(但可以修改指向)
  • 可变数据类型:拥有方法可以修改自己的值/引用

(1)不可变类型(immutable type)

        以String为例,String是不可变类型,一旦被创建,其值不能改变

         当String类型(以至于扩展到其他不可变类型)发生改变时,它会再开辟一个新空间,创建一个新的String对象,存储改变后的值,并指向这个新的内存空间(原来的内存空间如果不再被使用,就会变成垃圾)

  • 缺点:使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收)
  • 优点:安全,内部存储值不会被篡改

(但是当变量类型为基本数据类型(没有引用时),我讲不清内部修改值的具体过程,猜测有可能是在栈中开辟新空间存储新值,变量名对应新值而非旧值——或者变量名与变量值之间有类似这样的机制,如果有大佬知道可以告诉我orz)

(2)可变类型(mutable type)

        以StringBuilder为例,StringBuilder是可变类型,可以修改自己的值/引用

         当StringBuilder类型(以至于扩展到其他可变类型)发生改变时,直接对引用指向的内存修改数值,而非再开辟新的内存空间,创建新对象

  • 优点:可变类型因为最少化拷贝,可以提高效率
  • 缺点:如果有多个引用(别名),使用可变类型就非常不安全

(3)final的使用

To make a reference immutable, declare it with the keyword final

 对于final,它限制了变量的引用不可更改,但是并不限制引用指向的内存里的数据的修改

总结:

  • final + 基本数据类型 / 不可变的引用数据类型:如 final int——不可修改该变量

(基本数据类型没有引用,且本身为不可变类型,不具有方法来修改其值)

  •  final + 可变的引用数据类型:final List——只限制了引用不可更改,但是并不限制引用指向的内存里的数据的修改

二、问题代码范例

package aboutDate;import java.util.Date;public class Period {    private  Date start,end;    public Period(Date start,Date end)    { if(start.after(end))     System.out.println("Error"); this.start=start; this.end=end;    }    public Date getStart()    { return this.start;    }    public Date getEnd()    { return this.end;    }    public static void main(String argv[])    { Date start =new Date(); Date end =new Date(); //实例化类,并赋初值 Period p=new Period(start,end); System.out.println(p.getEnd()); //修改end end.setYear(78); System.out.println(p.getEnd()); //利用返回值修改 p.getEnd().setYear(2000); System.out.println(p.getEnd());    }}

运行结果: 

         尽管我们在设计类时,已经将start和end设计成私有属性,但由于Date为可变类型,当存在多个引用【main里的start(end),Period里的this.start(end),getStart(getEnd)返回值】,均指向同一个内存,这个内存里的值就有可能在无意中被篡改。这其实就是一种表示泄露,客户端获取了类内成员的引用,存在误改的可能,使得软件内部存在漏洞,这并不是我们期望看到的。

三、修改思路

(1)防御式拷贝——消除多个引用指向同一内存的情况

实际修改的地方

public Period(Date start,Date end)    { if(start.after(end))     System.out.println("Error"); this.start=new Date(start.getTime()); this.end=new Date(end.getTime());    }public Date getStart()    { return new Date(this.start.getTime());    }    public Date getEnd()    { return new Date(this.end.getTime());    }

完整代码如下: 

package aboutDate;import java.util.Date;public class Period {    private  Date start,end;    public Period(Date start,Date end)    { if(start.after(end))     System.out.println("Error"); this.start=new Date(start.getTime()); this.end=new Date(end.getTime());    }    public Date getStart()    { return new Date(this.start.getTime());    }    public Date getEnd()    { return new Date(this.end.getTime());    }    public static void main(String argv[])    { Date start =new Date(); Date end =new Date(); Period p=new Period(start,end); System.out.println(p.getEnd()); end.setYear(78); System.out.println(p.getEnd()); p.getEnd().setYear(2000); System.out.println(p.getEnd());    }}

运行结果(可见修改成功——客户端不能随意修改类的属性): 

        防御性拷贝,就是创建一个新的对象,而不是单纯的把引用指向同一个内存。这样实现了局部变量,不会涉及共享以及同一个内存,只有一个引用,保证了数据的安全性。

(2)修改数据结构

        使用Java内部其他的不可变类型,如LocalDateTime, Instant等等,但是需要对代码进行较大的修改(否则出现类型不匹配的问题),此处附上Date与LocalDateTime转化的博客链接

localDateTime与Date转化_小馒头味豆浆的博客-CSDN博客_localdatetime转换date

四、总结

        可变与不可变类型各有各的优缺点。可变类型更灵活,可以减少拷贝次数,在需要频繁修改变量值的场合效率更高;但是当有多个引用指向同一个变量时,就会存在安全隐患,要小心数据在无意中被篡改,这种情况下就要尽量避免使用可变类型。此外,当设计封装类时,如果使用了可变类型,要防止函数返回可变类型的引用,以及初始化时类属性和调用者里的变量指向同一内存,否则就存在表示泄露,不是一个好的ADT。

        不可变类型较为呆板,当需要频繁修改变量值时,可能会产生大量的内存垃圾;但是安全系数高,数据不会被篡改。

        选择可变与不可变类型,就是在效率与安全之间做折中,需要依据场合来判断。