目录
集合框架概述
Java集合框架(Java Collections Framework, JCF)是Java语言中用于存储和操作数据集合的一组接口、实现类和算法。它提供了一套标准化的方式来组织和处理数据,是Java编程中最常用的API之一。
集合框架的组成:
-
接口(Interfaces): 定义集合的抽象行为
-
实现类(Implementations): 接口的具体实现
-
算法(Algorithms): 对集合进行操作的方法,如排序、搜索等
集合框架的主要优点:
-
减少编程工作量
-
提高程序性能
-
促进API的互操作性
-
降低学习成本
-
提高软件复用性
集合框架核心接口
Java集合框架的核心接口主要位于java.util包中,主要包括:
-
Collection接口 - 集合层次结构的根接口
-
List接口 - 有序集合(序列),允许重复元素
-
Set接口 - 不包含重复元素的集合
-
Queue接口 - 队列,通常按FIFO(先进先出)排序元素
-
Deque接口 - 双端队列
-
Map接口 - 键值对集合,不是Collection的子接口
Collection接口详解
Collection接口是集合层次结构的根接口,定义了所有集合共有的基本操作:
public interface Collection<E> extends Iterable<E> {
// 基本操作
int size();
boolean isEmpty();
boolean contains(Object element);
boolean add(E element); // 可选操作
boolean remove(Object element); // 可选操作
Iterator<E> iterator();
// 批量操作
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c); // 可选操作
boolean removeAll(Collection<?> c); // 可选操作
boolean retainAll(Collection<?> c); // 可选操作
void clear(); // 可选操作
// 数组操作
Object[] toArray();
<T> T[] toArray(T[] a);
// Java 8新增的默认方法
default boolean removeIf(Predicate<? super E> filter) { ... }
default Spliterator<E> spliterator() { ... }
default Stream<E> stream() { ... }
default Stream<E> parallelStream() { ... }
}
Iterator
它为各种集合提供了一个统一的遍历方式,实现了"迭代器设计模式。
Iterator接口定义
public interface Iterator<E> {
// 判断集合中是否还有更多元素
boolean hasNext();
返回下一个元素,并且将迭代器指向下一个元素
//
E next();
// 从集合中移除next()返回的最后一个元素(可选操作)
default void remove() {
throw new UnsupportedOperationException("remove");
}
// Java 8新增:对剩余元素执行操作
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
Iterator的基本使用
- 基本遍历
List<String> list = Arrays.asList("Java", "Python", "C++");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String language = iterator.next();
System.out.println(language);
}
- 使用forEachRemaining(Java 8+)
Iterator<String> iterator = list.iterator();
iterator.forEachRemaining(System.out::println);
- 移除元素
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
Integer num = iterator.next();
if (num % 2 == 0) {
iterator.remove(); // 安全地移除元素
}
}
// numbers现在是[1, 3, 5]
Iterator的特点
-
单向遍历:只能向前遍历,不能后退
-
一次性使用:迭代器一旦遍历结束,就不能再使用,需要重新获取
-
快速失败(Fail-Fast):如果在迭代过程中集合被修改(除了通过迭代器自身的remove方法),会抛出
ConcurrentModificationException
-
统一访问方式:为不同集合提供统一的遍历接口
for-each循环+Lambda表达式
在Java中,for-each循环和Lambda表达式是两种强大的遍历集合的方式,它们可以使代码更加简洁和易读。
传统的for-each循环
List<String> list = Arrays.asList("Apple", "Banana", "Orange");
// 传统for-each循环
for (String fruit : list) {
System.out.println(fruit);
}
使用Lambda表达式遍历(Java 8+)
Java 8引入了Lambda表达式和函数式编程特性,提供了更简洁的遍历方式:
使用forEach()方法
List<String> list = Arrays.asList("Apple", "Banana", "Orange");
// 使用Lambda表达式
list.forEach(fruit -> System.out.println(fruit));
// 使用方法引用(更简洁的形式)
list.forEach(System.out::println);
带索引的遍历
List<String> list = Arrays.asList("Apple", "Banana", "Orange");
IntStream.range(0, list.size())
.forEach(i -> System.out.println("Index: " + i + ", Value: " + list.get(i)));
List接口及其实现类
- void add(int index, Object element):在指定位置index上插入元素element。
- boolean addAll(int index, Collection c):指定位置index上插入集合c中的所有元素,如果List对象发生变化返回true。
- Object get(int index):返回指定位置index上的元素。 Object remove(int index):删除指定位置index上的元素。
- Object set(int index, Object element):用元素element取代位置index上的元素,并且返回旧元素的取值。
- public int indexOf(Object obj):从列表的头部开始向后搜索元素obj,返回第一个出现元素obj的位置,否则返回-1。
- public int lastIndexOf(Object obj) :从列表的尾部开始向前搜索元素obj,返回第一个出现元素obj的位置,否则返回-1。
List接口的特点
-
有序集合(序列)
-
允许重复元素
-
允许null元素
-
可以通过索引访问元素
主要实现类
ArrayList
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。
ArrayList 继承了 AbstractList ,并实现了 List 接口。
-
基于动态数组实现
-
随机访问快(O(1))
-
插入和删除相对慢(O(n))
-
非线程安全
ArrayList 类位于 java.util 包中,使用前需要引入它,语法格式如下:
import java.util.ArrayList; // 引入 ArrayList 类
ArrayList<E> objectName =new ArrayList<>(); // 初始化
ArrayList的底层
ArrayList中的常用方法包括:
- ArrayList():从Java SE 8 开始,ArrayList()初始化时得到一个空数组,在第一次 add()运算时将数组扩容为10。
- ArrayList(int initialCapacity):创建ArrayList 对象,使用参数initialCapacity设置Object[]数组的长度。
- ensureCapacity():如果要向ArrayList中添加大量元素,可使用此方法对Object[]数组进行指定的扩容。
关于remove()方法
Collection接口中的remove()方法 boolean remove(Object obj) boolean removeAll(Collection c)。 List中声明了一个按位置索引删除的remove()方法 Object remove(int index)。
LinkedList
- LinkedList 继承了 AbstractSequentialList 类。
- LinkedList 实现了 Queue 接口,可作为队列使用。
- LinkedList 实现了 List 接口,可进行列表的相关操作。
- LinkedList 实现了 Deque 接口,可作为队列使用。
- LinkedList 实现了 Cloneable 接口,可实现克隆。
- LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。
LinkedList 类位于 java.util 包中,使用前需要引入它,语法格式如下:
// 引入 LinkedList 类
import java.util.LinkedList;
LinkedList<E> list = new LinkedList<E>(); // 普通创建方法
或者
LinkedList<E> list = new LinkedList(Collection<? extends E> c); // 使用集合创建链表
常用操作
- 在列表开头添加元素
LinkedList<String> sites = new LinkedList<String>();
sites.add("Google");
sites.add("Runoob");
sites.add("Taobao");
// 使用 addFirst() 在头部添加元素
sites.addFirst("Wiki");
- 在列表结尾田间元素
// 使用 addLast() 在尾部添加元素
sites.addLast("Wiki");
- 在列表开头移除元素
sites.removeFirst();
- 在列表结尾移除元素
sites.removeLast();
ArrayList和LinkedList的选用
- 如果经常要存取List集合中的元素,那么使用ArrayList采用随机访问的形式(get(index),set(index ,Object element))性能更好。
- 如果要经常执行插入、删除操作,或者改变List集合的大小,则应该使用LinkedList。
Set接口及其实现类
- Set接口继承自Collection 接口,它没有引入新方法,所以Set就是一个Collection,只是行为方式不同。
- Set不允许集合中存在重复项,如果试图将两个相同的元素加入同一个集合,则添加无效,add()方法返回false。
PS:Set的实现类依赖添加对象的equals()方法检查对象的唯一性:
- 只要两个对象使用equals()方法比较的结果为true,Set就会拒绝后一个对象的加入(哪怕它们实际上是不同的对象);
- 只要两个对象使用equals()方法比较的结果为false,Set就会接纳后一个对象(哪怕它们实际上是相同的对象)。
HashSet
-
基于哈希表实现
-
使用HashMap存储元素
-
插入、删除、查找都是O(1)时间复杂度
-
不保证迭代顺序
散列码和hashCode()方法
- 散列码是以某种方法从对象的属性字段产生的整数,Object类中hashCode()方法完成此任务。
- 要将对象存储在基于散列结构的HashSet,自定义类必须按规则重写Object中的hashCode()方法。
- 所谓的规则就是要保证hashCode()方法与重写的equals()方法完全兼容,即如果a.equals(b)为true,那么a和b也必须通过hashCode()方法得到相同的散列码。
- 同时,还要保证计算散列码是快速的。
向HashSet中添加元素
底层逻辑:
- 依据自定义类的hashCode()方法计算得到对象obj的散列码,它是一个整数。(需要自定义类重写hashCode()方法)
- 将散列码对表长求余,得到对象在散列表中的存储位置p
- 如果p位置不发生冲突,则将对象obj插入在p位置的链表中
- 如果p位置发生冲突,在p位置对应的链表中利用equals()方法查找是否已存在obj对象。(需要自定义类重写equals()方法,规则要与hashCode()方法互相匹配)
- 如果某个equals()比较的结果为true,说明obj对象已存在,将其舍弃。
- 如果与链表中所有对象的equals()比较的结果均为false,说明obj对象尚未存在,obj插入该链表。
TreeSet
TreeSe中对象的比较方法
- 有一部分类已实现了java.lang.Comparable接口,如基本类型的包装类、String类等,它们在compareTo()方法中定义好了比较对象的规则。像这样的对象可以直接插入TreeSet集合
- 使用TreeSet存储自定义类对象时,对象所在类要实现Comparable接口,在compareTo()方法中定义对象比较的规则
向TreeSet注入比较器
// 自定义比较器
Comparator<String> lengthComparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
};
// 创建TreeSet时注入比较器
TreeSet<String> treeSet = new TreeSet<>(lengthComparator);
HashSet和TreeSet的选用
- 原则:取决于集合中存放的对象,如果不需要对对象进行排序,那么就没有理由在排序上花费不必要的开销,使用HashSet即可。
- 散列的规则通常更容易定义,只需要打散排列各个对象就行。而TreeSet要求任何两个对象都必须具有可比性,可是在有的应用中比较的规则会很难定义。
Map及其实现类
Map用于保存具有映射关系的数据,它们以键值对<key,value>的形式存在,key与value之间存在一对一的关系
Map接口的特点
-
键(key)唯一,值(value)可重复
-
每个键最多只能映射到一个值
-
不保证元素的顺序(具体取决于实现类)
-
不是Collection的子接口
键的集合用Set存储,不允许重复、无序;值的集合用List存储,与Set对应、可以重复、有序。
事实上,Java是先实现了Map,然后通过包装一个所有value都为null的Map实现了Set,在底层只有Map。
HashMap
-
基于哈希表实现(数组+链表+红黑树,JDK8+)
-
允许null键和null值
-
非线程安全
-
不保证元素的顺序
-
默认初始容量16,负载因子0.75
HashMap的迭代方法
- 使用EntrySet迭代/键值对(最常用)
Map<String, Integer> map = new HashMap<>();
map.put("Apple", 10);
map.put("Banana", 20);
map.put("Orange", 30);
// 使用EntrySet迭代
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}
PS:
Object getKey():返回Entry对象的key值。
Object getValue():返回Entry对象的value值。
setValue(Object value):设置Entry对象的value值,并将新值返回。
- 按键集合迭代
// 先获取所有键,再通过键获取值
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println("Key: " + key + ", Value: " + value);
}
- 使用values()迭代值
for (Integer value : map.values()) {
System.out.println("Value: " + value);
}
Collection集合工具类
java.util.Collections是Java提供的操作List、Set、Map等集合的工具类:
- 所有的方法都是静态方法
- 对集合类功能进行补充
- 对集合进行再包装,将线程不安全的集合包装为线程安全的集合等
多线程封装
在集合框架中,ArrayList、HashSet、TreeSet、HashMap等都是线程不安全的,Collections类提供了多个synchronizedXxx()方法,将指定集合包装为线程同步安全的集合,从而使集合可以工作在并发访问的环境中。
List<String> list =
Collections.synchronizedList(new ArrayList<String>());
Map<String,String> map =
Collections.synchronizedMap(new HashMap<String,String>());