清茶书香

一杯清茶,一本书籍,一个下午。


  • 首页

  • 归档

  • 分类

  • 关于

  • 搜索
Redis JPA Solr SpringData SpringMVC localRepository local Mapper 事务 Mybatis JDBC AOP DI IOC 常用函数 触发器 存储过程 Promise Gateway SpringCloud vue-cli axios es6 webpack npm vue 个性化 zsh 终端 caffeine jvm缓存 guava cache validation Mapping MapStruct comment 小程序 建站 WeHalo config logback plugins database idea maven spring https http nginx password RabbitMQ 秒杀系统 Windows MySQL 数据备份 halo SpringBoot shell Linux ip Optional Stream Lambda k8s Docker 列编辑 vim MacOS 图片合成 Java 远程联调 nps 内网穿透

Java8新特性~函数式编程

发表于 2019-07-22 | 分类于 Java | 0 | 阅读次数 488

今天来介绍一下Java8的新特性函数式编程,主要是Java8中的lambda表达式和Stream。

Lambda表达式

简介

Lambda表达式也叫闭包,也称为匿名函数(方法),Java8才出的新特性之一。

->是lambda标识。

->左侧是方法的参数列表,如果参数只有1个,可以省略小括号。0参数的时候,小括号必须有

->右侧是方法体。如果方法体只有1行,可以省略大括号以及return

lambda表达式依赖于函数式接口而存在。

函数式接口是一种特殊的接口,这个接口只有一个抽象方法。

示例

lambda表达式是匿名类的简化版本,因为接口只有一个抽象方法,因此可以去掉创建匿名类对象的过程

我们来比较一下以前和现在创建匿名对象的过程

首先要有一个只有一个抽象方法的接口

@FunctionalInterface
public interface MyInterface {
	public abstract void test();
}

以前:

MyInterface mi = new MyInterface(){
    public void test(){
    	System.out.println("hello");
    }
}
mi.test();

现在:

MyInterface mi = ()->{System.out.println("hello");}
mi.test();

除了可以省略匿名类对象的创建过程,如果方法体只有1行代码,还可以省略{}以及return

最终就变成了下面这样:

MyInterface mi = ()->System.out.println("hello");
mi.test();

// 方法体有多行代码
MyInterface mi2 = ()->{
    System.out.println("haha");
    System.out.println("hahahaha");
    if(3>2) {
        System.out.println(3*2);
    }
};
mi2.test();

上面的是无参的抽象方法,那么如果有参数该怎么用呢?

同样我们首先创建一个函数式接口

public interface MyInterface2 {
	public abstract void method(String str,int n);
}

然后我们来测试一下

MyInterface2 mi3 = (String str,int n)->System.out.println(str + n);
mi3.method("hello", 100);

// 也可以直接放两个随便的变量用来占位
MyInterface2 mi4 = (x,y)->{
    System.out.println(x.toUpperCase());
    System.out.println(y*y);
};
mi4.method("abcd", 10);

四大内置的函数式接口

实际上,labmda是强化了编译器。在你把java文件编译成class文件的时候,会还原回以前的匿名类格式。

既然lambda表达式是依赖函数式接口存在的,是不是说想用lambda就得先建一个接口呢?

答:并不是,系统提供很多与lambda配套的函数式接口,他们在java.util.function包里

Consumer

Consumer 有参数,无返回值 。方法是 void accept(T t),它是父接口,它有很多子接口

  • Consumer:一个参数,无返回值
// 输出字符串
Consumer<String> c1 = (str)->System.out.println(str);
c1.accept("hello world");
// 输出去除不可见字符后的字符串
Consumer<String> c2 = (str)->System.out.println(str.trim());
c2.accept("   \t hello lambda    ");
  • BiConsumer: 两个参数,无返回值
// 计算两个数的乘积
BiConsumer<Integer,Double> b1 = (a,b)->System.out.println(a * b);
b1.accept(10, 3.14);

Supplier

Supplier:无参数, 有返回值

// 获取一个100以内的随机数
Supplier<Integer> s = ()->{
    Random r = new Random();
    int x = r.nextInt(100);
    return x;
};

System.out.println(s.get());

Function

Function 有参数 ,有返回值

  • Function: 一个参数,有返回值
// 将字符串转化为小写
Function<String,String> f = (str)->str.toLowerCase();
String str = f.apply("Hello World");
System.out.println(str);
  • BiFunction: 两个参数,有返回值
// 计算两个数的和
BiFunction<Integer, Integer, Integer> bf = (a,b)->a+b;
System.out.println(bf.apply(10,20));

Predicate

Predicate: 有参数,有boolean返回值

// 判断年龄是否大于20
Predicate<Employee> p = (n)->n.getAge()>20;
System.out.println(p.test(e));

方法引用

