2 集合,泛型

數(shù)組在存儲(chǔ)多個(gè)數(shù)據(jù)方面的缺點(diǎn):

  • 一旦初始化以后,其長(zhǎng)度就不可修改。
  • 數(shù)組中提供的方法非常有限,對(duì)于添加、刪除、插入數(shù)據(jù)等操作,非常不便,同時(shí)效率不高。
  • 獲取數(shù)組中實(shí)際元素的個(gè)數(shù)的需求,數(shù)組沒有現(xiàn)成的屬性或方法可用
  • 數(shù)組存儲(chǔ)數(shù)據(jù)的特點(diǎn):有序、可重復(fù)。對(duì)于無(wú)序、不可重復(fù)的需求,不能滿足。
    集合框架繼承關(guān)系圖
    image.png
Collection

向Collection接口的實(shí)現(xiàn)類的對(duì)象中添加數(shù)據(jù)obj時(shí),要求obj所在類要重寫equals()
對(duì)于remove 和contains 方法,內(nèi)部會(huì)去調(diào)用ArrayList的equals方法,所以我們使用contains方法時(shí),需要重寫自定義類的equals方法

    @Test
    public void test1(){
        Collection coll = new ArrayList();
        coll.add(new Person("Jerry",20));
        System.out.println(coll.contains(new Person("Jerry",20)));
    }

使用 Arrays.asList方法時(shí),注意 不要使用int[]類型,可以使用其包裝類類型

    @Test
    public void test4(){
        List arr1 = Arrays.asList(new int[]{123, 456});
        System.out.println(arr1.size());//1

        List arr2 = Arrays.asList(new Integer[]{123, 456});
        System.out.println(arr2.size());//2
    }

使用Iterator來(lái)操作Collection

        //錯(cuò)誤使用iterator的情況:集合對(duì)象每次調(diào)用iterator()方法都得到一個(gè)全新的迭代器對(duì)象,
        //默認(rèn)游標(biāo)都在集合的第一個(gè)元素之前。
        while (coll.iterator().hasNext()){
            System.out.println(coll.iterator().next());
        }

