Collection

  • 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
  • JDK 不提供此接口的任何直接实现.它提供更具体的子接口(如Set和List)实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// ---------- 1. 创建:面向接口编程 ----------
Collection<String> c = new ArrayList<>(); // List:可重复、可 null
Collection<String> set = new HashSet<>(); // Set:不重复,add 已存在返回 false

// ---------- 2. 添加:boolean = 集合是否真的发生了变化 ----------
boolean b1 = c.add("A"); // true
boolean b2 = c.add("B"); // true
boolean b3 = c.add("A"); // List:true / Set:false(重复被拒绝)
c.addAll(List.of("X", "Y")); // 参数必须是一个集合,不是可变参数

// ---------- 3. 查询 ----------
int n = c.size(); // 元素个数,返回 int
boolean empty = c.isEmpty(); // 是否为空
boolean has = c.contains("A"); // 依赖 equals();自定义对象必须重写 equals/hashCode
boolean all = c.containsAll(List.of("A","B")); // 是否包含全部

// ---------- 4. 删除:按对象删,没有索引概念 ----------
boolean r1 = c.remove("A"); // 只删第一个 equals 匹配的,返回是否删成功
// remove(Object) 不是按索引!List 才有 remove(int index)
Collection<Integer> nums = new ArrayList<>();
nums.add(0); nums.add(1);
nums.remove(0); // 删的是值为 0 的元素,不是第 0 个
((List<Integer>)nums).remove(0); // 想按索引删必须转成 List

c.removeAll(List.of("A","B")); // 差集:删掉所有在参数集合里出现的
c.retainAll(List.of("X")); // 交集:只保留参数集合里也有的
c.clear(); // void,清空后 size()==0(容量不变,引用置 null 才彻底释放)

// ---------- 5. 条件删除(JDK8+,最推荐,不会抛并发异常)----------
c.removeIf(s -> s == null); // 参数是 Predicate,不是具体元素
c.removeIf(s -> s.startsWith("A"));

// ---------- 6. 遍历 + 安全删除:唯一能用集合自身删除的方式是迭代器 ----------
Iterator<String> it = c.iterator();
while (it.hasNext()) {
String s = it.next();
if (s.length() > 3) it.remove(); // 正确
// c.remove(s); // ConcurrentModificationException
}
for (String s : c) { ... } // 增强 for 底层就是 iterator,同样不能 add/remove

// ---------- 7. 转数组 / 转流 ----------
Object[] a1 = c.toArray();
String[] a2 = c.toArray(new String[0]);
c.stream().filter(...).forEach(...); // JDK8+

// ---------- 8. 其他注意事项 ----------
// · 泛型不能写基本类型:Collection<Integer> Collection<int>
// · null:ArrayList/LinkedList 允许多个 null;HashSet 只允许一个;TreeSet 不允许(比较抛 NPE)
// · Set 判断重复:equals() + hashCode() 都要重写
// · 批量方法 removeAll/retainAll/containsAll/addAll 参数都是 Collection,别写成可变参数
// · size() 返回 int,超大集合理论上会溢出(一般不用管)

迭代器以及遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Collection<String> c = new ArrayList<>(List.of("A", "BB", "CCC", "DDDD"));

// ==================== 1. 增强 for(最常用,只读场景)====================
// 底层 = iterator,语法糖;简洁但遍历时不能 add/remove,否则 ConcurrentModificationException

for (String s : c) {
System.out.println(s); // s 是局部副本变量,修改 s 不影响集合元素
}
// 增强 for 里没有索引变量,需要序号时自己维护:
int i = 0;
for (String s : c) { System.out.println(i++ + ":" + s); }

// ==================== 2. Lambda / forEach(JDK8+,本质还是迭代器)====================
// Collection 继承 Iterable,默认方法 void forEach(Consumer<? super T> action)
c.forEach(s -> System.out.println(s));
c.forEach(System.out::println); // 方法引用,更短
c.forEach((String s) -> { System.out.println(s); }); // 多语句加大括号

// 常见变形
c.forEach(s -> { if (s.length() > 2) System.out.println(s); });
// 累加/统计(注意:lambda 内引用的局部变量必须 effectively final)
StringBuilder sb = new StringBuilder();
c.forEach(sb::append);

// 条件删除用 removeIf(也是 Consumer/Predicate 系,唯一能在 lambda 里安全删的方式)
c.removeIf(s -> s.length() > 2); // 安全
// c.forEach(s -> { if (...) c.remove(s); }); // ConcurrentModificationException

