泛型方法、初始集合和集合的遍歷

變成巨人發表於2020-10-20

一、泛型方法

1.什麼是泛型方法

語法格式:

【修飾符】<泛型的型別> 返回值型別 方法名(形參列表)[throws異常列表]{}

例如:public void method (T t){

}

3,為什麼有泛型方法

(1)對於靜態方法來說那我們之前宣告的<泛型>是不能用於靜態成員,所以如果某個靜態方法需要用到泛型,那麼可以考慮使用泛型方法

(2)類或者介面已經宣告,不是泛型類後新增的方法可以使用泛型

不管是(1)還是(2)都有一個原因,因為在設計這個方法的時候,可能它的形參型別或返回值型別未知。這個時候考慮使用泛型方法

4.注意

(1)泛型方法的<泛型型別形參列表>同樣可以宣告上限

<T extends 上限1 & 上限2 …>

上限中類只能有一個,必須在左邊,介面可以有多個

多個上限之間是&的關係

(2)泛型方法的<泛型型別形參列表>必須寫在返回值型別前面,修飾符的後面

(3)泛型方法的泛型型別不用手動指定,會根據實參的型別自動推斷形參的型別。

二、泛型萬用字元

1.長什麼樣子

(1)<?>: ?代表任意型別

(2)<? extends xx>

(3)<? super xx>

2、作用

(1)宣告一個變數時,該變數的型別如果是一個泛型類或泛型介面,呢麼可以加萬用字元,例如:Student 類

宣告變數student<?> stu;

(2)宣告一個形參時,該形參的型別如果是一個泛型類或泛型介面,那麼可以加萬用字元,

例如:public [static ] void method(Student <?> stu){}

(3)注意;不能用在宣告泛型的位置

public class MyClass <?>{}

public static <?> void method(){}

3、為什麼要有萬用字元?
(1)我們在使用泛型類或泛型介面時,除非泛型被擦除了,否則你指定了什麼型別,就必須是什麼型別。
如果希望是任意型別,又不想泛型擦除,只能用<?>。

public class TestWildCard {
    //需求一:宣告一個方法,該方法,可以接受任意一種學生物件,至於方法的功能我們暫時先不管
    /*public void method1(Student stu){//寫法一:可以,但是沒有指定泛型,相當於泛型擦除
        //...
    }*/
    public void method2(Student<Object> stu){//寫法二:有問題,只能接收new Student<Object>();
        //...
    }

    public void method3(Student<?> stu){//寫法三:可以,這個?代表的就是任意型別
        //...
    }

    @Test
    public void test01(){
        method2(new Student<Object>());
//        method2(new Student<String>());//錯誤的
//        method2(new Student<Integer>());//錯誤的
    }

    @Test
    public void test02(){
        method3(new Student<Object>());
        method3(new Student<String>());
        method3(new Student<Integer>());
    }

    //需求2:用泛型類Student,宣告一個陣列,該陣列中可以儲存任意學生物件
    @Test
    public void test03(){
//        Student<Object>[] arr = new Student[3];
        Student<?>[] arr = new Student[3];
        arr[0] = new Student<Object>();
        arr[1] = new Student<String>();
        arr[2] = new Student<Integer>();
    }
}
class Student<T>{
    private String name;
    private T score;

