Java Collections
任何一组单独的对象被表示为一个单元,被称为对象的集合。在Java中,一个名为”集合框架”的独立框架在JDK 1.2中被定义,它包含了所有的集合类和接口。
集合接口( java.util.Collection )和Map接口( java.util.Map)是Java集合类的两个主要 “根 “接口。
什么是框架?
框架是一组类和接口,它提供了一个现成的架构。为了实现一个新的功能或一个类,不需要定义一个框架。然而,一个最佳的面向对象的设计总是包括一个带有类集合的框架,这样所有的类都执行同一种任务。
需要一个单独的集合框架
在引入集合框架之前(或在JDK 1.2之前),分组Java对象(或集合)的标准方法是 Arrays 或 Vectors ,或 Hashtables。 所有这些集合都没有共同的接口。因此,尽管所有集合的主要目的是相同的,但所有这些集合的实现都是独立定义的,它们之间没有关联。而且,用户也很难记住每个集合类中存在的所有不同的 方法 、语法和 构造函数 。
让我们通过一个在hashtable和vector中添加元素的例子来理解这一点。
// Java program to demonstrate
// why collection framework was needed
import java.io.*;
import java.util.*;
class CollectionDemo {
public static void main(String[] args)
{
// Creating instances of the array,
// vector and hashtable
int arr[] = new int[] { 1, 2, 3, 4 };
Vector<Integer> v = new Vector();
Hashtable<Integer, String> h = new Hashtable();
// Adding the elements into the
// vector
v.addElement(1);
v.addElement(2);
// Adding the element into the
// hashtable
h.put(1, "geeks");
h.put(2, "4geeks");
// Array instance creation requires [],
// while Vector and hastable require ()
// Vector element insertion requires addElement(),
// but hashtable element insertion requires put()
// Accessing the first element of the
// array, vector and hashtable
System.out.println(arr[0]);
System.out.println(v.elementAt(0));
System.out.println(h.get(1));
// Array elements are accessed using [],
// vector elements using elementAt()
// and hashtable elements using get()
}
}
输出
1
1
geeks
我们可以看到,这些集合(Array, Vector, or Hashtable)没有一个实现了标准的成员访问接口,对于程序员来说,要写出能够适用于所有种类的集合的算法是非常困难的。另一个缺点是,大多数 “向量 “方法都是最终的,这意味着我们不能扩展 “向量 “类来实现类似的集合。因此,Java开发者决定提出一个通用的接口来处理上述问题,并在JDK 1.2中引入了Collection Framework,之后,传统的Vectors和Hashtables都被修改为符合Collection Framework。
Collection Framework的优点: 由于缺乏一个集合框架导致了上述一系列缺点,下面是集合框架的优点。
- 一致的API: API有一套基本的接口,如Collection、Set、List或Map ,所有实现这些接口的类(ArrayList、LinkedList、Vector等)都有一些共同的方法集。
-
减少编程工作: 程序员不必担心集合的设计,而是可以专注于它在程序中的最佳使用。因此,面向对象编程的基本概念(即)抽象已经成功实现。
-
提高程序速度和质量: 通过提供有用的数据结构和算法的高性能实现来提高性能,因为在这种情况下,程序员不需要考虑特定数据结构的最佳实现。他可以简单地使用最佳实现来大幅提高他的算法/程序的性能。
Collections框架的层次结构
实用程序包(java.util)包含集合框架所需的所有类和接口。集合框架包含一个名为iterable的接口,它提供了一个迭代器来迭代所有的集合。这个接口由主集合接口扩展,作为集合框架的根。所有的集合都扩展了这个集合接口,从而扩展了迭代器的属性和这个接口的方法。下图说明了集合框架的层次结构。
在理解上述框架中的不同组件之前,让我们首先了解一下类和接口。
- 类 : 类是一个用户定义的蓝图或原型,对象就是从这里创建的。它代表了一种类型的所有对象所共有的属性或方法的集合。
-
接口 : 和类一样,接口可以有方法和变量,但在接口中声明的方法默认是抽象的(只有方法签名,没有主体)。接口指定一个类必须做什么,而不是如何做。它是类的蓝图。
集合接口的方法
这个接口包含各种方法,这些方法可以被所有实现这个接口的集合直接使用。它们是
方法 | 方法描述
—|—
add(Object) | 该方法用于添加一个对象到集合中。
addAll(Collection c) | 该方法将给定集合中的所有元素添加到这个集合中。
clear() | 这个方法从这个集合中删除所有的元素。
contains(Object o) | 如果该集合包含指定的元素,该方法返回true。
containsAll(Collection c) | 如果该集合包含了给定集合中的所有元素,该方法返回真。
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() | 这个方法用来返回一个包含此集合中所有元素的数组。
扩展集合接口的接口
集合框架包含多个接口,每个接口都用于存储特定类型的数据。以下是该框架中存在的接口。
1.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的大小会自动增加,如果对象从集合中删除,则会自动缩小。Java ArrayList允许我们随机地访问列表。ArrayList不能用于原始类型,如int、char等。在这种情况下,我们需要一个封装类。让我们通过下面的例子来了解ArrayList。
// Java program to demonstrate the
// working of ArrayList
import java.io.*;
import java.util.*;
class GFG {
// Main Method
public static void main(String[] args)
{
// Declaring the ArrayList with
// initial size n
ArrayList<Integer> al = new ArrayList<Integer>();
// Appending new elements at
// the end of the list
for (int i = 1; i <= 5; i++)
al.add(i);
// Printing elements
System.out.println(al);
// Remove element at index 3
al.remove(3);
// Displaying the ArrayList
// after deletion
System.out.println(al);
// Printing elements one by one
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。
// Java program to demonstrate the
// working of LinkedList
import java.io.*;
import java.util.*;
class GFG {
// Main Method
public static void main(String[] args)
{
// Declaring the LinkedList
LinkedList<Integer> ll = new LinkedList<Integer>();
// Appending new elements at
// the end of the list
for (int i = 1; i <= 5; i++)
ll.add(i);
// Printing elements
System.out.println(ll);
// Remove element at index 3
ll.remove(3);
// Displaying the List
// after deletion
System.out.println(ll);
// Printing elements one by one
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相同。然而,向量和ArrayList的主要区别是,向量是同步的,ArrayList是非同步的。让我们通过一个例子来理解Vector。
// Java program to demonstrate the
// working of Vector
import java.io.*;
import java.util.*;
class GFG {
// Main Method
public static void main(String[] args)
{
// Declaring the Vector
Vector<Integer> v = new Vector<Integer>();
// Appending new elements at
// the end of the list
for (int i = 1; i <= 5; i++)
v.add(i);
// Printing elements
System.out.println(v);
// Remove element at index 3
v.remove(3);
// Displaying the Vector
// after deletion
System.out.println(v);
// Printing elements one by one
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.Stack: Stack类建模并实现了Stack数据结构。该类是基于后进先出的基本原则。 除了基本的push和pop操作外,该类还提供了empty、search和peek三种功能。该类也可以被称为Vector的子类。让我们通过一个例子来理解堆栈。
// Java program to demonstrate the
// working of a stack
import java.util.*;
public class GFG {
// Main Method
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 for the stack
Iterator<String> itr = stack.iterator();
// Printing the stack
while (itr.hasNext()) {
System.out.print(itr.next() + " ");
}
System.out.println();
stack.pop();
// Iterator for the stack
itr = stack.iterator();
// Printing the stack
while (itr.hasNext()) {
System.out.print(itr.next() + " ");
}
}
}
输出
Geeks For Geeks Geeks
Geeks For Geeks
注意: Stack是Vector的一个子类,是一个传统的类。它是线程安全的,在不需要线程安全的环境中可能会有开销。Stack的另一个选择是使用ArrayDequeue,它不是线程安全的,但有更快的数组实现。
4.队列接口: 顾名思义,队列接口保持FIFO(先进先出)的顺序,类似于现实世界中的队列线。这个接口是专门用来存储所有元素的顺序的。例如,每当我们试图订票时,票是以先到先得的方式出售的。因此,谁的请求先到达队列中,谁就能得到票。有各种类,如PriorityQueue、ArrayDeque等。由于所有这些子类都实现了队列,我们可以用这些类中的任何一个实例化一个队列对象。例如,
Queue <T> pq = new PriorityQueue<> ();
Queue <T> ad = new ArrayDeque<> ();
其中T是对象的类型。
队列接口最常用的实现是PriorityQueue
Priority Queue: 当对象应该根据优先级被处理时,就会使用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 program to demonstrate the
// ArrayDeque class in Java
import java.util.*;
public class ArrayDequeDemo {
public static void main(String[] args)
{
// Initializing an deque
ArrayDeque<Integer> de_que = new ArrayDeque<Integer>(10);
// add() method to insert
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() method
de_que.clear();
// addFirst() method to insert the
// elements at the head
de_que.addFirst(564);
de_que.addFirst(291);
// addLast() method to insert the
// elements at the tail
de_que.addLast(24);
de_que.addLast(14);
System.out.println(de_que);
}
}
输出
[10, 20, 30, 40, 50]
[291, 564, 24, 14]
6. 集合接口 : 集合是一个无序的对象集合,其中不能存储重复的值。当我们希望避免对象的重复并希望只存储唯一的对象时,就可以使用这种集合。这个集合接口由不同的类实现,如HashSet、TreeSet、LinkedHashSet等。由于所有的子类都实现了集合,我们可以用这些类中的任何一个实例化一个集合对象。比如说
Set<T> hs = new HashSet<> ();
Set<T> lhs = new LinkedHashSet<> ();
Set<T> ts = new TreeSet<> ();
其中T是对象的类型。
下面是实现Set接口的类
A.HashSet: HashSet类是哈希表数据结构的内在实现。我们插入到HashSet中的对象并不保证以相同的顺序插入。这些对象是根据它们的哈希码插入的。这个类也允许插入NULL元素。让我们通过一个例子来理解HashSet。
// Java program to demonstrate the
// working of a HashSet
import java.util.*;
public class HashSetDemo {
// Main Method
public static void main(String args[])
{
// Creating HashSet and
// adding elements
HashSet<String> hs = new HashSet<String>();
hs.add("Geeks");
hs.add("For");
hs.add("Geeks");
hs.add("Is");
hs.add("Very helpful");
// Traversing elements
Iterator<String> itr = hs.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
}
输出
Very helpful
Geeks
For
Is
B.LinkedHashSet: LinkedHashSet与HashSet非常相似。不同的是,它使用一个双链表来存储数据,并且保留了元素的顺序。让我们通过一个例子来理解LinkedHashSet。
// Java program to demonstrate the
// working of a LinkedHashSet
import java.util.*;
public class LinkedHashSetDemo {
// Main Method
public static void main(String args[])
{
// Creating LinkedHashSet and
// adding elements
LinkedHashSet<String> lhs = new LinkedHashSet<String>();
lhs.add("Geeks");
lhs.add("For");
lhs.add("Geeks");
lhs.add("Is");
lhs.add("Very helpful");
// Traversing elements
Iterator<String> itr = lhs.iterator();
while (itr.hasNext()) {
System.out.println(itr.next());
}
}
}
输出
Geeks
For
Is
Very helpful
7.排序的集合接口: 这个接口与集合接口非常相似。唯一的区别是,这个接口有额外的方法来维持元素的排序。排序集接口扩展了集合接口,用于处理需要排序的数据。实现这个接口的类是TreeSet。由于这个类实现了排序集,我们可以用这个类实例化一个排序集对象。比如说
SortedSet<T> ts = new TreeSet<> ();
其中T是对象的类型。
实现排序集接口的类是TreeSet
TreeSet: TreeSet类使用一个Tree来存储。无论是否提供显式比较器,元素的排序都是由一个集合使用其自然排序来维护的。如果要正确实现Set接口,这必须与equals一致。它也可以通过在集合创建时提供的比较器进行排序,这取决于使用哪个构造函数。让我们通过一个例子来理解TreeSet。
// Java program to demonstrate the
// working of a TreeSet
import java.util.*;
public class TreeSetDemo {
// Main Method
public static void main(String args[])
{
// Creating TreeSet and
// adding elements
TreeSet<String> ts = new TreeSet<String>();
ts.add("Geeks");
ts.add("For");
ts.add("Geeks");
ts.add("Is");
ts.add("Very helpful");
// Traversing elements
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使用了一种叫做Hashing的技术。Hashing是一种将大的字符串转换为代表同一字符串的小字符串的技术,这样索引和搜索操作就会更快。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