集合的概念
在计算机科学中,集合是一组元素的容器,这些元素可以是相同类型的对象,也可以是不同类型的对象。Java集合框架提供了一系列接口和类,用于处理和操作这些集合。
集合框架的层次结构
Java集合框架包括多个接口和类,它们按照一定的层次结构进行组织。以下是集合框架的主要接口:
单列集合
Collection接口
- 是所有集合类的根接口,它定义了一些基本的集合操作方法,如添加、删除、遍历等。
- 常用方法
add(E e)
:将给定的对象添加到集合中。clear()
:清空集合中的所有元素。remove(E e)
:从集合中删除给定的对象。contains(E e)
:判断集合中是否包含给定的对象。isEmpty()
:判断集合是否为空。size()
:返回集合中元素的个数。toArray()
:将集合中的元素存储到数组中
import java.util.*; public class CollectionDemo { public static void main(String[] args) { // 创建一个ArrayList来存放整数 List<Integer> numbers = new ArrayList<>(); // 添加元素到集合 numbers.add(10); numbers.add(20); numbers.add(30); // 判断集合是否为空 boolean isEmpty = numbers.isEmpty(); System.out.println("集合是否为空:" + isEmpty); // 获取集合中元素的个数 int size = numbers.size(); System.out.println("集合中元素的个数:" + size); // 从集合中删除元素 numbers.remove(Integer.valueOf(20)); } }
List接口
- 是有序的集合,允许重复元素。常见的实现类有ArrayList、LinkedList和Vector。
- 常用的List集合实现类:ArrayList和LinkedList
ArrayList
ArrayList
是一个基于数组实现的List集合。它具有以下特点:- 查询效率高:由于使用数组存储元素,可以通过索引直接访问元素,查询效率很高。
- 添加、删除效率较低:在添加和删除元素时,可能需要移动数组中的其他元素,导致效率相对较低。
- 底层实现:底层使用
Object[]
数组存储元素。
ArrayList() ArrayList(Collection<? extends E> c)
- List集合常用方法
- 在使用List集合时,我们常用的方法包括:
public boolean add(E e)
: 将给定的对象添加到集合中。public void clear()
: 清空集合中的所有元素。public boolean remove(E e)
: 从集合中删除给定的对象。public boolean contains(E e)
: 判断集合是否包含给定的对象。public boolean isEmpty()
: 判断集合是否为空。public int size()
: 返回集合中元素的个数。public Object[] toArray()
: 将集合中的元素存储到数组中。public void add(int index, E element)
: 将指定的元素添加到集合的指定位置。public E get(int index)
: 返回集合中指定位置的元素。public E remove(int index)
: 移除集合中指定位置的元素,并返回被移除的元素。public E set(int index, E element)
: 用指定的元素替换集合中指定位置的元素,并返回替换前的元素。
import java.util.ArrayList; import java.util.List; public class ListMethodsExample { public static void main(String[] args) { // 创建一个使用ArrayList的List List<String> myList = new ArrayList<>(); // 向List中添加元素 myList.add("苹果"); myList.add("香蕉"); myList.add("橙子"); // 显示List内容 System.out.println("原始列表: " + myList); // 在特定索引位置添加元素 myList.add(1, "葡萄"); System.out.println("在索引1添加'葡萄'后的列表: " + myList); // 获取特定索引位置的元素 String 在索引2的水果 = myList.get(2); System.out.println("索引2的元素: " + 在索引2的水果); // 移除特定索引位置的元素 String 移除的元素 = myList.remove(0); System.out.println("移除索引0的元素: " + 移除的元素); System.out.println("移除元素后的列表: " + myList); // 替换特定索引位置的元素 String 替换前的元素 = myList.set(1, "菠萝"); System.out.println("替换索引1的元素: " + 替换前的元素); System.out.println("替换元素后的列表: " + myList); // 检查列表是否包含特定元素 boolean 包含橙子 = myList.contains("橙子"); System.out.println("列表是否包含'橙子': " + 包含橙子); // 检查列表是否为空 boolean 是否为空 = myList.isEmpty(); System.out.println("列表是否为空? " + 是否为空); // 获取列表的大小 int 列表大小 = myList.size(); System.out.println("列表的大小: " + 列表大小); // 将列表转换为数组 Object[] 数组 = myList.toArray(); System.out.print("从列表转换的数组: "); for (Object item : 数组) { System.out.print(item + " "); } } }
LinkedList
LinkedList
是一个基于双向链表实现的List集合。它具有以下特点:
- 查询效率较低:由于需要遍历链表才能访问元素,查询效率较低。
- 添加、删除效率较高:在链表中添加和删除元素的效率很高。
- 底层实现:底层使用双向链表存储元素。
构造方法
//创建一个空列表 LinkedList(); //创建一个包含指定集合元素的列表,按集合的迭代器返回顺序添加。 LinkedList(Collection<? extends E> c);
常用方法
void addFirst(E e)
: 在列表的开始处插入指定元素。void addLast(E e)
: 在列表的末尾插入指定元素。E getFirst()
: 返回列表中的第一个元素。E getLast()
: 返回列表中的最后一个元素。E removeFirst()
: 移除并返回列表中的第一个元素。E removeLast()
: 移除并返回列表中的最后一个元素。void push(E e)
: 将元素推入到由该列表表示的堆栈上。E pollFirst()
: 检索并移除列表的第一个元素,若列表为空则返回null
。E pollLast()
: 检索并移除列表的最后一个元素,若列表为空则返回null
import java.util.LinkedList; public class LinkedListExample { public static void main(String[] args) { LinkedList<String> linkedList = new LinkedList<>(); // 添加元素 linkedList.addFirst("Apple"); linkedList.addLast("Banana"); // 获取第一个和最后一个元素 String firstElement = linkedList.getFirst(); String lastElement = linkedList.getLast(); System.out.println("First Element: " + firstElement); System.out.println("Last Element: " + lastElement); // 移除并返回第一个和最后一个元素 String removedFirstElement = linkedList.removeFirst(); String removedLastElement = linkedList.removeLast(); System.out.println("Removed First Element: " + removedFirstElement); System.out.println("Removed Last Element: " + removedLastElement); // 将元素推入堆栈 linkedList.push("Cherry"); // 检索并移除第一个和最后一个元素 String polledFirstElement = linkedList.pollFirst(); String polledLastElement = linkedList.pollLast(); System.out.println("Polled First Element: " + polledFirstElement); System.out.println("Polled Last Element: " + polledLastElement); } }
Set接口
- 不允许重复元素的集合。常见的实现类有HashSet、LinkedHashSet。
HashSet
- 无序性:
HashSet
中的元素是无序的,不能保证元素的顺序。 - 不允许重复:
HashSet
中的元素不允许重复,相同的元素只会保存一份。 - 高效的添加、查找和删除操作: 哈希表的底层实现使得添加、查找和删除元素的操作都具有很高的效率。
构造方法
HashSet<E> set = new HashSet<>();
示例
Set<String> hashSet = new HashSet<>(); hashSet.add("apple"); hashSet.add("banana"); hashSet.add("apple"); // 不会重复添加 System.out.println(hashSet); // 输出:[banana, apple]
HashSet
是Set
接口的一个实现类,它是一个无序的、不重复的集合。其底层实现是基于HashMap
,将元素作为键存储在HashMap
中。
LinkedHashSet
- 有序性:
LinkedHashSet
中的元素是有序的,它会根据元素的插入顺序维护一个链表,从而保持插入顺序。 - 不允许重复: 同样,
LinkedHashSet
中的元素不允许重复。
构造方法
LinkedHashSet<E> linkedHashSet = new LinkedHashSet<>();
示例
Set<String> linkedHashSet = new LinkedHashSet<>(); linkedHashSet.add("apple"); linkedHashSet.add("banana"); linkedHashSet.add("apple"); // 不会重复添加 System.out.println(linkedHashSet); // 输出:[apple, banana]
双列集合
Map
接口
Map
接口定义了键和值之间映射的规范,它有两个泛型参数:K
表示键的类型,V
表示值的类型。- 实现类:HashMap 和 LinkedHashMap
HashMap
- 基于哈希表实现的映射,具有高效的添加、查找和删除操作。它的键不允许重复,值可以重复。如果存放的元素中键值相同,后面的值会覆盖前面的值。键和值都可以为
null
,但一个键只能对应一个null
值。
构造方法
HashMap<K, V> hashMap = new HashMap<>();
常用方法
V put(K key, V value)
: 将指定的值与键关联。V get(Object key)
: 返回指定键映射的值,如果键不存在则返回null
。V remove(Object key)
: 从映射中移除指定键的映射。V replace(K key, V value)
: 仅当键当前映射到某一值时,替换指定键的值。int size()
: 返回键值对的数目。
HashMap<String, Integer> hashMap = new HashMap<>(); // 添加键值对 hashMap.put("apple", 3); hashMap.put("banana", 2); hashMap.put("orange", 4); // 获取值 int value = hashMap.get("apple"); // 移除键值对 hashMap.remove("banana"); // 替换值 hashMap.replace("orange", 5); // 获取键值对数目 int size = hashMap.size();
集合遍历
HashMap<String, Integer> hashMap = new HashMap<>(); // 遍历所有的键 Set<String> keys = hashMap.keySet(); for (String key : keys) { Integer value = hashMap.get(key); } // 获取所有的值 Collection<Integer> values = hashMap.values(); // 遍历所有的键值对 Set<Map.Entry<String, Integer>> entries = hashMap.entrySet(); for (Map.Entry<String, Integer> entry : entries) { String key = entry.getKey(); Integer value = entry.getValue(); }
LinkedHashMap
- 在
HashMap
的基础上增加了维护插入顺序的功能,保持键值对的插入顺序。其他特性与HashMap
类似。
补充知识
迭代器
迭代器是用于遍历集合中元素的工具。通过iterator()
方法可以获得一个迭代器对象,接着使用迭代器的方法来遍历集合。主要的迭代器接口是Iterator
,其中包括了hasNext()
、next()
和remove()
方法。
迭代器的原理
迭代器首先通过hasNext()
方法判断集合中是否还有下一个元素,然后通过next()
方法获取下一个元素的值。迭代器在遍历时会自动将索引指向下一个元素。
增强for循环
增强for循环是一种简化遍历集合或数组的方式。它可以直接在循环中获取每个元素,而不需要使用索引。需要注意的是,增强for循环不能用于删除元素,如果需要在遍历时删除元素,应该使用迭代器的方式。
// 使用迭代器遍历集合并输出元素 System.out.print("集合中的元素:"); Iterator<Integer> iterator = numbers.iterator(); while (iterator.hasNext()) { int number = iterator.next(); System.out.print(number + " "); } System.out.println(); // 使用增强for循环遍历集合并输出元素 System.out.print("使用增强for循环遍历集合:"); for (int num : numbers) { System.out.print(num + " "); } System.out.println();
泛型
泛型的最大好处是在编译期间捕获类型错误,从而避免在运行时出现异常。通过在代码中约定数据类型,编译器可以检查数据类型的匹配性,确保我们不会错误地使用错误的数据类型。这大大提高了代码的健壮性和可维护性。
1.泛型的使用场景
1.1 泛型类
可以在类的声明中使用泛型,以便在使用该类时指定数据类型。这使得类可以在不同的上下文中适用于不同的数据类型。
public class GenericClass<T> { private T data; public GenericClass(T data) { this.data = data; } public T getData() { return data; } }
1.2. 泛型方法
可以在方法中使用泛型,以便在方法调用时指定数据类型。这样,方法可以适用于不同类型的参数。
a. 类已经添加了泛型,方法参数可以直接使用:
public class GenericMethodClass<M> { void show(M m) { System.out.println(m); } }
b. 类未添加泛型,方法参数需要在返回值前添加泛型:
public class GenericMethodClass2 { public <M> void show(M m) { System.out.println(m); } }
1.3. 泛型接口
接口也可以使用泛型,实现类可以确定泛型的具体类型。
a. 接口的实现类确定泛型的类型:
public class InterfaceImpl implements MyInterface<String> { @Override public void show(String s) { System.out.println(s); } }
b. 接口的实现类继续使用泛型,实例化时确定泛型的类型:
public class InterfaceImpl2<E> implements MyInterface<E> { @Override public void show(E e) { System.out.println(e); } }
2.泛型通配符
泛型通配符用于处理不同泛型类型之间的关系。在Java中,通配符有两种使用方式:? extends T
和? super T
。
? extends T
:表示该类型应为指定类型T
或其子类。这被称为上限通配符。? super T
:表示该类型应为指定类型T
或其父类。这被称为下限通配符。