    public Student(String name, T score) {
        this.name = name;
        this.score = score;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public T getScore() {
        return score;
    }

    public void setScore(T score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

泛型萬用字元<?>等形式有侷限性:
如果某個泛型類或泛型介面使用時,用到<?>等形式,該型別對應的變數修改值就受到了限制。

三、集合

1.容器

java中容器:陣列(可以裝各種基本資料型別的值和物件)、

集合:可以裝各種物件,不能裝基本資料型別的值

分好多中:列表list,集:set,佇列Queue:,對映:Map…

2.有陣列還要有集合?

(1)陣列:

優點:速度快,可與根據下標定位元素

缺點:長度是固定的,

陣列要求開闢一整塊連續的空間GC回收導致效能降低

(2)集合

優點:型別很多,表示我們的可選性多,可以根據需求選擇合適的集合;集合的底層可以自動擴容

缺點:型別很多,需要了解每一種型別的特點,好選擇

3、集合的分類

(1)collection系列:包括列表List,集Set,佇列Queue等

儲存一組物件,比喻:party

(2)Map系列

儲存鍵值對(對映關係),(key,value),比喻

3.1collection

1.java.util.collection介面

(1)層次結構中的跟介面

(2)表示一組物件

(3)一些collection允許有重複的元素,另一些不允許

一些有序,一些無序。

(4)沒有類能直接實現collection介面,可以實現子介面(list、Set)

(5)此介面通常用來傳遞collection ,並在需要最大普遍性的地方操作這些collection.

即如果我需要List,Set等集合之間轉換的時候,可以使用Collection作為橋樑進行傳遞,說白了就是多型引用

2.collection的常用方法

他下面的子介面的實現類都有該介面的方法

代表了集合的通用操作:增、查、刪除等操作

Collection泛型介面

之前用T,代表Type,通用型別

現在用E代表元素的型別,

(1)新增

一次新增一個元素

boolean add(E e) 一次新增多個元素

boolean addAll(Collection<? extends E> c) :一次新增多個元素

<? extends E>:當前集合的元素是E,c集合的元素型別要求是E或E的子類物件。 ###### (2)刪除元素 void clear() : 清空 boolean remove(Object o):刪除一個元素 用Object的原因,早期沒有泛型,後期有了之後沒改,新增的時候要檢查型別是否符合,刪除時不用。 boolean removeAll(Collection <?> c): this當前集合 = this當前集合 - this當前集合 ∩ c集合 <?>表示任意型別
(3)修改:沒有提供修改元素的方法
(4)查詢

int size() 獲取元素的個數

boolean isEmpty() : 判斷是否是空集合

boolean contains(Object o):是否包含某個物件、

boolean containsAll(Collection<?> c):是否包含c集合的所有元素

返回true,c是this集合的子集

5.其他

boolean retainAll(Collection<?> c)保留this集合和c集合的交集,即this集合 = this集合 ∩ c集合、

Object[] toArray() :把集合中的元素放到一個Object陣列中返回
T[] toArray(T[] a):把集合的元素放到a陣列中,但是a陣列的型別沒有檢查是否符合當前集合的元素E型別。用的時候謹慎。

 public void test01(){
//        Collection coll = new Collection() ;//錯誤,介面不能直接new物件
        Collection<String> coll = new ArrayList<>() ;//ArrayList是List子介面的實現類,它也是Collection系列。
                                            //多型引用,coll編譯期間只能看Collection裡面的方法,關注點更集中。
                                            //<String>代表集合的元素型別是String
        coll.add("hello");
        coll.add("world");
        coll.add("java");

        System.out.println(coll);//說明ArrayList實現類重寫類toString方法
    }

二、集合的便利

1、Collection 系列的集合有一個方法:

Iteratoriterator()

1、Collection系列的集合支援foreach便利

foreach:稱為增強for迴圈,比普通的for迴圈簡潔,功能更強大,可以直接遍歷陣列、Collection系列的集合等等,不需要下標等資訊。
語法格式:
for(元素的型別 元素名 : 陣列/集合的名稱){
}
注意:元素名是自己命名的,只是一個臨時名稱,在當前for迴圈中使用的臨時變數。

問題1:foreach是如何工作的?
foreach其實本質上就是Iterator迭代器。

問題2:陣列為什麼支援呢?
陣列這個型別是編譯器動態生成的型別,它在生成的過程中,也會提供迭代器的實現。

問題3:是否所有的集合,或者容器都支援foreach迭代器呢?
不是。
只有實現了java.lang.Iterable介面。
實現這個Iterable介面允許物件成為 “foreach” 語句的目標。
因為Iterable介面的抽象方法是 Iterator iterator() 。如果要實現這個方法,就要手動實現迭代器,為foreach的執行提供基礎。

問題4:在foreach遍歷集合的過程中,能不能呼叫集合的remove方法呢?
不能

結論:根據條件刪除元素,只有一種方式,用Iterator迭代,並且用 Iterator迭代器的remove方法。
如果精確刪除,就直接呼叫集合remove方法即可。

Collection系列的集合有一個方法:

itertor()

java.util.Iterator迭代介面,有三個方法:

(1)boolean hasNext():判斷是否還有下一個元素

(2)E next(): 取出下一個元素

(3)void remove():刪除剛剛迭代的元素

Collection的remove適合用於精確刪除

迭代器中的無法精確的說明元素時用迭代器的remove方法

public class TestIterator {
    @Test
    public void test03() {
        Collection<String> coll = new ArrayList<>();
        coll.add("hello");
        coll.add("world");
        coll.add("java");
        coll.add("java");
        Iterator<String> iterator = coll.iterator();
        while(iterator.hasNext()) {
            String element = iterator.next();
            if (element != null && element.startsWith("j")) {
                coll.remove(element);
                //報ConcurrentModificationException:併發修改異常。  兩條線路在同時訪問與修改集合,一個是iterator,一個是coll
                //你的coll的remove方法刪除了元素之後,沒有把最新的資訊告知iterator
            }
        }
        System.out.println(coll);
    }
    @Test
    public void test02(){
        Collection<String> coll = new ArrayList<>();
        coll.add("hello");
        coll.add("world");
        coll.add("java");

        //需求:刪除以w開頭的字串
        /*
        思考:用Collection介面的remove方法能否完成?
         */
//        coll.remove();//無法完成,即Collection的remove適用於精確刪除
        //使用Iterator迭代器刪除,標準寫法
        Iterator<String> iterator = coll.iterator();
        while(iterator.hasNext()){
            String element = iterator.next();
            if(element!=null && element.startsWith("w")){
                iterator.remove();
            }
        }
    }

    @Test
    public void test01(){
        Collection<String> coll = new ArrayList<>();
        coll.add("hello");
        coll.add("world");
        coll.add("java");

        Iterator<String> iterator = coll.iterator();
 /*       System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());
        System.out.println(iterator.next());//NoSuchElementException:沒有這個元素了*/

        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

四、Set介面

1.java.util.Set

一個不包含重複元素的collection,對數學中的集的抽象

2.Set時Collection子介面

,但是Set介面沒有增加自己的方法和Collection的方法一樣

3.Set介面有一些實現類:
(1)HashSet:

不能重複,無序(和新增順序不一致)

根據hashCode和equals方法。

hashCode值,影響他的儲存位置

(2)TreeSet:

元素也不能重複。
元素有大小順序的,說明元素要支援比較大小,要求元素必須實現java.lang.Comparable介面。
如果元素沒有實現Comparable介面,或者實現這個介面的比較規則不適合當前的需求,就要藉助java.util.Comparator。
大小順序和不能重複依據的是什麼?
大小順序和Comparable的compareTo或Comparator的compare方法有關;
不可重複也是和Comparable的compareTo或Comparator的compare方法有關。
Comparable的compareTo或Comparator的compare方法返回0,就認為是“重複”元素。

    TreeSet底層用紅黑樹(是一種自平衡的二叉樹)實現。
3、LinkedHashSet
    元素也不能重複。
    元素有順序,遍歷結果是按照新增的順序遍歷的。
    底層實現:雜湊表 + 連結串列。 連結串列的作用是記錄新增的順序。

    什麼時候用LinkedHashSet?
    當我們需要元素不可重複,但是又要記錄新增順序時用它。

    LinkedHashSet比HashSet效率低,因為它在新增和刪除等過程中,要考慮順序的問題。

五、List集合

1、java.util.List介面:結合列表,也稱為序列。

有序的collection(也稱為序列)

此介面的使用者可以佇列表中每個元素的插入位置進行精確地控制。使用者根據元素的整數索引(在列表中的位置)訪問元素,並搜尋列表中的元素。 == >List的很多操作和【index】有關與set不用,列表通常允許重複的元素。

List:有序可重複。

2、List的方法,在Collection的基礎上增加了很多方法都和index有關

(1)新增

void add(int index, E element):在[index]位置新增一個元素

boolean addAll(int index,Collection<? extends E> c)在[index]位置新增多個元素

(2)刪除

E remove(int index)

(3)修改

E set(int index , E element);替換[index]位置的元素

(4)查詢

E get(int index); 獲取[index]位置的元素

int indexOf(Object o ):查詢o物件在當前List中的下標,如果沒有返回-1,如果多個返回第一個

int lastIndexOf(Object o):查詢o物件在當前List中的小標,如果沒有返回-1,如果有多個,返回最後一個

List subList(int fromIndex, int toIndex):擷取一段列表

(5)遍歷

ListIterator listIerator()

ListItertor listIterator(int index)

ListIterator是Itertor的子介面比Itertor多了幾個功能:

倒序遍歷、獲取下表資訊、遍歷過程中新增和替換元素

public class TestList {
    @Test
    public void test13() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("xxx");

        ListIterator<String> iter = list.listIterator();
        while(iter.hasNext()){
            String element = iter.next();
            if(element.startsWith("w")){
                iter.set("atguigu");
            }
        }
        System.out.println(list);//[hello, java, atguigu, xxx]
    }


    @Test
    public void test12() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("xxx");

        ListIterator<String> iter = list.listIterator();
        while(iter.hasNext()){
            String element = iter.next();
            if("world".equals(element)){
                iter.add("atguigu");//遍歷過程中插入元素
            }
        }
        System.out.println(list);//[hello, java, world, atguigu, xxx]
    }

    @Test
    public void test11() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("xxx");

        //倒序遍歷
        ListIterator<String> iter = list.listIterator(list.size());//遊標預設在列表的結尾
        while(iter.hasPrevious()){
            System.out.println(iter.previousIndex() + ":" + iter.previous());
        }
    }


    @Test
    public void test10() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("xxx");

        ListIterator<String> iter = list.listIterator();//遊標預設在列表的開頭
        while(iter.hasNext()){
            //遍歷過程中獲取元素的下標
            System.out.println(iter.nextIndex() + ":" + iter.next());
        }
    }

    @Test
    public void test09() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("xxx");

        ListIterator<String> iter = list.listIterator();
        while(iter.hasNext()){
            System.out.println(iter.next());
        }
    }