// lambda 三大坑:
// 1) 不能 break / continue(没有循环体概念)→ 改用 stream().anyMatch / 普通 for
// 2) 不能 return 跳出外层(return = continue),也不能 throw 受检异常
// 3) 捕获的局部变量隐式 final,不能在 lambda 里改外部计数变量(用 AtomicInteger 或数组绕)

// ==================== 3. Iterator 迭代器(唯一能在遍历中安全删除的方式) ====================

Iterator<String> it = c.iterator(); // 每次调用返回新的迭代器,从头开始
while (it.hasNext()) { // 先判断,再取
String s = it.next(); // next() 只调一次,调两次会跳元素/抛 NoSuchElementException

if (s == null) continue;
if (s.length() > 2) {
it.remove(); // 必须用迭代器自己的 remove,且必须在 next() 之后调用
}
}
// it.remove(); // IllegalStateException(还没 next 或已 remove 过一次)
// c.add("X"); // 迭代过程中用集合自身增删 → ConcurrentModificationException

// 带索引的写法(仅 List 有 ListIterator,支持双向 + 添加)
ListIterator<String> lit = ((List<String>)c).listIterator();
while (lit.hasNext()) {
int idx = lit.nextIndex(); // 当前元素下标
String s = lit.next();
if (s.length() > 2) lit.remove(); // 安全删除
else lit.add("X"); // 安全插入(Iterator 没有 add)
}
lit.hasPrevious(); lit.previous(); // 反向遍历

// ==================== 4. 对比速查 ====================
// 方式 能否拿索引 能否安全删除 能否 break 写法
// 增强 for 否 否 能 最简
// forEach(Lambda) 否 否(用removeIf能) 否 最简+可链式
// Iterator 否 能 能 略啰嗦但功能最全
// ListIterator 能 能 能 仅 List
// 普通 for(i) 能(倒序删才安全) 能 仅 List,删除要注意下标前移

// 普通 for 删除的正确姿势(List 专属):倒序遍历,避免下标错位漏元素
List<String> list = new ArrayList<>(c);
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).length() > 2) list.remove(i); // 正序删会漏掉相邻元素
}

List集合

方法名 描述
void add(int index,E element) 在此集合中的指定位置插入指定的元素
E remove(int index) 删除指定索引处的元素,返回被删除的元素
E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
E get(int index) 返回指定索引处的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// remove 优先调用,实参和形参类型一致的方法。
List<Integer> li = new ArrayList<>();
li.add(1);
li.add(2);
li.remove(1); // 这个是索引 1,如果要是本身删除 Integer.valueOf(1);

// 列表迭代器
//获取一个列表迭代器的对象,里面的指针默认也是指向 0 索引的
//额外添加了一个方法:在遍历的过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String str = it.next();
if("bbb".equals(str)){
//qqq
it.add("qqq");
}
}

LinkedList

方法名 说明
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class MyLinkedListDemo4 {

public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// public void addFirst(E e) 在该列表开头插入指定的元素
//method1(list);

// public void addLast(E e) 将指定的元素追加到此列表的末尾
// method2(list);

// public E getFirst() 返回此列表中的第一个元素
// public E getLast() 返回此列表中的最后一个元素
// method3(list);

// public E removeFirst() 从此列表中删除并返回第一个元素
// public E removeLast() 从此列表中删除并返回最后一个元素
// method4(list);
}

private static void method4(LinkedList<String> list) {
String first = list.removeFirst();
System.out.println(first);

String last = list.removeLast();
System.out.println(last);

System.out.println(list);
}

private static void method3(LinkedList<String> list) {
String first = list.getFirst();
String last = list.getLast();
System.out.println(first);
System.out.println(last);
}

private static void method2(LinkedList<String> list) {
list.addLast("www");
System.out.println(list);
}

private static void method1(LinkedList<String> list) {
list.addFirst("qqq");
System.out.println(list);
}
}

泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/* 泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制
泛型只支持引用类型
指定泛型的具体类型后,传递数据时,可以传入该类类型或其子类类型
如果不写泛型,类型默认是 Object
*/
// 泛型类
class Box<T> {
private final T value;
Box(T value) { this.value = value; }
T get() { return value; }
}

// 泛型接口
@FunctionalInterface
interface Converter<T, R> {
R convert(T t);
}

