为了便于理解,可以思考这样一个问题:改变一个变量和改变一个变量的值,有何区别?
从C语言的角度来看,改变一个变量是指改变变量指向的存储空间,改变“指针”的值;如
char c = 'a';
char *p = &c; //p是指向字符类型的指针,p指向变量c,p的值的是变量c的地址空间,而该地址空间存储字符a
char *q = 'b'; //q同p一样,但q的值与p的值不同,且q的值所对应的地址存储的字符是b
p = q; //将q对应的地址赋值给p,改变了p变量
(注:原理一致,但在Java中无指针的概念,具体见下文)
而改变一个变量的值,就是改变一个变量的内部存储的值,如
int a = 10; //变量a存储值为10
a = 5; //变量a对应的存储空间不变,地址不变,但是该地址内部存储的值由10变为5,改变了a的值
在C语言中理解可变与不可变是不完全不充分的,为了充分理解我们回到Java中。然而,在Java中抛弃了指针的概念,减少了很多麻烦与复杂,在Java中我们又如何理解可变与不可变呢
不变数据类型与可变数据类型
首先明确一点,可变与不可变是针对变量的值来说的
不变数据类型是指一旦被创建,其值就不能改变的数据类型;
如果是引用类型,也可以是不变的:一旦确定所引用的对象,就不能改变指向其它对象
为了使一个引用变得不可变,可以加上关键字final
final int m = 0; //原本可变的int变量a不再能改变引用
m = 1; //在编译阶段会报错,因为m不可变,却被又赋值10
final还有其它作用:
- 放于方法前,final方法不能被子类重写
- 放于类前,final类无法派生子类
可变数据类型,对于基本数据类型来说,都是可变的(通过赋值可变),对于一些可变对象,它们内部有相关方法实现‘可变’
可以通过一个例子来说明一下:
String s = "ab"; //String对象是不可变的,创建之后指向的值或引用不变
StringBuilder sb = "ab"; //StringBulider对象可变,内部有相关方法实现
s.concat("c"); //此时s指向的字符串不变,仍为ab,但是产生结果abc被创建为新的字符串
sb.append("c"); //sb可变,指向地址空间未变,在原来地址中存储值由ab变为abc
s = s.concat("c"); //将创建新的字符串abc对应的地址赋值给s
//可变不可变是针对存储空间对应的值来说的,原来存储空间仍存放着ab //值未变,然而s指向的地址空间可以变,这是s指向地址空间存储值为abc
二者的使用
在不同场景对二者有不同的使用
不可变数据类型在使用过程中,如果对其有大量的修改,就会产生大量的拷贝,造成内存资源的浪费,此时使用可变数据类型可以大大减少浪费,提高效率
String s = "";
StringBuilder sb = "";
for (int i = 0; i < 1000 * 1000; i++){
s = s.concat("a"); //s总共指向1000000个地址,内存浪费
}
for (int i = 0; i < 1000 * 1000; i++){
sb.append("a"); //sb只使用了一个内存地址,减少内存浪费
}
然而,使用可变数据类型是在一定情况下有风险的
public class Person{
private List<String> person = new ArrayList<>();
public void Person(List<String> list){
this.list = list;
}
public List<String> getlist(){
return this.list;
}
}
···
Person a = Person(list1);
···
List<String> list = a.getlist;
在上面的情况中,我们实现Person类想让其内部实现对外不可见,且它内部的list不应该被外部所影响,然而我调用它内部getlist方法,将其内部list直接传送到外面,而且List是可变的,我在外面对其进行操作,同样也是对Person a内部list的操作,可能造成破坏。
对于上述情况。可以采取防御式拷贝,将新生成一份同样的数据,值相同,但不指向相同的地址空间
从上面可以看出,可变数据类型是不安全的。
综上,对于我们的实现来说,要对安全和内存的两个指标进行权衡,从而采取不同实现的数据类型
[========]
在后续的复习中,发现本文中的一个错误,在学习ADT抽象数据类型中,认识到基本数据类型是不可变的
以int为例
int a = 5; //对应操作的creator,从无到有的产生一个int型变量
int b = 1;
a = a + b; //对应操作的producer,从一个存在的变量产生新的
if (a == 6){
//对应于操作的observer,观察变量a的值,是否与6相等
b = 0;
}
//基本数据类型不可变,无mutator
再看下面例子:
final m = 0;
m = 1;
这里final将m的引用不可变,m只能指向对于整数0对应地址的引用
而再次改变m的引用,让其指向整数1的地址空间,编译器则会报错
认真复习,反复复习,会有很多更深层次的理解
祝愿高考学子勇往直前,高考顺利!