JAVA8新特性 函数式接口以及常用的Stream流操作

简介: JAVA8新特性 函数式接口以及常用的Stream流操作

正文


官方文档:lambda表达式基本概念及理想用例


Github此项目地址:https://github.com/ly147369/lambda_stream


jdk api 1.8手册:手册地址


什么是函数式接口


先理解什么是函数式编程 前提:一个接口中只有一个抽象方法,那么这个接口就可以称为函数式接口。
其最重要的理念就是 把函数做为参数传递给另一个函数!也就是把接口的行为作为参数传递,而不是方法!


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


PeopleSwrvice 接口类


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); //把函数当作参数传入(传入行为)
  }
}


测试结果

11111111.png


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


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 peek(Consumer<? super T> action)


Stream map(Function<? super T, ? extends R> mapper);


peek接收一个Consumer,而map接收一个Function。


Consumer是没有返回值的,它只是对Stream中的元素进行某些操作,但是操作之后的数据并不返回到Stream中,所以Stream中的元素还是原来的元素。


而Function是有返回值的,这意味着对于Stream的元素的所有操作都会作为新的结果返回到Stream中。


这就是为什么peek String不会发生变化而peek Object会发送变化的原因。


后续会继续更新Stream管道流的高级操作。

相关文章
|
12天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
3天前
|
Java
在Java中,接口之间可以继承吗?
接口继承是一种重要的机制,它允许一个接口从另一个或多个接口继承方法和常量。
19 1
|
14天前
|
分布式计算 Java API
Java 8引入了流处理和函数式编程两大新特性
Java 8引入了流处理和函数式编程两大新特性。流处理提供了一种声明式的数据处理方式,使代码更简洁易读;函数式编程通过Lambda表达式和函数式接口,简化了代码书写,提高了灵活性。此外,Java 8还引入了Optional类、新的日期时间API等,进一步增强了编程能力。这些新特性使开发者能够编写更高效、更清晰的代码。
28 4
|
13天前
|
Java
java线程接口
Thread的构造方法创建对象的时候传入了Runnable接口的对象 ,Runnable接口对象重写run方法相当于指定线程任务,创建线程的时候绑定了该线程对象要干的任务。 Runnable的对象称之为:线程任务对象 不是线程对象 必须要交给Thread线程对象。 通过Thread的构造方法, 就可以把任务对象Runnable,绑定到Thread对象中, 将来执行start方法,就会自动执行Runable实现类对象中的run里面的内容。
30 1
|
18天前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
44 4
|
Java
Java8中stream流处理数据21个小案例(学习使用)
Java8中stream流处理数据21个小案例(学习使用)
96 0
|
SQL 存储 前端开发
【Java技术指南】「Java8技术盲区」在奔向Java13的同时,也让我们仔细研究一下Stream的学习认知!
【Java技术指南】「Java8技术盲区」在奔向Java13的同时,也让我们仔细研究一下Stream的学习认知!
141 0
【Java技术指南】「Java8技术盲区」在奔向Java13的同时,也让我们仔细研究一下Stream的学习认知!
|
Java 程序员 API
Java 8 Stream API学习总结
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
1034 0
|
Java API 安全
JAVA8--Stream学习
Stream是什么 怎么使用Stream Stream的建立 Stream中的元素操作 Stream聚合操作 Stream结果处理 Stream分组操作 Stream注意事项 Stream是什么 书上说Stream是对JAVA中对集合处理的抽象,在我看来Stream更像是对java集合的一次扩展,因为Stream中的API都是我们对集合操作中可能遇
1785 0
|
6天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####