List接口及其實(shí)現(xiàn)類ArrayLisyt,LinkedList,Vector

  • ArrayList:作為L(zhǎng)ist接口的主要實(shí)現(xiàn)類;線程不安全的,效率高;底層使用Object[] elementData存儲(chǔ)
  • LinkedList:對(duì)于頻繁的插入、刪除操作,使用此類效率比ArrayList高;底層使用雙向鏈表存儲(chǔ)
  • Vector:作為L(zhǎng)ist接口的古老實(shí)現(xiàn)類;線程安全的,效率低;底層使用Object[] elementData存儲(chǔ)
    -ArrayList的底層原理(jdk7)
    ArrayList list = new ArrayList();//底層創(chuàng)建了長(zhǎng)度是10的Object[]數(shù)組elementData,添加時(shí),先確認(rèn)容量,如果容量夠,那么直接將元素賦值,如果容量不夠,那么進(jìn)入擴(kuò)容步驟,默認(rèn)擴(kuò)容1.5倍,如果1.5倍依然不夠,那么就使用原數(shù)組的長(zhǎng)度加元素的長(zhǎng)度(1)之和,如果上一步得出來(lái)的結(jié)果大于(Integer.MAX_VALUE - 8), 那么就取Integer.MAX_VALUE做數(shù)組的容量,最后根據(jù)這個(gè)最新的容量去復(fù)制老數(shù)據(jù)老新數(shù)組中并且將其賦值到elementData引用。正是因?yàn)橛袛U(kuò)容這個(gè)步驟,所以建議開發(fā)中使用帶參的構(gòu)造器:ArrayList list = new ArrayList(int capacity)
  • (對(duì)于jdk8)第一次調(diào)用add()時(shí),底層才創(chuàng)建了長(zhǎng)度10的數(shù)組,并將數(shù)據(jù)添加到elementData[0],創(chuàng)建數(shù)組的步驟延遲了,其他操作基本和7一樣
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    private void ensureCapacityInternal(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

LinkedList,其內(nèi)部節(jié)點(diǎn)為Node結(jié)構(gòu),體現(xiàn)了雙向。

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

Set
Set:存儲(chǔ)無(wú)序的、不可重復(fù)的數(shù)據(jù)

  • 無(wú)序性:不等于隨機(jī)性。存儲(chǔ)的數(shù)據(jù)在底層數(shù)組中并非按照數(shù)組索引的順序添加,而是根據(jù)數(shù)據(jù)的哈希值決定的。

  • 不可重復(fù)性:底層使用equals()方法來(lái)判斷元素是否重復(fù),如果元素重復(fù),那么、添加失敗。

      HashSet底層:數(shù)組+鏈表的結(jié)構(gòu)。向HashSet中添加元素a,首先調(diào)用元素a所在類的hashCode()方法,計(jì)算元素a的哈希值,
      此哈希值接著通過某種算法計(jì)算出在HashSet底層數(shù)組中的存放位置(即為:索引位置),判斷
      數(shù)組此位置上是否已經(jīng)有元素:
          如果此位置上沒有其他元素,則元素a添加成功。 --->情況1
          如果此位置上有其他元素b(或以鏈表形式存在的多個(gè)元素),則比較元素a與元素b的hash值:
              如果hash值不相同,則元素a添加成功。--->情況2
              如果hash值相同,進(jìn)而需要調(diào)用元素a所在類的equals()方法:
                     equals()返回true,元素a添加失敗
                     equals()返回false,則元素a添加成功。--->情況3
    
      對(duì)于添加成功的情況2和情況3而言:元素a 與已經(jīng)存在指定索引位置上數(shù)據(jù)以鏈表的方式存儲(chǔ)。
      jdk7:頭插,jdk8:尾插(七頭八尾)
      因此:向Set(主要指:HashSet、LinkedHashSet)中添加的數(shù)據(jù),其所在的類一定要重寫hashCode()和equals()
    

根據(jù)以上hashset原理做這個(gè)題(已經(jīng)重寫equals()和hashcode())

    @Test
    public void test3(){
        HashSet set = new HashSet();
        Person p1 = new Person(1001,"AA");
        Person p2 = new Person(1002,"BB");

        set.add(p1);
        set.add(p2);
        System.out.println(set); // 輸出 [Person{id=1002, name='BB'}, Person{id=1001, name='AA'}]

        p1.name = "CC";
        set.remove(p1);
        System.out.println(set); // 輸出 [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}]
        set.add(new Person(1001,"CC"));
        System.out.println(set); // [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}]
        set.add(new Person(1001,"AA"));
        System.out.println(set);// [Person{id=1002, name='BB'}, Person{id=1001, name='CC'}, Person{id=1001, name='CC'}, Person{id=1001, name='AA'}]
    }

LinkedHashSet:可以按照添加的順序遍歷

    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add(456);
        set.add(123);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(129);
        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
//        輸出結(jié)果如下
//        456
//        123
//        AA
//        CC
//        129
    }

TreeSet
可以按照添加對(duì)象的指定屬性,進(jìn)行排序。

  • 兩種排序方式:默認(rèn)排序(實(shí)現(xiàn)Comparable接口) 和 定制排序(Comparator)
  • 默認(rèn)排序中,比較兩個(gè)對(duì)象是否相同的標(biāo)準(zhǔn)為:compareTo()返回0.不再是equals().
  • 定制排序中,比較兩個(gè)對(duì)象是否相同的標(biāo)準(zhǔn)為:compare()返回0.不再是equals().

