Java集合概述

JDK提供了大量优秀的集合实现供开发者使用,合格的程序员必须要能够通过功能场景和性能需求选用最合适的集合,这就要求开发者必须熟悉Java的常用集合类。本文将就Java Collections Framework中常用的集合及其特点、适用场景、实现原理进行介绍,供学习者参考。当然,要真正深入理解Java的集合实现,还是要推荐去阅读JDK的源码。

集合框架

​ java集合框架提供了一套性能优良,使用方便的接口和类,它位于java.util中,集合中的元素全部都是对象,即Object类的实列,不同的集合有不同的功能和特点,适合不同的场合。

  • 集合类主要负责保存数据,因此集合类也被称为容器类
  • Java 所有的集合类都位于 java.util 包下
  • 集合与数组的差别
    • 数组元素既可以是基本类型的值,也可以是对象
    • 而集合里只能保存对象

Java集合框包含的内容以下图示:

Java集合框架

从图中可以看出Java提供的众多集合类由两大接口衍生而来:Collection接口和Map接口。Collection接口和Map接口是Java集合框架的根接口,这些两个接口又包含了一些子类接口或实现类。其中Collection接口常用子接口包含List接口和Set接口;另外一个重要接口是Map接口,它是独立一支,以上三者都是集合接口,其实现类为Java中经常使用的集合类型。Java集合框架常用接口说明如表所示:

名称描述
Collection接口 Collection 是最基本的集合接口,一个 Collection 代表一组 Object,即 Collection 的元素, Java不提供直接继承自Collection的类,只提供继承于的子接口(如List和set)。Collection 接口存储一组不唯一,无序的对象。
ListList 接口 List接口是一个有序的 Collection,使用此接口能够精确的控制每个元素插入的位置,能够通过索引(元素在List中位置,类似于数组的下标)来访问List中的元素,第一个元素的索引为 0,而且允许有相同的元素。List 接口存储一组不唯一,有序(插入顺序)的对象。
Set继承Collection 接口,存储一组可重复的有序对象。
SortedSetSortedSet 继承于Set保存有序的集合。
MapMap 接口存储一组键值对象,提供key(键)到value(值)的映射。
Map.Entry描述在一个Map中的一个元素(键/值对)。是一个 Map 的内部接口。
SortedMap继承于 Map,使 Key 保持在升序排列。
Enumeration这是一个传统的接口和定义的方法,通过它可以枚举(一次获得一个)对象集合中的元素。这个传统接口已被迭代器取代。
Iterator集合迭代器,能够遍历集合元素的接口

Iterator(迭代器)

​ 集合的输出接口,主要用于遍历输出集合中的元素,Iterator 对象被称之为迭代器

常用方法

方法描述
boolean hasNext()如果被迭代的集合元素还没有被遍历完,则返回 true
Object next()返回集合里的下一个元素
void remove()删除集合里上一次 next方法返回的元素
void forEachRemaining(Consumer action)这是 Java 8 为 Iterator 新增的默认方法,该方法可使用 Lambda 表达式来遍历集合元素

集合框架

迭代器使用步骤

1.集合对象创造迭代器对象

2.使用hasNext()方法判断迭代器是否存有元素

3.使用next()获取迭代器中的元素

List<String> arrayList = new ArrayList();
Iterator<String> it = arrayList.iterator();
while (it.hasNext()) {
    String next =  it.next();
    System.out.println(next);
}

Collection接口

  • Collection 接口是最基本的集合接口,可以存储一组不唯一 无序的对象
  • Collection接口有两个常用的子接口--List接口和Set接口

常用方法

方法描述
boolean add(Object obj)在列表的末尾顺序添加元素,起始索引位置从0开始,如果集合添加元素了,则返回 true
boolean addAll(Collection c)向集合中添加集合 c 中的所有元素,如果原集合添加元素了,则返回 true。
int size()返回集合中元素的个数
void clear()清除集合中的所有元素,将集合长度变为0。
boolean contains(Object o)判断集合中是否存在指定元素
boolean containsAll(Collection c)判断集合中是否包含集合 c 中的所有元素
boolean isEmpty()判断集合是否为空
Iterator<E> iterator()返回一个 Iterator 对象,用于遍历集合中的元素
boolean remove(Object o)从集合中删除一个指定元素,当集合中包含了一个或多个元素 o 时,该方法只删除第一个符合条件的元素,该方法将返回 true
boolean removeAll(Collection c)从集合中删除所有在集合 c 中出现的元素(相当于把调用该方法的集合减去集合 c)。如果原集合改变了,则返回 true
boolean retainAll(Collection c)从集合中删除集合 c 里不包含的元素(相当于把调用该方法的集合变成该集合和集合 c 的交集),如果原集合改变了,则返回 true
Object[] toArray()把集合转换为一个数组,所有的集合元素变成对应的数组元素