// 泛型方法
class Utils {
static <T> T first(List<T> list) {
return list.get(0);
}

static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
}

// 使用
Box<String> box = new Box<>("hello");
String s = box.get();
Converter<String, Integer> len = String::length; // 类名::实例方法(省去参数和括号的冗余)
Converter<String, Integer> len2 = str -> str.length(); // Lambda 写法
Integer n = len.convert("java");
List<Integer> nums = List.of(1, 2, 3);
Integer first = Utils.first(nums);
Integer max = Utils.max(3, 7);

// ---------- 泛型不具备继承性,但是数据具备继承性 ----------
class Ye {}
class Fu extends Ye {}
class Zi extends Fu {}
// 创建集合对象
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
method(list1); // OK,类型完全一致 ArrayList<Ye>
// method(list2); // 编译错误!ArrayList<Fu> 不是 ArrayList<Ye> 的子类
// method(list3); // 编译错误!ArrayList<Zi> 不是 ArrayList<Ye> 的子类
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
public static void method(ArrayList<Ye> list) {}

// ---------- 4. 通配符 ----------
// ? extends Number:元素是 Number 或其子类,适合读,不适合写
double sum(List<? extends Number> list) {
double total = 0;
for (Number num : list) total += num.doubleValue();
return total;
}
sum(List.of(1, 2, 3)); // List<Integer> OK
sum(List.of(1.5, 2.5)); // List<Double> OK

// ? super Integer:元素是 Integer 或其父类,适合写,读只能读成 Object
void addInts(List<? super Integer> list) {
list.add(1); // OK
Object o = list.get(0); // 只能读成 Object
}
List<Number> numberList = new ArrayList<>();
addInts(numberList); // List<Number> OK
addInts(new ArrayList<Object>()); // List<Object> OK

// ---------- 5. PECS 原则 ----------
// 生产者用 extends(只读),消费者用 super(只写)
static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T t : src) dest.add(t);
}
// 通配符 ? extends T:接收 T 或 T 的子类列表,读取安全
// 通配符 ? super T:接收 T 或 T 的父类列表,写入安全

数据结构

  • 栈结构:先进后出,后进先出
  • 队列结构:先进先出,后进后出
  • 数组结构:查询快、增删慢
  • 链表结构:查询慢、增删快

二叉树

  • 二叉树中,任意一个节点的度要小于等于2
    • 节点:在树结构中,每一个元素称之为节点
    • 度:每一个节点的子节点数量称之为度

二叉查找树

  • 每一个节点上最多有两个子节点
  • 左子树上所有节点的值都小于根节点的值
  • 右子树上所有节点的值都大于根节点的值

添加节点规则:小的存左边,大的存右边,一样的不存

遍历:

  • 前序遍历:当前节点,左子节点,右子结点
  • 中序遍历:左子节点,当前节点,右子结点
  • 后序遍历:左子节点,右子结点,当前节点
  • 层序遍历:一层一层的去遍历

平衡二叉树

  • 二叉树左右两个子树的高度差不超过1
  • 任意节点的左右两个子树都是一颗平衡二叉树

左旋:就是将根节点的右侧往左拉,原先的右子节点变成新的父节点,并把多余的左子节点出让,给已经降级的根节点当右子节点

右旋:就是将根节点的左侧往右拉,左子节点变成了新的父节点,并把多余的右子节点出让,给已经降级根节点当左子节点

平衡二叉树旋转的四种情况

  • 左左
    • 左左:当根节点左子树的左子树有节点插入,导致二叉树不平衡
    • 如何旋转:直接对整体进行右旋即可
  • 左右
    • 左右:当根节点左子树的右子树有节点插入,导致二叉树不平衡
    • 如何旋转:先在左子树对应的节点位置进行左旋,在对整体进行右旋
  • 右右
    • 右右:当根节点右子树的右子树有节点插入,导致二叉树不平衡
    • 如何旋转:直接对整体进行左旋即可
  • 右左
    • 右左:当根节点右子树的左子树有节点插入,导致二叉树不平衡
    • 如何旋转:先在右子树对应的节点位置进行右旋,在对整体进行左旋

红黑树

  • 平衡二叉B树
  • 每一个节点可以是红或者黑
  • 红黑树不是高度平衡的,它的平衡是通过“自己的红黑规则”进行实现的