Map
以HashMap為例子

  • Map中的key:無(wú)序的、不可重復(fù)的,使用Set存儲(chǔ)所有的key ---> key所在的類要重寫equals()和hashCode() (以HashMap為例)
  • Map中的value:無(wú)序的、可重復(fù)的,使用Collection存儲(chǔ)所有的value --->value所在的類要重寫equals()
  • 一個(gè)鍵值對(duì):key-value構(gòu)成了一個(gè)Entry對(duì)象。
  • Map中的entry:無(wú)序的、不可重復(fù)的,使用Set存儲(chǔ)所有的entry
  •  HashMap map = new HashMap():
      *      在實(shí)例化以后,底層創(chuàng)建了長(zhǎng)度是16的一維數(shù)組Entry[] table。
      *      ...可能已經(jīng)執(zhí)行過多次put...
      *      map.put(key1,value1):
      *      首先,調(diào)用key1所在類的hashCode()計(jì)算key1哈希值,此哈希值經(jīng)過某種算法計(jì)算以后,得到在Entry數(shù)組中的存放位置。
      *      如果此位置上的數(shù)據(jù)為空,此時(shí)的key1-value1添加成功。 ----情況1
      *      如果此位置上的數(shù)據(jù)不為空,(意味著此位置上存在一個(gè)或多個(gè)數(shù)據(jù)(以鏈表形式存在)),比較key1和已經(jīng)存在的一個(gè)或多個(gè)數(shù)據(jù)
      *      的哈希值:
      *              如果key1的哈希值與已經(jīng)存在的數(shù)據(jù)的哈希值都不相同,此時(shí)key1-value1添加成功。----情況2
      *              如果key1的哈希值和已經(jīng)存在的某一個(gè)數(shù)據(jù)(key2-value2)的哈希值相同,繼續(xù)比較:調(diào)用key1所在類的equals(key2)方法,比較:
      *                      如果equals()返回false:此時(shí)key1-value1添加成功。----情況3
      *                      如果equals()返回true:使用value1替換value2。
      *
      *      在不斷的添加過程中,會(huì)涉及到擴(kuò)容問題,當(dāng)超出臨界值(且要存放的位置非空)時(shí),擴(kuò)容。默認(rèn)的擴(kuò)容方式:擴(kuò)容為原來(lái)容量的2倍,并將原有的數(shù)據(jù)復(fù)制過來(lái)。
      *
      *      jdk8 相較于jdk7在底層實(shí)現(xiàn)方面的不同:
      *      1. new HashMap()時(shí):底層沒有創(chuàng)建一個(gè)長(zhǎng)度為16的數(shù)組
      *      2. jdk 8底層的數(shù)組是:Node[],而非Entry[]
      *      3. 首次調(diào)用put()方法時(shí),底層創(chuàng)建長(zhǎng)度為16的數(shù)組
      *      4. jdk7底層結(jié)構(gòu)只有:數(shù)組+鏈表。jdk8中底層結(jié)構(gòu):數(shù)組+鏈表+紅黑樹。
      *         4.1 形成鏈表時(shí),七頭八尾
      *         4.2 當(dāng)數(shù)組的某一個(gè)索引位置上的元素以鏈表形式存在的數(shù)據(jù)個(gè)數(shù) > 8 且當(dāng)前數(shù)組的長(zhǎng)度 > 64時(shí),此時(shí)此索引位置上的所數(shù)據(jù)改為使用紅黑樹存儲(chǔ)。
    

LinkedHashMap數(shù)據(jù)節(jié)點(diǎn)

        static class Entry<K,V> extends HashMap.Node<K,V> {
             Entry<K,V> before, after;//能夠記錄添加的元素的先后順序
             Entry(int hash, K key, V value, Node<K,V> next) {
                super(hash, key, value, next);
             }
         }

TreeMap : 按照key進(jìn)行排序:默認(rèn)排序 、定制排序

泛型

泛型類

public class Order<T> {

    String orderName;
    int orderId;
    T orderT;

    public Order(String orderName,int orderId,T orderT){
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }

    //如下的方法都不是泛型方法
    public T getOrderT(){
        return orderT;
    }

    public void setOrderT(T orderT){
        this.orderT = orderT;
    }
}

使用泛型類

    @Test
    public void test1(){
        //如果定義了泛型類,實(shí)例化沒有指明類的泛型,則認(rèn)為此泛型類型為Object類型
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("ABC");

        //建議:實(shí)例化時(shí)指明類的泛型
        Order<String> order1 = new Order<String>("orderAA",1001,"order:AA");
        order1.setOrderT("AA:hello");
    }