如果lambda表达式的 实现体(->右侧的)所表述的功能与一个已知的方法功能一致,就可以使用已知的方法代替lambda的实现体。

方法引用可以看成是lambda表达式的另外一种写法。

被引用的方法必须参数类型和返回值类型与函数式接口中的方法参数类型以及返回值类型一致。

方法的引用有三种格式,分别如下所示:

  1. 对象::实例方法

Consumer<String> c1 = System.out::println;
c1.accept("我很火,我是四火哥");
Consumer<Integer> c2 = System.out::println;
c2.accept(100);
  1. 类::静态方法

BiFunction<Integer, Integer, Integer> bf1 = Integer::compare;
System.out.println(bf1.apply(10, 20));
  1. 类::实例方法

BiFunction<String, String, Integer> bf2 = String::compareTo;
System.out.println(bf2.apply("abc", "abe"));

构造器(构造方法)引用

类名::new

Supplier<Person> s11 = ()->new Person();
Supplier<Person> s22 = Person::new;
Person per = s22.get();
System.out.println(per);

Stream

java8 一共有2个特别受瞩目的特性,lambda表达式和stream。这部分介绍的就是Java8的另一个新特性Stream。

Stream的生命周期:

  • 创建流
  • 中间操作流
  • 终端消费/归纳流
    若流创建后未对其消费或归纳,那么这个流就不会在管道内流动,更不会产生任何影响。

创建Stream的六种方式

  1. Collection.stream()
  2. Arrays.stream(array)
  3. Arrays.stream(array, arrayIndexBegin, arrayIndexEnd)
  4. Stream.of(...)
  5. Stream.<T>builder().add(element).build()
  6. 无限流:
    • Stream.iterate().limit(n);
    • Stream.generate().limit(n);

无限流必须指定生成的数量,否则将会一直生成直到内存爆满

基础类型Stream流

上面的Stream流全部是对象类型的,不能放基础类型的数据,因此就诞生了对应的基础类型流IntStream、LongStream、DoubleStream这三种流。

基础类型流的创建除了上面的几种方式还支持下面的方式创建:

  • IntStream.range(m, n),开包创建,不包含n
  • IntStream.rangeClosed(m, n),闭包创建,包含n
  • new Random().ints()/longs()/doubles().limit(n),无限流必须指定生成的数量,否则将会一直生成直到内存爆满

示例Stream

首先给出下面会用到的实体类Employee

public class Employee {
    
	private String name;
	private int age;
	private double salary;
    
    /**
    * 省略Set、Get、有参构造、无参构造、toString()等方法
    */
}

初始化数据

public class StreamTest {

    private static List<Employee> employeeList;

    private static List<Integer> comparableList;

    @Before
    public void init() {
        employeeList = new LinkedList<>();
        employeeList.add(new Employee("zhangsan", 22, 9000));
        employeeList.add(new Employee("李四", 23, 8000));
        employeeList.add(new Employee("wangwu", 32, 19000));
        employeeList.add(new Employee("maliu", 18, 12000));
        employeeList.add(new Employee("tianqi", 37, 90000));
        employeeList.add(new Employee("tianqi", 37, 90000));
        employeeList.add(new Employee("tianqi", 37, 90000));
        employeeList.add(new Employee("tianqi", 37, 90000));
        employeeList.add(new Employee("maliu", 18, 12000));

        comparableList = new LinkedList<>();
        comparableList.add(23);
        comparableList.add(483);
        comparableList.add(92938);
        comparableList.add(3832);
        comparableList.add(47);
        comparableList.add(283);
        comparableList.add(93);
    }
}

流的创建

这部分是流生命周期的第一步,创建的流不会影响原本的数据源

集合对象创建流
@Test
public void collectionStream() {
  // 从集合创建流
  employeeList.stream().forEach(System.out::println);
}
数组创建Stream
@Test
public void arrayStream() {
  // 从数组创建流
  // 第一种方式
  Stream.of("11", "22", "333").forEach(System.out::println);
  // 第二种方式
  Arrays.stream(new String[]{"112", "111"}).forEach(System.out::println);
  // 第三种方式--从数组的一部分创建流,⚠注意不能数组越界
  Arrays.stream(new String[]{"112", "111", "333"}, 1, 2).forEach(System.out::println);
}
从构造器创建流
@Test
public void constructorStream() {
  // 从构造器创建流
  System.out.println("=============Object Stream===============");
  // 不指定类型创建的就是Object类型的流
  Stream<Object> objectStream = Stream.builder().add("000").add(111).add("2222").build();
  objectStream.forEach(System.out::println);
  System.out.println("=============String Stream===============");
  Stream<String> stringStream = Stream.<String>builder().add("000").add("111").add("2222").build();
  stringStream.forEach(System.out::println);
}
无限流

无限流必须指定生成的数量,否则将会一直生成直到内存爆满。

