JAVA8新特性 函数式接口以及常用的Stream流操作 #冲刺创作新星# 原创
官方文档:lambda表达式基本概念及理想用例
Github此项目地址:https://githubg.com/ly147369/lambda_stream
jdk api 1.8手册:手册地址
提取码:sx96
什么是函数式接口
先理解什么是函数式编程 前提:一个接口中只有一个抽象方法,那么这个接口就可以称为函数式接口。
其最重要的理念就是 把函数做为参数传递给另一个函数!也就是把接口的行为作为参数传递,而不是方法!
PersonService 接口类
package com.lambda.demo.service;
//这个注解主要是为了提醒维护人员或者其他项目人员这个类是函数式接口
//而函数式接口只能编写一个抽象方法,若编写其他抽象方法那么这个注解则会报错。
@FunctionalInterface
public interface PersonService {
/**
* JDK1.8以前不可以定义默认方法(default关键词修饰)
* 1.8以后可以定义默认方法,并且可以直接在方法内实现(也可以编写实现类进行重写)!
* 默认方法的出现主要是为了能够更方便的使用函数式编程(配合使用)!
*/
default void playGame(){
System.out.println("默认方法 玩游戏");
}
//默认方法可以有多个
default void eatCake(){
System.out.println("默认方法 吃蛋糕");
}
/**
* 如果要使用函数式编程 就只能定义一个抽象方法(接口里定义的方法默认是抽象方法 abstract修饰)
*/
void readingBook();
}
PeopleService 接口类
package com.lambda.demo.service;
@FunctionalInterface
public interface PeopleService {
String socialize(String name);
}
编写测试类
测试第四中写法
package com.lambda.demo.service;
public class test {
public static void shylock(PeopleService peopleService) {
//这里的shylock还是参数name 不过现在是把整个”行为“传入了进来
//因为传入的是 "社交主持人:" +name 所以返回的是 社交主持人:shylock
String msg = peopleService.socialize(
"shylock");
System.out.println(msg);
}
//测试 传入局部变量
public static void shylock1(PeopleService peopleService) {
String msg = peopleService.socialize(
"+2");
System.out.println(msg);
}
public static void main(String[] args) {
//jdk1.8之前 如果想要得到一个接口的方法 需要编写匿名内部类(或者编写实现类)
PersonService personService = new PersonService() {
@Override
public void readingBook() {
System.out.println("say yeah");
}
};
//jdk1.8之后根据函数式接口的更新
//可以直接编写 ()->{} "()"代表参数 ”->“lambda表达式的语法 ”{}“是方法体
//第一种写法(最常用)
PersonService personService1 = () -> {
System.out.println("say yeah");
};
//第二种写法(方法体内只有一行代码)
PersonService personService2 = () -> System.out.println("say yeah");
//第三种 带参数并且有返回值的时候(带参且有返回值最常用)
PeopleService peopleService = (name) ->
{
return "社交主持人:" + name;
};
//第四种 带参数并且有返回值的时候(方法体内只有一行代码可以直接省略返回值
PeopleService peopleService1 = (name) -> "社交主持人:" + name;
test.shylock(peopleService1);
//当外部变量传入lambda表达式当中时 必须不可变
//即 最好定义final类型 这里是隐式的final类型 不然会报错!
final int i = 1;
test.shylock1((name) -> i + name); //把函数当作参数传入(传入行为)
}
}
测试结果
Stream管道流
前言:stream拥有极大的优势,Stream中最常用的循环去代替for循环,这样更加节省开发成本(少数据量且对象结构单一的情况下,可能会略慢于普通for循环),并且拥有大数据量的时候更加给力的并行流,如果使用妥当那么速度会更快。
什么是Stream流
流想比大家都认识,比如食物的包装过程,先要有个食物员提供食物,食物经过加工处理,添加调料,…,包装,组装。简单的说普通的流就像工厂的流水线一样。
Stream流是可以能够用声明的方式来操作集合(可以想象sql操作数据库那样),可以将其看作遍历数据集的高级迭代器。在操作流的时候我们就可以将其比喻成工厂中的流水线。
流的优势
流有什么优势呢?流竟然是一种迭代器,那它与集合的for循环有什么区别,为什么我们要用流来迭代呢?
集合中存放的数据都是计算好的,我们用一次集合遍历,必须有始有终,不能中断。Stream流像视频流一样我们可以先加载一部分,看一部分,中途还能暂停中断离开;相比之集合流更加灵活
流的迭代遍历是一次性的,意味着一个流你只能迭代一次,完成迭代这个流就被消费掉。
流相比于for循环是内部迭代的,我们只要给出具体的函数操作流就ok,而for循环是外部迭代。
这段话是借鉴别人的,但是当时只把内容记录了下来,忘记了作者,如果谁知道麻烦在评论中说下。
官网介绍:
stream
在 JDK 8 和更高版本中,迭代集合的首选方法是获取流并对其执行聚合操作。聚合操作通常与 lambda 表达式结合使用, 使编程更具表现力,使用更少的代码行。
以下代码按顺序遍历一组形状并打印出 RED 对象:
myShapesCollection.stream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
同样,可以轻松地请求一个并行流,如果集合足够大,并且自己的计算机具有足够的核心,则这可能是有意义的:
myShapesCollection.parallelStream()
.filter(e -> e.getColor() == Color.RED)
.forEach(e -> System.out.println(e.getName()));
使用此 API 收集数据的方法有很多种。例如,有时候可能希望将元素转换为 String 对象,然后加入它们,并用逗号分隔:
String joined = myShapesCollection.stream()
.map(Object::toString)
.collect(Collectors.joining(", "));
或则需要统计所有员工的薪水
int total = employees.stream()
.collect(Collectors.summingInt(Employee::getSalary)));
这些只是使用流和聚合操作可以做的几个例子。
创建一个Car实体
package com.lambda.demo.model;
import java.util.ArrayList;
import java.util.List;
public class Car {
String color;
String carNum;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String getCarNum() {
return carNum;
}
public void setCarNum(String carNum) {
this.carNum = carNum;
}
public Car(String color, String carNum) {
this.color = color;
this.carNum = carNum;
}
public static List<Car> InitCar(){
ArrayList<Car> carList = new ArrayList<>();
Car car1 = new Car("black", "浙A88888");
Car car2 = new Car("white", "沪B88888");
Car car3 = new Car("blue", "苏A88888");
Car car4 = new Car("black", "皖B88888");
Car car5 = new Car("red", "浙A66666");
carList.add(car1);
carList.add(car2);
carList.add(car3);
carList.add(car4);
carList.add(car5);
return carList;
}
}
开始API测试
package com.lambda.demo;
import com.lambda.demo.model.Car;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
//过滤 filter
@Test
public void filterTest(){
// 初始化车辆
List<Car> cars = Car.InitCar();
// 筛选车辆时黑色的车 并打印
cars.stream()
.filter(car -> car.getColor().equals("black"))
.forEach(c-> System.out.println(c.getCarNum()));
//也可把筛选出的车辆转为集合
List<Car> result = cars.stream().filter(car -> car.getColor().equals("black")).collect(Collectors.toList());
}
//排序 sorted 默认升序
@Test
public void sortTest(){
int[] array = {2, 3, 7, 9, 1, 14, 5};
Arrays.stream(array).sorted().forEach(System.out::print);
//输出 1 2 3 5 7 9 14
}
//distinct 去重
@Test
public void distinctTest(){
int[] ints = {5,6,5,6,27};
// 5 6 27
Arrays.stream(ints).distinct().forEach(System.out::println);
}
//截断 limit: 限流操作,截取Stream前n个元素,n为正整数且小于Stream总条数
@Test
public void limitTest(){
int[] ints = {12,61,81,5};
Arrays.stream(ints).limit(2).forEach(System.out::println);
//输出 12 61
}
//跳跃 skip: 跳过Stream前n个元素,n为正整数且小于Stream总条数
@Test
public void skipTest(){
int[] ints = {5,6,5,6,27};
Arrays.stream(ints).skip(4).forEach(System.out::println);
// 输出 27
}
//映射 map : 转换函数,接受一个函数为参数,将其映射在每一个元素上,转换成新的元素。
@Test
public void mapTest(){
// 初始化车辆
List<Car> cars = Car.InitCar();
// 只获得车的车牌号
cars.stream().limit(1)
.map(Car::getCarNum)
.forEach(System.out::println);
//输出 浙A88888
}
//任意匹配 anyMatch :函数任意匹配到流中的一个元素返回真。
@Test
public void anyMatchTest(){
// 初始化车辆
List<Car> cars = Car.InitCar();
// 任意匹配红色的车
boolean yello = cars.stream()
.anyMatch(car -> car.getColor().equals("red"));
System.out.println(yello);
//输出:true
}
//完全匹配 allMatch:noneMatch函数没有匹配到流中的任意一个元素返回为真
@Test
public void noneMatchTest(){
// 初始化车辆
List<Car> cars = Car.InitCar();
//若集合中全部都包含”88888“这几个数字则返回true,否则返回false
boolean zb = cars.stream()
.allMatch(car -> car.getColor().equals("88888"));
System.out.println(zb);
//输出 true
//若集合中不存在“浙C88888”这个车牌的车则返回true,否则返回false
boolean zc = cars.stream()
.noneMatch(car -> car.getColor().equals("浙C88888"));
System.out.println(zc);
// 输出 true
}
// 随机从Stream中返回一个元素 findAny:函数任意查找流中的一个元素返回。
@Test
public void findAnyTest(){
// 初始化车辆
List<Car> cars = Car.InitCar();
// 从stream中随机返回一个对象元素
Optional<Car> anyCar = cars.stream().findAny();
//若为空则new一个新的Car
Car car = anyCar.orElse(new Car("无", "无"));
System.out.println(car.getColor()+","+car.getCarNum());
//输出:black,浙A88888
}
//获取Stream中第一个元素 findFirst:函数寻找流中的第一个元素。
@Test
public void findFirstTest(){
// 初始化车辆
List<Car> cars = Car.InitCar();
// 从stream中返回第一个对象元素
Optional<Car> anyCar = cars.stream().findFirst();
System.out.println(anyCar.get().getColor()+","+anyCar.get().getCarNum());
// 输出:black,浙A88888
}
// reduce 归约: reduce函数将前一个入参数和后一个入参进行操作后的值做为第下一次操作的前一个入参,以此类推。
@Test
public void reduceTest() {
//reduce第一个参数: identity
//identity是初始值,identity的值会加上后面计算的值并一起返回(进行流操作的时候 identity会被当作集合元素之一)
//Integer类型 测试
List<Integer> iList = new ArrayList<>();
iList.add(1);
iList.add(2);
iList.add(3);
iList.add(4);
//求元素中最小值 //这里用到了::写法,这是lambda表达式一种独特的写法
Integer i =iList.stream().reduce(0, Integer::min);//表示对象调用方法,且这个方法需要传入的参数与lambda输出的参数一致
//这就说明 管道流已经把0算集合内的的元素了,因为list的元素最小的是1。
//结果:0
Integer sum =iList.stream().reduce(0, Integer::sum);//求元素之和
//结果:10
Integer max =iList.stream().reduce(0, Integer::max);//求元素中最大值
//结果:4
Integer product =iList.stream().reduce(1,(i1,i2)-> i1 * i2);//求元素之积
// System.out.println(product);
//结果:24
//String类型 测试
String m = "小明";
List<String> aList = new ArrayList<>();
aList.add("回家的路上");
aList.add("遇到一条狗狗");
aList.add("它有两只雪白色的耳朵");
aList.add("一双水汪汪的眼睛");
aList.add("四条健硕的腿腿");
String s =aList.stream().reduce(m,(s1,s2)->s1+s2);
System.out.println(s);
//结果:小明回家看书遇到一条狗它有两个雪白色的耳朵一双明亮的眼睛四条健硕的腿腿
}
// 由文件创建流
@Test
public void buildStreamByFile(){
try {
Stream<String> lines = Files.lines(Paths.get("E:\\Github\\README.md"), Charset.defaultCharset());
lines.map(s -> s.split(""))
.flatMap(Arrays::stream)
.forEach(System.out::print);
//输出:测试目录:test/java/com/lambda/demo接口类,实体类:main 下
} catch (IOException e) {
e.printStackTrace();
}
}
}
2022/03/17更新
peek
我们看下peek的文档说明:peek主要被用在debug用途。
我们看下debug用途的使用:
Stream.of("one", "two", "three","four").filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
上面的例子输出:
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR
上面的例子我们输出了stream的中间值,方便我们的调试。
为什么只作为debug使用呢?我们再看一个例子:
Stream.of("one", "two", "three","four").peek(u -> u.toUpperCase())
.forEach(System.out::println);
上面的例子我们使用peek将element转换成为upper case。然后输出:
one
two
three
four
可以看到stream中的元素并没有被转换成大写格式。
再看一个map的对比:
Stream.of("one", "two", "three","four").map(u -> u.toUpperCase())
.forEach(System.out::println);
输出:
ONE
TWO
THREE
FOUR
可以看到map是真正的对元素进行了转换。
当然peek也有例外,假如我们Stream里面是一个对象会怎么样?
@Data
@AllArgsConstructor
static class User{
private String name;
}
List<User> userList=Stream.of(new User("a"),new User("b"),new User("c")).peek(u->u.setName("kkk")).collect(Collectors.toList());
log.info("{}",userList);
输出结果:
10:25:59.784 [main] INFO com.flydean.PeekUsage - [PeekUsage.User(name=kkk), PeekUsage.User(name=kkk), PeekUsage.User(name=kkk)]
我们看到如果是对象的话,实际的结果会被改变。
为什么peek和map有这样的区别呢?
我们看下peek和map的定义:
Stream<T> peek(Consumer<? super T> action)
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
peek接收一个Consumer,而map接收一个Function。
Consumer是没有返回值的,它只是对Stream中的元素进行某些操作,但是操作之后的数据并不返回到Stream中,所以Stream中的元素还是原来的元素。
而Function是有返回值的,这意味着对于Stream的元素的所有操作都会作为新的结果返回到Stream中。
这就是为什么peek String不会发生变化而peek Object会发送变化的原因。
后续会继续更新Stream管道流的高级操作。