基本概念
不可变数据类型: 当该数据类型的对应变量的值发生了改变,那么它对应的内存地址也会发生改变,对于这种数据类型,就称不可变数据类型。例如String
可变数据类型 :当该数据类型的对应变量的值发生了改变,那么它对应的内存地址不发生改变,对于这种数据类型,就称可变数据类型。例如StringBuilder
具体差别
当一个数据只有一个引用时,可变数据与不可变数据实际上并没有太大的差别。但是很明显真正的编程中一个数据经常会有多个引用,这是二者的区别就明显的变现出来了。先看两段段代码:
public static void main(String[] args) {
String s1="1";
String s2=s1;
s2=s2+"2";
System.out.println("s1:"+s1);
System.out.println("s2:"+s2);
}
public static void main(String[] args) {
StringBuilder s1=new StringBuilder("1");
StringBuilder s2=s1;
s2.append("2");
System.out.println("s1:"+s1);
System.out.println("s2:"+s2);
}
可以看出,当对String(不可变数据类型)进行更改时,对s2的更改不会影响s1.但当对StringBuilder(可变数据类型)进行更改时,对s2的更改会影响s1。原因就是对于不可变数据类型,更改它的值会让它指向一个新的地址空间。可变数据类型会直接更改原地址空间存储的内容,而此时s1和s2指向的还是同一片地址空间,因此s1的值也变了。具体用一张图很明显就能表现出来;
图和代码并不一致,但不影响理解
使用可变数据类型的风险以及解决方法
因为上述的特点,使用可变数据类型时会有一定风险。具体如下:
public static int sumAbsolute(List<Integer> list) {
int sum=0;
for(int i = 0; i < list.size(); i++)
list.set(i,Math.abs(list.get(i)));
for(Integer k:list) {
sum=sum+k;
}
return sum;
}
这里使用传入的list属于可变类型的数据,虽然达到了目的(求绝对值和),但也可能更改了List里面的值。但是Java在无特别说明时我们并不期望改变参数的值,因此有必要做出一定的措施,而这个措施就是防御性拷贝:
public static int sumAbsolute(List<Integer> list) {
int sum=0;
List<Integer> list1=new ArrayList<>(list);//防御式编程
for(int i = 0; i < list.size(); i++)
list1.set(i,Math.abs(list.get(i)));
for(Integer k:list1) {
sum=sum+k;
}
return sum;
}
public static void main(String[] args) {
List<Integer> l=new ArrayList<>();
l.add(1);
l.add(-1);
l.add(-9);
sumAbsolute(l);
for(Integer i:l) {
System.out.print(i+" ");
}
}
结果如图:
在代码第三行,我们添加了一条语句:
List<Integer> list1=new ArrayList<>(list);
这里我们采用防御式编程重新获得了一个新的List对象用来替代参数中的list,这样一来就不用了担心list被方法修改了。