collection集合 介绍_Java核心技术笔记分享------ 集合类

一、Java集合概述

1、一方面,面向对象的的语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行储存,另一方面,使用Array储存对象方面具有一些弊端,而Java集合就像一个容器,可以动态的把多个对象的引用放入容器中。

2、Java集合类可以用于储存数量不等的多个对象,还可用于保存具有映射关系的关联数组。

Java集合类的继承层次

d128bf54820884401aa0d33394e1ac9f.png

二、Collection类

从上面的集合类的继承层次图可以看出,集合由两个基本接口:Collection类和Map类。

1> Collection接口与迭代器

2.1.1 Collection接口

在Java类库中,集合类的基本接口是Collection接口。这个接口有两个基本方法:

public interface Collection<E>{
    boolean add(E element);
    Iterator<E> iterator();
    ...
}
  • add( )方法用于向集合中添加元素,如果添加元素确实改变了集合就返回true;如果集合没有发生变化就返回false
  • iterator( )方法用于返回了一个实现了Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素。

补充:Collection中的其他方法

  • int size( )

​ 返回一个用于访问集合中各个元素的迭代器

  • boolean isEmpty( )

​ 如果集合中没有元素,则返回true

  • boolean contains(Object obj)

​ 如果集合中包含了一个与obj对象相等的对象,则返回true

  • boolean containAll(Collection<?> other)

​ 如果这个集合元素包含other集合中的所有元素,则返回true

  • boolean add(E element) [前面有讲到,此处就省略了]
  • boolean addAll(Collection<? extends E> other)

​ 将other集合中的所有元素添加到这个集合中。如果由于这个调用改变了集合,则返回true。否则,false

  • boolean remove(Object obj)

​ 从这个集合中删除等于obj的对象。如果有匹配的对象被删除,则返回true。

  • boolean removeAll(Collection<?> other)

​ 从这个集合中删除other集合中所存在的所有元素。如果由于这个调用改变了集合,则返回true

  • default boolean removeIf(Predicate<? super E> filter)

从这个集合删除filter返回true的所有元素。如果由于这个调用改变了集合,则返回true

  • void clear( )

从这个集合中删除所有元素

  • boolean retainAll(Collection<?> other)

从这个集合中删除所有与other集合中存在的所有元素。如果由于这个调用改变了集合,返回true。

  • Object[ ] toArray( )

返回这个集合中的对象的按钮

  • T[ ] toArray(T[ ] arrayToFill)

返回这个集合中的对象的数组。如果arrayToFill足够大,就将集合中的元素填入这个数组中。剩余空间用null填补;否则,分配一个新数组,其成员类型与arrayToFill的成员类型相同,其长度等于集合的大小,并填充集合元素。

2.1.2 Java中的迭代器

2.1.2.1 Iterator接口

Iterator接口包含4个方法

public Interface Iterator<E>{
    E next();
    boolean hasNext();
    void remove();
    default void forEachRemaining(Consumer<? super E> action);
}
  • next( )方法:返回即将要访问的下一个对象元素。如果已经到达了集合末尾,扔掉用next( )方法的话,将会抛出一个NoSuchElementException。
  • hasNext( )方法:如果迭代器对象后面还有可以访问的对象元素,这个方法就返回true。所以在调用next( )方法以前,要调用一个hasNext( )方法来确保是否还有可以访问

有了这两个基础方法,我们就可以介绍迭代器了:

Collection<String> collection = ...;
Iterator<String> iterator = collection.iterator(); //返回一个Collection的迭代器
while(iterator.hasNext()){
    String element = iterator.next();
    doing something with element...;
}

在Java中,迭代器位于两个元素之间。当调用next( )方法时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。

c0a4d0c1c3244445c37313359137e1cf.png
  • remove( )方法:将会删除上次调用next( )方法时返回的元素。如果调用remove( )方法以前没有调用next( )方法,这将是不合法的,如果这样做,将会抛出一个IllegalStateException异常
