Java 集合
Java集合概述
-
Java数组的局限性
- 数组初始化后大小不可变;
- 数组只能按索引顺序存取。
-
Java的java.util包主要提供了以下三种类型的集合:
- List:一种有序列表的集合,例如,按索引排列的Student的List;
- Set:一种保证没有重复元素的集合,例如,所有无重复名称的Student的Set;
- Map:一种通过键值(key-value)查找的映射表集合,例如,根据Student的name查找对应Student的Map。
-
Java集合类:Collection,,它是除Map外所有其他集合类的根接口。
-
Java集合的设计特点
- 一是实现了接口和实现类相分离
例如,有序表的接口是List,具体的实现类有ArrayList,LinkedList等 - 二是支持泛型
我们可以限制在一个集合中只能放入同一种数据类型的元素,例如:List list = new ArrayList<>(); // 只能放入String类型
- 一是实现了接口和实现类相分离
-
Java集合的访问
Java访问集合总是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。
使用List
ListIntro.java
ArrayList API
在Java中,List
是一个接口,它继承自Collection
接口,并表示一个有序的集合。List
可以包含重复的元素,并且每个元素都有其索引。List
接口的两个常用实现类是ArrayList
和LinkedList
。
- ArrayLisy的简易实现与存在的问题
public class ArrayList {
private Object[] array;
private int size;
public void add(Object e) {...}
public void remove(int index) {...}
public Object get(int index) {...}
}
问题:没有使用泛型,所以使用这个ArrayList时,插入和检索元素都不会有编译时类型检查,可能需要强制类型转换,并且存在类型转换错误的风险。如果不使用泛型重新编写就必须这么写,泛型稍后再讲:
ArrayList list = new ArrayList();
list.add("Hello");
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);
基本使用
以下是如何声明和使用List
的一些基本示例:
声明并初始化一个List
List<String> list = new ArrayList<String>(); // 使用泛型来指定List的元素类型为String
或者使用Java 7以后的菱形语法(diamond syntax):
List<String> list = new ArrayList<>(); // Java 7+可以自动推断出泛型类型
添加元素
使用add
方法可以向List
中添加元素:
list.add("Apple");
list.add("Banana");
list.add("Cherry");
访问元素
可以使用get
方法通过索引访问List
中的元素:
String fruit = list.get(0); // 获取第一个元素,索引是0
修改元素
通过指定索引,我们可以替换List
中的元素:
list.set(1, "Blueberry"); // 将索引位置1的元素(即"Banana")替换为"Blueberry"
删除元素
从List
中删除元素可以使用remove
方法:
list.remove("Cherry"); // 删除指定的对象
list.remove(0); // 删除索引位置0的元素
遍历List
可以使用for-each循环来遍历List
中的元素:
for (String fruit : list) {
System.out.println(fruit);
}
或者使用传统的for循环来通过索引访问:
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
还可以使用Iterator
或ListIterator
来遍历:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
列表大小
获取List
的元素个数:
int size = list.size();
判断列表是否为空
判断List
是否为空:
boolean isEmpty = list.isEmpty();
清空列表
删除List
中的所有元素:
list.clear();
检查元素是否存在
检查某个元素是否在List
中:
boolean contains = list.contains("Apple");
ArrayList vs LinkedList
-
ArrayList:基于动态数组实现,提供快速的随机访问和快速的遍历。但添加或删除元素(尤其是在列表的前面或中间)可能较慢,因为可能涉及到数组的复制和移动。
-
LinkedList:基于双向链表实现,添加和删除元素通常更快,但随机访问较慢。
根据具体的使用场景选择合适的List
实现是重要的,如果频繁进行随机访问,ArrayList
通常是更好的选择;如果频繁插入和删除元素,LinkedList
可能更有优势。
编写equals和hashCode
EqualsMethod.java
编写一个正确的 equals()
方法很重要,以确保对象在比较时表现出正确的行为。以下是编写 equals()
方法的标准准则和步骤:
-
使用
==
检查“引用相等”:先检查被比较的对象是否是对同一个对象的引用。如果是,直接返回true
。这不仅是一个性能优化,也是逻辑正确性的保障。if (this == obj) return true;
-
检查
null
:检查传入的对象是否为null
。如果是,返回false
。因为按照约定,任何非空引用值与null
比较时,都应该返回false
。if (obj == null) return false;
-
比较类类型是否相同:检查
this
和obj
是否属于同一个类。如果你希望具有相同类继承层次的对象在逻辑上视为相等,可以使用instanceof
检查。否则,使用getClass()
方法进行比较以确保类型完全相同。if (getClass() != obj.getClass()) return false;
-
类型转换:将传入的对象转换(强制类型转换)为正确的类型,以便可以访问其属性或方法。
MyClass myClassObj = (MyClass) obj;
-
比较关键字段:对类中每个“重要”属性进行比较。如果是基本数据类型,可以直接使用
==
;对于对象类型,则应使用equals()
进行比较。对于数组类型的字段,可以使用Arrays.equals()
检查每个元素。return (this.field1 == myClassObj.field1) && (this.field2.equals(myClassObj.field2));
-
编写
hashCode()
方法:当重写equals()
方法时,也必须重写hashCode()
方法,以保持hashCode
的一致性。即,如果两个对象通过equals()
方法判断相等,它们的hashCode
值也必须相同。@Override public int hashCode() { // 使用Objects.hash或者自定义逻辑来生成hashCode return Objects.hash(field1, field2); }
遵循以上准则,可以确保您的 equals()
方法是正确、高效且符合Java相等性约定的。如果不调用List的contains()、indexOf()这些方法,那么放入的元素就不需要实现equals()方法。
使用Map
MapIntro.java
以下是一些常用的 Map
接口操作:
put(K key, V value)
: 将指定的值与此映射中的指定键关联(可选操作)。get(Object key)
: 返回指定键所映射的值;如果此映射不含该键的映射关系,则返回null
。remove(Object key)
: 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。containsKey(Object key)
: 如果此映射包含指定键的映射关系,则返回true
。containsValue(Object value)
: 如果此映射将一个或多个键映射到指定值,则返回true
。size()
: 返回此映射中的键值映射关系数。isEmpty()
: 如果此映射不包含键值映射关系,则返回true
。clear()
: 从此映射中移除所有的映射关系(可选操作)。keySet()
: 返回此映射中包含的键的Set
视图。values()
: 返回此映射中包含的值的Collection
视图。entrySet()
: 返回此映射中包含的映射关系的Set
视图。
使用Set
SetIntro.java
Map用于存储key-value的映射,对于充当key的对象,是不能重复的,并且,不但需要正确覆写equals()方法,还要正确覆写hashCode()方法。
如果我们只需要存储不重复的key,并不需要存储映射的value,那么就可以使用Set。无序,不重复。
以下是 Set
接口中的一些基本操作:
add(E e)
: 如果指定元素尚未存在于集合中,则添加该元素(可选操作)。remove(Object o)
: 如果指定元素存在于集合中,则移除它(可选操作)。contains(Object o)
: 如果集合包含指定元素,则返回true
。size()
: 返回集合中元素的数量。isEmpty()
: 如果集合不包含元素,则返回true
。clear()
: 移除集合中所有元素(可选操作)。iterator()
: 返回在此集合中元素上进行迭代的迭代器。
使用Collections
CollectionsIntro.java
Java 的 Collections
类(注意是复数形式)是一个包含静态方法的工具类,这些方法可以用于对集合(如列表、集合和映射)进行操作和转换。Collections
类位于 java.util
包中,提供了一系列的静态方法,用于实现集合的搜索、排序、线程安全化、只读化等操作。
这里是 Collections
类的一些常用方法:
排序和混排
- sort(List list): 对指定列表按照自然顺序进行排序。
- sort(List list, Comparator<? super T> c): 根据提供的比较器对列表进行排序。
- shuffle(List<?> list): 使用默认随机源对列表进行置换,打乱列表中元素的顺序。
查找和替换
- binarySearch(List<? extends Comparable<? super T>> list, T key): 使用二分搜索法搜索指定列表,以获得指定对象,前提是列表已经排序。
- binarySearch(List<? extends T> list, T key, Comparator<? super T> c): 根据指定比较器使用二分搜索法搜索指定列表。
- replace(List list, T oldVal, T newVal): 替换列表中所有出现的旧元素。
反转和旋转
- reverse(List<?> list): 反转指定列表中元素的顺序。
- rotate(List<?> list, int distance): 根据指定的距离旋转列表。
最大值和最小值
- max(Collection<? extends T> coll): 根据元素的自然顺序,返回给定集合的最大元素。
- max(Collection<? extends T> coll, Comparator<? super T> comp): 根据指定比较器,返回给定集合的最大元素。
- min(Collection<? extends T> coll): 根据元素的自然顺序,返回给定集合的最小元素。
- min(Collection<? extends T> coll, Comparator<? super T> comp): 根据指定比较器,返回给定集合的最小元素。
同步控制
- synchronizedCollection(Collection c): 返回由指定集合支持的同步(线程安全的)集合。
- synchronizedList(List list): 返回由指定列表支持的同步(线程安全的)列表。
- 等等,类似的还有
synchronizedMap
,synchronizedSet
,synchronizedSortedMap
,synchronizedSortedSet
。
不可修改的集合
- unmodifiableCollection(Collection<? extends T> c): 返回指定集合的不可修改视图。
- unmodifiableList(List<? extends T> list): 返回指定列表的不可修改视图。
- 等等,类似的还有
unmodifiableMap
,unmodifiableSet
,unmodifiableSortedMap
,unmodifiableSortedSet
。
示例:使用 Collections.sort()
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("banana");
list.add("apple");
list.add("cherry");
// 排序前
System.out.println("Before sorting: " + list);
// 排序
Collections.sort(list);
// 排序后
System.out.println("After sorting: " + list);
}
}
在这个示例中,创建了一个字符串列表,并使用 Collections.sort()
方法对其进行了排序。这是 Collections
类中许多有用方法之一,可以大大简化常见的集合操作。