Java中的集合
任何被表示为单个单元的个别对象组合被称为该对象的集合。在Java中,一个名为 “Collection Framework” 的独立框架已经在JDK 1.2中定义,其中存储了所有包含集合类和接口的内容。
集合接口( java.util.Collection )和映射接口( java.util.Map )是Java集合类的两个主要的“根”接口。
什么是框架?
框架是一组提供现成的类和接口的可用架构。在Java中实现新的功能或类时,没有必要定义一个框架。但是,最优的面向对象设计始终包括一个集合类框架,所有类都执行相同类型的任务。
需要一个单独的集合框架
在引入集合框架(或在JDK 1.2之前),标准方法用于分组Java对象(或集合)实际上是使用 数组 , 向量 ,或 哈希表 ,所有这些集合都没有公共接口。 因此,虽然所有集合的主要目标相同,但所有这些集合的实现都是独立定义,它们之间没有关联。并且,对于用户来说,非常难记住每个集合类中存在的所有不同的 方法 ,语法和 构造函数 。
让我们以向哈希表和向量添加元素的示例来理解这一点。
// Java程序演示
// 为何需要集合框架
import java.io.*;
import java.util.*;
class CollectionDemo {
public static void main(String[] args)
{
// 创建数组,向量和哈希表实例
int arr[] = new int[] { 1, 2, 3, 4 };
Vector<Integer> v = new Vector();
Hashtable<Integer, String> h = new Hashtable();
// 将元素添加到向量中
v.addElement(1);
v.addElement(2);
// 将元素添加到哈希表
h.put(1, "geeks");
h.put(2, "4geeks");
// 数组实例创建需要[],
// 而向量和哈希表需要()
// 向量元素插入需要addElement(),
// 但哈希表元素插入需要put()
// 访问数组,向量和哈希表的第一个元素
System.out.println(arr[0]);
System.out.println(v.elementAt(0));
System.out.println(h.get(1));
// 使用[]访问数组元素,
// 使用elementAt()访问向量元素
// 以及使用get()访问哈希表元素
}
}
输出:
1
1
geeks
正如我们所观察到的那样,这些集合(Array、Vector或Hashtable)中没有一个实现了标准的成员访问接口,这使得程序员很难编写可以适用于所有类型集合的算法。另一个缺点是,大多数“Vector”方法都是final,这意味着我们不能扩展’Vector’类来实现类似的集合。因此,Java开发人员决定引入一种通用的接口来解决上述问题,并在JDK 1.2中介绍了集合框架,此后,旧的Vectors和Hashtables被修改以符合集合框架。
集合框架的优点: 由于缺乏集合框架导致了上述一系列缺点,以下是集合框架的优点。
- 一致的API: API具有基本集合接口,如 集合 , 集 , 列表 或 图 ,所有实现这些接口的类(ArrayList、LinkedList、Vector等)都有 一些 共同的方法。
-
减少编程工作量: 程序员不必担心集合的设计,而可以关注它在程序中的最佳使用。因此,面向对象编程的基本概念(即)抽象已成功实现。
-
提高程序速度和质量: 通过提供实用数据结构和算法的高性能实现来提高性能,因为在这种情况下,程序员无需考虑特定数据结构的最佳实现。他可以简单地使用最佳实现来大幅提高算法/程序的性能。
集合框架的层次结构
实用程序包(java.util)包含集合框架所需的所有类和接口。集合框架包含一个名为可迭代接口的接口,为所有集合提供迭代器。此接口由主集合接口扩展,其作为集合框架的根。所有集合都扩展了这个集合接口,从而扩展了迭代器的属性和此接口的方法。下图说明了集合框架的层次结构。
在了解上述框架中的不同组件之前,让我们先了解一个类和一个接口。
- 类 : 类是用户定义的蓝图或原型,用于创建对象。它表示该类型所有对象的一组属性或方法。
-
接口 : 与类一样,接口也可以具有方法和变量,但接口中声明的方法默认为抽象(仅是方法签名,没有主体)。接口指定类必须做什么而不是如何做。它是类的蓝图。
集合接口的方法
此接口包含各种可由实现此接口的所有集合直接使用的方法。它们是:
方法 | 描述 |
---|---|
add(Object) | 此方法用于将对象添加到集合中。 |
addAll(Collection c) | 此方法将给定集合中的所有元素添加到此集合中。 |
clear() | 此方法从集合中删除所有元素。 |
contains(Object o) | 如果集合包含指定的元素,则此方法返回true。 |
containsAll(Collection c) | 如果集合包含给定集合中的所有元素,则此方法返回true。 |
equals(Object o) | 此方法将指定对象与此集合进行比较,以确定它们是否相等。 |
hashCode() | 此方法用于返回该集合的哈希码值。 |
isEmpty() | 如果此集合不包含任何元素,则此方法返回true。 |
iterator() | 此方法返回一个迭代器,可遍历此集合中的所有元素。 |
max() | 此方法用于返回集合中存在的最大值。 |
parallelStream() | 此方法返回一个并行流,其源为此集合。 |
remove(Object o) | 此方法用于从集合中删除给定对象。如果存在重复值,则此方法会删除对象的第一个出现。 |
removeAll(Collection c) | 此方法用于从集合中删除给定集合中提到的所有对象。 |
removeIf(Predicate filter) | 此方法用于删除满足给定条件的此集合的所有元素。 |
retainAll(Collection c) | 此方法仅保留此集合中包含在指定集合中的元素。 |
size() | 此方法用于返回集合中的元素数量。 |
spliterator() | 此方法用于在此集合中的元素上创建Spliterator。 |
stream() | 此方法用于返回具有此集合作为源的顺序流。 |
toArray() | 此方法用于返回包含此集合中所有元素的数组。 |
扩展 Collections 接口的接口
集合框架包含多个接口,其中每个接口用于存储特定类型的数据。以下是框架中存在的接口。
1. Iterable 接口: 这是整个集合框架的根接口。集合接口扩展了iterable接口。因此,固有地,所有的接口和类都实现了这个接口。该接口的主要功能是为集合提供迭代器。因此,该接口仅包含一个抽象方法,即迭代器,它返回
Iterator iterator();
2. 集合接口: 此接口扩展了可迭代接口,并由集合框架中的所有类实现。此接口包含每个集合都具有的所有基本方法,例如将数据添加到集合中、删除数据、清除数据等。所有这些方法都实现在此接口中,因为这些方法由所有类实现,而不考虑它们的实现方式。此外,在此接口中具有这些方法可以确保方法名称对于所有集合都是通用的。因此,简单地说,我们可以说该接口构建了集合类的基础。
3. 列表接口: 这是集合接口的子接口。此接口专用于列表类型的数据,其中我们可以存储所有对象的有序集合。这也允许在其中存在重复数据。该列表接口由各种类如ArrayList, Vector, Stack等实现。由于所有的子类都实现了列表,因此我们可以使用这些类中的任何一个来实例化一个列表对象。例如,
List<T> al = new ArrayList<> ();
List<T> ll = new LinkedList<> ();
List<T> v = new Vector<> ();
其中T是对象的类型
实现List接口的类如下:
A. ArrayList: ArrayList 在 Java 中为我们提供了动态数组。虽然它可能比标准数组慢,但在需要在数组中进行许多操作的程序中可能很有帮助。如果集合增长,则 ArrayList 的大小会自动增加,如果从集合中删除对象,则ArrayList的大小会自动缩小。Java ArrayList 允许我们随机访问列表。ArrayList 不能用于基本类型,如 int、char等。对于这种情况,我们需要一个包装类。让我们通过以下示例了解 ArrayList:
//Java程序演示
//演示ArrayList的工作原理
import java.io.*;
import java.util.*;
class GFG {
// 主方法
public static void main(String[] args) {
// 声明具有初始大小n的ArrayList
ArrayList<Integer> al = new ArrayList<Integer>();
// 在列表末尾添加新元素
for (int i = 1; i <= 5; i++)
al.add(i);
// 打印元素
System.out.println(al);
// 删除索引3处的元素
al.remove(3);
// 删除后显示ArrayList
System.out.println(al);
// 逐个打印元素
for (int i = 0; i < al.size(); i++)
System.out.print(al.get(i) + " ");
}
}
输出:
[1,2,3,4,5]
[1,2,3,5]
1 2 3 5
B. LinkedList: LinkedList 类是 LinkedList 数据结构的实现,它是一个线性数据结构,其中的元素不存储在连续的位置上,每个元素都是一个带有数据部分和地址部分的单独对象。元素使用指针和地址链接。每个元素称为节点。让我们通过以下示例了解LinkedList:
//演示LinkedList工作的Java程序
import java.io.*;
import java.util.*;
class GFG {
//主方法
public static void main(String[] args)
{
//声明LinkedList
LinkedList<Integer> ll = new LinkedList<Integer>();
//将新元素追加到列表末尾
for (int i = 1; i <= 5; i++)
ll.add(i);
//输出元素
System.out.println(ll);
//移除索引3的元素
ll.remove(3);
//删除后显示列表
System.out.println(ll);
//逐个打印元素
for (int i = 0; i < ll.size(); i++)
System.out.print(ll.get(i) + " ");
}
}
输出:
[1, 2, 3, 4, 5]
[1, 2, 3, 5]
1 2 3 5
C. 矢量: 矢量为我们提供了Java中的动态数组。尽管它可能比标准数组慢,但在需要对数组进行大量操作的程序中可能很有帮助。在实施方面类似于ArrayList。但是,Vector和ArrayList之间的主要区别是Vector是同步的,而ArrayList是非同步的。让我们通过示例来了解Vector:
//演示Vector工作的Java程序
import java.io.*;
import java.util.*;
class GFG {
//主方法
public static void main(String[] args)
{
//声明Vector
Vector<Integer> v = new Vector<Integer>();
//将新元素追加到列表末尾
for (int i = 1; i <= 5; i++)
v.add(i);
//输出元素
System.out.println(v);
//移除索引3的元素
v.remove(3);
//显示删除后的Vector
System.out.println(v);
//逐个打印元素
for (int i = 0; i < v.size(); i++)
System.out.print(v.get(i) + " ");
}
}
输出:
[1, 2, 3, 4, 5]
[1, 2, 3, 5]
1 2 3 5
D. 栈: 栈类模型和实现堆栈数据结构。该类基于后进先出的基本原则。除了基本的push和pop操作之外,该类还提供了三个函数empty、search和peek。该类也可以被称为Vector的子类。让我们通过示例来了解Stack:
//演示堆栈工作的Java程序
import java.util.*;
public class GFG {
//主方法
public static void main(String args[])
{
Stack<String> stack = new Stack<String>();
stack.push("Geeks");
stack.push("For");
stack.push("Geeks");
stack.push("Geeks");
//堆栈的迭代器
Iterator<String> itr = stack.iterator();
//打印堆栈
while (itr.hasNext()) {
System.out.print(itr.next() + " ");
}
System.out.println();
stack.pop();
//堆栈的迭代器
itr = stack.iterator();
//打印堆栈
while (itr.hasNext()) {
System.out.print(itr.next() + " ");
}
}
}
输出:
Geeks For Geeks Geeks
Geeks For Geeks
注意: 栈是Vector的子类和传统类。它是线程安全的,但在线程安全不需要的环境中可能会有额外的开销。Stack的替代选择是使用ArrayDequeue,它不是线程安全的,并且具有更快的数组实现。
4. 队列接口 : 顾名思义,队列接口维护FIFO(先进先出)顺序,类似于现实生活中的排队。此接口专门用于存储所有元素,其中元素的顺序很重要。例如,每当我们尝试预订车票时,车票都是按照先来先服务的原则出售的。因此,请求最先进入队列的人会得到车票。有各种类别,例如PriorityQueue,ArrayDeque等。由于所有这些子类都实现队列,因此我们可以使用这些类之一实例化队列对象。例如,
Queue <T> pq = new PriorityQueue<> ();
Queue <T> ad = new ArrayDeque<> ();
在这里,T是对象的类型。
最常用的队列接口实现是PriorityQueue。
优先队列: 当对象需要按优先级进行处理时使用PriorityQueue。已知队列遵循先进先出算法,但有时需要根据优先级处理队列的元素,在这种情况下使用此类。优先队列基于优先堆。优先队列的元素根据优先自然排序或由在构造队列时提供的比较器排序,具体取决于使用哪种构造函数。让我们通过一个示例来了解优先队列:
// Java program to demonstrate the working of
// priority queue in Java
import java.util.*;
class GfG {
// Main Method
public static void main(String args[])
{
// Creating empty priority queue
PriorityQueue<Integer> pQueue = new PriorityQueue<Integer>();
// Adding items to the pQueue using add()
pQueue.add(10);
pQueue.add(20);
pQueue.add(15);
// Printing the top element of PriorityQueue
System.out.println(pQueue.peek());
// Printing the top element and removing it
// from the PriorityQueue container
System.out.println(pQueue.poll());
// Printing the top element again
System.out.println(pQueue.peek());
}
}
输出:
10
10
15
5. Deque接口 : 这是队列数据结构的微小变体。Deque,也称为双端队列,是一种数据结构,可以在队列的两端添加和删除元素。该接口扩展了队列接口。实现此接口的类是ArrayDeque。由于ArrayDeque类实现了Deque接口,因此可以使用该类实例化deque对象。例如,
Deque<T> ad = new ArrayDeque<> ();
在这里,T是对象的类型。
实现deque接口的类是ArrayDeque。
ArrayDeque: ArrayDeque类在集合框架中的实现为我们提供了一种应用可调整大小数组的方法。这是一种特殊类型的数组,它可以增加并允许用户从队列的两侧添加或删除元素。数组双端队列没有容量限制,如果需要,可以根据使用情况扩展容量大小。 让我们通过一个例子来了解ArrayDeque:
// Java演示了
// ArrayDeque类在Java中的用法
import java.util.*;
public class ArrayDequeDemo {
public static void main(String[] args)
{
// 初始化deque
ArrayDeque<Integer> de_que = new ArrayDeque<Integer>(10);
// add()方法添加元素
de_que.add(10);
de_que.add(20);
de_que.add(30);
de_que.add(40);
de_que.add(50);
System.out.println(de_que);
// clear()方法清空
de_que.clear();
// addFirst()方法添加到头部
de_que.addFirst(564);
de_que.addFirst(291);
// addLast()方法添加到尾部
de_que.addLast(24);
de_que.addLast(14);
System.out.println(de_que);
}
}
输出:
[10, 20, 30, 40, 50]
[291, 564, 24, 14]
6. Set接口 : Set是一个无序的对象集合,其中不能存储重复值。当我们希望避免对象重复且只希望存储唯一对象时,可以使用此集合。Set接口由各种类实现,如HashSet,TreeSet,LinkedHashSet等。由于所有子类都实现了Set,因此我们可以使用任何这些类之一实例化Set对象。例如,
Set<T> hs = new HashSet<> ();
Set<T> lhs = new LinkedHashSet<> ();
Set<T> ts = new TreeSet<> ();
其中T是对象的类型。
以下是实现Set接口的类:
A. HashSet: HashSet类是哈希表数据结构的内置实现。我们插入HashSet中的对象不能保证按相同的顺序插入。对象是基于它们的哈希码进行插入的。该类还允许插入NULL元素。让我们通过一个例子来了解HashSet:
// Java演示了
// HashSet的操作
import java.util.*;
public class HashSetDemo {
// Main方法
public static void main(String args[])
{
// 创建HashSet并添加元素
HashSet<String> hs = new HashSet<String>();
hs.add("Geeks");
hs.add("For");
hs.add("Geeks");
hs.add("Is");
hs.add("Very helpful");
// 遍历元素
Iterator<String> itr = hs.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
}
输出:
Very helpful
Geeks
For
Is
B. LinkedHashSet :
LinkedHashSet与HashSet非常相似。区别在于这个集合使用双向链表来存储数据并保留元素的顺序。让我们通过一个例子来了解LinkedHashSet:
// Java程序演示LinkedHashSet的工作原理
import java.util.*;
public class LinkedHashSetDemo {
// 主方法
public static void main(String args[])
{
//创建LinkedHashSet并添加元素
LinkedHashSet<String> lhs = new LinkedHashSet<String>();
lhs.add("Geeks");
lhs.add("For");
lhs.add("Geeks");
lhs.add("Is");
lhs.add("Very helpful");
// 遍历元素
Iterator<String> itr = lhs.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
}
输出:
Geeks
For
Is
Very helpful
7. Sorted Set接口: 此接口类似于Set接口。唯一的区别是此接口具有额外的方法,可以维护元素的顺序。Sorted Set接口扩展了Set接口,并用于处理需要排序的数据。实现此接口的类是TreeSet。由于此类实现了SortedSet,因此可以使用此类实例化SortedSet对象。例如,
SortedSet<T> ts = new TreeSet<> ();
其中T是对象的类型。
实现排序集接口的类是TreeSet。
TreeSet: TreeSet类使用树进行存储。使用他们的自然排序来维护元素的顺序,无论是否提供显式比较器。如果要正确实现Set接口,则必须与equals保持一致。根据使用哪个构造函数,也可以由在设置创建时间提供的比较器进行排序。让我们用一个例子来理解TreeSet:
// Java程序演示TreeSet的工作原理
import java.util.*;
public class TreeSetDemo {
// 主方法
public static void main(String args[])
{
//创建TreeSet并添加元素
TreeSet<String> ts = new TreeSet<String>();
ts.add("Geeks");
ts.add("For");
ts.add("Geeks");
ts.add("Is");
ts.add("Very helpful");
// 遍历元素
Iterator<String> itr = ts.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
}
输出:
For
Geeks
Is
Very helpful
8. Map接口: Map是支持键值对映射数据的数据结构。此接口不支持重复的键,因为相同的键不能具有多个映射,但允许不同键中具有重复的值。如果存在数据希望根据键执行操作,则Map很有用。此Map接口由各种类实现,如HashMap,TreeMap等。由于所有子类都实现了map,因此可以使用这些类中的任何一个实例化map对象。例如,
Map<T> hm = new HashMap<> ();
Map<T> tm = new TreeMap<> ();
其中T是对象的类型。
常用的实现Map接口的方法是HashMap。
HashMap: HashMap提供了Java的Map接口的基本实现。它以(Key, Value)的形式存储数据。要在HashMap中访问一个值,我们必须知道它的键。HashMap使用一种叫做哈希的技术。哈希是一种将一个大字符串转换成表示相同字符串的小字符串的技术,从而使索引和搜索操作更快速。HashSet也内部使用HashMap。让我们通过一个例子了解一下HashMap:
// Java program to demonstrate the
// working of a HashMap
import java.util.*;
public class HashMapDemo {
// Main Method
public static void main(String args[]) {
// Creating HashMap and adding elements
HashMap<Integer, String> hm = new HashMap<Integer, String>();
hm.put(1, "Geeks");
hm.put(2, "For");
hm.put(3, "Geeks");
// Finding the value for a key
System.out.println("Value for 1 is " + hm.get(1));
// Traversing through the HashMap
for (Map.Entry<Integer, String> e : hm.entrySet())
System.out.println(e.getKey() + " " + e.getValue());
}
}
输出:
Value for 1 is Geeks
1 Geeks
2 For
3 Geeks