红黑规则

  1. 每一个节点或是红色的,或者是黑色的
  2. 根节点必须是黑色
  3. 红黑树中,所有空指针(null)在逻辑上均被视为指向一个黑色的虚拟叶子节点(NIL/哨兵节点),它不存数据,仅作为路径终点统一黑高计算基准。
  4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
  5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点

红黑树添加节点的默认颜色:添加节点时,默认为红色,效率高

保持红黑规则:

  • 根节点位置:直接变为黑色
  • 非根节点位置
    • 父节点为黑色
      • 不需要任何操作,默认红色即可
    • 父节点为红色
      • 叔叔节点为红色
        • 将“父节点”设为黑色,将“叔叔节点”设为黑色
        • 将“祖父节点”设为红色
        • 如果“祖父节点”为根节点,则将根节点再次变成黑色
      • 叔叔节点为黑色(当前节点是父的右孩子)
        • 把父作为当前节点并左旋,再进行判断
      • 叔叔节点为黑色(当前节点是父的左孩子)
        • 将“父节点”设为黑色
        • 将“祖父节点”设为红色
        • 以“祖父节点”为支点进行右旋转

Set

无序、不重复、无索引

  • 不可以存储重复元素
  • 没有索引,不能使用普通for循环遍历
1
2
3
4
5
6
7
8
9
10
11
12
Set<String> set = new TreeSet<>(); //创建集合对象
set.add("aaa"); //添加元素
//遍历集合
Iterator<String> it = set.iterator();
while (it.hasNext()){
String s = it.next();
System.out.println(s);
}

for (String s : set) {
System.out.println(s);
}

HashSet:无序、不重复、无索引

LinkedHashSet:有序(存储和取出的元素顺利一致)、不重复、无索引

TreeSet:可排序、不重复、无索引

TreeSet():根据其元素的自然排序进行排序

TreeSet(Comparator comparator):根据指定的比较器进行排序


HashSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 哈希值:public int hashCode(),是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
JDK1.8以前:数组 + 链表;
JDK1.8以后:节点个数少于等于8个 数组 + 链表,节点个数多于8个 数组 + 链表 + 红黑树
没有重写 hashCode 方法,不同对象计算的 hash 值是不同的
重写 hashCode 方法,不同对象的只要属性值相同,hash 相同
在部分情况下,hash 会出现重复
*/
// HashSet集合存储自定义类型元素,要想实现元素的唯一,要求必须重写hashCode方法和equals方法
@Override
public boolean equals(Object o) {}
@Override
public int hashCode() {
return Objects.hash(color, age);
}

// 要求去重且存取有序,才使用 LinkedHashSet

TreeSet

TreeSet():根据其元素的自然排序进行排序

TreeSet(Comparator comparator):根据指定的比较器进行排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 自然排序Comparable的使用
// 自定义的Student类实现Comparable接口,重写接口中的compareTo方法
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public int compareTo(Student o) {
// 返回值:正数添加的元素this是大的存右边;负数添加的元素this是小的存左边,0 舍弃
//按照对象的年龄进行排序
//主要判断条件: 按照年龄从小到大排序
int result = this.age - o.age;
//次要判断条件: 年龄相同时,按照姓名的字母顺序排序
result = result == 0 ? this.name.compareTo(o.getName()) : result;
return result;
}
}
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("zhangsan",28);
Student s2 = new Student("lisi",27);

// 比较器排序Comparator的使用
public class Teacher {
private String name;
private int age;
public Teacher() { }
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
TreeSet<Teacher> teacherSet = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
//o1表示现在要存入的那个元素
//o2表示已经存入到集合中的元素

//主要条件
int result = o1.getAge() - o2.getAge();
//次要条件
result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
return result;
}
});

Map

  • 双列集合,一个键对应一个值
  • 键不可以重复,值可以重复
