MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java Collection接口的继承体系及关系梳理

2022-12-163.2k 阅读

Java Collection接口的继承体系概述

在Java编程中,Collection接口是集合框架的根接口之一,它定义了一组用于操作对象集合的方法。Java集合框架提供了一套丰富的接口和类,用于存储、检索、操作和管理数据集合。Collection接口的继承体系展示了不同类型集合之间的关系,理解这个体系对于有效使用Java集合框架至关重要。

Java集合框架主要分为两大类:CollectionMapMap用于存储键值对,而Collection则用于存储单个元素。Collection接口继承体系中包含了多个子接口和实现类,这些接口和类根据其特性和用途又可以进一步分类。

主要子接口

  1. List接口List接口继承自Collection接口,它表示一个有序的集合,允许重复元素。List接口提供了基于索引的访问方法,使得可以精确地控制元素的插入位置、获取特定位置的元素以及对列表进行排序等操作。常见的List实现类有ArrayListLinkedListVector
  2. Set接口Set接口同样继承自Collection接口,它表示一个不包含重复元素的集合。集合中的元素无序且唯一,这意味着向Set中添加重复元素时,Set会忽略该操作。常见的Set实现类有HashSetTreeSetLinkedHashSet
  3. Queue接口Queue接口继承自Collection接口,它用于存储等待处理的元素,通常遵循先进先出(FIFO)的原则。Queue接口提供了一些特定的方法,如offer用于向队列中添加元素,poll用于从队列中移除并返回头元素,peek用于返回头元素但不移除。常见的Queue实现类有PriorityQueueLinkedListLinkedList实现了Queue接口)。

List接口及其实现类

List接口的特点与方法

List接口是Collection接口的子接口,它具有以下特点:

  • 有序性List中的元素按照插入顺序或指定的顺序存储,每个元素都有一个索引位置。
  • 可重复性List允许存储重复的元素。

List接口定义了许多方法,除了继承自Collection接口的方法外,还增加了一些基于索引的操作方法:

  • add(int index, E element):将指定元素插入到列表的指定位置。
  • get(int index):返回列表中指定位置的元素。
  • set(int index, E element):用指定元素替换列表中指定位置的元素。
  • remove(int index):移除列表中指定位置的元素。
  • indexOf(Object o):返回指定元素在列表中第一次出现的索引,如果列表不包含该元素,则返回 -1。
  • lastIndexOf(Object o):返回指定元素在列表中最后一次出现的索引,如果列表不包含该元素,则返回 -1。

ArrayList

ArrayListList接口的一个常用实现类,它基于数组实现。ArrayList具有以下特点:

  • 动态数组ArrayList的容量会根据需要自动增长。当向ArrayList中添加元素时,如果当前容量不足以容纳新元素,ArrayList会自动创建一个更大的数组,并将原数组中的元素复制到新数组中。
  • 随机访问效率高:由于ArrayList基于数组实现,通过索引访问元素的时间复杂度为O(1),因此随机访问效率很高。
  • 插入和删除效率低:在ArrayList中插入和删除元素时,需要移动数组中的其他元素,时间复杂度为O(n),因此在列表中间插入和删除元素的效率较低。

以下是一个使用ArrayList的代码示例:

import java.util.ArrayList;
import java.util.List;

public class ArrayListExample {
    public static void main(String[] args) {
        // 创建一个ArrayList对象
        List<String> list = new ArrayList<>();

        // 添加元素
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 打印列表
        System.out.println("List: " + list);

        // 通过索引获取元素
        String element = list.get(1);
        System.out.println("Element at index 1: " + element);

        // 在指定位置插入元素
        list.add(1, "Grape");
        System.out.println("List after insertion: " + list);

        // 删除指定位置的元素
        String removedElement = list.remove(2);
        System.out.println("Removed element: " + removedElement);
        System.out.println("List after removal: " + list);
    }
}

LinkedList