示列

创建、添加、输出、删除

// 创建集合 list1
ArrayList list1 = new ArrayList(); 
// 创建集合 list2
ArrayList list2 = new ArrayList(); 
// 向 list1 添加一个元素
list1.add("one"); 
// 向 list1 添加一个元素
list1.add("two"); 
// 将 list1 的所有元素添加到 list2
list2.addAll(list1); 
// 向 list2 添加一个元素
list2.add("three"); 
// 输出list1中的元素数量
System.out.println("list1 集合中的元素数量:" + list1.size()); 

System.out.println("list2 集合中的元素如下:");
Iterator it1 = list2.iterator();
while (it1.hasNext()) {
 System.out.print(it1.next() + "、");
}
// 删除第 3 个元素
list2.remove(2);
System.out.println("list2集合中的元素数量:" + list2.size()); 

//list1集合移除与list2集合相同的元素
list1.removeAll(list2);
System.out.println("\nremoveAll() 方法之后 list1 集合中的元素数量:" + list1.size());
System.out.println("list1 集合中的元素如下:");
Iterator it2 = list1.iterator();
while (it2.hasNext()) {
 System.out.print(it2.next() + "、");
}

//注意:retainAll( ) 方法的作用与 removeAll( ) 方法相反,即保留两个集合中相同的元素,
//其他全部删除。

​ 下面通过示列介绍如何创建并操作集合。使用Collection集合存储果商采购的水果品类信息,并且实现获取水果品类总数、删除指定品类数据、判断集合中是否包含指定数据、输出全部水果品类信息和清空集合等操作。实现代码如示列1所示。

示列1

package CollectionsFramework.collectionPractice;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

public class CollectionTest {
    public static void main(String[] args) {
        //创建Collection第1个集合 fruitList1
        Collection fruitList1 = new ArrayList();

        System.out.println("********添加元素********");
        fruitList1.add("香水梨");
        fruitList1.add("苹果梨");
        fruitList1.add("皇冠梨");
        //虽然集合不能放基本类型值,但是Java支持自动封装
        fruitList1.add(5);
        System.out.println("第一个集合里的元素:" + fruitList1);
        
        
        System.out.println("********删除元素 remove()********");
        fruitList1.remove(5);
        System.out.println("第一个集合里的元素:" + fruitList1);
        
        
        System.out.println("********获取元素个数 size()********");
        System.out.println("第1个集合一共添加了" + fruitList1.size() + "类水果信息");
     
        
        System.out.println("********判断集合是否包含指定元素 contains()********");
        System.out.println("第一个集合中是否包含富士苹果?" + fruitList1.contains("富士苹果"));
        
        
        System.out.println("********判断集合包含关系 ContainsAll()********");
        //创建Collection第2个集合 fruitList2
        Collection fruitList2 = new HashSet();
        fruitList2.add("香水梨");
        fruitList2.add("苹果梨");
        fruitList2.add("富士苹果");
        fruitList2.add("金帅苹果");
        System.out.println("第2个集合里的元素" + fruitList2);
        System.out.println("第2个集合是否包含第1个集合?" + fruitList2.containsAll(fruitList1));
        //删除第1个集合的皇冠梨
        fruitList1.remove("皇冠梨");
        System.out.println("第2个集合是否包含第1个集合?" + fruitList2.containsAll(fruitList1));
        
        
        System.out.println("********只保留集合共同元素 retainAll()********");
        fruitList2.removeAll(fruitList1);
        System.out.println("第2个集合里的元素" + fruitList2);
        
        
        System.out.println("********删除集合2里的元素 clear()********");
        fruitList2.clear();
        System.out.println("第2个集合里的元素:" + fruitList2);


    }
}

运行结果如下