泛型方法

    //泛型方法:在方法中出現(xiàn)了泛型的結(jié)構(gòu),泛型參數(shù)與類的泛型參數(shù)沒有任何關(guān)系。
    //換句話說,泛型方法所屬的類是不是泛型類都沒有關(guān)系。
    //泛型方法,可以聲明為靜態(tài)的。原因:泛型參數(shù)是在調(diào)用方法時(shí)確定的。并非在實(shí)例化類時(shí)確定。
    public static <E>  List<E> copyFromArrayToList(E[] arr){
        ArrayList<E> list = new ArrayList<>();
        for(E e : arr){
            list.add(e);
        }
        return list;
    }

在泛型的繼承中

        //數(shù)組中是可以這么操作的
        Object[] arr1 = null;
        String[] arr2 = null;
        arr1 = arr2;

但是泛型是不允許這樣做的,雖然類A是類B的父類,但是G<A> 和G<B>二者不具備子父類關(guān)系,二者是并列關(guān)系。

        List<Object> list1 = null;
        List<String> list2 = new ArrayList<String>();
        //此時(shí)的list1和list2的類型不具有子父類關(guān)系
        //編譯不通過
//        list1 = list2;

但是:類A是類B的父類,A<G> 是 B<G> 的父類,這樣是可以的
通配符?的使用

    @Test
    public void test3(){
        //類A是類B的父類,G<A>和G<B>是沒有關(guān)系的,二者共同的父類是:G<?>
        List<Object> list1 = null;
        List<String> list2 = null;
        List<?> list = null;
        list = list1;
        list = list2;
        //使用通配符之后  可以讀但是不能寫
        List<String> list3 = new ArrayList<>();
        list3.add("AA");
        list3.add("BB");
        list = list3;
        //添加(寫入):對(duì)于List<?>就不能向其內(nèi)部添加數(shù)據(jù)。
        //除了添加null之外。
//        list.add("DD");
        list.add(null);
        //獲取(讀取):允許讀取數(shù)據(jù),讀取的數(shù)據(jù)類型為Object。
        Object o = list.get(0);
        System.out.println(o);
    }

extend和super

    @Test
    public void test4(){
        //這里的 Student是extends  Person的
        List<? extends Person> list1 = null; //? extends Person 表示Person類或者Person類的子類
        List<? super Person> list2 = null;   //? super Person 表示Person類或者Person類的父類

        List<Student> list3 = new ArrayList<Student>();
        List<Person> list4 = new ArrayList<Person>();
        List<Object> list5 = new ArrayList<Object>();

        list1 = list3;
        list1 = list4;
    }

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、基礎(chǔ)知識(shí):1、JVM、JRE和JDK的區(qū)別:JVM(Java Virtual Machine):java虛擬機(jī)...
    殺小賊閱讀 2,563評(píng)論 0 4
  • 四、集合框架 1:String類:字符串(重點(diǎn)) (1)多個(gè)字符組成的一個(gè)序列,叫字符串。生活中很多數(shù)據(jù)的描述都采...
    佘大將軍閱讀 874評(píng)論 0 2
  • Java 語(yǔ)言支持的類型分為兩類:基本類型和引用類型。整型(byte 1, short 2, int 4, lon...
    xiaogmail閱讀 1,450評(píng)論 0 10
  • 父類與子類 在Java術(shù)語(yǔ)中,如果C1類擴(kuò)展自另一個(gè)類C2,我們稱C2為父類,也稱超類或基類,稱C1為子類,也稱次...
    Steven1997閱讀 1,320評(píng)論 1 2
  • 洗凈母雞分成四大塊,加料酒10g,加滿水,20分鐘/varoma/反轉(zhuǎn)小勺,放入黑木耳后再設(shè)30分鐘/100度/反...
    F怡然自得閱讀 458評(píng)論 0 0

友情鏈接更多精彩內(nèi)容