本文包含以下几个方面:
1.编程语言的数据类型
2.静态检查与动态检查
3.可变类型与不可变类型
4.数组与集合
5.聚合体的迭代器
6.巧用不可变性
正文:
一、编程语言的数据类型:
基本数据类型:int,long,boolean(在java中与整型隔离),double,char。(小写)
对象数据类型(object reference):String,BigInteger等。(首字母大写)
在实际使用中,基本数据类型是很容易使用的:
int value=3;
char text="c";
但是,当想对基本数据类型执行相应操作,或者借助它们生成聚合体(例如List)就必须使用相应的对象类型,例如:
List<Integer> value=new ArrayList<Integer>();
int s=Integer.parseInt("18");
二、静态检查与动态检查:
Java是一个静态类型语言,它所有变量类型在编译时已知,因此编译器可以推导表达式类型,并在编译阶段进行类型检查(动态语言在运行时检查类型)。静态语言会检查语法错误(动态则会检查除了类型的语法错误,例如非法参数值,索引越界,空指针)、类名、函数名错误、参数数目错误、参数类型错误、返回值类型错误等。所以总的来说静态检查是检查类型而不是值,动态检查正相反。
例如:
int a="c";//Eclipse直接在代码区提示"type mismatch"
for(int i=0;i<=list.size();i++)//Eclipse只有在运行后才提示索引超限异常
这里的type mismatch应当强调的是两种变量的使用契约是一致的,例如double a=2中2被隐式转化为2.0,或者long和int在某些情景可以混用,这些都是不破坏使用契约的情景。
三、可变类型与不可变类型
变量的"变"有两层意思:一是改变变量,将该变量名指向另一个存储空间;二是改变变量值,将该变量指向的存储空间写入新值。
在可变性中我们强调第二层意思。所以不变数据类型(Immutable)就是一旦被创建就不得修改值(通常是final)。引用类型也可以是不可变的,说明它不能更改指向对象(但被指对象的值可以变)。编译器进行静态检查时,若发现final首次赋值后值发生改变就会报错(cannot assigned)。编程时尽量用final变量作为方法的输入参数和局部变量。注意,final不能派生子类、不能改变值和引用、方法不能被子类重写。
例如,对于Immutable的String和Mutable的StringBuiler,有不同的用法:
String s="m";
s=s+"n";//原来的"m"成为垃圾,s重新指向存储着"mn"的一个内存
StringBuilder sb="a";
sb.append("b");//在sb指向的内存上直接修改
这样看来,不可变类型对待修改会产生大量的临时拷贝(垃圾回收)但是更安全、其他质量指标更好,而可变类型可以最少化拷贝以提高效率所以性能更好、适于共享,但是函数调用可能会改变可变类型的值,引发其他相关函数的结果错误。例如:
class A(){
public int a=一个正数;//假定对于这个类来说a是可变的
function1(int a)
{将a变成相反数}
function2(int a)
{计算a的算术平方根}
}
//一旦先调用A.function1再调用A.function2,就会引发错误!
对于这种情况,可变类型在使用时最好使用防御式拷贝。最简单的防御式拷贝就是返回一个新类型,这个类型的内容等价于原来想传递的变量。
四、数组与集合
Java的聚合体与C语言相比可以说晦涩难懂。C语言的聚合体类型相对统一,但Java对每个聚合体还分支出了不同的子类聚合体。常见的有:
List类:
ArrayList——动态数组,查询快,增删慢(移动相关),线性不安全,效率高。
LinkedList——链表数组,查询慢(遍历相关),增删快,线性不安全,效率高。
Vector——与C语言的Vector类似。
Set类:
HashSet——哈希表只依赖与hashCode()和equals(),当放入新元素时借助这两个方法检查是否有重复元素。最终这个集合是无序的。
LinkedHashSet——哈希表和链表的结合,它是有序的,顺序是元素添加顺序。
TreeSet——由红黑树实现,调用compareTo实现自动排序。
Map类:
HashMap、LinkedHashMap、Hashtable、TreeMap。
它们的常见操作如下:
1)数组(array):T[] a=new in[len]是不可改变长度的定长数组。
操作:索引a[i],赋值a[2]=0,长度a.length等。
2)列表(list):List<T> list=new ArrayList<T>()是边长数组,是抽象接口。
操作:索引list.get(2),赋值list.set(2,0),长度list.size()等。
3)集合(set):Set s1=new HashSet()。元素不可重复,而且排列是无序的。集合是抽象接口。
操作:s1.contains(),s1.containsAll()检查子集,s1.removeAll()等。
4)地图(map):Map<T1,T2> map=new HashMap<T1,T2>()存储键值对。地图是抽象接口。
操作:map.put(key,val),map.get(key),map.containsKey(key),map.remove(key)。
五、聚合体的迭代器
除了Array,其余聚合体都可以采用迭代器循环方法。例如:
int[] a={1,2,3,4,5};
for(int i=0;i<a.length;i++){...}
List<L> A=new ArrayList<L>();
for(L item:a){...}//迭代器循环1
Iterator it=A.iterator();
while(it.hasNext()){...}//迭代器循环2
注意,在动态数组或迭代器循环中,切不可循环remove()!这是因为这些聚合体动态地删除元素后内存位置发生改变,但迭代器或索引没有变,所以程序找不到下一个要删除的元素,导致删除失败!
六、巧用不可变性
基本类型及其封装对象是不可变的,但是这种不可变性是运行时产生的,静态检查无法发现。于是使用不可修改封装(unmodifiable wrap),剔除任何修改它的操作,并对修改操作返回“不支持操作异常”。它用来保护只读数据结构,定义方式为public static <T> src<T>。