    @Test
    public void test08() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");
        list.add("world");
        list.add("xxx");

        List<String> subList = list.subList(1, 3);
        System.out.println(subList);//[java, world]

    }

    @Test
    public void test07() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");

        list.set(0,"atguigu");
        System.out.println(list);//[atguigu, java]
    }
    @Test
    public void test06() {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);

        //刪除元素值是1
//        list.remove((Object)1);

//        Collection<Integer> coll = list;
//        coll.remove(1);

        list.remove(Integer.valueOf(1));
        System.out.println(list);//[2, 3, 4, 5]
    }

    @Test
    public void test05() {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);

        list.remove(1);//刪除[1]位置的元素
        System.out.println(list);//[1, 3, 4, 5]
    }


    @Test
    public void test04() {
        List<Integer> list = new ArrayList<>();
        list.add(10);
        list.add(20);
        list.add(30);
        list.add(40);
        list.add(50);

        list.remove(1);
        System.out.println(list);//[10, 30, 40, 50]
    }

    @Test
    public void test03() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");

        list.remove(0);
        System.out.println(list);
    }

    @Test
    public void test02() {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("java");


        List<String> list2 = new ArrayList<>();
        list2.add("chai");
        list2.add("xxx");

        list.addAll(1, list2);
        System.out.println(list);//[hello, chai, xxx, java]
    }

    @Test
    public void test01(){
        List<String> list = new ArrayList<>();

        list.add("hello");
        list.add("java");

        list.add(0,"atguigu");//[atguigu, hello, java]
        list.add(5,"chai");//IndexOutOfBoundsException: Index: 5, Size: 3
                                        //我們之前見過的ArrayIndexOutOfBoundsException是IndexOutOfBoundsException的子類

        System.out.println(list);
    }
}