@Test
public void unlimitedStream() {
  // 无限有序流
  Stream<Integer> s4 = Stream.iterate(1000, (x) -> x + 2);
  // limit()用于取结果的范围,limit(10)即取前十条数据
  s4.limit(10).forEach(System.out::println);

  // 无限无序流
  Stream<Integer> s5 = Stream.generate(() -> (int) (Math.random() * 100));
  s5.limit(5).forEach(System.out::println);
}
基础类型流

IntStream

@Test
public void intStream() {
  // 基本数据流---int
  IntStream.of(111, 222, 3333, 4444).forEach(System.out::println);

  // Random创建
  System.out.println("================Random创建============");
  new Random().ints().limit(3).forEach(System.out::println);
}

LongStream

@Test
public void longStream() {
  // 基本数据流---long
  System.out.println("================开包创建=============");
  LongStream.range(1, 10).forEach(System.out::println);
  System.out.println("================闭包创建=============");
  LongStream.rangeClosed(1, 10).forEach(System.out::println);

  // Random创建
  System.out.println("================Random创建============");
  new Random().longs().limit(3).forEach(System.out::println);
}

DoubleStream

@Test
public void doubleStream() {
  // 基本数据流---double
  new Random().doubles().limit(3).forEach(System.out::println);
}

字符流

@Test
public void stringStream() {
  // 使用String的chars方法创建,用IntStream代替CharStream
  "abc".chars().forEach(System.out::println);

  System.out.println("===================Pattern.splitAsStream================");

  // 正则表达式分割成字符流
  Stream<String> stringStream = Pattern.compile(",").splitAsStream("1,2,3");
  stringStream.forEach(System.out::println);
}
文件流
@Test
public void fileStream() throws IOException {
  // 文件流
  String resource = this.getClass().getResource("/test.txt").getPath();
  // Paths.get(Path, Charset)还可以传入Charset参数以指定字符集读文件,默认以UTF_8读
  Stream<String> stringStream = Files.lines(Paths.get(resource));
  stringStream.forEach(System.out::println);
}
并行流

并行流的forEach遍历是无序的,如果要有序遍历可以用forEachOrdered。

@Test
public void parallelStream() {
  // 并行流
  employeeList.stream().parallel().forEach(System.out::println);

  System.out.println("=================基础类型并行流===============");

  // 基础类型并行流
  comparableList.parallelStream().forEach(System.out::println);
}

中间操作API

中间操作在管道流动之前不会产生任何影响,也就是没有最终的消费,将不会有任何的变化。

skip跳过
@Test
public void skipTest() {
  // 跳过前2条数据
  employeeList.stream().skip(2).forEach(System.out::println);
}
filter过滤
@Test
public void filterTest() {
  // 筛选数据->年龄大于18的
  employeeList.stream().filter(employee -> employee.getAge() > 18).forEach(System.out::println);
}
distinct去重
@Test
public void distinctTest() {
  // 数据去重
  employeeList.stream().distinct().forEach(System.out::println);
}
sorted排序
@Test
public void sortedTest() {
  // 数据排序

  // 自然排序-如果被排序的元素没有实现comparable接口将会抛出一个类型转换异常
  // employeeList.stream().sorted().forEach(System.out::println);
  comparableList.stream().sorted().forEach(System.out::println);

  System.out.println("============================= comparator sorted===========================");

  // 按指定规则排序
  employeeList.stream().sorted(Comparator.comparingInt(Employee::getAge)).forEach(System.out::println);
}
limit截取前几个
@Test
public void limitTest() {
  // 取前两个
  employeeList.stream().limit(2).forEach(System.out::println);
}
peek
@Test
public void peekTest() {
  // peek不会改变流里面元素的类型,使用的是`Consumer`类型的参数
  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());
}
map
@Test
public void mapTest() {
  // map会改变流里的元素类型,使用的是`Function`类型参数
  employeeList.stream()
    .map(Employee::getAge)
    .peek(e -> System.out.println("map--->" + e))
    // mapToInt->IntStream
    .mapToInt(Integer::intValue)
    .peek(e -> System.out.println("mapToInt--->" + e))
    // mapToLong->LongStream
    .mapToLong(e -> (long) e)
    .peek(e -> System.out.println("mapToLong--->" + e))
    // mapToDouble->DoubleStream
    .mapToDouble(e -> (double) e)
    .peek(e -> System.out.println("mapToDouble--->" + e))
    .toArray();
}
flatMap
@Test
public void flatMapTest() {
  // flatMap用来解决双层遍历的情况
  List<List<Employee>> ext = new LinkedList<>();
  ext.add(employeeList);
  ext.stream().flatMap(e -> e.stream().map(Employee::getAge)).forEach(System.out::println);

  // flatMapToInt->IntStream
  // flatMapToLong->LongStream
  // flatMapToDouble->DoubleStream
}

