当前位置: 首页 > news >正文

Java 黑马程序员学习笔记(进阶篇20)

1. Stream 流综合练习

(1) 题目 1:筛选并转换年龄符合条件的记录为 Map

现有一个ArrayList<String>集合,其中存储的元素均为字符串,格式统一为 “姓名,年龄”(例如"zhangsan,23"表示姓名为zhangsan,年龄为 23)。

请使用 Java Stream 流完成以下操作:

① 从集合中筛选出年龄大于等于 24的元素;

② 将筛选后的元素转换为Map<String, Integer>集合,其中:

  • Map 的键(Key) 为元素中的 “姓名”(字符串类型);
  • Map 的值(Value) 为元素中的 “年龄”(整数类型);

③ 打印转换后的Map集合。

已知该ArrayList中的元素固定为:["zhangsan,23","lisi,24","wangwu,25"]

package demo3;import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;public class test3 {public static void main(String[] args) {ArrayList<String> list = new ArrayList<>();Collections.addAll(list,"zhangsan,23","lisi,24","wangwu,25");Map<String, Integer> map = list.stream().filter(s -> Integer.parseInt(s.split(",")[1]) >= 24).collect(Collectors.toMap(s -> s.split(",")[0],s -> Integer.parseInt(s.split(",")[1])));System.out.println(map);}
}
(2) 题目 2:合并处理两个列表并转换为对象集合

现有两个ArrayList<String>集合,分别存储男性和女性信息,元素格式均为 “姓名,年龄”(例如"星期日,22"表示姓名为星期日,年龄为 22)。

两个集合的具体元素如下:

  • manList["星期日,22","刃,24","饮月,23","白厄,30","瓦尔特,35","万敌,25"]
  • womanList["杨颖,26","镜流,28","星,5","黑天鹅,30","飞霄,100","杨幂,20"]

请使用 Java Stream 流完成以下操作:

① 处理manList

  • 筛选出姓名长度为 3的元素;
  • 仅保留前 2 个符合条件的元素,得到流stream1

② 处理womanList

  • 筛选出姓名以 “杨” 字开头的元素;
  • 跳过第一个符合条件的元素,得到流stream2

③ 合并流stream1stream2,并将合并后的每个元素转换为Actor对象(Actor类包含String nameint age两个属性,且有对应的构造方法public Actor(String name, int age))。

④ 将所有Actor对象收集到List<Actor>集合中,并打印该集合。

package demo3;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;public class test4 {public static void main(String[] args) {ArrayList<String> manList = new ArrayList<>();ArrayList<String> womanList = new ArrayList<>();Collections.addAll(manList,"星期日,22","刃,24","饮月,23","白厄,30","瓦尔特,35","万敌,25");Collections.addAll(womanList,"杨颖,26","镜流,28","星,5","黑天鹅,30","飞霄,100","杨幂,20");Stream<String> stream1 = manList.stream().filter(s -> s.split(",")[0].length() == 3).limit(2);Stream<String> stream2 = womanList.stream().filter(s -> s.split(",")[0].startsWith("杨")).skip(1);/*        Stream.concat(stream1, stream2).map(new Function<String, Actor>() {@Overridepublic Actor apply(String s) {String name = s.split(",")[0];int age = Integer.parseInt(s.split(",")[1]);return new Actor(name, age);}}).forEach(s -> System.out.println(s));*/List<Actor> list = Stream.concat(stream1, stream2).map(s -> new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1]))).collect(Collectors.toList());System.out.println(list);}
}
关键逻辑 1:关于 return new Actor(name, age); —— 为什么必须返回?

map 方法的作用是将流中的元素转换为另一种类型(这里是将 String 转换为 Actor)。它依赖 Function 接口的 apply 方法来完成转换,而 Function 接口的定义是:

关键逻辑 2:函数式接口的抽象方法可以有返回值,也可以没有返回值(即返回void

函数式接口的核心是 “有且仅有一个抽象方法”,但这个方法的返回值类型是灵活的 —— 可以是任意类型(包括基本类型、对象类型),也可以是void(无返回值)。

① 有返回值的函数式接口(如Function<T, R>

Function<T, R>的抽象方法是:

R apply(T t); // 接收T类型参数,返回R类型结果

它的作用是 “将 T 类型转换为 R 类型”,所以必须有返回值(否则无法完成 “转换” 的功能)。你之前代码中用的map方法就依赖Function,所以apply必须返回转换后的结果(比如Actor对象)。

② 无返回值的函数式接口(如Consumer<T>

Consumer<T>的抽象方法是:

void accept(T t); // 接收T类型参数,无返回值

它的作用是 “消费 T 类型数据”(比如打印、修改外部状态等),不需要返回值。例如forEach方法就依赖Consumer,只需执行操作,不用返回结果:

list.stream().forEach(s -> System.out.println(s)); // accept方法无返回值

2. 方法引用

(1) 什么是方法引用?

方法引用是Lambda 表达式的简化写法。当 Lambda 表达式的主体仅包含一个方法调用时,就可以用方法引用替代 Lambda,让代码更简洁。

  • 本质:复用已有的方法,作为函数式接口的抽象方法的实现。
  • 语法类名/对象名::方法名(双冒号::是方法引用的运算符)
(2) 常见方法引用类型(重点)

根据引用的方法类型不同,分为 4 类,结合函数式接口实例理解:

① 类::静态方法(引用类的静态方法)

场景:Lambda 表达式中调用的是某个类的静态方法。

示例:用Integercompare静态方法排序
import java.util.Arrays;public class MethodRefDemo2 {public static void main(String[] args) {Integer[] nums = {3, 1, 2};// Lambda写法:调用Integer的静态方法compareArrays.sort(nums, (a, b) -> Integer.compare(a, b));// 方法引用写法:类::静态方法(Integer是类,compare是静态方法)Arrays.sort(nums, Integer::compare); }
}

解析

  • Arrays.sort的第二个参数是Comparator接口(函数式接口),抽象方法为int compare(T o1, T o2)
  • Lambda(a,b) -> Integer.compare(a,b)的主体是调用Integer类的静态方法compare(a,b)
  • 方法引用Integer::compare直接复用静态方法,简化代码。
② 类::new(引用构造器,创建对象)

场景:Lambda 表达式的主体是调用某个类的构造器创建对象。

示例:将字符串转换为Person对象
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;// 定义Person类
class Person {private String name;public Person(String name) { this.name = name; }@Overridepublic String toString() { return "Person{name='" + name + "'}"; }
}public class MethodRefDemo4 {public static void main(String[] args) {List<String> nameList = new ArrayList<>();Collections.addAll(nameList, "张三", "李四");// Lambda写法:调用Person的构造器创建对象List<Person> personList1 = nameList.stream().map(name -> new Person(name)).collect(Collectors.toList());// 方法引用写法:类::new(Person是类,new是构造器)List<Person> personList2 = nameList.stream().map(Person::new) // 等价于name -> new Person(name).collect(Collectors.toList());System.out.println(personList2); // [Person{name='张三'}, Person{name='李四'}]}
}

解析

  • map的参数是Function接口,抽象方法为R apply(T t)
  • Lambdaname -> new Person(name)的主体是调用Person的构造器(参数为name);
  • 方法引用Person::new直接复用构造器,简化对象创建逻辑。
③ 类::实例方法(引用类的实例方法,特殊场景)

场景:Lambda 的第一个参数是方法的调用者,第二个参数是方法的参数(或无参数)。

import java.util.Arrays;public class MethodRefDemo3 {public static void main(String[] args) {String[] names = {"张三", "李四", "王五"};// Lambda写法:调用第一个参数s1的compareTo方法,参数是s2Arrays.sort(names, (s1, s2) -> s1.compareTo(s2));// 方法引用写法:类::实例方法(String是类,compareTo是实例方法)Arrays.sort(names, String::compareTo); }
}

解析

  • Comparatorcompare方法有两个参数:(s1, s2)
  • Lambda 中s1.compareTo(s2)的调用者是第一个参数s1,参数是第二个参数s2
  • 满足 “第一个参数是调用者,第二个参数是方法参数”,因此可用String::compareTo替代。
④ 类型[ ]::new

数组的构造器需要接收一个int 类型的参数(数组的长度),并返回一个对应类型的数组(如String[]Integer[])。因此,它只能匹配 **IntFunction<T[]>** 函数式接口:

@FunctionalInterface
public interface IntFunction<T[]> {T[] apply(int length); // 接收数组长度,返回T类型的数组
}

当 Lambda 表达式的逻辑是 “根据一个 int 类型的长度,创建对应类型的数组” 时,可直接用类型[]::new替代。最典型的场景是 Stream 流的toArray方法(将流转换为数组时)。

将 Stream 流中的元素转换为String[]数组

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class ArrayConstructorRef {public static void main(String[] args) {List<String> list = new ArrayList<>();Collections.addAll(list, "a", "b", "c");// 1. Lambda表达式写法:根据长度创建String数组String[] arr1 = list.stream().toArray(length -> new String[length]); // length是流中元素的数量// 2. 数组构造方法引用写法:简化LambdaString[] arr2 = list.stream().toArray(String[]::new); // 等价于length -> new String[length]System.out.println(arr1.length); // 3System.out.println(arr2.length); // 3}
}

解析

  • 流中元素数量为 3,toArray方法会将这个数量(3)作为参数传给apply方法;
  • String[]::new本质是引用String数组的构造器,接收长度 3,创建new String[3]数组;
  • 两种写法完全等价,但方法引用更简洁。
(3) 综合练习
题目 1:学生信息转换与输出

请完成以下编程任务:

① 定义一个Student类,包含私有属性name(字符串类型,存储姓名)和age(整数类型,存储年龄)。

② 为Student类编写一个构造方法,参数为String类型的字符串,该字符串格式为 “姓名,年龄”(例如 “张无忌,15”)。构造方法需要将该字符串拆分,分别为nameage属性赋值(提示:可使用split(",")方法拆分字符串)。

③ 重写Student类的toString()方法,使其返回格式为"Student{name='XXX', age=XX}"的字符串(其中 XXX 为姓名,XX 为年龄)。

④ 创建一个test2类,在其main方法中完成以下操作:

  • 初始化一个ArrayList<String>集合,并向集合中添加以下元素:"张无忌,15""周芷若,14""赵敏,13""张强,20""张良,35"
  • 使用 Stream 流将上述集合转换为Student[]数组(要求使用map(Student::new)进行转换,并使用toArray(Student[]::new)转换为数组)。
  • 打印转换后的Student数组(使用Arrays.toString()方法)。

预期输出[Student{name='张无忌', age=15}, Student{name='周芷若', age=14}, Student{name='赵敏', age=13}, Student{name='张强', age=20}, Student{name='张良', age=35}]

package demo1;import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;public class test2 {public static void main(String[] args) {ArrayList<String> list = new ArrayList<String>();Collections.addAll(list,"张无忌,15","周芷若,14","赵敏,13","张强,20","张良,35");Student[] arr = list.stream().map(Student::new).toArray(Student[]::new);System.out.println(Arrays.toString(arr));}
}
关键逻辑 1:map(Student::new):转换元素类型

① map()是流的中间操作,作用是将流中的每个元素按照指定规则转换为另一种类型。

② 这里的Student::new构造方法引用,等价于一个 lambda 表达式:(String s) -> new Student(s)

③ 意思是:对流中的每个字符串元素(比如"张无忌,15"),调用Student类的构造方法(参数为这个字符串),创建一个Student对象。

④ 经过map()操作后,流的类型从Stream<String>(存储字符串)变成了Stream<Student>(存储Student对象)。

关键逻辑 2:toArray(Student[]::new):转换为数组

① toArray()是流的终端操作,作用是将流中的元素收集到一个数组中。

② 这里的Student[]::new数组构造器引用,等价于一个 lambda 表达式:(int length) -> new Student[length]

③ 意思是:告诉toArray()方法,需要创建一个Student类型的数组,数组长度由流中元素的数量决定,最终将流中的Student对象存入这个数组。

关键逻辑 3:为什么使用 Arrays.toString(arr)

在 Java 中,当我们直接打印一个数组(比如System.out.println(arr))时,得到的结果并不是数组中元素的具体内容,而是数组的 “内存地址标识”(类似[Ldemo1.Student;@1b6d3586这样的字符串),这是因为数组的默认toString()方法(继承自Object类)就是这么实现的,它无法直观展示数组中的元素。

题目 2:提取学生姓名并转换为数组

请完成以下编程任务:

① 定义一个Student类,包含私有属性name(字符串类型,姓名)和age(整数类型,年龄)。

② 为Student类编写一个带参构造方法,参数为nameage,用于初始化属性。

③ 为Student类编写getName()方法(getter 方法),用于获取name属性的值。

④ 创建test3类,在其main方法中完成以下操作:

  • 初始化一个ArrayList<Student>集合,并添加 3 个Student对象:new Student("zhangsan",23)new Student("lisi",24)new Student("wangwu",25)
  • 使用 Stream 流将上述集合中的Student对象转换为只包含姓名的String[]数组(要求使用map(Student::getName)提取姓名,并使用toArray(String[]::new)转换为数组)。
  • 打印转换后的字符串数组(使用Arrays.toString()方法)。

预期输出[zhangsan, lisi, wangwu]

package demo1;import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Function;public class test3 {public static void main(String[] args) {ArrayList<Student> list = new ArrayList<>();list.add(new Student("zhangsan",23));list.add(new Student("lisi",24));list.add(new Student("wangwu",25));String[] arr = list.stream().map(Student::getName).toArray(String[]::new);System.out.println(Arrays.toString(arr));}
}
http://www.dtcms.com/a/512602.html

相关文章:

  • Google 推荐 ViewBinding 作为 DataBinding 的轻量级替代
  • AI体测设备哪家口碑好
  • 阿里云 企业 网站城市介绍网站模板
  • 株洲网站建设团队萧山区建设工程质量监督站网站
  • 【CTF | 比赛篇】Newstar ctf web
  • MySQL decimal类型+IN查询异常:为何非目标数据被检出?
  • 浙江荣盛建设集团网站wordpress自动排版
  • 网站界面设计工具怎样申请电子邮箱
  • 构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
  • 好用的外贸网站深圳网络推广培训学校
  • 第5章—STM32工程创建
  • 网站建设公司宣传标语用百度地图 做gis网站
  • c 还可以做网站微信推广是什么意思
  • 代码随想录 112.路径总和
  • 51单片机基础-定时器中断
  • xtuoj 两个数
  • Android Studio新手开发第二十六天
  • 中国平安网站建设成都网站建设易维达好
  • 继保:对于线路两侧的电流互感器型号系数选取
  • Redis分布式集群:从分区算法到扩容实战
  • AI大模型:(二)1.6 DeepSeek-OCR部署尝鲜
  • 在昇腾NPU上跑Llama大模型:从零开始的真实测试之旅
  • 直播类网站开发wordpress 图片自动分页
  • JADX下载和安装图解教程(附安装包)
  • 矽塔 SA8203 2.5A可调过流保护 输入耐压36V 过压/过流保护芯片
  • 网站开发饼图样式wordpress 如何登陆地址
  • 工业相机 “即插即用” vs 采集卡依赖
  • wordpress手机视频播放器免费seo营销软件
  • 【系统分析师】预测试卷一:论文及写作要点(包括对应素材和论文案例)
  • 私有云不私有?Nextcloud+cpolar让文件随身走