11 Collection集合、Map集合:分类、功能、遍历、底层原理,Stream流:获取、中间方法、终结方法 (黑马Java视频笔记)
文章目录
- 集合
- 1. 认识集合
- 2. Collection单列集合特点
- 3. Collection单列集合的常用功能
- 4. Collection的遍历方式
- 1)迭代器遍历 Iterator
- 2)增强for循环(for...each遍历)
- 3)Lambda表达式:forEach()
- 5. 三种遍历方式的区别
- **5.1 并发修改异常问题**
- **5.2 一般应对方案:**
- 5.3 ⭐方案一:迭代器遍历并删除
- 💡`modCount` 和 `expectedModCount` 的作用
- 5.4 方案二:用增强for和Lambda都没有办法解决并发修改异常
- 6. Collection-List集合:有序,可重复,有索引
- 1) LIst集合因为支持索引,所以除了继承了Collection的功能外,还有很多与索引相关的方法
- 2)遍历方式
- 3)ArrayList和LinkedList的区别:
- 4)LinkedList 应用场景:在首尾增删快
- 7. Collection-Set系列集合:无序(添加顺序和获取顺序不一致)、不重复、无索引
- 7.1 HashSet集合的底层原理
- 1)哈希值
- 2)对象哈希值的特点
- 3)基于哈希表存储数据 >>> 无序、不重复、无索引
- 存储方式变化过程:数组+链表 >>> 数组+链表+红黑树
- 默认加载因子
- 4)红黑树
- 5)为什么性能好
- 7.2 ⭐HashSet集合元素的去重操作 -> 自定义对象
- 思考🤔🤔🤔
- 7.3 LinkedHashSet集合的底层原理
- **有序** → 每个元素都额外的多了一个**双链表**的机制记录它前后元素的位置
- 7.4 TreeSet集合的原理
- 7.5 什么时候用什么集合呢?
- 8. Map集合(键值对集合)
- 8.1 Map集合分类
- 8.2 Map集合的常用方法
- 8.3 Map集合的遍历方式
- 1)键找值
- 2)键值对
- 3)Lambda表达式
- 8.4 Map集合的实现类
- 1)HashMap集合的底层原理
- 2)LinkedHashMap的底层原理
- 3)TreeMap的底层原理,同TreeSet集合
- 8.5 Map集合的案例-统计投票信息
- Stream流
- 1. 获取Stream流
- 2. 常用中间方法
- 3. 常用终结方法
- 3.1 常用终结方法
- 3.2 收集Stream流:
集合
1. 认识集合
💡容器 >> 装数据
体系结构:
- Collection 单列集合 :每个元素(数据)只包含一个值
- Map 双列集合:每个元素包含两个值(键值对)
2. Collection单列集合特点
- List系列集合:
ArrayList
、LinekdList
:添加的元素是有序、可重复、有索引
- Set系列集合:添加的元素是无序、不重复、无索引
HashSet
:无序、不重复、无索引LinkedHashSet
:有序、不重复、无索引TreeSet
:按照大小默认升序排序、不重复、无索引
public static void main(String[] args) {
// 目标:搞清楚Collection集合的整体特点
// 1、List家族的集合:有序、可重复、有索引
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list); // [张三, 李四, 王五]
System.out.println(list.get(0)); // 张三
// 2、Set家族的集合:无序、不可重复、无索引
Set<String> set = new HashSet<>();
set.add("张三");
set.add("张三");
set.add("李四");
set.add("王五");
System.out.println(set); // [李四, 张三, 王五]
System.out.println(set.contains("张三")); // true
}
3. Collection单列集合的常用功能
Collection
作为单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的
toArray
方法的使用
public static void main(String[] args) {
Collection<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list); // [张三, 李四, 王五]
// 把集合转换成数组:toArray() 默认转Object数组
Object[] arr = list.toArray();
System.out.println(arr.getClass()); // class [Ljava.lang.Object;
System.out.println(Arrays.toString(arr)); // [张三, 李四, 王五]
// 把集合转换成字符串数组
String[] arr2 = list.toArray(String[]::new);
// String[] arr2 = list.toArray(new String[0]);
/* 完整代码
String[] arr3 = list.toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int value) {
return new String[0];
}
});
*/
System.out.println(arr2.getClass()); // class [Ljava.lang.String;
}
❓:具体询问ai理解
4. Collection的遍历方式
1)迭代器遍历 Iterator
- 迭代器是用来遍历集合的专用方式(数组没有)
① 得到这个集合的迭代器对象
- 初始默认指在下标为0的位置
② 使用一个while循环
来遍历
// 1. 迭代器遍历:Iterator
Collection<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
// 获取迭代器对象
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
2)增强for循环(for…each遍历)
-
即可以遍历数组,也可以遍历集合
for (元素的数据类型 变量名:数组或者集合){ }
for (String s : list) {
System.out.println(s);
}
3)Lambda表达式:forEach()
// forEach本质上就是for增强遍历
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
5. 三种遍历方式的区别
5.1 并发修改异常问题
- 如果在遍历集合的同时又存在增删集合元素的行为,可能会出现业务异常,这种现象被称之为并发修改异常问题
// list = [宁夏枸杞, 枸杞, 西洋参, 枸杞子, 红枣]
// 需求1:删除全部枸杞
for(int i=0;i<list.size();i++){
String name = list.get(i);
System.out.println(name); // 只有List集合才有索引
if(name.contains("枸杞"))
{
list.remove(i);
}
}
System.out.println(list); // [枸杞, 西洋参, 红枣]
❓可以看到 list
集合中有一个遗漏的:这是因为在遍历到下标为 0 的 ” 宁夏枸杞 “ 时,i = 0 ,此时 ” 枸杞 “ 下标为 1,if 条件语句为 true → 删除下标为 0 的 ” 宁夏枸杞 “ ,” 枸杞 ” 的下标变为 0 ,“ 西洋参 ” 的下标变为 1 ,此时开始遍历 i = 1 的元素,不会遍历到 “ 枸杞 ”。
通过遍历时输出的 name ,也可以看到,并没有输出 “ 枸杞 ”
5.2 一般应对方案:
- 在删除之后进行
i--
(编译器也会给出警告提示)
- 倒着遍历并删除**(前提:支持索引)**
for(int i = list.size() - 1; i >= 0; i--){
String name = list.get(i);
System.out.println(name);
if (name.contains("枸杞")) {
list.remove(i);
}
}
5.3 ⭐方案一:迭代器遍历并删除
使用迭代器来删除,而不是list
集合删除(用list集合删除还是会存在并发修改异常)
迭代器内部会进行i--
// 迭代器遍历并删除
Iterator<String> it = list.iterator();
while(it.hasNext()){
String name = it.next();
System.out.println(name);
if(name.contains("枸杞"))
{
it.remove();
}
}
System.out.println(list);
⚠️如果使用迭代器遍历,使用集合方法删除 >> 报错!!!
🤔报错原因:modCount != expectedModCount
,可以看到后台modCount = 6,expectedModCount = 5
💡modCount
和 expectedModCount
的作用
5.4 方案二:用增强for和Lambda都没有办法解决并发修改异常
-
拿不到迭代器
-
只适合做遍历,不适合做遍历并修改操作
6. Collection-List集合:有序,可重复,有索引
ArrayList
:有序,可重复,有索引LinkedList
:有序,可重复,有索引
1) LIst集合因为支持索引,所以除了继承了Collection的功能外,还有很多与索引相关的方法
List<String> names = new ArrayList<>(); // 多态引用
List<String> names = new ArrayList<>();
// 1. 添加元素:add(E e)
names.add("张三");
names.add("李四");
names.add("王五");
System.out.println(names);
// 根据下标添加元素:add(int index, E e)
names.add(1, "赵六");
System.out.println(names);
// 2. 获取元素:get(int index)
String name = names.get(1);
System.out.println(name);
// 3. 修改元素:set(int index, E e)
names.set(1, "赵六");
System.out.println(names);
// 4. 删除元素:remove(int index)
names.remove(0);
System.out.println(names);
// 5. 获取长度:size()
int size = names.size();
System.out.println(size);
2)遍历方式
- for循环
for (int i = 0; i < names.size(); i++) {
String name2 = names.get(i);
System.out.println(name2);
}
- 迭代器
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name2 = it.next();
System.out.println(name2);
}
- 增强for
for (String name2 : names) {
System.out.println(name2);
}
- lambda表达式
names.forEach(System.out::println);
// System.out::println 是一种 实例方法引用,它引用了 PrintStream 类的 println 方法。
// names.forEach(name2 -> System.out.println(name2));
3)ArrayList和LinkedList的区别:
ArrayList
基于数组存储数据:连续存储区域 -> 根据索引查询数据较快,查询任意数据耗时与链表查询相同;增删数据效率低(扩容、迁移等)LinkedList
基于双链表存储数据:数据由结点组成,一个结点包含当前的数据值和下一个结点的地址(双链表的结点包含上一个结点地址和下一个结点地址),查询慢(需要从头开始找),增删相对快(不用迁移其他数据),但对首位元素进行增删改查的速度是极快的
4)LinkedList 应用场景:在首尾增删快
- 设计队列:先进先出,后进后出 >>> 尾进头出
- 入队:
addLast
- 出队:
removeFirst
- 入队:
// 目标:用LinkedList做一个队列对象
LinkedList<String> queue = new LinkedList<>();
// linkedlist独有功能,用list多态引用可能会使用不了
// 入队:addLast()从队尾入队
queue.addLast("张三");
// 出队:removeFirst()从队首出队
String name = queue.removeFirst();
- 设计栈:后进先出,先进后出 >>> 头进头出
- 进栈:
push
- 出栈:
pop
- 进栈:
// 做一个栈
LinkedList<String> stack = new LinkedList<>();
// 入栈:addFirst()从队首入栈 push()
stack.addFirst("张三");
stack.push("李四");
// 出栈:removeFirst()从队首出栈 pop()
String name2 = stack.removeFirst();
System.out.println(name2);
String name3 = stack.pop();
System.out.println(name3);
7. Collection-Set系列集合:无序(添加顺序和获取顺序不一致)、不重复、无索引
HashSet
:无序、不重复、无索引LinkedHashSet
:有序(哈希表)、不重复、无索引TreeSet
:排序(红黑树)、不重复、无索引
其功能就是Collection
的功能继承过来的,因为没有索引,所以也没有新增的功能。
// HashSet无序不重复、无索引
Set<String> set = new HashSet<>();
set.add("java");
set.add("java");
set.add("鸿蒙");
set.add("python");
set.add("新媒体");
System.out.println(set); // [python, java, 鸿蒙, 新媒体]
// LinkedHashSet有序不重复、有索引
Set<String> set1 = new LinkedHashSet<>();
set1.add("java");
set1.add("java");
set1.add("鸿蒙");
set1.add("python");
set1.add("新媒体");
System.out.println(set1); // [java, 鸿蒙, python, 新媒体]
// 2、创建一个TreeSet集合:排序(默认要大小升序排序)、不重复、无索引
Set<Double> set2 = new TreeSet<>();
set2.add(3.14);
set2.add(3.14);
set2.add(5.6);
set2.add(1.2);
System.out.println(set2); // [1.2, 3.14, 5.6]
7.1 HashSet集合的底层原理
课程链接
1)哈希值
- 一个int类型的随机值,java中每个对象都有一个哈希值
- Java中的所有对象,都可以调用
Object类
提供的hashCode方法
,返回该对象自己的哈希值- public int hashCode()
2)对象哈希值的特点
- 同一个对象多次调用
hashCode()
方法返回的哈希值是相同的 - 不同的对象,他们的哈希值大概率不相等,但也有可能会相等(哈希碰撞)
- int 范围 (-21亿多~21亿多) vs 创建了 50亿个对象 >>> 就会有重复的哈希值
3)基于哈希表存储数据 >>> 无序、不重复、无索引
哈希表
存储方式变化过程:数组+链表 >>> 数组+链表+红黑树
默认加载因子
- 数据太多时,哈希表中的链表会很长,而链表查询效率很低(需要从头开始查找),所以在达到 加载因子*总长度 个元素时,对哈希表进行扩容
4)红黑树
关于集中排序树的具体含义
当数据已经排好序时,使用链表存储时的二叉查找树就相当于一个单链表
⬇
平衡二叉树(左右子树高度差不超过1)
⬇
红黑树(可以自平衡的二叉树)
- 增删改查效率高
5)为什么性能好
查/存/改:底层基于数组,拿到 hash 值后就能通过映射关系算出元素存储的位置
删:找到位置后,直接抹除即可,不用进行数据迁移等操作
7.2 ⭐HashSet集合元素的去重操作 -> 自定义对象
需求:
- 创建一个存储学生对象的集合,存储多个学生对象
要求:
- 多个学生对象的成员变量值相同时,我们就认为是同一个对象,要求只保留一个
分析:
① 定义学生类、创建HashSet集合
对象、创建学生对象
- 学生类
public class Student {
private String name;
private int age;
private int score;
Student() {
}
Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}'+ '\n';
}
}
- 创建
HashSet集合
对象,并通过Collections工具类
批量添加学生数据
Student s1 = new Student("小明", 14, 90);
// ....
Set<Student> students = new HashSet<>();
// 使用Collections集合工具类批量添加数据
Collections.addAll(students, s1, s2, s3, s4, s5);
System.out.println(students);
②在学生类中重写两个方法 : hashCode()和equals()
,自动生成即可
③ 遍历集合(增强for)
for (Student student : students) {
System.out.println(student);
}
思考🤔🤔🤔
-
如果在类中使用了 @Data等构造器,会默认重写
hashCode()
和equals()
方法的 -
为什么这些对象在Set集合中没有去重,需要重写方法 ?
-
创建多个对象时,这几个对象的
hash值
是不同的,Set不会对其进行去重 -
要使Set集合认为2个内容一样的对象是重复的,必须重写对象的
hashCode()
和equals()
方法
-
7.3 LinkedHashSet集合的底层原理
- 有序、不重复、无索引
基于哈希表(数组、链表、红黑树)实现,基于LinkedHashMap
编写的
有序 → 每个元素都额外的多了一个双链表的机制记录它前后元素的位置
7.4 TreeSet集合的原理
底层基于红黑树实现的排序
- 对于数值类型:从小到大
Set<Double> ts = new TreeSet<>();
Collections.addAll(ts, 3.14, 5.6, 1.2, 3.14);
System.out.println(ts); // [1.2, 3.14, 5.6]
- 对于字符串类型:首字符的顺序
Set<String> ts2 = new TreeSet<>();
Collections.addAll(ts2, "java", "c++", "python", "java");
System.out.println(ts2); // [c++, java, python]
-
自定义类型:默认是无法排序的,不知道大小规则
- 对象类实现
Comparable比较接口
,重写CompareTo方法
,指定大小比较规则
public class Student implements Comparable<Student>{ private String name; private int age; private int score; @Override // 重写compareTo方法 :按照年龄升序排序 public int compareTo(Student o) { return this.age - o.age; } }
Set<Student> ts3 = new TreeSet<>(); Collections.addAll(ts3, new Student("小明", 18, 100), new Student("小红", 22, 99), new Student("小刚", 20, 98), new Student("小明", 18, 100)); System.out.println(ts3);
- public TreeSet 集合自带比较器
Comparator对象
,指定比较规则
Set<Student> ts3 = new TreeSet<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getAge() - o2.getAge(); } }); // 简化:Set<Student> ts3 = new TreeSet<>(Comparator.comparingInt(Student::getAge));
- 对象类实现
7.5 什么时候用什么集合呢?
8. Map集合(键值对集合)
{ key1 = value1 , key2 = value2....}
Map<K,V>
⚠️键不重复,值可重复:一个键对应一个值
场景:
- 需要存储一一对应的数据时
8.1 Map集合分类
// HashMap无序不重复、无索引
Map<String,Integer> map = new HashMap<>();
map.put("嫦娥",29);
map.put("紫霞",31);
map.put("太白金星",33);
map.put("太白金星",33);
System.out.println(map); // {嫦娥=29, 太白金星=33, 紫霞=31}
// LinedHashMap有序不重复、无索引
Map<String,Integer> map2 = new LinkedHashMap<>();
// Collections时Collection的工具,不是Map的
map2.put("嫦娥",29);
map2.put("紫霞",31);
map2.put("太白金星",33);
map2.put("太白金星",33);
System.out.println(map2); // {嫦娥=29, 紫霞=31, 太白金星=33}
// TreeMap:按照键可排序,不重复,无索引
Map<String,Integer> map3 = new TreeMap<>();
map3.put("嫦娥",29);
map3.put("紫霞",31);
map3.put("太白金星",33);
System.out.println(map3); // {太白金星=33, 嫦娥=29, 紫霞=31}
8.2 Map集合的常用方法
Map是双列集合的祖宗,它的功能是全部双列集合(HashMap,LinkedHashMap,TreeMap)都可以继承的
Map<String, Integer> map = new HashMap<>();
map.put("嫦娥",29);
map.put("紫霞",31);
map.put("太白金星",33);
map.put("太白金星",33);
map.put(null,null);
System.out.println(map); //{null=null, 嫦娥=29, 太白金星=33, 紫霞=31}
// get(key):根据键获取值
System.out.println(map.get("嫦娥")); // 29
// getOrDefault(key, defaultValue):根据键获取值,如果找不到就返回默认值
System.out.println(map.getOrDefault("白骨精", 0)); // 0
// containsKey(key):判断集合中是否有这个键
System.out.println(map.containsKey("嫦娥")); // true
// containsValue(value):判断集合中是否有这个值
System.out.println(map.containsValue(33)); // true
// remove(key):根据键删除键值对
map.remove("嫦娥");
System.out.println(map); // {null=null, 太白金星=33, 紫霞=31}
// size():获取集合长度
System.out.println(map.size()); // 3
// keySet():获取所有的键
for (String key : map.keySet()) {
System.out.println(key); // null, 太白金星, 紫霞
}
// values():获取所有的值
Collection<Integer> values = map.values();
System.out.println(values); // [null, 33, 31]
// clear():清空集合
map.clear();
System.out.println(map); // {}
// isEmpty():判断集合是否为空
System.out.println(map.isEmpty()); // true
8.3 Map集合的遍历方式
1)键找值
keySet()
方法获取全部键 >>> get()
方法通过遍历键找值
- 提起Map集合的全部键到一个Set集合中
- 遍历Set集合,得到每一个键
- 根据键去找值
Set<String> keys = map.keySet(); //获取全部键
for (String key : keys) {
System.out.println(key + ":" + map.get(key));
}
2)键值对
把 “ 键值对 ” 看成一个整体进行遍历
💡Map提供的entrySet()
方法 >>> 将键值对转为Entry对象
,将Map转为Set集合
// 将Map集合转换为Set集合,Set集合中存储的元素是Map.Entry类型
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
3)Lambda表达式
JDK 1.8 后的新技术
map.forEach((key, value) -> {
System.out.println(key + ":" + value);
});
源码解析:内部通过将其转为entry对象
后遍历,再转为键值对传给重写的accept() 方法
8.4 Map集合的实现类
1)HashMap集合的底层原理
7.1的Set
集合的底层是基于Map
实现的,只是Set集合中的元素只要键数据,不要值数据
2)LinkedHashMap的底层原理
LinkedHashSet
底层原理基于LinkedHashMap
3)TreeMap的底层原理,同TreeSet集合
- 特点:不重复、无索引、可排序(按照键的大小默认升序排序,只能对键排序)
- 原理:
TreeMap
跟TreeSet
集合的底层原理是一样的,都是基于红黑树实现的排序
排序方式
- 让类实现
Comparable接口
,重写比较规则,同TreeSet - TreeMap集合有一个有参数构造器,支持构建
Comparator比较器
对象,以便用来指定比较规则
// 使用TreeMap的构造器传入比较器对象,并简化
// 按照分数升序
Map<Student, String> studentMap = new TreeMap<>((o1, o2) -> o1.getScore() - o2.getScore());
// Map<Student, String> studentMap = new TreeMap<>(Comparator.comparingInt(Student::getScore));
studentMap.put(new Student("小明", 18, 100), "北京");
studentMap.put(new Student("小红", 19, 94), "上海");
studentMap.put(new Student("小亮", 22, 98), "广州");
studentMap.put(new Student("小丽", 21, 95), "深圳");
studentMap.put(new Student("小华", 22, 96), "武汉");
System.out.println(studentMap);
8.5 Map集合的案例-统计投票信息
需求
- 某个班级80名学生,现在需要组织秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多
分析:
- 出现了一 一对应的数据 >>> 键值对存储
- 将80个学生选择的数据拿到程序中去
- 准备一个Map集合用于存储统计的结果
- 遍历80个学生选择的景点,每遍历一个景点,就看Map集合中是否存在该景点,不存在存入"景点 = 1",存在则其对应值 + 1
// 四个景点
String[] names = {"A", "B", "C", "D"};
// 80名学生的投票数据:用List集合存储
List<String> votes = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 80; i++) {
int index = r.nextInt(names.length); // 随机获取索引 0 1 2 3
votes.add(names[index]);
}
// 整理投票结果,用Map集合存储统计结果
Map<String,Integer> map = new HashMap<>();
for (String vote : votes) {
if(map.containsKey(vote)){ // 如果Map集合中包含该景点,则将投票人数加1
map.put(vote, map.get(vote) + 1);
}else{ // 如果Map集合中不包含该景点,则将投票人数设置为1
map.put(vote, 1);
}
}
- 统计投票简化代码
for (String vote : votes) {
// map.put(vote, map.getOrDefault(vote, 0) + 1);
map.put(vote, map.containsKey(vote) ? map.get(vote) + 1 : 1);
}
Stream流
👍用于操作集合或者数组的数据
💡Stream流大量的结合了Lambda
的语法风格来编程,功能强大,性能高效,代码简洁,可读性好
- 处理数据的步骤
1)准备数据源
List<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","周芷若","赵敏","张强","张三丰");
2)获取数据源的Stream流
3)调用流水线的各种方法,对数据进行处理
4)获取处理的结果
newList = list.stream()
.filter(name -> name.startsWith("张")) // 以“张”开头的 -> 姓张的
.filter(name -> name.length() == 3) // 名字是三个字的
.collect(Collectors.toList()); // 收集方法:将结果流转为list集合
- 对比传统方式
List<String> newList = new ArrayList<>();
for (String name : list) {
if (name.startsWith("张") && name.length() == 3) {
newList.add(name);
}
}
System.out.println(newList);
1. 获取Stream流
-
获取集合的Stream流
Collection
提供了获取当前集合对象的Stream流
的方法
Collection<String> list = new ArrayList<>(); Stream<String> s1 = list.stream();
-
获取数组的Stream流
Arrays类
提供了获取当前数组的Stream流的方法
String[] arr = {"林青霞", "张曼玉", "王祖蓝"}; Stream<String> s5 = Arrays.stream(arr); System.out.println(s5.count());
Stream类
提供了获取当前接收数据的Stream流的方法
Stream<String> s6 = Stream.of(arr); Stream<String> s7 = Stream.of("林青霞", "张曼玉", "王祖蓝");
-
获取Map集合的Stream流
- 获取键流
Stream<String> s2 = map.keySet().stream();
- 获取值流
Stream<Integer> s3 = map.values().stream();
- 获取键值流:将Map集合转为Set集合,再获取Stream流
Stream<Map.Entry<String, Integer>> s4 = map.entrySet().stream();
2. 常用中间方法
🤔作为中间方法,调完后会返回新流
ArrayList<Double> list = new ArrayList<>();
Collections.addAll(list, 1.2, 3.4, 5.6, 7.8, 9.0);
System.out.println(list);
// 过滤方法 filter()
list.stream()
.filter(num -> num > 5)
.forEach(System.out::println);
System.out.println("--------------------------------------------------");
// 排序方法 sorted() 默认升序 降序需重写Comparator接口的compare方法
// 从大到小 :
Stream<Double> sorted1 =list.stream()
.sorted((num1, num2) -> num2.compareTo(num1));
sorted1.forEach(System.out::println);
// 从小到大
Stream<Double> sorted2 =list.stream()
.sorted(Double::compareTo); // 进一步简化了的(num1, num2) -> num1.compareTo(num2)
sorted2.forEach(System.out::println);
System.out.println("--------------------------------------------------");
// 获取前几个元素 limit()
list.stream()
.limit(3)
.forEach(System.out::println);
// 跳过前几个元素 skip()
list.stream()
.skip(3)
.forEach(System.out::println);
System.out.println("--------------------------------------------------");
// 对元素进行加工,并返回对应的新流 map()
list.stream()
.map(num -> num * 2)
.forEach(System.out::println);
System.out.println("--------------------------------------------------");
// 合并两个流 concat()
Stream<Double> stream1 = list.stream();
Stream<Double> stream2 = Stream.of(1.2, 3.4, 5.6, 7.8, 9.0);
Stream<Double> stream3 = Stream.concat(stream1, stream2);
System.out.println(stream3.count());
3. 常用终结方法
💡终结:使用后不会返回新的流
3.1 常用终结方法
在获取最大值时,还需要通过get
获取Optional对象
中的元素
// 1、统计流中元素的个数:count()
ArrayList<Student> students = new ArrayList<>();
Collections.addAll(students, new Student("张三", 18, 100),
new Student("李四", 22, 79),
new Student("王五", 20, 80));
System.out.println(students.stream().count()); // 3
System.out.println("===========================================================");
// 2、获取此流运算后的最大值元素:max()
Optional<Student> max = students.stream().max((s1, s2) -> s1.getScore() - s2.getScore());
Student student = max.get();
System.out.println(student); // Student{name='张三', age=18, score=100}
3.2 收集Stream流:
把Stream流操作后的结果转回到集合或者数组中
⚠️流只能收集一次
collect(Collector collector)
: 把流处理后的结果收集到一个指定的集合中去Collectors
又分为toList()
、toSet()
、toMap()
ArrayList<String> names = new ArrayList<>();
Collections.addAll(names, "张三", "李四", "王五", "赵六", "张无忌");
Stream<String> s1 = names.stream().filter(name -> name.startsWith("张"));
// 收集Stream流到Set集合中
Set<String> set = s1.collect(Collectors.toSet());
System.out.println(set);
// 收集Stream流到数组中
Stream<String> s2 = names.stream().filter(name -> name.startsWith("张"));
Object[] arr = s2.toArray(); // 注意返回类型为Object
System.out.println("数组:" + Arrays.toString(arr));
toArray()
方法的返回类型是Object[]
, 因为Stream
默认不知道具体的泛型类型,因此只能返回Object[]
Arrays.toString
将数组的内容转换为字符串形式,便于打印和调试
// 收集到Map集合中
ArrayList<Student> students = new ArrayList<>();
Collections.addAll(students,
new Student("张三", 18, 100),
new Student("李四", 22, 99),
new Student("王五", 20, 98),
new Student("赵六", 19, 97),
new Student("张无忌", 22, 96)
);
// 简化:Map<String,Integer> map = students.stream().collect(Collectors.toMap(Student::getName, Student::getAge));
Map<String, Integer> map = students.stream().collect(Collectors.toMap(new Function<Student, String>() {
@Override
public String apply(Student student) {
return student.getName();
}
}, new Function<Student, Integer>() {
@Override
public Integer apply(Student student) {
return student.getAge();
}
}
));
System.out.println(map);