3、List介面的實現類們

(1)ArrayList:動態陣列

底層實現:陣列

(2)Vector:動態陣列

底層實現:陣列
(3)LinkdList:雙向連結串列

(4)Stack: 棧

底層實現:陣列,因為他是Vector的子類

問題1:ArrayList與Vector的區別?
Vector是最古老的動態陣列,比Collection和List介面還要早。執行緒安全。
Vector在擴容時,預設擴容為2倍。(擴容的頻率第,浪費空間的可能性增加)
Vector的預設初始化容量是10。
ArrayList是比Vector新一點。執行緒不安全。
ArrayList在擴容時,預設擴容為1.5倍。(擴容的頻率高,空間利用率高)
ArrayList的預設初始化容量,在JDK1.7之前也是10,在JDK1.7之後,一開始是0,第一次新增元素時初始化為10.

    無論是Vector還是ArrayList使用時,如果對元素的個數有預估,最好使用:
      ArrayList(int initialCapacity):一開始就指定初始化容量,就避免擴容。
      Vector(int initialCapacity) :一開始就指定初始化容量,就避免擴容。
      Vector(int initialCapacity, int capacityIncrement):除了指定初始化容量,還可以指定每次增加幾個元素。

問題2:ArrayList從JDK1.7之後為什麼預設初始化容量是0?
我們很多方法的返回值型別是List或ArrayList,特別是DAO(資料庫層)方法。
這些方法在實現時,如果沒有結果返回,一般不返回null。因為返回null,呼叫者就必須加if(xx != null)避免空指標,太麻煩。
不返回null,但是又沒有資料,就返回了一個空的ArrayList物件。
那麼如果初始化容量是10的話,其實這10個空間是浪費的。所以,就初始化容量為0。

