1. Collection
1.1 Set
定义
Set集合不允许包含相同的元素,如果试图将两个相同的元素加入同一个Set集合中,则添加操作失败,add()方法返回false,新元素不会被加入。
上面的内容,完全适用于HashSet、TreeSet和EnumSet三个实现类。
(1) 常用方法
Modifier and Type | Method and Description |
---|---|
boolean | **add(E e) **如果指定的元素不存在,则将其指定的元素添加(可选操作)。 |
boolean | **addAll(Collection<? extends E> c) **将指定集合中的所有元素添加到此集合(如果尚未存在)(可选操作)。 |
void | clear() 从此集合中删除所有元素(可选操作)。 |
boolean | contains(Object o) 如果此集合包含指定的元素,则返回 true 。 |
boolean | containsAll(Collection<?> c) 返回 true 如果此集合包含所有指定集合的元素。 |
boolean | equals(Object o) 将指定的对象与此集合进行比较以实现相等。 |
int | hashCode() 返回此集合的哈希码值。 |
boolean | isEmpty() 如果此集合不包含元素,则返回 true 。 |
Iterator<E> | iterator() 返回此集合中元素的迭代器。 |
boolean | remove(Object o) 如果存在,则从该集合中删除指定的元素(可选操作)。 |
boolean | removeAll(Collection<?> c) 从此集合中删除指定集合中包含的所有元素(可选操作)。 |
boolean | retainAll(Collection<?> c) 仅保留该集合中包含在指定集合中的元素(可选操作)。 |
int | size() 返回此集合中的元素数(其基数)。 |
default Spliterator<E> | spliterator() 在此集合中的元素上创建一个 Spliterator 。 |
Object[] | toArray() 返回一个包含此集合中所有元素的数组。 |
<T> T[] | toArray(T[] a) 返回一个包含此集合中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型。 |
(2) 创建Set
Set<String> set = new HashSet<>();
/* 方法一:通过add()方法添加元素 */
set.add("小勇子");
set.add("一定要好好加油啊!");
System.out.println(set);
/* 方法二:addAll() 批量添加元素 */
String[] str = {"小明", "小红", "小黄"};
// 将Array数组转换为集合
List<String> strList = Arrays.asList(str);
set.addAll(strList);
System.out.println(set);
// 和上面是等价的
List<String> strList2 = new ArrayList<>(Arrays.asList("White", "Red", "Yellow"));
set.addAll(strList2);
System.out.println(set);
/* 方法三:Collection.addAll() 直接将数组添加进集合里 */
String[] numStr = {"1", "2", "3"};
Collections.addAll(set, numStr);
System.out.println(set);
注意这里的Arrays.asList() 使用!
(3) 遍历Set
/* 方法一: 集合类通用遍历方式,用迭代器迭代 */
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
/* 与上面的方法等价 */
for (Iterator it = set.iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
/* for循环遍历 */
for (String val : set) {
System.out.println(val);
}
1.1.1 HashSet类
1.1.1.1 LinkedHahsSet类
1.1.2 TreeSet类
1.1.3 EnumSet类
1.2 List
在集合类中,List
是最基础的一种集合:它是一种有序列表。
List
的行为和数组几乎完全相同:List
内部按照放入元素的先后顺序存放,每个元素都可以通过索引确定自己的位置,List
的索引和数组一样,从0
开始。
数组和List
类似,也是有序结构,如果我们使用数组,在添加和删除元素的时候,会非常不方便。例如,从一个已有的数组{'A', 'B', 'C', 'D', 'E'}
中删除索引为2
的元素:
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ │
└───┴───┴───┴───┴───┴───┘
│ │
┌───┘ │
│ ┌───┘
│ │
▼ ▼
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ D │ E │ │ │
└───┴───┴───┴───┴───┴───┘
这个“删除”操作实际上是把'C'
后面的元素依次往前挪一个位置,而“添加”操作实际上是把指定位置以后的元素都依次向后挪一个位置,腾出来的位置给新加的元素。这两种操作,用数组实现非常麻烦。
参考:https://www.liaoxuefeng.com/wiki/1252599548343744/1265112034799552
1.2.1 ArrayList类
因此,在实际应用中,需要增删元素的有序列表,我们使用最多的是ArrayList
。实际上,ArrayList
在内部使用了数组来存储所有元素。例如,一个ArrayList
拥有5个元素,实际数组大小为6
(即有一个空位):
size=5
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ │
└───┴───┴───┴───┴───┴───┘
当添加一个元素并指定索引到ArrayList
时,ArrayList
自动移动需要移动的元素:
size=5
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ │ C │ D │ E │
└───┴───┴───┴───┴───┴───┘
然后,往内部指定索引的数组位置添加一个元素,然后把size
加1
:
size=6
┌───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │
└───┴───┴───┴───┴───┴───┘
继续添加元素,但是数组已满,没有空闲位置的时候,ArrayList
先创建一个更大的新数组,然后把旧数组的所有元素复制到新数组,紧接着用新数组取代旧数组:
size=6
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
现在,新数组就有了空位,可以继续添加一个元素到数组末尾,同时size
加1
:
size=7
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ F │ C │ D │ E │ G │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
可见,ArrayList
把添加和删除的操作封装起来,让我们操作List
类似于操作数组,却不用关心内部元素如何移动。
(1) 常用方法
我们考察List<E>
接口,可以看到几个主要的接口方法:
- 在末尾添加一个元素:
boolean add(E e)
- 在指定索引添加一个元素:
boolean add(int index, E e)
- 删除指定索引的元素:
int remove(int index)
- 删除某个元素:
int remove(Object e)
- 获取指定索引的元素:
E get(int index)
- 获取链表大小(包含元素的个数):
int size()
但是,实现List
接口并非只能通过数组(即ArrayList
的实现方式)来实现,另一种LinkedList
通过“链表”也实现了List接口。在LinkedList
中,它的内部每个元素都指向下一个元素:
┌───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐
HEAD ──>│ A │ ●─┼──>│ B │ ●─┼──>│ C │ ●─┼──>│ D │ │
└───┴───┘ └───┴───┘ └───┴───┘ └───┴───┘
我们来比较一下ArrayList
和LinkedList
:
ArrayList | LinkedList | |
---|---|---|
获取指定元素 | 速度很快 | 需要从头开始查找元素 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置添加/删除 | 需要移动元素 | 不需要移动元素 |
内存占用 | 少 | 较大 |
通常情况下,我们总是优先使用ArrayList
。
(2) 创建List
-
使用
ArrayList
和LinkedList
-
List接口可以添加重复的元素
-
List中允许添加null
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple"); // size=1
list.add("pear"); // size=2
list.add("apple"); // 允许重复添加元素,size=3
list.add(null); // size=4
System.out.println(list.size());
String second = list.get(3); // null
System.out.println(second);
}
}
- 还可以通过
List
接口提供的of()
方法,根据给定元素快速创建List
:
List<Integer> list = List.of(1, 2, 5);
但是List.of()
方法不接受null
值,如果传入null
,会抛出NullPointerException
异常。
(3) 遍历List
- **方法一:**使用
for
循环根据索引配合get(int)
方法遍历:
import java.util.List;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// 方法一:使用for循环,根据索引配合get(int)方法遍历
List<String> list = Arrays.asList("apple", "pear", "banana");
for (int i=0; i<list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
}
}
但这种方式并不推荐,一是代码复杂,二是因为get(int)
方法只有ArrayList
的实现是高效的,换成LinkedList
后,索引越大,访问速度越慢。
- 方法二: 使用迭代器
Iterator
来访问List
。(下面第二个代码块常用!)
Iterator
本身也是一个对象,但它是由List
的实例调用iterator()
方法的时候创建的。Iterator
对象知道如何遍历一个List
,并且不同的List
类型,返回的Iterator
对象实现也是不同的,但总是具有最高的访问效率。
Iterator
对象有两个方法:boolean hasNext()
判断是否有下一个元素,E next()
返回下一个元素。因此,使用Iterator
遍历List
代码如下:
import java.util.Iterator;
import java.util.List;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// 这种方式不是平时最常用的,下面的才是
List<String> list = Arrays.asList("apple", "pear", "banana");
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
}
}
有童鞋可能觉得使用Iterator
访问List
的代码比使用索引更复杂。但是,要记住,通过Iterator
遍历List
永远是最高效的方式。并且,由于Iterator
遍历是如此常用,所以,Java的for each
循环本身就可以帮我们使用Iterator
遍历。把上面的代码再改写如下:
import java.util.List;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// foreach循环进行遍历是平时最常用的
List<String> list = Arrays.asList("apple", "pear", "banana");
for (String s : list) {
System.out.println(s);
}
}
}
实际上,只要实现了Iterable
接口的集合类都可以直接用for each
循环来遍历,Java编译器本身并不知道如何遍历集合对象,但它会自动把for each
循环变成Iterator
的调用,原因就在于Iterable
接口定义了一个Iterator<E> iterator()
方法,强迫集合类必须返回一个Iterator
实例。
(4) List 转换为 Array
把List
变为Array
有三种方法。
- 第一种是调用
toArray()
方法直接返回一个Object[]
数组.
import java.util.List;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
List<String> list = Arrays.asList("apple", "pear", "banana");
Object[] array = list.toArray();
for (Object s : array) {
System.out.println(s);
}
}
}
这种方法会丢失类型信息,所以实际应用很少。
- 第二种方式是给
toArray(T[])
传入一个类型相同的Array
,List
内部自动把元素复制到传入的Array
中:
import java.util.List;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(12, 34, 56);
Integer[] array = list.toArray(new Integer[3]);
for (Integer n : array) {
System.out.println(n);
}
}
}
注意到这个toArray(T[])
方法的泛型参数<T>
并不是List
接口定义的泛型参数<E>
,所以,我们实际上可以传入其他类型的数组,例如我们传入Number
类型的数组,返回的仍然是Number
类型:
import java.util.List;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(12, 34, 56);
Number[] array = list.toArray(new Number[3]);
for (Number n : array) {
System.out.println(n);
}
}
}
但是,如果我们传入类型不匹配的数组,例如,String[]
类型的数组,由于List
的元素是Integer
,所以无法放入String
数组,这个方法会抛出ArrayStoreException
。
如果我们传入的数组大小和List
实际的元素个数不一致怎么办?根据List接口的文档,我们可以知道:
如果传入的数组不够大,那么List
内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比List
元素还要多,那么填充完元素后,剩下的数组元素一律填充null
。
实际上,最常用的是传入一个“恰好”大小的数组:
Integer[] array = list.toArray(new Integer[list.size()]);
- 第三种:更简洁的写法是通过
List
接口定义的T[] toArray(IntFunction<T[]> generator)
方法:
Integer[] array = list.toArray(Integer[]::new);
这种函数式写法我们会在后续讲到。
(5) Array转换为List
- 方法一:通过
List.of(T...)
方法最简单
Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);
- 方法二:通过使用
java.util.Arrays.asList()
方法,常用!(易错,要好好理解!)
import java.util.Arrays;
String[] myArray = { "Apple", "Banana", "Orange" };
List<String> myList = Arrays.asList(myArray);
// 或者
List<String> myList = Arrays.asList("Apple", "Orange");
方法二:易错点
后续有时间再补充
1.2.2 Vector类
1.2.2.1 Stack类
1.3 Queue
2. Map
Map与Collection在集合框架中属并列存在
Map存储的是键值对
Map存储元素使用put方法,Collection使用add方法
Map集合没有直接取出元素的方法,而是先转成Set集合,再通过迭代获取元素
Map集合中键要保证唯一性
也就是Collection是单列集合, Map 是双列集合。
(1) 常用方法
Modifier and Type | Method and Description |
---|---|
void | clear() 从该地图中删除所有的映射(可选操作) |
default V | compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 尝试计算指定键的映射及其当前映射的值(如果没有当前映射, null ) |
default V | computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 如果指定的键尚未与值相关联(或映射到 null ),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非 null |
default V | computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 如果指定的密钥的值存在且非空,则尝试计算给定密钥及其当前映射值的新映射 |
boolean | containsKey(Object key) 如果此映射包含指定键的映射,则返回 true |
boolean | containsValue(Object value) 如果此地图将一个或多个键映射到指定的值,则返回 true |
Set<Map.Entry<K,V>> | entrySet() 返回此地图中包含的映射的Set 视图 |
boolean | equals(Object o) 将指定的对象与此映射进行比较以获得相等性 |
default void | forEach(BiConsumer<? super K,? super V> action) 对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常 |
V | get(Object key) 返回到指定键所映射的值,或 null 如果此映射包含该键的映射 |
default V | getOrDefault(Object key, V defaultValue) 返回到指定键所映射的值,或 defaultValue 如果此映射包含该键的映射 |
int | hashCode() 返回此地图的哈希码值 |
boolean | isEmpty() 如果此地图不包含键值映射,则返回 true |
Set<K> | keySet() 返回此地图中包含的键的Set 视图 |
default V | merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果指定的键尚未与值相关联或与null相关联,则将其与给定的非空值相关联 |
V | put(K key, V value) 将指定的值与该映射中的指定键相关联(可选操作) |
void | putAll(Map<? extends K,? extends V> m) 将指定地图的所有映射复制到此映射(可选操作) |
default V | putIfAbsent(K key, V value) 如果指定的键尚未与某个值相关联(或映射到 null )将其与给定值相关联并返回 null ,否则返回当前值。 |
V | remove(Object key) 如果存在(从可选的操作),从该地图中删除一个键的映射 |
default boolean | remove(Object key, Object value) 仅当指定的密钥当前映射到指定的值时删除该条目 |
default V | replace(K key, V value) 只有当目标映射到某个值时,才能替换指定键的条目。 |
default boolean | replace(K key, V oldValue, V newValue) 仅当当前映射到指定的值时,才能替换指定键的条目 |
default void | replaceAll(BiFunction<? super K,? super V,? extends V> function) 将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常 |
int | size() 返回此地图中键值映射的数量 |
Collection<V> | values() 返回此地图中包含的值的Collection 视图 |
(2) 创建Map
/* ------------------------- 添加元素 ------------------------ */
Map<String, Integer> map1 = new HashMap<>();
/* 方法一:put()方法添加元素 */
map1.put("jack", 20);
map1.put("rose", 18);
map1.put("lucy", 17);
map1.put("java", 25);
map1.put("jack", 30); // 添加重复的键值(值不同),会返回几何中原有(重复键)的值
System.out.println("map1: " + map1);
Map<String, Integer> map2 = new HashMap<>();
map2.put("小明", 100);
map2.put("小红", 90);
System.out.println("map2: " + map2);
/* 方法二:putAll()方法添加元素 */
map1.putAll(map2);
System.out.println("map1: " + map1);
/* ------------------------- 删除元素 ------------------------ */
/* 方法一:remove()方法删除指定key元素*/
map1.remove("rose");
System.out.println("mpa1: " + map1);
/* 方法二:清空所有集合 */
map2.clear();
System.out.println("map2: " + map2);
/* ------------------------- 获取元素 ------------------------ */
/* 方法一:get()获取元素 */
System.out.println("value: " + map1.get("小明"));
(3) 遍历Map
/* ---------------------------------------------------------- */
/* ------------------------- 遍历元素 ------------------------ */
/* ---------------------------------------------------------- */
/* 方法一:增强for循环遍历,使用keySet()遍历,分别获取key和value。
* Set<K> keySet() 返回所有key对象的Set集合
*/
for (String key : map1.keySet()) {
System.out.println(key + ": " + map1.get(key));
}
/* 方法二:增强for循环遍历,使用entrySet()遍历 */
for (Map.Entry<String, Integer> entry : map1.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
/* ------------------- 主要看前面两种方法 ------------------ */
/* 方法三:使用迭代器遍历,使用keySet()遍历,分别获取key和value。
* Set<K> keySet() 返回所有key对象的Set集合
*/
// 和上面的等价的
// Set<String> ks = map1.keySet();
// Iterator<String> it = ks.iterator();
// 上面两句可以合并为下面一句
Iterator<String> it = map1.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
System.out.println(key + ": " + map1.get(key));
}
/* 方法四:使用entrySet()遍历
* public static interface Map.Entry<K, V>
* 通过Map中的entrySet()方法,获取存放Map.Entry<K, V>对象Set集合
* Set<Map.Entry<K, V>> entrySet()
* 面向对象的思想将map集合中的键和值映射关系打包为一个对象,就是Map.Entry,
* 将该对象存入Set集合,Map.Entry是一个对象,那么该对象具备的getKey,getValue获得键和值。
*/
// 返回的Map.Entry对象的Set集合 Map.Entry包含了key和value对象
// Set<Map.Entry<String, Integer>> es = map1.entrySet();
// Iterator<Map.Entry<String, Integer>> iterater = es.iterator();
// 上面两句等价于下面的一句
Iterator<Map.Entry<String, Integer>> iterater = map1.entrySet().iterator();
while (iterater.hasNext()) {
// 返回的是封装了key和value对象的Map.Entry对象
Map.Entry<String, Integer> en = iterater.next();
// 获取Map.Entry对象中封装的key和value对象
System.out.println(en.getKey() + " " + en.getValue());
}
/* 方法五:使用迭代器遍历,通过values()获取所有值,不能获取到key对象
* Collection<V> values()
*/
Collection<Integer> vs = map1.values();
Iterator<Integer> iterator = vs.iterator();
while (iterator.hasNext()) {
Integer value = iterator.next();
System.out.println("value: " + value);
}
3. 常用排序
3.1 List排序
3.2 Set排序
3.3 Map排序
4. Reference
- https://blog.youkuaiyun.com/qq_43437465/article/details/89437637