方法名 说明
V put(K key,V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数

获取功能

方法名 说明
V get(Object key) 根据键获取值
Set keySet() 获取所有键的集合
Collection values() 获取所有值的集合
Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// interface Map<K,V> K:键的类型;V:值的类型
Map<String,String> map = new HashMap<String,String>();
//添加元素
map.put("张无忌", "赵敏1");
map.put("郭靖", "黄蓉");
map.put("杨过", "小龙女");
// map.put("张无忌", "赵敏2"); 键存在覆盖原来的值,返回值被覆盖的值。

// Collection<V> values():获取所有值的集合
Collection<String> values = map.values();
for(String value : values) {
System.out.println(value);
}

//获取所有键的集合。用 keySet() 方法实现
Set<String> keySet = map.keySet();
//遍历键的集合,获取到每一个键。用增强for实现
for (String key : keySet) {
//根据键去找值。用get(Object key)方法实现
String value = map.get(key);
System.out.println(key + "," + value);
}

//获取所有键值对对象的集合
Set<Map.Entry<String, String>> entrySet = map.entrySet();
//遍历键值对对象的集合,得到每一个键值对对象
for (Map.Entry<String, String> me : entrySet) {
//根据键值对对象获取键和值
String key = me.getKey();
String value = me.getValue();
System.out.println(key + "," + value);
}

// Lambda 表达式
// default void forEach(BiConsumer<? super k, ? super V> action)
map.forEach((key, value) -> System.out.println(key + "=" + value));

HashMap

  • HashMap底层是哈希表结构的
  • 依赖hashCode方法和equals方法保证键的唯一
  • 如果键要存储的是自定义对象,需要重写hashCode和equals方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Student {
private String name;
private int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name != null ? name.equals(student.name) : student.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}

HashMap<Student, String> hm = new HashMap<Student, String>();
Student s1 = new Student("林青霞", 30);
//遍历集合
Set<Student> keySet = hm.keySet();
for (Student key : keySet) {
String value = hm.get(key);
System.out.println(key.getName() + "," + key.getAge() + "," + value);
}

LinkedHashMap

由键决定:有序、不重复、无索引。

这里的有序指的是保证存储和取出的元素顺序一致


TreeMap

  • TreeMap底层是红黑树结构
  • 依赖自然排序或者比较器排序,对键进行排序
  • 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Student implements Comparable<Student>{
// ...
@Override
public int compareTo(Student o) {
//按照年龄进行排序
int result = o.getAge() - this.getAge();
//次要条件,按照姓名排序。
result = result == 0 ? o.getName().compareTo(this.getName()) : result;
return result;
}
}

// 创建TreeMap集合对象
TreeMap<Student,String> tm = new TreeMap<>();
// 遍历TreeMap集合,打印每个学生的信息
tm.forEach(
(Student key, String value)->{
System.out.println(key + "---" + value);
}
);

可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 语法:类型... 参数名,必须是最后一个参数
void f(String s, int... nums) {
// nums 本质是 int[]
for (int n : nums) { ... }
}

// 调用
f("a"); // nums = new int[0]
f("a", 1, 2, 3); // nums = {1, 2, 3}
f("a", new int[]{1, 2}); // 也可直接传数组

/* 规则
1. 一个方法只能有一个可变参数
2. 必须放在参数列表最后
3. 不能和对应数组参数重载:f(int...) 与 f(int[]) 冲突
4. 固定参数方法优先:f(int, int) 优先于 f(int...)
5. 泛型可变参数可能有警告,可用 @SafeVarargs
*/

// 注意:null 表示 null 数组,不是空数组。方法内部当数组用,注意空指针

Collections类

  • java.utils.Collections 是集合工具类,用来对集合进行操作。

常用方法如下:

  • public static void shuffle(List<?> list) :打乱集合顺序。
  • public static <T> void sort(List<T> list) :将集合中元素按照默认规则排序。
  • public static <T> void sort(List<T> list,Comparator<? super T> ) :将集合中元素按照指定规则排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class CollectionsDemo {

public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();

list.add(100);
list.add(300);
list.add(200);
list.add(50);
//排序方法
Collections.sort(list);
System.out.println(list);
}
}
// 结果:[50,100, 200, 300]

// Comparator 比较器
public class Student{
private String name;
private int age;
}
public class Demo {
public static void main(String[] args) {
// 创建四个学生对象 存储到集合中
ArrayList<Student> list = new ArrayList<Student>();

list.add(new Student("rose",18));
list.add(new Student("jack",16));
list.add(new Student("abc",20));
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge()-o2.getAge();//以学生的年龄升序
}
});
for (Student student : list) {
System.out.println(student);
}
}
}
Student{name='jack', age=16}
Student{name='rose', age=18}
Student{name='abc', age=20}

不可变集合