問題3:動態陣列與雙向連結串列有什麼區別?
區別一:
動態陣列的元素:資料本身
雙向連結串列的元素:結點

結點包含:
①前一個結點的首地址:prev
②資料本身
③後一個結點的首地址:next

區別二:
動態陣列的元素是連續儲存,提前開闢一整塊連續的記憶體空間。
雙向連結串列的元素不要求連續儲存,來一個申請一個連線一個。

連續的好處:查詢快,可以根據陣列的首地址,快速的定位到某個元素。
不連續的缺點:只能從頭或尾挨個查詢,定位到對應的位置。

連續的壞處:插入,刪除時,需要移動元素,插入時可能考慮擴容。
不連續的好處:不用提前申請空間,插入和刪除只需要修改前後元素的prev和next

問題4:Stack
它是Vector的子類。
在Vector的基礎上增加了幾個方法:
①E peek():檢視棧頂元素
②E pop():返回棧頂元素,棧頂換元素
③ E push(E item):把元素壓入棧稱為新的棧頂元素

棧的結構特點:後進先出(LIFO)或先進後出(FILO)
    LIFO:Last In First Out
    FILO:First In Last Out
    像桶,箱子

|       |  棧頂
|       |
|       |
|______ |  棧底

現在不太建議我們用Stack,如果要用棧結構,建議用LinkedList。因為LinkedList也提供了這些方法,使我們實現棧的效果。

問題5:LinkedList說起來是雙向連結串列,但是它又是Queue,又是Deque,還可以用作棧等。
名稱 deque 是“double ended queue(雙端佇列)”的縮寫,通常讀為“deck”。

    佇列結構特點:先進先出(FIFO)

相關文章