LinkedList也是List接口的一个实现类,它基于双向链表实现。LinkedList具有以下特点:

  • 动态链表LinkedList的每个节点包含前驱节点和后继节点的引用,因此可以在链表的任意位置高效地插入和删除元素。
  • 随机访问效率低:由于LinkedList不是基于数组实现,通过索引访问元素时需要从头或从尾遍历链表,时间复杂度为O(n),因此随机访问效率较低。
  • 插入和删除效率高:在LinkedList中插入和删除元素只需要修改节点的引用,时间复杂度为O(1),因此在列表中间插入和删除元素的效率很高。

以下是一个使用LinkedList的代码示例:

import java.util.LinkedList;
import java.util.List;

public class LinkedListExample {
    public static void main(String[] args) {
        // 创建一个LinkedList对象
        List<String> list = new LinkedList<>();

        // 添加元素
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 打印列表
        System.out.println("List: " + list);

        // 通过索引获取元素
        String element = list.get(1);
        System.out.println("Element at index 1: " + element);

        // 在指定位置插入元素
        list.add(1, "Grape");
        System.out.println("List after insertion: " + list);

        // 删除指定位置的元素
        String removedElement = list.remove(2);
        System.out.println("Removed element: " + removedElement);
        System.out.println("List after removal: " + list);
    }
}

Vector

VectorList接口的一个古老实现类,它也基于数组实现。VectorArrayList类似,但Vector是线程安全的,而ArrayList是非线程安全的。由于线程安全会带来性能开销,因此在单线程环境下,ArrayList通常比Vector更高效。

Vector的大部分方法与ArrayList相同,但Vector的一些方法,如addElementelementAt等,是在Java早期版本中定义的,在现代Java编程中,推荐使用ArrayListCollections.synchronizedList来创建线程安全的列表。

以下是一个使用Vector的代码示例:

import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        // 创建一个Vector对象
        Vector<String> vector = new Vector<>();

        // 添加元素
        vector.addElement("Apple");
        vector.addElement("Banana");
        vector.addElement("Orange");

        // 打印向量
        System.out.println("Vector: " + vector);

        // 通过索引获取元素
        String element = vector.elementAt(1);
        System.out.println("Element at index 1: " + element);

        // 在指定位置插入元素
        vector.insertElementAt("Grape", 1);
        System.out.println("Vector after insertion: " + vector);

        // 删除指定位置的元素
        vector.removeElementAt(2);
        System.out.println("Vector after removal: " + vector);
    }
}

Set接口及其实现类

Set接口的特点与方法

Set接口继承自Collection接口,它具有以下特点:

  • 唯一性Set中不允许存储重复的元素,当向Set中添加重复元素时,Set会忽略该操作。
  • 无序性Set中的元素通常是无序的,即元素的存储顺序与插入顺序无关。

Set接口继承了Collection接口的所有方法,没有额外定义新的方法。

HashSet

HashSetSet接口的一个常用实现类,它基于哈希表实现。HashSet具有以下特点:

  • 快速查找HashSet使用哈希表存储元素,因此添加、删除和查找元素的时间复杂度通常为O(1),效率很高。
  • 无序性HashSet中的元素是无序的,元素的存储顺序与插入顺序无关。

以下是一个使用HashSet的代码示例:

import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        // 创建一个HashSet对象
        Set<String> set = new HashSet<>();

        // 添加元素
        set.add("Apple");
        set.add("Banana");
        set.add("Orange");
        set.add("Apple"); // 重复元素,将被忽略

        // 打印集合
        System.out.println("Set: " + set);

        // 检查集合是否包含某个元素
        boolean contains = set.contains("Banana");
        System.out.println("Set contains Banana: " + contains);

        // 删除元素
        set.remove("Orange");
        System.out.println("Set after removal: " + set);
    }
}

TreeSet

