Java的java.util包主要提供了三种类型的集合:List、Set、Map
Java集合的设计特点:
- 接口和实现类相分离,例如有序表的接口是List,具体的实现类有ArrayList、LinkedList等
- 支持泛型
- 访问集合总是通过迭代器
注意:避免使用历史遗留类和接口
有一小部分集合类是遗留类,不应该继续使用:
- Hashtable:一种线程安全的Map实现;
- Vector:一种线程安全的List实现;
- Stack:基于Vector实现的LIFO的栈。
还有一小部分接口是遗留接口,也不应该继续使用:
- Enumeration:已被Iterator取代。
List 有序列表
List<E>
接口主要有以下方法:
在末尾添加一个元素:boolean add(E e)
在指定索引添加一个元素:boolean add(int index, E e)
删除指定索引的元素:E remove(int index)
删除某个元素:boolean remove(Object e)
获取指定索引的元素:E get(int index)
获取链表大小(包含元素的个数):int size()
常用的实现有:ArrayList和LinkedList
ArrayList
ArrayList内部由数组实现,增删元素时会自动将其余元素挪位置,加满时会自动创建一个更大的数组,将原数组的所有元素复制过去,取代旧数组。
LinkedList
LinkedList内部由链表实现。
创建List
方法一:创建空List,用add添加元素
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
或用双括号:
List<Integer> list=new ArrayList<Integer>(){
{
add(1);
add(2);
add(3);
}};
方法二:使用Arrays.asList()
//创建List
List<Integer> list=Arrays.asList(1, 2, 3); //注意要用Integer
//创建元素为List的list
List<List<Integer>> list = Arrays.asList(Arrays.asList(1,2), Arrays.asList(3,4));
注意Arrays.asList()
创建的List是一个只读对象,不能再用add操作了:
List<Integer> list = List.of(12, 34, 56);
list.add(999); // UnsupportedOperationException
asList 是 java.util.Arrays 类的一个方法
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
容易犯的错误:
int[] array = {
1,2,3};
List myList = Arrays.asList(array); //将array对象作为list元素了
因为asList的接口是泛型:List<T> asList(T...)
,int作为原始类型不属于Object,所以不能被作为泛类型参数。整个数组倒是一个Object,所以整个数组就用来取代T
,所以得到了一个List<int[]>
。正确的做法是传入一个Integer[]
,就能得到一个List<Integer>
了。
方法三:从Stream中创建
@Test
public void givenStream_thenInitializeList(){
List<String> list = Stream.of("foo", "bar")
.collect(Collectors.toList());
assertTrue(list.contains("foo"));
}
方法四:工厂方法 .of (Java 9)
Java 9+ 版本,可以用List.of()
取代Arrays.asList()
。
遍历List
方法一:for循环 + get(i)
for (int i=0; i<list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
不推荐这种方法,因为只对ArrayList高效,对LinkedList很低效。
方法二:迭代器Iterator (推荐!)
迭代器:
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
简化版为:
//自动使用迭代器
for (String s : list) {
System.out.println(s);
}
不同的List类型,返回的Iterator对象实现也是不同的,但总是具有最高的访问效率。
Java的for each
循环会自动使用Iterator遍历。实际上,只要实现了Iterable接口的集合类都可以直接用for each循环来遍历。
List转为Array
Integer[] array = list.toArray(new Integer[3]);
Integer[] array = list.toArray(new Integer[list.size()]);
Integer[] array = list.toArray(Integer[]::new); //函数式写法
编写equals方法
List中contains()、indexOf()等方法使用equals()方法判断两个元素是否相等。
因此,对于自定义的类型,要想正确使用List的contains()、indexOf()这些方法,放入的实例必须正确覆写equals()方法,否则,放进去的实例查找不到。
public class Person {
public String name;
public int age;
}
public boolean equals(Object o) {
if (o instanceof Person) {
Person p = (Person) o;
//引用字段用equals,基本类型字段用==
return this.name.equals(p.name) && this.age == p.age;
}
return false;
}
但上面方法中若this.name
为null
,equals()
方法就会报错。一种解决方式是手动判断是否为null
,但这显然太麻烦。一般使用Objects.equals()
静态方法即可。
标准覆写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;
}
Map 映射表
put(),get()分别用于添加、获取映射。
Student s = new Student("Xiao Ming", 99);
Map<String, Student> map = new HashMap<>(); //创建HashMap
map.put("Xiao Ming", s); // 添加元素
Student target = map.get("Xiao Ming"); // 查找元素
遍历Map,顺序是不确定的:
//方法一:通过key遍历,keySet()相当于python中的keys()
for (String key : map.keySet()) {
Integer value = map.get(key);
System.out.println(key + " = " + value);
}
//方法二:直接遍历key、value,entrySet()相当于python中的items()
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + " = " + value);
}
用List存储全量数据,用Map做缓存的例子:
class Students {
List<Student> list;//定义了一个Student的List,可能十分巨大
Map<String, Integer> cache;//定义了一个Map,缓存