Stéphane's Java course : Lecture3

在提到容器(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. 使用迭代器而不是索引

猜你喜欢

转载自blog.csdn.net/weixin_40804987/article/details/80033074