是一个长度不可变,内容也无法修改的集合,只能进行查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 不可变的list集合
List<String> list = List.of("张三", "李四", "王五", "赵六");
// 不可变的Set集合
Set<String> set = Set.of("张三", "张三", "李四", "王五", "赵六");
// 不可变的Map集合,Map 的 of 方法最多传递20个参数。
Map<String, String> map = Map.of("张三", "南京", "张三", "北京");
// Map 键值对个数大于10
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "南京");
// ...
hm.put("陈二", "嘉兴");
/*
//获取到所有的键值对对象(Entry对象)
Set<Map.Entry<String, String>> entries = hm.entrySet();
//把entries变成一个数组
Map.Entry[] arr1 = new Map.Entry[0];
Map.Entry[] arr2 = entries.toArray(arr1);
//不可变的map集合
Map map = Map.ofEntries(arr2);
*/
// Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
Map<String, String> immutableMap = Map.copyOf(hm);

Stream流

  • 中间方法:一次操作完毕之后,还可以继续进行其他操作
  • 终结方法:一个Stream流只能有一个终结方法,是流水线上的最后一个操作

生成Stream流的方式

  • Collection体系集合:使用默认方法stream()生成流, default Stream stream()
  • Map体系集合:把Map转成Set集合,间接的生成流
  • 数组:通过Arrays中的静态方法stream生成流
  • 同种数据类型的多个数据:通过Stream接口的静态方法of(T… values)生成流
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//Collection体系的集合可以使用默认方法stream()生成流
List<String> list = new ArrayList<String>();
Stream<String> listStream = list.stream();
Set<String> set = new HashSet<String>();
Stream<String> setStream = set.stream();

//Map体系的集合间接的生成流
Map<String,Integer> map = new HashMap<String, Integer>();
Stream<String> keyStream = map.keySet().stream();
Stream<Integer> valueStream = map.values().stream();
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

//数组可以通过Arrays中的静态方法stream生成流
String[] strArray = {"hello","world","java"};
Stream<String> strArrayStream = Arrays.stream(strArray);

//同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
Stream<Integer> intStream = Stream.of(10, 20, 30);
//注意:
//Stream接口中静态方法of的细节
//方法的形参是一个可变参数,可以传递一堆零散的数据,也可以传递数组
//但是数组必须是引用数据类型的,如果传递基本数据类型,是会把整个数组当做一个元素,放到Stream当中。

int arr1 = {1, 2, 3};
String arr2 = {"a", "b", "c"}
Stream.of(arr1).forEach(s-> System.out.println(s));//[I@41629346

中间方法

方法名 说明
Stream filter(Predicate predicate) 用于对流中的数据进行过滤
Stream limit(long maxSize) 返回此流中的元素组成的流,截取前指定参数个数的数据
Stream skip(long n) 跳过指定参数个数的数据,返回由该流的剩余元素组成的流
static Stream concat(Stream a, Stream b) 合并a和b两个流为一个流
Stream distinct() 返回由该流的不同元素(根据 Object.equals(Object) )组成的流
Stream Map(Function<T,R> mapper) 转换流中的数据类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("张无忌");
list.add("王祖贤");
/*
注意1:中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
注意2:修改Stream流中的数据,不会影响原来集合或者数组中的数据
*/
// filter
list.stream().filter(s ->s.startsWith("张")).forEach(s-> System.out.println(s));

// limit、skip
//需求1:取前3个数据在控制台输出
list.stream().limit(3).forEach(s-> System.out.println(s));
System.out.println("--------");
//需求2:跳过3个元素,把剩下的元素在控制台输出
list.stream().skip(3).forEach(s-> System.out.println(s));
System.out.println("--------");
//需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(s-> System.out.println(s));

// concat、distinct
//需求1:取前4个数据组成一个流
Stream<String> s1 = list.stream().limit(4);
//需求2:跳过2个数据组成一个流
Stream<String> s2 = list.stream().skip(2);
//需求3:合并需求1和需求2得到的流,并把结果在控制台输出
// Stream.concat(s1,s2).forEach(s-> System.out.println(s));
//需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(s1,s2).distinct().forEach(s-> System.out.println(s));

// map
ArrayList<String> strList = new ArrayList<>();
Collections.addAll(strList, "a-1", "b-2");
strList.stream().map(s -> Integer.parseInt(s.split("-")[1]));

终结方法

方法名 说明
void forEach(Consumer action) 对此流的每个元素执行操作
long count() 返回此流中的元素数
Object[] toArray() 返回包含此流元素的数组
1
2
3
4
5
6
7
8
9
// long 返回此流中的元素数
long count = list.stream().count();

// forEach
list.stream().forEach(s -> System.out.println(s));