TreeSetSet接口的另一个实现类,它基于红黑树实现。TreeSet具有以下特点:

  • 有序性TreeSet中的元素是有序的,默认按照自然顺序(如数字从小到大、字符串按字典序)排序。也可以通过构造函数传入自定义的比较器来指定排序规则。
  • 元素唯一性:与其他Set实现类一样,TreeSet不允许存储重复元素。

以下是一个使用TreeSet的代码示例:

import java.util.Set;
import java.util.TreeSet;

public class TreeSetExample {
    public static void main(String[] args) {
        // 创建一个TreeSet对象
        Set<String> set = new TreeSet<>();

        // 添加元素
        set.add("Banana");
        set.add("Apple");
        set.add("Orange");

        // 打印集合,元素按自然顺序排序
        System.out.println("Set: " + set);

        // 检查集合是否包含某个元素
        boolean contains = set.contains("Banana");
        System.out.println("Set contains Banana: " + contains);

        // 删除元素
        set.remove("Orange");
        System.out.println("Set after removal: " + set);
    }
}

LinkedHashSet

LinkedHashSetHashSet的子类,它基于哈希表和双向链表实现。LinkedHashSet具有以下特点:

  • 有序性LinkedHashSet中的元素按照插入顺序或访问顺序存储,保持了元素的插入顺序。
  • 快速查找:由于LinkedHashSet基于哈希表实现,添加、删除和查找元素的时间复杂度通常为O(1),效率很高。

以下是一个使用LinkedHashSet的代码示例:

import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetExample {
    public static void main(String[] args) {
        // 创建一个LinkedHashSet对象
        Set<String> set = new LinkedHashSet<>();

        // 添加元素
        set.add("Apple");
        set.add("Banana");
        set.add("Orange");

        // 打印集合,元素按插入顺序排列
        System.out.println("Set: " + set);

        // 检查集合是否包含某个元素
        boolean contains = set.contains("Banana");
        System.out.println("Set contains Banana: " + contains);

        // 删除元素
        set.remove("Orange");
        System.out.println("Set after removal: " + set);
    }
}

Queue接口及其实现类

Queue接口的特点与方法

Queue接口继承自Collection接口,它用于存储等待处理的元素,通常遵循先进先出(FIFO)的原则。Queue接口定义了一些特定的方法:

  • offer(E e):将指定元素添加到队列的尾部,如果队列已满,返回false;否则返回true
  • poll():移除并返回队列的头元素,如果队列为空,返回null
  • peek():返回队列的头元素,但不移除,如果队列为空,返回null
  • add(E e):将指定元素添加到队列的尾部,如果队列已满,抛出IllegalStateException
  • remove():移除并返回队列的头元素,如果队列为空,抛出NoSuchElementException

PriorityQueue

PriorityQueueQueue接口的一个实现类,它基于堆数据结构实现。PriorityQueue中的元素按照自然顺序或自定义的比较器顺序进行排序,每次从队列中取出的元素是优先级最高的元素。

以下是一个使用PriorityQueue的代码示例:

import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        // 创建一个PriorityQueue对象
        Queue<Integer> queue = new PriorityQueue<>();

        // 添加元素
        queue.offer(3);
        queue.offer(1);
        queue.offer(2);

        // 打印队列
        System.out.println("Queue: " + queue);

        // 取出并打印优先级最高的元素
        Integer element = queue.poll();
        System.out.println("Polled element: " + element);
        System.out.println("Queue after poll: " + queue);
    }
}

LinkedList类作为Queue的实现

前面提到LinkedList不仅实现了List接口,还实现了Queue接口。因此,可以将LinkedList当作Queue来使用,利用其链表结构的特点实现高效的队列操作。

以下是一个将LinkedList当作Queue使用的代码示例:

import java.util.LinkedList;
import java.util.Queue;

