十七、JDK8 新特性
17.1 Lambda表达式介绍
17.1.1 Lambda表达式案例
先来看案例:
package com.itheima.lambda;
/**
* @author: Carl Zhang
* @create: 2021-12-31 14:57
* 体验Lambda表达式和传统表达式区别
* 实作游泳
*/
public class Lambda01 {
public static void main(String[] args) {
/*
* 结论:1. 传统的匿名内部类方法解决界面自变量问题,需要 创建匿名内部类物件,实作界面方法,两步 --关注点 怎么做
* 2. Lambda表达式只需要一条式子,代码简介,关注点更明确在方法功能和输出 -- 关注点 做什么
* 3. 这种关注方法能做什么的思想就是函式式编程思想
* */
//传统方法实作游泳 - 匿名内部类
swim(new Swim() {
@Override
public void swimming() {
System.out.println("匿名内部类的游泳....");
}
});
//Lambda表达式实作
swim(() -> System.out.println("匿名内部类的游泳...."));
}
public static void swim (Swim swim) {
swim.swimming();
}
}
interface Swim {
/**
* 游泳
* */
void swimming();
}
注意:lambda
表达式可以理解为对匿名内部类的一种简化 , 但是本质是有区别的
17.1.2 引入函式式编程思想
介绍:
- 在数学中,函式就是有输入量、输出量的一套计算方案,也就是“拿资料做操作”
- 而
lambda
是就是函式式编程思想的一种体现
17.1.3 函式式编程思想和面向物件编程思想的对比
- 面向物件思想 :
- 强调的是用物件去完成某些功能 -- 怎么做
- 函式式编程思想 :
- 强调的是结果 , 而不是怎么去做 -- 做什么
17.2 函式式界面
17.2.1 函式式界面介绍
-
概念:
- 只有一个抽象方法需要重写的界面就是函式式界面,
- 函式式界面是允许有其他的非抽象方法的存在例如静态方法,默认方法,私有方法,
-
注解: 为了标识界面是一个函式式界面,可以在界面之上加上一个注解:
@FunctionalInterface
-
相关API :
JDK
中的java.util.function
包里的界面都是函式式界面
我们以前学的 Runnable
界面也是函式式界面
也可以自定义一个函式式界面:
package com.itheima.lambda;
/**
* @author CarlZhang
* 自定义一个函式式界面
*/
@SuppressWarnings("ALL")
@FunctionalInterface
public interface FunctionalInterface01 {
/**
* 只有一个 要重写的 抽象方法
*/
void method01();
/**
* 继承Object类的方法
*/
@Override
String toString();
/**
* jdk1.8 界面里可以有静态方法和默认方法
* */
static void method02() {
System.out.println("FunctionalInterface01界面里的静态方法");
}
/**
* 默认方法
* */
default void method03() {
System.out.println("FunctionalInterface01界面里的默认方法");
}
}
@FunctionalInterface
interface FunctionalInterface02 extends FunctionalInterface01{
//报错:因为此处有两个要重写的抽象法, 一个父类界面的一个此界面的,
//结论:函式式界面里只能有一个要重写的抽象方法,父类界面里的也算,而Object类比较特殊,
// 界面里有重写Object类的抽象方法不影响函式式界面的判定
//void method02();
}
17.2.2 注意事项和使用细节
- 界面里只能有一个要重写的抽象方法,继承自
**Object**
类的方法除外 - 可以用注解
@FunctionalInterface
来表示函式式界面
17.3 Lambda表达式的使用
17.3.1 Lambda表达式语法
标准格式 :(形参串列) - > { //要实作方法的方法体... }
17.3.2 Lambda表达式使用案例
/**
* @author: Carl Zhang
* @create: 2021-12-31 16:32
* 练习1:
* 1 撰写一个界面(ShowHandler)
* 2 在该界面中存在一个抽象方法(show),该方法是无自变量无回传值
* 3 在测验类(ShowHandlerDemo)中存在一个方法(useShowHandler)
* 方法的的自变量是ShowHandler型别的,在方法内部呼叫了ShowHandler的show方法
*/
public class ShowHandlerDemo {
public static void useShowHandler(ShowHandler showHandler) {
showHandler.show();
}
public static void main(String[] args) {
//呼叫useShowHandler方法
//使用Lambda表达式实作ShowHandler界面作为自变量
useShowHandler(() -> {
System.out.println("使用Lambda表达式实作ShowHandler界面作为自变量");
});
}
/*
* Lambda表达式格式决议
* 1. () 表示实作的界面里方法的形参串列
* 2. -> 语法规定,指向要实作的方法内容
* 3. {} 要实作的方法的方法体
*
* 注意:Lambda表达式实作的界面必须是函式式界面
* */
}
/**
* @author CarlZhang
*/
@FunctionalInterface
public interface ShowHandler {
/**
* 在该界面中存在一个抽象方法(show),该方法是无自变量无回传值
* */
void show();
}
/**
* @author: Carl Zhang
* @create: 2021-12-31 16:48
* 需求
* 1 首先存在一个界面(StringHandler)
* 2 在该界面中存在一个抽象方法(printMessage),该方法是有自变量无回传值
* 3 在测验类(StringHandlerDemo)中存在一个方法(useStringHandler),
* 方法的的自变量是StringHandler型别的,
* 在方法内部呼叫了StringHandler的printMessage方法
*/
public class StringHandlerDemo {
public static void useStringHandler(StringHandler stringHandler) {
stringHandler.printMessage("Hello, World");
}
public static void main(String[] args) {
//使用lambda表达式实作StringHandler, 作为自变量传递
//结论:
// 1. () 里的内容对应界面里方法()的内容,是形式自变量,lambda表达式看作一个界面实作类
// 2. 只有一个自变量情况下,可以省略()
useStringHandler(String s -> {
System.out.println("呼叫Lambda表达式的代码块 " + s);
});
//匿名内部类的方式实作
useStringHandler(new StringHandler() {
@Override
public void printMessage(String s) {
System.out.println("匿名内部类的方法 " + s);
}
});
}
}
@FunctionalInterface
public interface StringHandler {
/**
* 在该界面中存在一个抽象方法(printMessage),该方法是有自变量无回传值
* @param s 任意字符串
*/
void printMessage(String s);
}
package com.heima.lambda;
import org.omg.CORBA.PUBLIC_MEMBER;
/**
* @author Carl Zhang
* @description
* @date 2022/1/1 20:32
* 1 首先存在一个界面(Calculator)
* 2 在该界面中存在一个抽象方法(calc),该方法是有自变量也有回传值
* 3 在测验类(CalculatorDemo)中存在一个方法(useCalculator)
* 方法的的自变量是Calculator型别的
* 在方法内部呼叫了Calculator的calc方法
*/
public class CalculatorDemo {
public static void useCalculator(Calculator calculator) {
System.out.println(calculator.calc(11, 12));
}
public static void main(String[] args) {
/*
* 1. 有参有回传值的方法,直接写(形参串列) -> { return 回传值; },
* 进一步体现 (输入) - > {输出} 的函式式编程思想
* 2. 自变量型别可以省略,有多个自变量不能只省略一个
* 3. 代码块只有一句,则可以省略大括号和分号,甚至return
* */
//useCalculator((int num1, int num2) -> {
// return num1 + num2;
//});
useCalculator((num1, num2) -> num1 + num2);
}
}
/**
* @author CarlZhang
* 1 首先存在一个界面(Calculator)
* 2 在该界面中存在一个抽象方法(calc),该方法是有自变量也有回传值
*/
@FunctionalInterface
public interface Calculator {
/**
* 计算两数之和
* @param num1 第一个数
* @param num2 第二个数
* @return 两数之和
*/
int calc(int num1, int num2);
}
17.3.2 注意事项和使用细节
使用前提 :Lambda
表达式实作的界面必须是函式式界面
格式决议:
Lambda
表达式可看做函式式界面的一个实作类物件()
表示实作的界面里方法的形参串列,没有可以空着,- 自变量型别可以省略,有多个自变量不能只省略一个
- 只有一个自变量可以省略
()
->
语法规定,指向要实作的方法内容{}
要实作的方法的方法体- 代码块里只有一句,则可以省略
{}
和;
,甚至return
- 代码块里只有一句,则可以省略
(形参) -> {回传值}
的格式体现了(输入) -> {输出}
的函式式编程思想
17.4 Lambda表达式和匿名内部类的区别
- 作用物件不同 :
- 匿名内部类:可以是界面,也可以是抽象类,还可以是具体类
Lambda表达式
:只能是函式式界面
- 使用场景不同 :
- 如果界面中有且仅有一个抽象方法,可以使用
Lambda
表达式,也可以使用匿名内部类 - 如果界面中多于一个抽象方法,只能使用匿名内部类,而不能使用
Lambda
表达式
- 如果界面中有且仅有一个抽象方法,可以使用
- 实作原理不同 :
- 匿名内部类:编译之后,产生一个单独的
.class
字节码档案 Lambda
表达式:编译之后,没有一个单独的.class
字节码档案,对应的字节码会在运行的时候动态生成
- 匿名内部类:编译之后,产生一个单独的
/**
* @author Carl Zhang
* @description Lambda表达式和匿名内部类的区别
* @date 2022/1/1 21:36
*/
public class LambdaVsAnonymous {
public static void main(String[] args) {
//Lambda表达式呼叫show方法 -- 编译后没有.class档案
Test test = () -> System.out.println("Hello, World");
test.show();
//匿名内部类呼叫show方法 -- 有LambdaVsAnonymous$1.class档案
new Test() {
@Override
public void show() {
System.out.println("Hello, World");
}
}.show();
}
}
@FunctionalInterface
interface Test {
/**
* 打印方法
*/
void show();
}
17.5 Stream 流
17.5.1 Stream的体验
import java.util.ArrayList;
/**
* @author Carl Zhang
* @description 体验Stream流的好处
* @date 2022/1/2 17:28
* 需求:按照下面的要求完成集合的创建和遍历
* <p>
* 1 创建一个集合,存盘多个字符串元素
* "张无忌" , "张翠山" , "张三丰" , "谢广坤" , "赵四" , "刘能" , "小沈阳" , "张良"
* 2 把集合中所有以"张"开头的元素存盘到一个新的集合
* 3 把"张"开头的集合中的长度为3的元素存盘到一个新的集合
* 4 遍历上一步得到的集合
*/
public class Stream01 {
public static void main(String[] args) {
//集合的方式
//1.创建集合,添加资料
ArrayList<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张翠山");
list.add("张三丰");
list.add("谢广坤");
list.add("赵四");
list.add("刘能");
list.add("小沈阳");
list.add("张良");
ArrayList<String> newList = new ArrayList<>();
ArrayList<String> newList2 = new ArrayList<>();
//"张"开头的元素添加到新集合
for (String s : list) {
if (s.startsWith("张")) {
//3."张"开头的元素添加到新集合
newList.add(s);
}
}
//"张"开头的且长度为3的添加到另一个元素
for (String s : newList) {
if (s.startsWith("张") && s.length() == 3) {
newList2.add(s);
}
}
//4.打印
System.out.println(newList);
System.out.println(newList2);
System.out.println("===================");
//Stream流的方式 获取并列印"张"开头的且长度为3的元素 -- 使对容器里资料的操作进行了简化
list.stream().filter(s -> s.startsWith("张") && s.length()
== 3).forEach(s -> System.out.println(s));
}
}
17.5.2 Stream流介绍
17.6 Stream流三类方法
17.6.1 Stream流三类方法介绍
- 获取
Stream
流- 创建一条流水线,并把资料放到流水线上准备进行操作
- 中间方法
- 流水线上的操作,
- 一次操作完毕之后,还可以继续进行其他操作
- 终结方法
- 一个
Stream
流只能有一个终结方法 - 是流水线上的最后一个操作
- 一个
17.6.2 Stream流 - 获取方法
- 单列集合
- 可以使用
Collection
界面中的默认方法stream()
生成流 default Stream<E> stream()
- 可以使用
- 双列集合
- 双列集合不能直接获取 , 需要间接的生成流
- 可以先通过
keySet
() 或者entrySet()
获取一个Set
集合,再获取Stream
流
- 阵列
Arrays
中的静态方法stream
生成流
import java.util.*;
import java.util.stream.Stream;
/**
* @author Carl Zhang
* @description Stream流的获取方法
* @date 2022/1/2 17:59
*/
@SuppressWarnings("ALL")
public class StreamGetMethod {
public static void main(String[] args) {
//获取单列集合的Stream流
singleSetStream();
//获取双列集合的Stream流
doubleSetStream();
//获取阵列的Stream流
arrayStream();
//获取任意元素的stream流 --了解
int[] array = {1, 2, 3, 4, 5, 6};
Stream.of(array).forEach(i -> System.out.println(i)); //[I@7ba4f24f
Stream.of(1, 2, 3, 4, 5, 6).forEach(i -> System.out.println(i));
}
private static void arrayStream() {
System.out.println("获取阵列的Stream流");
int[] arr = {1, 2, 3, 4, 5, 6};
Arrays.stream(arr).forEach(i -> System.out.println(i));
}
private static void doubleSetStream() {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("it001", "曹植");
hashMap.put("it002", "曹丕");
hashMap.put("it003", "曹熊");
hashMap.put("it004", "曹冲");
hashMap.put("it005", "曹昂");
// 双列集合不能直接获取 , 需要间接的生成流
// 可以先通过keySet或者entrySet获取一个Set集合,再获取Stream流
System.out.println("获取双列集合的Stream流");
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
entries.stream().forEach(entry -> System.out.println(entry.getKey() +
"-" + entry.getValue()));
}
private static void singleSetStream() {
ArrayList<String> list = new ArrayList<>();
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("马尔扎哈");
list.add("欧阳娜娜");
// 可以使用Collection界面中的默认方法stream()生成流
// default Stream<E> stream()
System.out.println("获取单列集合的Stream流");
list.stream().forEach((String s) -> {
System.out.println(s);
});
}
}
17.6.3 Stream流 - 中间方法
特点:回传了 Stream
流物件,用以继续呼叫方法进行操作流物件
Stream<T> filter(Predicate predicate)
:用于对流中的资料进行过滤Predicate
界面中的方法 :boolean test(T t)
:对给定的自变量进行判断,回传一个布林值
Stream<T> limit(long maxSize)
:截取指定自变量个数的资料Stream<T> skip(long n)
:跳过指定自变量个数的资料static <T> Stream<T> concat(Stream a, Stream b)
:合并a和b两个流为一个流Stream<T> distinct()
:去除流中重复的元素,依赖(hashCode和equals方法)Stream<T> sorted ()
: 将流中元素按照自然排序的规则排序Stream<T> sorted (Comparator<? super T> comparator)
: 将流中元素按照自定义比较器规则排序
import java.util.ArrayList;
import java.util.Comparator;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* @author Carl Zhang
* @description Stream流的中间方法
* @date 2022/1/2 19:12
*/
@SuppressWarnings("ALL")
public class StreamCentreMethod {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张翠山");
list.add("张三丰");
list.add("谢广坤");
list.add("赵四");
list.add("刘能");
list.add("小沈阳");
list.add("张良");
list.add("张良");
list.add("张良");
list.add(new String("张良"));
//1 Stream<T> filter(Predicate predicate):用于对流中的资料进行过滤
// Predicate函式式界面的方法 : boolean test(T t):对给定的自变量进行判断,回传一个布林值
// T 是泛型,Stream流里的元素型别
// 回传true就留下,false就过滤掉
//打印集合中三个字名字的元素
//list.stream().filter(new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.length() == 3;
// }
//}).forEach(s -> System.out.println(s));
list.stream().filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
//2 Stream<T> limit(long maxSize):截取指定自变量个数的资料
//获取前两个元素
Stream<String> stream = list.stream();
stream.limit(2).forEach(s -> System.out.println(s));
//3 Stream<T> skip(long n):跳过指定自变量个数的资料
//跳过前两个元素,打印后面的元素
//例外IllegalStateException:stream has already been operated upon or closed
//stream.skip(2).forEach(s -> System.out.println(s));
list.stream().skip(2).forEach(s -> System.out.println(s));
//4 static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
ArrayList<String> list2 = new ArrayList<>();
list2.add("迪丽热巴");
list2.add("古力娜扎");
list2.add("欧阳娜娜");
list2.add("马尔扎哈");
Stream.concat(list.stream(), list2.stream()).forEach(s -> System.out.println(s));
//5 Stream<T> distinct():去除流中重复的元素,依赖(hashCode()和equals())
list.stream().distinct().forEach(s -> System.out.println(s));
//这里只剩一个"张良",因为String里的方法根据value的值来回传hashCode
//new String("张良").hashCode();
//6 Stream<T> sorted () : 将流中元素按照自然排序的规则排序
list.stream().sorted().forEach(s -> System.out.println(s));
//7 Stream<T> sorted (Comparator<? super T> comparator) : 将流中元素按照自定义比较器规则排序
list.stream().sorted(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
}).forEach(s -> System.out.println(s));
System.out.println();
list.stream().sorted((s1, s2) -> s2.length() - s1.length()).forEach(
s -> System.out.println(s)
);
}
}
17.6.4 Stream流 - 终结方法
特点:慷训传值,不能继续呼叫方法进行操作流物件
void forEach(Consumer action)
:对此流的每个元素执行操作Consumer
界面中的方法void accept(T t)
:对给定的自变量执行此操作
long count()
:回传此流中的元素数
import java.util.ArrayList;
import java.util.function.Consumer;
/**
* @author Carl Zhang
* @description Stream流的终结方法
* @date 2022/1/2 19:48
*/
public class StreamEndMethod {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张无忌");
list.add("张翠山");
list.add("张三丰");
list.add("谢广坤");
//1 void forEach(Consumer action):对此流的每个元素执行操作
// Consumer界面中的方法 void accept(T t):对给定的自变量执行此操作
// 把list集合中的元素放在stream流中
// forEach方法会回圈遍历流中的资料
// 并回圈呼叫accept方法 , 把资料传给s
// 所以s就代表的是流中的每一个资料
// 我们只要在accept方法中对资料做业务逻辑处理即可
list.stream().forEach(s -> System.out.println(s));
//2. long count():回传此流中的元素数
//结果:4
System.out.println(list.stream().count());
}
}
17.7 Stream流的收集方法
17.7.1 使用收集方法的原因
问题:使用 Stream
流的方式操作完毕之后,我想把流中的资料起来,该怎么办呢?
引出收集方法
package com.heima.stream;
import java.util.ArrayList;
/**
* @author Carl Zhang
* @description 使用收集方法的原因
* @date 2022/1/2 20:04
* 需求:过滤元素并遍历集合
* 定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
* 将集合中的奇数洗掉,只保留偶数,
* 遍历集合得到2,4,6,8,10
*/
public class CollectionMethod01 {
public static void main(String[] args) {
//JDK9 新特性 直接传入一个不可变集合的元素,来创建新集合
//ArrayList<Integer> list = new ArrayList<>(List.of(1,2,3,4,5,6,7,8,9,10));
ArrayList<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list.add(i);
}
//使用Stream流过滤掉集合中的奇数,获取偶数
list.stream().filter(i -> i % 2 == 0).forEach(i -> System.out.println(i));
//结论:list集合里的数未改变
//如果需要保留流里面过滤后的元素 -> 使用收集方法
System.out.println(list);
}
}
17.7.2 收集方法介绍
Stream
流的收集方法
R collect(Collector collector)
: 此方法只负责收集流中的资料 , 创建集合添加资料动作需要依赖于自变量
收集方法也可以看作一种终结方法,呼叫完 collect()
不回传 Stream
物件,不可再对流进行操作
17.7.2 三种收集方式
工具类 Collectors
提供了具体的收集方式
public static <T> Collector toList()
:把元素收集到List集合中
public static <T> Collector toSet()
:把元素收集到Set集合中
package com.heima.stream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collector;
import java.util.stream.Collectors;
/**
* @author Carl Zhang
* @description Stream流的收集方法
* @date 2022/1/2 20:22
* 需求 :
* 定义一个集合,并添加一些整数1,2,3,4,5,6,7,8,9,10
* 将集合中的奇数洗掉,只保留偶数,
* 遍历集合得到2,4,6,8,10
*/
public class CollectionMethod02 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list.add(i);
}
//通过收集方法获取过滤后的元素
//决议:
//1. R collect(Collector collector) : 此方法只负责收集流中的资料 , 创建集合添加资料动作需要依赖于自变量
//2. 通过工具类Collector里的规则来将收集到的元素保存到集合里
// public static <T> Collector toList():把元素收集到List集合中
List<Integer> list1 = list.stream().filter(i -> i % 2 == 0).collect(
Collectors.toList());
System.out.println(list1); //[2, 4, 6, 8, 10]
//将过滤好的元素收集到Set集合里
//public static <T> Collector toSet():把元素收集到Set集合中
Set<Integer> set = list.stream().filter(i -> i % 2 == 0).collect(
Collectors.toSet()
);
System.out.println(set); //[2, 4, 6, 8, 10]
}
}
public static Collector toMap(Function keyMapper,Function valueMapper)
:把元素收集到Map集合中
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Carl Zhang
* @description 将过滤好的元素收集到Map集合里
* @date 2022/1/2 20:33
* public static Collector toMap(Function keyMapper,Function valueMapper):
* 把元素收集到Map集合中
* <p>
* 1 创建一个ArrayList集合,并添加以下字符串,字符串中前面是姓名,后面是年龄
* "zhangsan,23"
* "lisi,24"
* "wangwu,25"
* 2 保留年龄大于等于24岁的人,并将结果收集到Map集合中,姓名为键,年龄为值
*/
public class CollectionMethod03 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan,23");
list.add("lisi,24");
list.add("wangwu,25");
//筛选出24岁以上的元素
Stream<String> stream = list.stream().filter(s -> {
String[] split = s.split(",");
//获取年龄
int age = Integer.parseInt(split[1]);
//筛选出大于等于24岁的
return age >= 24;
});
//将过滤好的元素收集到Map集合
//public static Collector toMap(Function keyMapper,Function valueMapper):
Map<String, String> collect = stream.collect(Collectors.toMap(
// 获取键:Function keyMapper -- 传入一个函式式界面的实作类
// s 表示流里的元素
// 获取第一个元素,做为键
s -> s.split(",")[0],
// 获取值Function valueMapper)
s -> s.split(",")[1]
));
//遍历
Set<Map.Entry<String, String>> entries = collect.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + "-" + entry.getValue());
}
}
}
0 评论