一、基本数据类型和对象类型
1.基本数据类型
八种基本数据类型:boolean,char,byte,short,int,long,float,double
2.对象类型
常见的对象类型:List,Set,Map,Character,Integer,Double,String,BigInteger,BigDecimal等
其中,Character,Integer,Double属于包装类
当然,用户自己定义的类也可以分为可变类型(mutable)与不可变类型(immutable)。
3.基本数据类型与对象类型的区别
①基本数据类型通常是小写的;对象类型通常大写
②基本数据类型没有内部结构;对象类型可以包含内部结构
③基本数据使用少量固定的内存;对象数据可能会使用不同的内存,有时会是大量的内存空间
二、可变数据类型与不可变数据类型
1.可变数据类型
可变数据类型指的是修改该数据类型变量值时,指向的内存地址不变,对应内存的值发生改变的数据类型
2.不可变数据类型
不可变数据类型指的是修改该数据类型变量值时,指向的内存地址改变,新内存地址指向新的变量值
常见的不可变数据类型:
①基本数据类型及其包装类
②String类,BigInteger类,BigDecimal类
三、可变数据类型的优点
以String和StringBuilder为例,两者都是针对字符串的对象类型,其中String是不可变数据类型,StringBuilder是可变数据类型。
当对字符串进行修改时,若使用不可变的String,String会产生新的副本,并修改内存地址以指向新的副本,频繁的修改就会产生大量的临时副本,造成较大的内存开销,同时也会有较大的时间开销。
若使用可变的StringBuilder,StringBuilder通过内部简单精妙的数据结构直接对内存地址指向的字符串进行修改,提高了程序性能。
三、可变性带来的风险
1.可变数据类型别名的使用
当不同的可变数据类型变量指向了同一个内存地址空间,一个变量值的改变会导致另一个变量值的改变
如下例所示
List<String> a = new ArrayList<>();
a.add("Hello");
List<String> b = a;
b.add("world!");
System.out.println(a);
System.out.println(b);
由于a和b所指向的内存空间是同一个List,因此最终输出的都是“Helloworld!”。
当对变量和方法对可变数据类型的进行拷贝时,采取防御式拷贝,可以避免对原始数据的影响,但是会造成一定的额外内存开销。
2.对可变数据类型潜在的修改
当方法引用一个可变数据类型变量时,若对该变量进行了潜在的修改(例如开发者未意识到的修改、规约中未声明的修改等),那么当方法内部再次引用时或其他方法对该变量进行引用时,可能会导致未知的错误。
例如,迭代器迭代过程中对可变数据类型变量进行修改
public static void removeOne(ArrayList<String> arrayList) {
Iterator<String> iter = arrayList.iterator();
while (iter.hasNext()) {
String number = iter.next();
if (number.equals("One")) {
arrayList.remove(number);
}
}
}
当初始的arrayList为["One","One","One"]时,方法调用完后返回的会是["One"]。因为当调用
remove时,被删除元素后的所有的元素都向前位移了一个位置,而iter.next()所指向的下标并没有改变。
因此,当迭代器对可变类型变量变量进行删除时,应该使用iterator.remove
public static void removeOne(ArrayList<String> arrayList) {
Iterator<String> iter = arrayList.iterator();
while (iter.hasNext()) {
String number = iter.next();
if (number.equals("One")) {
iter.remove(number);
}
}
}
四、 不可变类型的优点
针对以上可变性可能带来的风险,关键的设计原则是利用不可变性,即尽可能使用不可变数据类型。
因为不可变数据类型更安全,不会出现错误,更容易理解,并且更易于更改。