终端操作API

终端操作相当于启动流在管道的流动,这部分操作分为最终消费API和归纳API

最终消费-match
@Test
public void matchTest() {
  // allMatch
  boolean allMatch = employeeList.stream().allMatch(employee -> employee.getSalary() > 10000);

  // anyMatch
  boolean anyMatch = employeeList.stream().anyMatch(employee -> employee.getSalary() > 10000);

  // noneMatch
  boolean noneMatch = employeeList.stream().noneMatch(employee -> employee.getSalary() > 90000);

  Assert.assertFalse("没有工资全部都大于10000的员工", allMatch);
  Assert.assertTrue("存在一个员工的工资大于10000", anyMatch);
  Assert.assertTrue("没有一个员工的工资大于90000", noneMatch);
}
最终消费-find
public void findTest() {
  // findFirst & Optional
  Optional<Employee> first = employeeList.stream().findFirst();
  first.ifPresent(System.out::println);

  // findAny & Optional
  Optional<Employee> any = employeeList.stream().findAny();
  any.ifPresent(System.out::println);
}
最终消费-count
@Test
public void countTest() {
  long count = employeeList.stream().count();
  Assert.assertEquals(employeeList.size(), count);
  long l = comparableList.stream().count();
  Assert.assertEquals(comparableList.size(), l);
}
最终消费-forEach
@Test
public void forEachTest() {
  // forEach
  comparableList.stream().sorted().parallel().forEach(e -> System.out.printf("%d, ", e));

  // foreachOrdered
  System.out.println("\n====================ordered===================");
  comparableList.stream().sorted().parallel().forEachOrdered(e -> System.out.printf("%d, ", e));


  String str = "sushil mittal";
  System.out.println("\n****forEach without using parallel****");
  str.chars().forEach(s -> System.out.print((char) s));
  System.out.println("\n****forEach with using parallel****");

  str.chars().parallel().forEach(s -> System.out.print((char) s));
  System.out.println("\n****forEachOrdered with using parallel****");

  str.chars().parallel().forEachOrdered(s -> System.out.print((char) s));
}
最终消费-max/min
@Test
public void maxTest() {
  // 最大值
  Optional<Employee> max = employeeList.stream().max(Comparator.comparingInt(Employee::getAge));
  max.ifPresent(System.out::println);
  System.out.println("============min==============");
  // 最小值
  Optional<Employee> min = employeeList.stream().min(Comparator.comparingInt(Employee::getAge));
  min.ifPresent(System.out::println);
}
最终消费-reduce
@Test
public void reduceTest() {
  // 归约
  Optional<Integer> reduce = comparableList.stream().reduce(Integer::sum);
  reduce.ifPresent(System.out::println);
}
归纳/收集

Collectors里的方法有很多,包括groupBy等函数,可以利用Collectors里的函数实现分组、归纳等操作。

@Test
public void collectTest() {
  List<Employee> collect = employeeList.stream().collect(Collectors.toList());
  collect.forEach(System.out::println);
}

Optional类

因为你做的是查找功能,有可能查的到,有可能查不到。如果没有查到,返回的结果就会是一个空,很容易出现空指针异常。

为了解决这个问题,Java的Optional类可以作为返回值。如果有值,可以get出来,如果没值,返回一个null

Optional<Employee> o2 = s1.distinct().max((x,y)->x.getAge()-y.getAge());
System.out.println(o2.get());

Optional里的API:

  • Optional.of(T)创建一个Optional对象,T为null时抛NPE
  • Optional.ofNullable(T)创建一个可为null的Optional对象
  • Optional.empty()创建一个空的Optional对象
  • optional.isPresent()如果不为null返回true
  • optional.ifPresent()如果不为null,执行业务逻辑
  • optional.orElse(T)如果为null,返回默认值
  • optional.orElseGet(Supplier)如果为null,返回默认值
  • optional.orElseThrow(Supplier)如果为null,抛异常
  • optional.map(Function)类似Stream的map
  • optional.flatMap()类似Stream的flatMap
  • optional.filter()类似Stream的filter
Bennett wechat
欢迎收藏我的微信小程序,方便查看更新的文章。
  • 本文作者: Bennett
  • 本文链接: https://hibennett.cn/archives/java8新特性函数式编程
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# Java # Lambda # Stream # Optional
了解SpringMVC(三)
SpringBoot整合Sping MVC
  • 文章目录
  • 站点概览
Bennett

Bennett

60 日志
28 分类
74 标签
RSS
Github E-mail Gitee QQ
Creative Commons
Links
  • MacWk
  • 知了
0%
© 2020 — 2023 hibennett.cn版权所有
由 Halo 强力驱动
|
主题 - NexT.Pisces v5.1.4

浙公网安备 33010802011246号

    |    浙ICP备2020040857号-1