********添加元素********
第一个集合里的元素:[香水梨, 苹果梨, 皇冠梨, 5]

********删除元素 remove()********
第一个集合里的元素:[香水梨, 苹果梨, 皇冠梨]

********获取元素个数 size()********
第1个集合一共添加了3类水果信息

********判断集合是否包含指定元素 contains()********
第一个集合中是否包含富士苹果?false

********判断集合包含关系 ContainsAll()********
第2个集合里的元素[香水梨, 富士苹果, 苹果梨, 金帅苹果]
第2个集合是否包含第1个集合?false
第2个集合是否包含第1个集合?true

********只保留集合共同元素 retainAll()********
第2个集合里的元素[富士苹果, 金帅苹果]

********删除集合2里的元素 clear()********
第2个集合里的元素:[]

Process finished with exit code 0

​ 在示列1中,创建了两个Collection集合 fruitList1和fruitList2Collection为接口,具体的集合类型由其实现类决定。其中fruitList1是ArrayList类的对象,fruitList2是HashSet类的对象。虽然它们具体类型不同,但是这里统一把它们当作Collection集合,使用Collection接口定义的方法来操作ArrayList类集合和HashSet类集合没有任何区别。从示列1的运行结果可以看出,Collection集合的常用操作包括:添加元素、删除元素、返回Collection集合元素个数及清空整个集合等。

数组的遍历

​ 当使用System.out.println()方法输出集合对象时就比较麻烦需要一个个写,这是因为所有的Collection实现类都重写了toString()方法,该方法可以一次性地输出集合中的所有元素。但是,如果想依次访问集合里的每个元素,并实现对该元素的操作,则需要使用某种方式遍历集合元素。下面介绍遍历集合迭代两种方法。

使用Iterator接口遍历集合元素

Iterator接口也是Java集合框的成员,但它与集合不一样。集合主要用于存储其他对象,而Iterator接口主要用于遍历(迭代访问)Collection集合中的元素,Iterator对象也被称为迭代器。

`接口隐藏了各种Collection实现类的底层细节,向应用提供了遍历Collection集合元素的统一编程接口,Iterator`接口定义了如下三个方法,如表所示。

方法描述
boolean hasNext()判断是否存在下一个遍历元素,存在则返回true
Object next()返回遍历的下一个元素
void remove()删除集合里上一次next()方法返回的元素

​ 在示列1中,每个集合中的元素只记录了水果品类信息,现在需要记录水果更完整的信息,除了品类,还需要记录每类水果的价格,遍历集合输出每类水果的品类和价格信息。使用Iterator接口实现集合遍历,代码如示列2所示。

示列2

​ 定义Fruit类,用于描述Fruit对象,代码如下。

package CollectionsFramework.IteratorPractice;

/**
 * 水果类
 */
public class Fruit {
    //水果品种
    private String brand;
    //价格
    private double price;
    //省略 setter/getter()方法
    public Fruit(){}
    public Fruit (String brand,double price){
        this.brand=brand;
        //每斤价格
        this.price=price;
    }
    //输出信息
    public void show(){
        System.out.println(this.brand+"每斤"+this.price+"元");
    }
}

​ 定义Test类,实现向集合中添加数据和遍历集合。

package CollectionsFramework.IteratorPractice;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

public class IteratorTest {
    public static void main(String[] args) {
        //创建一个集合
        Collection fruitList = new HashSet();
        Fruit fruit1 = new Fruit("香水梨", 2.5);
        Fruit fruit2 = new Fruit("苹果梨", 2.0);
        Fruit fruit3 = new Fruit("富士苹果", 3.5);
        Fruit fruit4 = new Fruit("金帅苹果", 3.0);
        //添加元素
        fruitList.add(fruit1);
        fruitList.add(fruit2);
        fruitList.add(fruit3);
        fruitList.add(fruit4);
        //使用System.out.println(fruitList);
        //获取集合迭代器
        Iterator it = fruitList.iterator();
        while (it.hasNext()) {
            Fruit fruit = (Fruit) it.next();
            fruit.show();
        }

    }
}

运行结果如下

富士苹果每斤3.5元
香水梨每斤2.5元
苹果梨每斤2.0元
金帅苹果每斤3.0元

Process finished with exit code 0

