在提到容器(Collection)之前,应该要先了解泛型(Generics)。泛型对代码的复用性(Reusing code)非常重要。你一定在使用ArrayList的时候,了解了一下泛型。
强类型语言(Strong typing):编译器不会让你随意混淆数据类型,除非它们本身可以兼容(Compatible),例如:int 和 float(个人理解应该是在做基本算术运算的时候的自动转换吧)。
如果想将一个参数为String[]类型的方法运用在int[]上,需要做到的事就是重载(Overloading)。重载会允许你有同名,同返回类型,不同参数列表的方法。
方法(Function&Method)的签名(Signature)的定义方式:参数列表的参数个数以及参数类型。
重载让代码保持安全性,但会付出时间的代价来重复写同样的代码。更糟的是,如果我们想修改代码,我们必须修改每一个重载方法。
泛型只能用于对象(Object)(引用(References)),如int,char,boolean这样的基本类型不能使用泛型。因此我们应该使用其对应的类:Integer,Character,Boolean等。这样类允许基本类型和对应的对象进行封装(boxing)和拆装(unboxing)。
实例代码:
public static void displayArray(T[] arr){
for(int i = 0 ; i< arr.length ; i ++)
System.out.print(arr[i]+" ");
}
注意:在返回类型(void)之前,你需要说明一个或多个符号来表示对应的类,如:,。
泛型方法可以被重载。例如:
public static void displayArray(T[] arr)
public static void displayArray(T[] arr, String sep)
public static void displayArray(Date[] arr, String format)
方法可以被泛型化,类(Classes)也可以被泛型化。 下面讲Collections.
当你创建一个array来存储对象时(而非基本类型),每个元素并非是对象本身,而是存储它的引用(Reference)
对auto-boxing多说几句:int类型的变量可以用在参数类型为Integer的方法上,但int[]类型的变量不能用在参数类型为Integer[]的方法上。为什么后者是错误的?因为unboxing/boxing知道如何对基本类型和类进行相互转换,但是不知道如何对类和类进行转换。因为int[]和Integer[]两者都是类(很明显,它们被声明出来的时候使用了new关键字)。
回到Collections。
所有存在array里的对象,都必须实例化(be instantiated),这和基本类型有很大的不同。
数组(array)是易于查找的(Search)。当我们把所有东西加载都内存的时候,所有对象都会变成引用代表一个实际值。查找数组时,我们只需要从索引0开始递增就可以了。查找复杂度为O(n)。
为了更快的查找,我们需要使用二分查找法(Binary Search),进行二分查找之前,需要先对数组进行排序。除了用快速排序(quick sort),我们还可以使用库方法:java.util.Arrays.sort(ArraysName, start, end)。
为了排序,数组内的元素必须可比较(Comparable)。
为了可比较,我们需要重新提到接口(Interface)。
说到接口,我们不得不提到抽象类(Abstract class):抽象类中的方法可以定义出来,但其方法体还是需要子类的来写。
接口就是特殊的"轻量级"类,如果一个类实现了一个接口,那么他必须拥有接口中的所有方法。需要注意的是,有一些接口本身就没有方法。方法实现了没有方法的接口只是为了给javac说明,让javac生成特殊的代码。
Arrays.sort()只有在类实现了Comparable这个接口的时候,才可以被调用在这个类上。而Comparable接口提供了comparaTo()方法。
完成了上述一系列操作之后,我们便可以用排序方法将数组排序,再使用二分查找。二分查找步骤数为2*log_2(N-1),因此,其复杂度为O(logn)。
用循环实现二分查找法 :
1. 输入参数: 数组,元素个数, 目标元素。
2. 设置变量 头序号,尾序号,中值,比较值,布尔类型:found = false
3. 当头序号小于等于尾序号时,循环进行:
a, 中值赋值为头尾序号和的一半
b, 比较值赋值为arr[mid].compareTo(lookedFor)
c, 若比较值小于0,头序号赋值为中值加一;若比较值大于0, 尾序号赋值为中值减一,若比较值为0,则找到目标元素,found = true 破坏循环条件,结束循环。
4. 当found为false,则没有找到。当found为true,则找到该元素。
Arrays在Objects上的部分操作是比较方便的,但还是具有一定的局限性。如果数据过于动态化(dynamic),将对Arrays影响非常大。查找的效率和准确性依赖于数组的排序保持。如果元素的插入过于随机性,那么保持数组的顺序将十分困难。再者,大多数情况下,索引值(index)并不是一个很自然的获取数据的方式。
Java Collections定义了一些类和接口,允许你把数据比用arrays更好地整合起来。java.util有很多容器类,不同的类有不同的特性,你需要选择合适的容器来应付不同的数据。
ArrayList在Objects上的应用:
ArrayList是自动增大的,但这需要高昂的代价。ArrayList类会根据插入的数据自动创建更大的array。ArrayList中的常用方法,add(), get(), size(), remove(), removeAll()是实现了某个与List接口有的特性(Behaviour)。而ArrayList中的Array说明了数据是怎么存储的,你可以做出Array相同的操作,但其内部机制还是不一样的。
ArrayList和其他所有的Collection类一样,可以存储任何类型的数据,即便将不同数据类型的数据存储进同一个ArrayList中(即使这样做,javac会加以提示["...and the javac compiler isn't too happy with it. You can neverthe less run the program"])。在实际操作中,ArrayList还是应该存储相同数据类型的元素。 如果基于ArrayList设计一个只可以存储String的类,javac还是会警告(javac is still unhappy)。毕竟,这个方法可以被重载,参数列表就会可以放String以外的数据类型。
"所有的类继承于Object": 参数列表传入Object看起来是一个较好的方法。但尝试后,javac依然会发出警告(javac not happier) [Stéphane : It still doesn't work because javac is at heart a control freak]
当然,可以指定引用的ArrayList是对应什么参数类型的,像ArrayList, ArrayList。但这会影响程序的复用性。
为javac定义一个模板:泛型。如果基于ArrayList类写一个容器类,传入泛型参数列表。
示例代码 :
import java.util.ArrayList;
public static MyGenericList{
private ArrayList _list;
public void setElement(T o){
_list.add(o)
}
public T getElement(int i){
return _list.get(i);
}
}
这样,javac就不会有任何的警告。
所有的容器类都应该注明数据类型。
一般的泛型命名惯例:
元素(容器类)
和 索引和值(表类(maps))
数
类型
,, 用于多个参数类型的方法。
标记1:通配符(wildcards)未深入解释 page:16
对ArrayList排序:Collections.sort(arrayListName)
Collections类是一种像Arrays这样的dummy class[标注2],它只有静态函数。它不仅提供了排序函数,也提供了切换为另一种容器的方法。
可以通过定义如何比较(compare)一些奇怪的(fanciful)对象来定义排序方法。一种较好的习惯就是,写新的类的时候实现Comparable接口。
你可以传入一个Comparator到sort作为你的第二个参数
示例代码 :
Collection.sort(MyList,
new Comparator(){
public int compare(T o1, T o2){
//Logic goes here
}})
当数据较动态化时,使用ArrayList会更好。
其他的容器类:
List :
保持轨迹的序列性(Keeps track of order),同样的值可以被装入多次。
关于lists,有Queue和Deque两个类。
Queue是FIFO的,意思是先进先出(First in first out), Deque则是FIFO/LIFO的。这样的特性使得Queue/Deque是动态的过程。
Set :
Set(集合)另外一种容器的大种类,它的特性是不可以存储相同的值,即每个在集合的元素都是特殊的。且集合是支持快速查找的。
Map:(Not a true Collection)
图或表(Map)中的元素的索引值不是从0自增的数,而是特殊的,称为键(Key),其对应的值称为值(Value)。
容器类的接口:
List, Queue/Deque, Set, Map
可变的Array:
当需要扩展Array时,会自动创建一个新的更大的Array,并将原来的Array内的元素复制到这个新的Array中。Array的内存是连续的。
不同的是,LinkedList类的内存是不连续的,添加一个新的元素时,其地址会存储在上一个元素的内存。LinkedList类的主要方法和ArrayList差不多,着说明它们实现了同样的接口。LinkedList也有转换为Array的方法。在LinkedList的查找中,必须通过上一个元素来找到当前元素,所以使用循环增加来查找是不推荐的。更好的方法是使用迭代器(Iterator)。
示例代码 :
import java.util.ListIterator ;
ListIterator li = li.listIterator() ;
while(li.hasNext()){
System.out.println(li.next());
}
也可以直接通过使用foreach来使用Iterator:
for( o : ) { . . . }
LinkedLists特点:
1. 当数据是动态时,LinkedLists非常合适
2. 查找效率低
3. 随机插入时可以很好地保持有序性
4. 使用迭代器而不是索引