Iterator iter = collection.iterator;
//legal
iter.next();
iter.remove();
//Illegal
iter.remove //error
  • forEachRemaining( )方法: 可以这样调用forEachRemaining( )方法
iter.forEachRemaining(element -> do something with element);

2.1.2.2 Iterable接口

public interface Iterable<E>{
    Iterator<E> iterator();
    ...
}

可以看出这个接口的作用就是返回一个新的迭代器。

有很多的集合类并没有实现Iterator接口,反而是实现了Iterable接口,这是为什么呢

(一下内容摘自一篇博客:https://www.iteye.com/blog/perfy315-1459201)

为什么一定要实现Iterable接口,为什么不直接实现Iterator接口呢? 看一下JDK中的集合类,比如List一族或者Set一族,都是实现了Iterable接口,但并不直接实现Iterator接口。 仔细想一下这么做是有道理的。
因为Iterator接口的核心方法next()或者hasNext() 是依赖于迭代器的当前迭代位置的。 如果Collection直接实现Iterator接口,势必导致集合对象中包含当前迭代位置的数据(指针)。 当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么next()方法的结果会变成不可预知。 除非再为Iterator接口添加一个reset()方法,用来重置当前迭代位置。 但即时这样,Collection也只能同时存在一个当前迭代位置。 而Iterable则不然,每次调用都会返回一个从头开始计数的迭代器。 多个迭代器是互不干扰的。

2> 具体集合:List类

在Java程序设计语言中,所有的链表都是双向链接的------即每个链接还存放着其前驱的引用。如下图:

b867190c5eb5c462395a738fbd89a5dc.png

我们来介绍List类的一些方法:

  • ListIterator listIterator( )

返回一个列表迭代器,用来访问列表中的元素

  • ListIterator listIterator(int index)

返回一个列表迭代器,用来访问列表的元素,第一次调用这个迭代器的next( )方法会返回给定索引的元素

  • void add(int i, E element)

在给定位置添加一个元素

  • void addAll(int i, Colection<? extends E> elemens)

将一个集合中的所有元素添加到给定的位置

  • E remove(int i)

删除并返回给定位置的元素

  • E get(int i)

获取给定位置的元素

  • E set(int i, E element)

用一个新元素替换给定位置的元素,并返回原来的那个元素

  • int indexOf(Object element)

返回与指定元素相等的元素在列表中第一次出现的位置,如果没有这样的元素将会返回-1

  • int lastindexOf(Object element)

返回与指定元素相等的元素在列表中最后一次出现的位置,如果没有这样的元素将会返回-1

2.2.1 链表:LinkedList类

LinkedList类与泛型Collection集合有一个重要的区别。链表是一个有序集合每个对象的位置十分重要LinkedList.add( )方法将对象添加到链表的尾部。但是,常常需要将元素添加到链表的中间。由于迭代器描述了集合中的位置,所以这种依赖于位置的add( )方法将由迭代器负责

2.2.1.1 LinkedList的一些基本方法

  • LinkedList( )

构造一个空链表

  • LinkedList(Collection<? extends E> elements)

构造一个链表,并将集合中的所有的元素都添加到这个链表中

  • void addFirst(E element)
  • void addLast(E element)

将某个元素添加到列表的头部或尾部

  • E getFirst( )
  • E getLast( )

返回列表头部或尾部的元素

  • E removeFirst( )
  • E removeLast( )

删除并返回列表头部或尾部的元素

bd4ddfa3bd6f98c85c78cdbb1379e402.png

2.2.1.2 LinkedList类的列表迭代器

LinkedList类除了能获取一个Iterator迭代器,还可以获取一个自己独特的一个列表迭代器-----ListIterator,它是Iterator的子接口

interface ListIterator<E> extends Iterator<E>{
    ...
}

我们来介绍ListIterator的一些方法

  • void add(E element)

在当前位置前添加一个元素

  • next( )
  • hasNext( ) 这两个方法与Iterator的相同
  • E previous( )

返回前一个对象。如果已经达到了列表的头部,就抛出一个NoSuchElementException异常

  • boolean hasPrevious( )

当反向迭代列表时,如果还有可以访问的元素,则返回true

这两个方法可以实现反向遍历,使用的方法与next( )方法和hasNext( )方法遍历集合元素一致

  • void set(E newElement)

用新元素替换next( )或previous( )访问的上一个元素。如果在上一个next( )或previous( )调用之后列表结构被修改了,将抛出一个IllegalStateException异常

  • int nextIndex( )

返回下一次调用next( )方法将返回的元素的索引

  • int previousIndex( )

返回下一次调用previous( )方法时将返回的元素的索引

2.2.1.3 链表与其链表迭代器详述

2.2.1.3.1 添加元素示例代码

ListIterator 的add( )方法在迭代器位置之前添加一个新对象。

我们来看一段示例代码:

var staff = new LinkedList<String>();
ListIterator<String> iter = staff.listIterator();
staff.add("Amy");
staff.add("Bob");
staff.add("Carl");
//以下为越过第一个元素,在第二个元素之前添加“Juliet”
iter.next();
iter.add("Juliet");

如果多次调用add( )方法,将按照提供的次序把元素添加到链表中。它们被一次添加到迭代器的当前位置之前。

2.2.1.3.2 多个迭代器的情形

set( )方法用一个新元素替换调用next( )方法或previous( )方法返回的上一个元素。

例如,下面的代码将用一个新值替换链表的第一个元素:

ListIterator<String> iter = list.listIterator();
String oldValue = iter.next();
iter.set(newValue);

可以想象,如果在某个迭代器修改集合时,另一个迭代器却在遍历这个集合,那么一定会出现混乱。例如,假设一个迭代器向一个元素前面的位置,而另一个迭代器刚刚删除了这个元素,现在前一个迭代器就是无效的,并且不能再使用。

而链表迭代器设计为可以检测到这种修改。如果一个迭代器发现它的集合被另一个迭代器修改了,或是被该集合自身的某个方法修改了,就会抛出一个ConcurrentModificationException异常

例如我们考虑下面这段代码:

List<String> list = ...;
ListIterator<String> iter1 = list.listIterator();
ListIterator<String> iter2 = list.listIterator();
iter1.next();
iter1.remove();
iter2.next(); //throw ConcurrentModificationException

由于iter2检测出这个链表从外部修改了,所以iter2.next( )的调用抛出了一个异常

为了避免发生并发(并发为多线程的内容,后面的章节将会讲到)修改异常,请遵循这样一个简单的规则:可以根据需要为一个集合关联多个迭代器,前提是这些迭代器只能读取集合。或者可以再关联一个能同时读写的迭代器

2.2.1.3.3 链表示例代码:

我们来练习以下LinkedList类以及它的迭代器的使用,将会创建两个列表,然后将他们合在一起

package Collection.List;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;

/**
 * @param
 * @author myW_Yh
 * @Description
 * @return
 * @creat 2020-05-15-9:21
 */
public class LinkedList_Test {
    public static void main(String[] args) {
        var a = new LinkedList<String>();
        a.add("Amy");
        a.add("Carl");
        a.add("Erica");

        var b = new LinkedList<String>();
        b.add("Bob");
        b.add("Doug");
        b.add("Frances");
        b.add("Gloria");
        //merge the words from b into a
        ListIterator<String> aIter = a.listIterator();
        Iterator<String> bIter = b.iterator();

        while(bIter.hasNext()){
            if (aIter.hasNext()) aIter.next();
            aIter.add(bIter.next());
        }
        System.out.println(a.toString());

        //remove every second word form b
        bIter = b.iterator();
        while(bIter.hasNext()){
            bIter.next();
            if (bIter.hasNext()){
                bIter.next();
                bIter.remove();
            }
        }
        System.out.println(b.toString());

        //bulk operation: remove all words in b from a
        a.removeAll(b);
        System.out.println(a);

    }
}

2.2.2 数组列表:ArrayList类

2.2.2.1 LinkedList和ArrayList优势

  1. LinkedList
  2. 优势:对于频繁的删除,添加操作很有优势,比起ArrayList减去很大的开销
  3. ArrayList
  4. 优势:它是以数组的方式来实现的,数组的特性是可以使用索引的方式来快速定位对象的位置,因此对于快速的随机取得对象的需求,使用ArrayList实现执行效率上会比较好。

2.2.2.2 ArrayList详述

在java中,有两种访问元素的协议:一种是通过迭代器,另一种是通过get( )和set( )方法随机地访问每个元素。后者不适用于链表。所以自然而然地,适用于ArrayLIst类,这个类实现了List接口,并且ArrayList封装了一个动态再分配地对象数组

2.2.2.3 ArrayList方法

List接口中所有方法ArrayList都可以使用

除此之外,ArrayList还可以使用数组地一些方法,例如sort( )等

3>具体集合:set类:不能有相同的元素

链表和数组允许你根据一元指定元素地次序。但是,如果想要查看某个指定的元素,却又不记得它的位置,就需要访问所有元素,直到找到为止。如果集合中包含的元素很多这将会需要很长时间。如果不在意元素的顺序,我们这时候就可以使用set集合。但是它的缺点是无法控制元素出现的次序,是无序的,这些数据结构将按照对自己最方便的方式组织元素

2.3.1 散列集:hashSet

有一种众所周知的一个数据结构,可以用于快速地超找对象,这就是散列表。散列表为每一个对象计算一个整数,称为散列码。有不同的数据地修电工将产生不同的散列码。它们是由hashcode方法产生的。

2.3.1.1 hashcode( )方法:

在所有类地超类中,就定义着一个默认的hashcoder( )方法,会从对象的存储地址得出散列码。

如果新写的类中重新定义了equals方法,就必须为用户可能插入散列表的对象重新定义hashcode( )方法。

这是因为,equals( )方法必须和hashcode( )方法实现兼容:如果x.equals(y)返回true,那么x.hashcode( ) 与y.hashcode( )返回的hash值应该相等

我们来看以下hashcode( )方法应该怎样重写实现:

我们假设的是重写前面几章写过的Eployee类:成员变量有String 类型的name,double 类型的salary, Date类的hireDay

public int hashCode(){
    return Objects.hash(name,salary,hireDay);
}

(有关于hashCode的底层实现原理与hash算法,笔者由于能力有限,此处不作叙述,感兴趣的可以查查资料)

2.3.1.2 hashSet中的方法

Collection中的方法hashSet都可以使用。

2.3.2 树集:TreeSet

树集是一个有序集合。可以以任意顺序将元素插入到集合中。在对集合进行遍历时,值将自动的按照排序后的顺序呈现。

TreeSet要注意的事项

a.向TreeSet中添加的元素必须是同一个类型的

b.可以按照添加进集合中的元素的指定的顺序遍历。像String、包装类等默认按照从小到大的顺序遍历 。

c.当向TreeSet中添加自定义类的对象时,有两种排序方法:自然排序和定制排序

d.自然排序:要求自定义类实现java.lang.Comparable接口并重写compareTo(Object obj) 在此方法中,指明按照自定义类的哪个属性进行排序。

e.向TreeSet中添加元素时,首先按照compareTo() 进行比较,一旦返回0,虽然仅是两个对象的此属性值相同,但是程序会认为这两个对象是相同的,进而后一个对象就不能进来。

f.定制排序:

  • 创建一个实现了Comparator接口的类对象
  • 将此对象作为形参传入TreeSet的构造器
  • 向TreeSet中添加元素

补充注意:compareTo()【compare()】与hashCode()以及equals() 三者保持一致

2.3.2LinkedHashSet(插入性略低,但是迭代性好)

使用链表维护了一个添加进集合中的顺序,导致当我们遍历LinkedHashSet集合元素时,是按照添加进去的顺序遍历的

4> 队列

2.4.1队列与双端队列

队列(Queue)允许高校地在尾部添加元素,并在头部删除元素

双端队列(Deque)允许在头部和尾部都高效地添加和删除元素。

它们都不允许在队列中间添加元素

java.util.Queue
  • boolean add(E element)
  • boolean offer(E element)

如果队列没有满,将给定的元素添加到这个队列的队尾并返回true。如果队列已满,第一个方法抛出一个IllegalStateStateException,而第二个方法返回false

  • E remove( )
  • E poll( )

假如队列不为空,删除并返回这个队列队头的元素。如果队列是空的,第一个方法抛出NoSuchElementWException,而第二个方法返回null

  • E element( )
  • E peek( )

如果队列不为空,返回这个队列的队头,但不删除。如果队列空,第一个方法抛出NoSuchElementWException,而第二个方法返回null

java.util.Deque
  • void addFirst(E element)
  • void addLast(E element)
  • boolean offerFirst(E element)
  • boolean offerLast(E element)

将给定的对象添加到队列的队头或队尾。如果这个双端队列已满,前两个方法抛出一个IllegalStateStateException,后两个方法返回false

  • E removeFirst( )
  • E removeLast( )
  • E pollFirst( )
  • E popllLast( )

如果这个双端队列不为空,删除并返回双端队列队头或队尾的元素。如果这个双端队列为空,前两个方法抛出一个IllegalStateStateException,后两个方法返回false

  • E getFirst( )
  • E getLast( )
  • E peekFirst( )
  • E peekLast( )

如果这个双端队列非空,返回双端队列或队尾的元素,但不删除。如果这个双端队列为空,前两个方法抛出一个IllegalStateStateException,后两个方法返回false

2.4.2 无限定双端队列 ArrayDeque

  • ArrayDeque( )
  • ArrayDeque(int initialCapacity)

用初始容量16或给定的初始容量构造一个无限定双端队列

2.4.3 优先队列

优先队列中的元素可以按照任意的顺序插入,但会按照有序的顺序进行检索。也就是说,无论何时调用remove( ) 方法,总会获得当前优先队列中最小的元素

优先队列并没有对所有元素进行排序。如果迭代处理这些元素,并不需要对它们进行排序。优先队列使用了一个精巧且高校的数据结构,称为堆。堆是一个可以自己组织的二叉树,其添加和删除操作可以让最小的元素移动到根,而不必花费时间对元素进行排序。

与TreeSet一样,优先队列既可以保存实现了Comparable接口的类对象,也可以保存构造器中提供的Compatator对象

优先队列的典型用法是任务调度。每一个任务都有一个任务级,任务以随即顺序添加到队列中。每当启动一个新的任务时,都将优先级最好的任务从队列中删除。(由于习惯上将1设为“最高”优先级,所以remove( ) 操作会将最小的元素删除

java.util.PriorityQueue
  • PriorityQueue( )
  • PriorityQueue(int initialCapacity)

构造一个存放Comparable对象的优先队列

  • PriorityQueue(int initialCapacity, Comparator<? super E> c)

构造一个优先队列,并使用指定的比较器对元素进行排序

三、映射Map

集是一个集合(与数学中的集合一样),允许快速的查找现有的元素。但是,要查找一个元素,需要所有要查找的那个元素的准确副本。这不是一种常见的查找方式。通常,我们需要知道某些关键信息,希望查找与之关联的元素。映射(Map)数据结构就是为此设计的

映射用来存放键/值对。如果提供了键,就能找到值。

1> 基本映射操作:增删改查与迭代处理

Java类库提供了两个通用的实现:HashMap和TreeMap。这两个类都实现了Map接口。

3.1.1 创建一个Map对象以及“增”的操作

散列映射对键进行散列,树映射根据键的顺序将元素组织为一个搜索树,散列或者比较函数都只用于键,与键关联的值不进行散列或比较

那什么是键与值呢?来看一个示例代码:

var staff = new HashMap<String, Employee>();
var harry = new Employee("Harry Hacker");
staff.put("987-96-8896",harry);
...

其中,那一串数字就是一个键,而与之关联的Employee类的harry对象就是值

我们要记住:

  • 每当往映射中添加一个对象时,必须提供一个键
  • 键必须是唯一的,不能对同一个键存放两个值。如果对同一个键调用两次put( )方法,第二个值就会取代第一个值。实际上,put( )方法将返回与这个键参数关联的上一个值。

其他的一些“增”的方法:

  • void putAll(Map<? extends K, ? extends V> entries)

将给定映射中所有映射条目添加到这个映射中

3.1.2 “删”

remove( )方法从映射中删除给定键对应的元素。

3.1.3 “改”

可以用put( )方法来改

  • default V putIfAbsent(K key, V value)

如果指定的键尚未与值关联(或映射到 null ), null其与给定值关联并返回 null,否则返回当前值。

  • default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

在所有映射条目上应用这个函数。将键与非null结果相关联,对于null结果,则将相应的键删除

3.1.4 “查”

3.1.4.1 查集合中的元素的个数

size( )方法返回映射中的元素数

3.1.4.2 根据键来查找值

  • get(Object key)

它会获取与键关联的值;返回与键关联的对象,或者如果映射中没有这个对象,则返回null。实现类可以禁止键为null。

  • default V getOrDefault(Object key, V defaultValue)

返回指定键映射到的值,如果此映射不包含键的映射,则返回 defaultValue

3.1.4.3 其他的一些“查”的方法

  • boolean containsKey(Object key)

如果在映射中已经有这个键,返回true

  • boolean containsValue(Object value)

如果映射中已经有这个值,返回true

3.1.5 迭代处理

  • 可以用forEach(BiConsumer<? super K, ? super V> action)内嵌套一个lambda表达式来遍历

例如:

java Map<String, Interger> scores = ...; int score = scores.getOrDefault(id,0); scores.forEach((k,v) -> System.out.println("Key= "+ k + " Value= " + v));

其他的迭代处理方法将在下面的映射视图讲到。

2> 映射视图: 键集,值集,键/值对集(entry集)

  • key集是用Set存放的,不可重复

我们可以用Map类中的ketSet( )方法来获取键集:

java HashMap<> map = new HashMap(); Set<> keySet = map.keySet();

  • value集是用Collection存放的,可重复的

我们可以用Map类的values( )方法来获取值集:

Collection<> collection = map.values();
  • 一个key-value对,是一个Entry,所有的Entry是用Set存放的,不可重复。(映射条目集的元素是实现了Map.Entry接口的类的对象)

我们可以用entrySet( )来获取entry集:

java Set<> entrySet = map.entrySet();

3.2.1 entry中的方法

java.util.Map.Entry
  • K getKey( )
  • V getValue( )

返回这个映射条目的键或值

  • V setValue(V newValue)

将相关映射中的值改为新值,并返回原来的值。

3.2.2 遍历方法

//遍历key集
Set set = map.keySet();
for(Object obj:set){
    System.out.println(obj);
}
//2.遍历value集
Colllection values = map.values();
Iterator i = values.iterator();
while(i.hasNext()){
    System.out,println(i.next());
}
//3.如何遍历key - value
//方式一:
Set set1 = map.keySet();
for(Object obj : set1){
    System.out,println(obj+map.get(obj))
}
//方式二:
Set set2 = map.entrySet();
for(Object obj:set2){
    Map.Entry entry=(Map.Entry)obj;
    System.out.println(entry.getKey()+entry.getValue());
    System.out.println(entry);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值