目录

11-Collection集合Map集合分类功能遍历底层原理,Stream流获取中间方法终结方法-黑马Java视频笔记

目录

11 Collection集合、Map集合:分类、功能、遍历、底层原理,Stream流:获取、中间方法、终结方法 (黑马Java视频笔记)

https://i-blog.csdnimg.cn/direct/ee0e0105e66d4ee7949ba1d846bfa059.png#pic_center

集合

1. 认识集合

💡容器 » 装数据

体系结构:

  • Collection 单列集合 :每个元素(数据)只包含一个值
  • Map 双列集合 :每个元素包含两个值(键值对)

2. Collection单列集合特点

  • List系列集合
    • ArrayListLinekdList :添加的元素是 有序、可重复、有索引
  • 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 作为单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的

https://i-blog.csdnimg.cn/direct/10c876360aea44688eaa62b4abf5233f.png

  • 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()

https://i-blog.csdnimg.cn/direct/e2394a97ee534e9287db9492934525cd.png

// 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 ,也可以看到,并没有输出 “ 枸杞 ”

https://i-blog.csdnimg.cn/direct/fcef18c5da794403b6cf997b6b7444fd.png

5.2 一般应对方案:
  • 在删除之后进行 i-- (编译器也会给出警告提示)

https://i-blog.csdnimg.cn/direct/4dcb9eaf8194431285488e25ae7db823.png

  • 倒着遍历并删除**(前提:支持索引)**
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);

⚠️如果使用迭代器遍历,使用集合方法删除 » 报错!!!

https://i-blog.csdnimg.cn/direct/34e03515389e4f17aec397a18dbc9375.png

🤔 报错原因: modCount != expectedModCount ,可以看到后台 modCount = 6,expectedModCount = 5

https://i-blog.csdnimg.cn/direct/ba46a33e2e7345998dcf841f6c19368c.png

💡 modCountexpectedModCount 的作用

https://i-blog.csdnimg.cn/direct/beb3c8ecc05f42adae5347bdd2d96b67.png

5.4 方案二:用增强for和Lambda都没有办法解决并发修改异常
  • 拿不到迭代器
  • 只适合做遍历,不适合做遍历并修改操作

6. Collection-List集合:有序,可重复,有索引

  • ArrayList :有序,可重复,有索引
  • LinkedList :有序,可重复,有索引
1) LIst集合因为支持索引,所以除了继承了Collection的功能外,还有很多与索引相关的方法

https://i-blog.csdnimg.cn/direct/53f4458aadfd46fcb58c5bfa3f007439.png

List<String> names = new ArrayList<>(); // 多态引用

https://i-blog.csdnimg.cn/direct/bab3b5e0902c42ecacb4b48f05a0dea7.png

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 基于 双链表 存储数据:数据由结点组成,一个结点包含当前的数据值和下一个结点的地址(双链表的结点包含上一个结点地址和下一个结点地址),查询慢(需要从头开始找),增删相对快(不用迁移其他数据),但对首位元素进行增删改查的速度是极快的

https://i-blog.csdnimg.cn/direct/e945fa278ed54386a16c7f3fb36cba6a.png

https://i-blog.csdnimg.cn/direct/315073e9a7924583b7f5dca7c7c963a3.png

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)基于哈希表存储数据 »> 无序、不重复、无索引

存储方式变化过程:数组+链表 »> 数组+链表+红黑树

https://i-blog.csdnimg.cn/direct/576eefe49d30445a8d570d5bba1144bc.png

https://i-blog.csdnimg.cn/direct/e62c371b34554d078691914faf6f858a.png

默认加载因子
  • 数据太多时,哈希表中的链表会很长,而链表查询效率很低(需要从头开始查找),所以在达到 加载因子*总长度 个元素时,对哈希表进行扩容
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);

https://i-blog.csdnimg.cn/direct/35bd75769b124a1ca4a4e0c9846b1199.png

②在学生类中 重写两个方法 : hashCode()和equals() ,自动生成即可

https://i-blog.csdnimg.cn/direct/eba705b55a454343909cd9532448df2d.png

③ 遍历集合(增强for)

for (Student student : students) {
    System.out.println(student);
}