​ 从示列2中可以看出,Iterator接口的使用必须依赖于Collection对象,若有一个Iterator对象,则必然有一与之关联的Collection集合。没有集合的Iterator接口就像无本之木,无法发挥作用。Iterator类提供了两个方法访问Collection集合里的元素,并且可以通过remove()方法删除集合中上一次遍历的集合元素,但Iterator接口仅用于遍历集合,本身并不提供存储数据的功能。

注意

​ 当使用Iterator接口遍历Collection集合元素时,使用remove()方法删除元素的代码如下。

//省略代码
Iterator it = fruitList.iterator();
while(it.hasNext()){
    Fruit fruit = (Fruit)it.next();
    if(fruit.brand.equals("苹果梨")){
        it.remove();
    }else{
        fruit.show();
    }
}

​ 在对Collection集合里面的元素进行遍历操作时,其中的元素不能被改变,不能使用集合自带的remove()方法删除集合元素本身,如果将以上代码的"it.remove()"修改为"fruits.remove(fruit)",将会引发ConcurrentModificationException(并发修改异常)。

​ 而当使用Iterator接口对集合元素进行遍历时,并不是把集合元素本身传给了迭代器,而是把集合元素的值传给迭代器,所以修改迭代器存储的值对集合元素本身没有任何影响。

使用foreach循环遍历集合元素

​ 除可以使用Iterator接口遍历Collection集合中的元素外,使用foreach循环遍历集合元素会更加便捷,foreach循环是JDK1.5引入的语法结构,也被称为增强for循环,可用于遍历集合和数组。其语法结构如下:

语法

for(数据类型 type 迭代变量名 value:迭代对象object){
    //引用迭代变量value的语句
}

​ 使用foreach循环遍历水果品类集合,如示列3所示。

示列3

package CollectionsFramework.foreachPractice;

import CollectionsFramework.IteratorPractice.Fruit;

import java.util.Collection;
import java.util.HashSet;

/**
 * 使用foreach遍历集合
 */
public class ForTest {
    public static void main(String[] args) {
        //1、创建集合及元素对象
        Collection fruits = new HashSet();
        Fruit fruit1 = new Fruit("香水梨", 2.5);
        Fruit fruit2 = new Fruit("苹果梨", 2.0);
        Fruit fruit3 = new Fruit("富士苹果", 3.5);
        Fruit fruit4 = new Fruit("金帅苹果", 3.0);
        //2、添加元素
        fruits.add(fruit1);
        fruits.add(fruit2);
        fruits.add(fruit3);
        fruits.add(fruit4);
        //3、使用foreach遍历集合
        for (Object obj : fruits) {
            Fruit fruit = (Fruit) obj;
            fruit.show();
        }
    }
}

运行结果如下

富士苹果每斤3.5元
香水梨每斤2.5元
苹果梨每斤2.0元
金帅苹果每斤3.0元

Process finished with exit code 0

​ 上面的代码使用foreach循环遍历Collection集合里的元素,代码更加简洁,与Iterator接口遍历集合元素类似,foreach循环中的迭代变量也不是集合元素本身,系统只是依次把集合元素的值赋值给迭代变量,因此在foreach循环中修改迭代变量的值也没有任何实际意义。

List接口

  • List继承Collection接口,是有序集合 List接口中允许存放重复的元素
  • List可以存储一组不唯一 有序的对象
  • 包含 Collection 接口中的所有方法

ArrayList 类

对数组的封装,达到动态数组

可以使用List接口所有方法

构造方法

方法描述
public ArrayList()构造一个初始容量为 10 的空列表
public ArrayList(Collection<?extends E> c)构造一个包含指定 Collection 元素的列表,这些元素是按照该 Collection 的迭代器返回它们的顺序排列的

示列:

 List list = new ArrayList();
// <T> 是 Java 中的泛型,用于指定集合中元素的数据类型
// 例如指定元素类型为<String>,则该集合中不能添加非 String 类型的元素。
 List<T> list = new ArrayList<T>();

常用方法

