集合
一、集合分类及关系
Java的java.util
包主要提供了以下三种类型的集合:
List
:一种有序元素可重复列表的集合,例如,按索引排列的Student
的List
;Set
:一种无序元素不能重复的集合,例如,所有无重复名称的Student
的Set
;Map
:一种通过键值(key-value)查找的映射表集合,例如,根据Student
的name
查找对应Student
的Map
。
Java标准库自带的java.util
包提供了集合类:Collection
,它是除Map
外所有其他集合类的根接口
二、集合的特点
-
一是实现了接口和实现类相分离,例如,有序表的接口是
List
,具体的实现类有ArrayList
,LinkedList
等 -
二是支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素
ArrayList<String> al = new ArrayList<String>();
-
三是访问集合总是通过统一的方式——迭代器(Iterator)来实现
三、List接口
1、接口方法
List接口有两个实现类:ArrayList类
和LinkedList类
// List接口的接口方法
void add(E e) // 在末尾添加一个元素
void add(int index, E e) // 在指定索引添加一个元素
int remove(int index) // 删除指定索引的元素
int remove(Object e) // 删除某个元素
E get(int index) // 获取指定索引的元素
int size() // 获取链表大小(包含元素的个数)
2、实现类区别
-
ArrayList类
添加元素,但是数组已满,没有空闲位置的时候,ArrayList
先创建一个更大的新数组,然后把旧数组的所有元素复制到新数组,紧接着用新数组取代旧数组 -
LinkedList类
它的内部每个元素都指向下一个元素
ArrayList | LinkedList | |
---|---|---|
获取指定元素 | 速度很快 | 需要从头开始查找元素 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置添加/删除 | 需要移动元素 | 不需要移动元素 |
内存占用 | 少 | 较大 |
所以,优先使用ArrayList
3、遍历List
-
使用
list.size()
进行for循环,只能应用于ArrayList,不能应用于LinkedList -
使用迭代器
Iterator
来访问List
,Iterator
是由List
的实例调用iterator()
方法的时候创建的
Iterator
对象有两个方法:boolean hasNext()
判断是否有下一个元素,E next()
返回下一个元素
import java.util.Iterator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = List.of("apple", "pear", "banana");
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
}
}
for each循环遍历集合
// for each循环本身就可以帮我们使用Iterator遍历
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = List.of("apple", "pear", "banana");
for (String s : list) {
System.out.println(s);
}
}
}
4、比较List中的元素是否相等
-
在
List
中查找元素时,List
的实现类通过元素的equals()
方法比较两个元素是否相等,因此,放入的元素必须正确覆写equals()
方法,Java标准库提供的String
、Integer
等已经覆写了equals()
方法 -
如果不调用
List
的contains()
、indexOf()
这些方法,那么放入的元素就不需要实现equals()
方法。
元素对象重写equals()
方法:
-
先确定实例“相等”的逻辑,即哪些字段相等,就认为实例相等;
-
用
instanceof
判断传入的待比较的Object
是不是当前类型,如果是,继续比较,否则,返回false
; -
对引用类型用
Objects.equals(a, b)
比较,对基本类型直接用==
比较。
public boolean equals(Object o) {
if (o instanceof Person) {
Person p = (Person) o;
boolean nameEquals = false;
if (this.name == null && p.name == null) {
nameEquals = true;
}
if (this.name != null) {
nameEquals = this.name.equals(p.name);
}
return nameEquals && this.age == p.age;
}
return false;
}
// 重写equals()方法,简写版
public boolean equals(Object o) {
if (o instanceof Person) {
Person p = (Person) o;
return Objects.equals(this.name, p.name) && this.age == p.age;
}
return false;
}
四、Set接口
放入Set
的元素,要正确实现equals()
和hashCode()
方法,否则该元素无法正确地放入Set
1、接口方法
boolean add(E e) // 将元素添加进Set<E>
boolean remove(Object e) // 将元素从Set<E>删除
boolean contains(Object e) // 判断是否包含元素
2、实现类
HashSet
是无序的,因为它实现了Set
接口,并没有实现SortedSet
接口TreeSet
是有序的,因为它实现了SortedSet
接口
3、遍历
public class Main {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("pear");
set.add("orange");
for (String s : set) {
System.out.println(s);
}
}
}
五、Map接口
Map
也是一个接口,最常用的实现类是HashMap
Map
是一种 键-值 映射表,当我们调用put(K key, V value)
方法时,就把key
和value
做了映射并放入Map
。当我们调用V get(K key)
时,就可以通过key
获取到对应的value
。如果key
不存在,则返回null
。
1、存键值对——put(key, value)
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 99);
Map<String, Student> map = new HashMap<>();
map.put("Xiao Ming", s); // 将"Xiao Ming"和Student实例映射并关联
}
}
2、取数据——get(key)
public class Main {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 99);
Map<String, Student> map = new HashMap<>();
map.put("Xiao Ming", s); // 将"Xiao Ming"和Student实例映射并关联
Student target = map.get("Xiao Ming"); // 通过key查找并返回映射的Student实例
}
}
3、其他方法
boolean containsKey(K key) // 查询某个key是否存在
boolean isEmpty() // 判断是否为空
Set<K> keySet() // 获取map中所有key,一个Set集合
V remove(Object key) // 更具key删除数据
int size() // 获取 key-value的数量
4、遍历Map
(1)遍历 key来获取所有value
通过for each
遍历keySet()
// 遍历 key来获取所有value
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 456);
map.put("banana", 789);
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
}
(2)同时遍历key和value
for each
遍历entrySet()
,直接获取key-value
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 123);
map.put("pear", 456);
map.put("banana", 789);
// 利用entrySet()将映射关系存到Set中
for (Map.Entry<String, Integer> entry : map.entrySet()) {
// 通过Entry对象的getKey,getValue获取 key和value
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
}
5、获取value原理
-
它内部通过空间换时间的方法,用一个大数组存储所有
value
,并根据key直接计算出value
应该存储在哪个索引 -
计算索引是调用
key
对象的hashCode()
方法
6、注意点
当key
是自定义类型的时候,必须重写hashCode()
,equals()