// toArray
Object[] arr1 = list.stream().toArray();
String[] arr2 = list.stream().toArray(value -> new String[value]);

收集方法

方法名 说明
R collect(Collector collector) 把结果收集到集合中

工具类Collectors提供了具体的收集方式

方法名 说明
public static Collector toList() 把元素收集到List集合中
public static Collector toSet() 把元素收集到Set集合中
public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
List<Integer> list1 = new ArrayList<>(List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 10, 10));

// toList: 收集到 List 集合(保留重复元素)
List<Integer> evenList = list1.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
// 输出: [2, 4, 6, 8, 10, 10, 10, 10, 10, 10]

// toSet: 收集到 Set 集合(自动去重)
Set<Integer> evenSet = list1.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toSet());
// 输出: [2, 4, 6, 8, 10]

// toMap 需求:筛选年龄>=24岁的人,收集到Map中(姓名为键,年龄为值)
List<String> list2 = new ArrayList<>(List.of("zhangsan,23", "lisi,24", "wangwu,25"));

Map<String, Integer> ageMap = list2.stream()
.filter(s -> Integer.parseInt(s.split(",")[1]) >= 24) // 过滤年龄
.collect(Collectors.toMap(
s -> s.split(",")[0], // 键:姓名
s -> Integer.parseInt(s.split(",")[1]) // 值:年龄
));
// 输出: {lisi=24, wangwu=25}

/*
笔记要点:
1. collect() 负责收集流中剩余的数据,但不负责创建容器和添加数据。
2. Collectors.toList(): 底层创建 List 并添加数据,保留重复。
3. Collectors.toSet(): 底层创建 Set 并添加数据,自动去重。
4. Collectors.toMap(): 底层创建 Map,需要两个 Lambda(分别指定键 Key 和值 Value 的生成规则)。
*/

方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class FunctionDemo1 {

public static void main(String[] args) {
Integer[] arr = {3, 5, 4, 1, 6, 2};
//1.引用处需要是函数式接口
//2.被引用的方法需要已经存在
//3.被引用方法的形参和返回值需要跟抽象方法的形参和返回值保持一致
//4.被引用方法的功能需要满足当前的要求
Arrays.sort(arr, FunctionDemo1::subtraction);
//可以是Java已经写好的,也可以是一些第三方的工具类
}
public static int subtraction(int num1, int num2) {
return num2 - num1;
}
}

// 引用类的静态方法(类名::静态方法)
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"1","2","3","4","5");
list.stream().map(Integer::parseInt).forEach(s-> System.out.println(s));

// 引用类中的成员方法(对象::成员方法)
// 其他类 其他类对象::方法名
// 本类 super::方法名
// 父类 super::方法名
public class StringOperation {
public boolean stringJudge(String s){
return s.startsWith("张") && s.length() == 3;
}
}
public class FunctionDemo3 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰");
list.stream().filter(new FunctionDemo3()::stringJudge).forEach(s-> System.out.println(s));

// 静态方法中是没有this的,创建本类的对象
list.stream().filter(new FunctionDemo3()::stringJudge).forEach(s-> System.out.println(s));

public class StringOperation {...}
}
}

// 引用构造方法(类名::new)
public class Student {
private String name;
private int age;
public Student(String str) {
String[] arr = str.split(",");
this.name = arr[0];
this.age = Integer.parseInt(arr[1]);
}
}
List<Student> newList2 = list.stream().map(Student::new).collect(Collectors.toList());

// 其他方式
// 1.使用类名引用成员方法(类名::成员方法)
/* 注意:
被引用方法的形参,需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要保持一致。
抽象方法形参的详解:
第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法
在Stream流当中,第一个参数一般都表示流里面的每一个数据。
假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String这个类中的方法
第二个参数到最后一个参数:跟被引用方法的形参保持一致,如果没有第二个参数,说明被引用的方法需要是无参的成员方法
局限性:
不能引用所有类中的成员方法。
是跟抽象方法的第一个参数有关,这个参数是什么类型的,那么就只能引用这个类中的方法。
*/
list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
// 2.数组的构造方法(数据类型[]::new)
// 细节:数组的类型需要和流中的数据类型保持一致
ArrayList<Integer> numList = new ArrayList<>();
Collections.addAll(numList, 1, 2, 3, 4, 5);
Integer[] arr2 = numList.stream().toArray(Integer[]::new);

IO