方法描述
void add(int index,Object element)将元素(element)插入到List的指定位置(index)处
boolean addAll(int index,Collection c)将集合c所包含的所有元素都插入List集合指定位置(index)处
Object get(int index)获取此集合中指定索引位置的元素
int indexOf(Object o)返回此集合中第一次出现指定元素的索引,如果此集合不包含该元素,则返回 -1
int lastIndexOf(Object o)返回此集合中最后一次出现指定元素的索引,如果此集合不包含该元素,则返回 -1
E set(int index, Eelement)将此集合中指定索引位置的元素修改为 element 参数指定的对象。此方法返回此集合中指定索引位置的原元素
List<E> subList(int fromlndex, int tolndex)返回一个新的集合,新集合中包含 fromlndex 和 tolndex 索引之间的所有元素。 包含 fromlndex 处的元素,不包含 tolndex 索引处的元素

LinkedList类

双向链表,可以从任意节点快速访问到上下节点信息

可以使用List接口所有方法

常用方法

方法描述
void addFirst(E e)将指定元素添加到此集合的开头
void addLast(E e)将指定元素添加到此集合的末尾
E getFirst()返回此集合的第一个元素
E getLast()返回此集合的最后一个元素
E removeFirst()删除此集合中的第一个元素
E removeLast()删除此集合中的最后一个元素

Set 接口

如果向 Set 集合中添加两个相同的元素,则后添加的会覆盖前面添加的元素,即在 Set 集合中不会出现相同的元素。

HashSet 类

  • HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
  • HashSet 允许有 null 值。
  • HashSet 是无序的,即不会记录插入的顺序。
  • HashSet 不是线程安全的

TreeSet 类

方法描述
E first()返回此集合中的第一个元素。其中,E 表示集合中元素的数据类型
E last()返回此集合中的最后一个元素
E poolFirst()获取并移除此集合中的第一个元素
E poolLast()获取并移除此集合中的最后一个元素
SortedSet<E> subSet(E fromElement,E toElement)返回一个新的集合,新集合包含原集合中 fromElement 对象与 toElement对象之间的所有对象。包含 fromElement 对象,不包含 toElement 对象
SortedSet<E> headSet<E toElement〉返回一个新的集合,新集合包含原集合中 toElement 对象之前的所有对象。不包含 toElement 对象
SortedSet<E> tailSet(E fromElement)返回一个新的集合,新集合包含原集合中 fromElement 对象之后的所有对象。包含 fromElement 对象

示列

TreeSet<Double> scores = new TreeSet<Double>(); // 创建 TreeSet 集合
Scanner input = new Scanner(System.in);
System.out.println("------------学生成绩管理系统-------------");
 for (int i = 0; i < 5; i++) {
     System.out.println("第" + (i + 1) + "个学生成绩:");
     double score = input.nextDouble();
     // 将学生成绩转换为Double类型,添加到TreeSet集合中
     scores.add(Double.valueOf(score));
 }
 Iterator<Double> it = scores.iterator(); // 创建 Iterator 对象
 System.out.println("学生成绩从低到高的排序为:");
     while (it.hasNext()) {
         System.out.print(it.next() + "\t");
     }
 System.out.println("\n请输入要查询的成绩:");
 double searchScore = input.nextDouble();
    if (scores.contains(searchScore)) {
         System.out.println("成绩为: " + searchScore + " 的学生存在!");
     } else {
         System.out.println("成绩为: " + searchScore + " 的学生不存在!");
     }
 // 查询不及格的学生成绩
 SortedSet<Double> score1 = scores.headSet(60.0);
     System.out.println("\n不及格的成绩有:");
     for (int i = 0; i < score1.toArray().length; i++) {
     System.out.print(score1.toArray()[i] + "\t");
     }
 // 查询90分以上的学生成绩
 SortedSet<Double> score2 = scores.tailSet(90.0);
     System.out.println("\n90 分以上的成绩有:");
     for (int i = 0; i < score2.toArray().length; i++) {
         System.out.print(score2.toArray()[i] + "\t");
     }

泛型集合

当录入对象的数据类型与希望转换的目标类型不符时,会引发ClassCastException异常。泛型可以约束录入集合的元素类型,大大提高了数据安全性,从集合取出数据无需进行类型转换,从而让代码更加简洁,程序更加健壮。

应用

泛型集合可以把类型当作参数一样传递。可以在创建集合时,指定这些类型参数,即元素的数据类型,添加的元素必须是指定类型的对象。

集合框架思维导图

最后修改:2022 年 11 月 09 日
如果觉得我的文章对你有用,请随意赞赏