https://i-blog.csdnimg.cn/direct/4b35d01090884fc99060d8ee999b51cb.png

思考🤔🤔🤔
  • 如果在类中使用了 @Data等构造器,会默认重写 hashCode()equals() 方法的

  • 为什么这些对象在Set集合中没有去重,需要重写方法 ?

    • 创建多个对象时,这几个对象的 hash值 是不同的,Set不会对其进行去重
    • 要使Set集合认为2个内容一样的对象是重复的,必须重写对象的 hashCode()equals() 方法
7.3 LinkedHashSet集合的底层原理
  • 有序、不重复、无索引

基于哈希表(数组、链表、红黑树)实现,基于 LinkedHashMap 编写的

https://i-blog.csdnimg.cn/direct/d65fe4bc6c0c40d2ac45a3d9d4bd983d.png

有序 → 每个元素都额外的多了一个 双链表 的机制记录它前后元素的位置

https://i-blog.csdnimg.cn/direct/dbdf44d427324e8ca3e5f582a6d335fd.png https://i-blog.csdnimg.cn/direct/d190cc8e767143f9a0425cf81a6f394b.png

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));

https://i-blog.csdnimg.cn/direct/a510345813864763bb82a38dbae6c906.png

7.5 什么时候用什么集合呢?

https://i-blog.csdnimg.cn/direct/e2c57225ed974ed8906f1a8414c4fb9f.png

8. Map集合(键值对集合)

{ key1 = value1 , key2 = value2....}

Map<K,V>

⚠️键不重复,值可重复:一个键对应一个值

场景:

  • 需要存储一一对应的数据时
8.1 Map集合分类

https://i-blog.csdnimg.cn/direct/1e2fcac704644766aa091f0d8a962150.png

// 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)都可以继承的

https://i-blog.csdnimg.cn/direct/5e226ae4e0234d9796ce88b674c01aef.png

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集合

https://i-blog.csdnimg.cn/direct/2cbd76119975412295c6b36c4a22c98c.png

// 将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 后的新技术

https://i-blog.csdnimg.cn/direct/d6580f13ed3c43ba93d19e5411fa0a1f.png

map.forEach((key, value) -> {
    System.out.println(key + ":" + value);
});

https://i-blog.csdnimg.cn/direct/f82c2a3aa3cc4c639e5e950f4ea69305.png

源码解析:内部通过将其转为 entry对象 后遍历,再转为键值对传给重写的 accept() 方法

8.4 Map集合的实现类
1)HashMap集合的底层原理

7.1的 Set 集合的底层是基于 Map 实现的,只是Set集合中的元素只要键数据,不要值数据

2)LinkedHashMap的底层原理

LinkedHashSet 底层原理基于 LinkedHashMap https://i-blog.csdnimg.cn/direct/7daaa4a9a11544b0b3ccc94f59322d3a.png

3)TreeMap的底层原理,同TreeSet集合
  • 特点:不重复、无索引、可排序(按照键的大小默认升序排序,只能对键排序)
  • 原理: TreeMapTreeSet 集合的底层原理是一样的,都是基于红黑树实现的排序

排序方式

  • 让类实现 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);

https://i-blog.csdnimg.cn/direct/71e47c4709ef4b13a41a84f199cfc7c4.png

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);
}

https://i-blog.csdnimg.cn/direct/905e41b4c56f47949de448a2d7d72e7d.png


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);

https://i-blog.csdnimg.cn/direct/9a7a5e72e05b4256b93d8572c673a368.png

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. 常用中间方法

🤔作为中间方法, 调完后会返回新流

https://i-blog.csdnimg.cn/direct/b0b0c7525fe14f0ba69b32e5bdb03625.png

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. 常用终结方法

💡 终结:使用后不会返回新的流

https://i-blog.csdnimg.cn/direct/4e5b074d15f44041a11f2709e7ec651e.png

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流操作后的结果转回到集合或者数组中

⚠️流只能收集一次

https://i-blog.csdnimg.cn/direct/b66fff9764834bc8b6af74b3cd02b0a1.png

  • 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);

https://i-blog.csdnimg.cn/direct/64fedcdf062846b79b56212c4cb38ea1.png