public class LinkedListAsQueueExample {
    public static void main(String[] args) {
        // 创建一个LinkedList对象,并当作Queue使用
        Queue<String> queue = new LinkedList<>();

        // 添加元素
        queue.offer("Apple");
        queue.offer("Banana");
        queue.offer("Orange");

        // 打印队列
        System.out.println("Queue: " + queue);

        // 取出并打印队列头元素
        String element = queue.poll();
        System.out.println("Polled element: " + element);
        System.out.println("Queue after poll: " + queue);
    }
}

Collection接口继承体系中的其他接口和类

Deque接口及其实现类

Deque(双端队列)接口继承自Queue接口,它允许在队列的两端进行插入和删除操作。Deque接口提供了更多的方法,如addFirstaddLastremoveFirstremoveLast等。

ArrayDequeLinkedListDeque接口的两个常见实现类。ArrayDeque基于数组实现,具有较高的性能;LinkedList基于链表实现,在插入和删除操作上具有灵活性。

以下是一个使用ArrayDeque的代码示例:

import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeExample {
    public static void main(String[] args) {
        // 创建一个ArrayDeque对象
        Deque<String> deque = new ArrayDeque<>();

        // 添加元素到队首和队尾
        deque.addFirst("Apple");
        deque.addLast("Banana");
        deque.addFirst("Grape");

        // 打印双端队列
        System.out.println("Deque: " + deque);

        // 移除并打印队首和队尾元素
        String removedFirst = deque.removeFirst();
        System.out.println("Removed first element: " + removedFirst);
        String removedLast = deque.removeLast();
        System.out.println("Removed last element: " + removedLast);
        System.out.println("Deque after removal: " + deque);
    }
}

BlockingQueue接口及其实现类

BlockingQueue接口继承自Queue接口,它提供了线程安全的队列操作。BlockingQueue的特点是当队列为空时,从队列中获取元素的操作会阻塞;当队列已满时,向队列中添加元素的操作会阻塞。

常见的BlockingQueue实现类有ArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueue等。这些实现类在多线程编程中常用于生产者 - 消费者模型。

以下是一个简单的生产者 - 消费者模型示例,使用ArrayBlockingQueue

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class Producer implements Runnable {
    private BlockingQueue<Integer> queue;

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Producing: " + i);
                queue.put(i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

class Consumer implements Runnable {
    private BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            for (int i = 1; i <= 5; i++) {
                Integer num = queue.take();
                System.out.println("Consuming: " + num);
                Thread.sleep(1500);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

public class BlockingQueueExample {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
        Thread producerThread = new Thread(new Producer(queue));
        Thread consumerThread = new Thread(new Consumer(queue));

        producerThread.start();
        consumerThread.start();

        try {
            producerThread.join();
            consumerThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

NavigableSet接口及其实现类

NavigableSet接口继承自SortedSet接口,它提供了更丰富的导航方法,如lowerfloorceilinghigher等,用于在有序集合中查找与给定元素相关的元素。

TreeSetNavigableSet接口的主要实现类,通过TreeSet可以方便地使用NavigableSet接口定义的导航功能。

以下是一个使用TreeSet作为NavigableSet的代码示例:

import java.util.NavigableSet;
import java.util.TreeSet;

public class NavigableSetExample {
    public static void main(String[] args) {
        NavigableSet<Integer> set = new TreeSet<>();
        set.add(1);
        set.add(3);
        set.add(5);
        set.add(7);

        // 使用导航方法
        Integer lower = set.lower(4);
        System.out.println("Lower than 4: " + lower);

        Integer floor = set.floor(4);
        System.out.println("Floor of 4: " + floor);

        Integer ceiling = set.ceiling(4);
        System.out.println("Ceiling of 4: " + ceiling);

        Integer higher = set.higher(4);
        System.out.println("Higher than 4: " + higher);
    }
}

通过深入理解Collection接口的继承体系及各个接口和类的特点与关系,开发者能够根据具体的需求选择最合适的集合类型,从而编写出高效、健壮的Java程序。在实际应用中,要综合考虑集合的性能、线程安全性、元素唯一性和有序性等因素,以充分发挥Java集合框架的强大功能。