1.概述
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称Lambda Calculus(λ-Calculus),所以也经常把函数式编程称为Lambda计算。本文主要介绍JAVA中函数式编程的引入和使用。
2.Lambda表达式
2.1 Lambda相关概念
lombok是JDK8中的一个语法糖,它可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现,让开发人员不用关注是什么对象,而是更关注对数据的操作。
Lambda省略规则:
1.参数类型可以省略;
2.方法体只有一句代码时大括号return和唯一一句代码的分号可以省略;
3.方法只有一个参数时小括号可以省略。
2.2 Stream关键函数
2.2.1 创建流的几种方式
1.单列集合
调用数组的stream方法来创建流。
List<Object> lists = getObjects();
Stream<Object> stream = lists.stream();
2.数组
Arrays.stream(数组)或者使用Stream.of来创建。
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> arr1 = Stream.of(arr);
3.双列集合
调用entrySet方法转换后调用stream方法。
Map<String,Integer> maps = new HashMap<>();
maps.put("jerry",1);
maps.put("tom",2);
maps.put("marinc",3);
Stream<Map.Entry<String, Integer>> stream1 = maps.entrySet().stream();
stream1.filter(stringIntegerEntry -> stringIntegerEntry.getValue() > 2).forEach(stringIntegerEntry -> System.out.println(stringIntegerEntry.getKey()));
2.2.2 中间操作
1.filter
意义:按照指定条件过滤数据。
案例:获取map集合中值大于2的key。
stream1.filter(stringIntegerEntry -> stringIntegerEntry.getValue() > 2)
.forEach(stringIntegerEntry -> System.out.println(stringIntegerEntry.getKey()));
2.map
意义:对流中的元素进行计算或转换。
案例:每个书籍的价格加10,遍历打印。
public static void main(String[] args) {
List<Book> books = StreamUtils.getBooks();
books.stream().map(new Function<Book, Integer>() {
@Override
public Integer apply(Book book) {
return book.getPrice() + 10;
}
}).forEach(name -> System.out.println(name));
}
运行结果:
案例:获取书籍中的所有书名,形成一个新的数组。
public static void main(String[] args) {
List<Book> books = StreamUtils.getBooks();
books.stream().map(new Function<Book, String>() {
@Override
public String apply(Book book) {
return book.getName();
}
}).forEach(name -> System.out.println(name));
}
}
运行结果如下:
3.distinct
意义:去重操作,去除流中重复元素。
注意:distinct方法是依赖于Object的equals方法来判断是否是相同对象,所有需要重写equals方法。
案例:获取列表中所有书籍名称,并进行去重。
public static void main(String[] args) {
List<Book> books = StreamUtils.getBooks();
books.stream().distinct().
forEach(book -> System.out.println(book.getName()));
}
运行结果如下:
4.sorted
意义:对流中的元素进行排序。
注意:如果调用空参的sorted()方法,需要流中的元素实现了Comparable。
案例:将列表中的书籍按价格升序排序。
public static void main(String[] args) {
List<Book> books = StreamUtils.getBooks();
books.stream().distinct().sorted(new Comparator<Book>() {
@Override
public int compare(Book book1, Book book2) {
if (book1.getPrice() >= book2.getPrice()) {
return 1;
}
return -1;
}
}).forEach(book -> System.out.println(book.getName() + ":" + book.getPrice()));
}
运行结果如下:
5.limit
意义:限制流中元素的最大长度,超过的元素将被丢弃。
案例:将列表中的书籍按价格升序排序,获取最低价格的两个元素。
public static void main(String[] args) {
List<Book> books = StreamUtils.getBooks();
books.stream().distinct().sorted(new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
return o1.getPrice() - o2.getPrice() > 0 ? 1 : -1;
}
}).limit(2).forEach(book -> System.out.println(book));
运行结果如下:
6.skip
意义:跳过元素中前N个元素,返回剩下元素。
案例:将列表中的书籍按价格升序排序,获取最低价格的两个元素,跳过第一个元素。
public static void main(String[] args) {
List<Book> books = StreamUtils.getBooks();
books.stream().distinct().sorted(new Comparator<Book>() {
@Override
public int compare(Book o1, Book o2) {
return o1.getPrice() - o2.getPrice() > 0 ? 1 : -1;
}
}).limit(2).skip(1).forEach(book -> System.out.println(book));
}
测试结果如下:
7.flatMap
意义:map只能把一个对象转换成另一个对象来作为流中的元素,而flatMap可以把一个对象转换成多个对象作为流中的元素。
案例:Book对象中有多个出版社Corp属性,需要对书籍列表中的所有厂商进行规整去重。
public static void main(String[] args) {
List<Book> books = StreamUtils.getBooks();
books.stream().flatMap((Function<Book, Stream<Corp>>)
book -> book.getCorps().stream()).
distinct().forEach(corp -> System.out.println(corp.getName()));
}
运行结果如下:
2.2.3 终结流操作
1.foreach
意义:对流中的元素进行遍历操作,通过传入的参数去指定对遍历的元素进行具体操作。
2.count
意义:统计当前流中元素的个数。
案例:统计书籍列表中厂商的个数(去重)。
public static void main(String[] args) {
List<Book> books = StreamUtils.getBooks();
long count = books.stream().flatMap((Function<Book, Stream<Corp>>)
book -> book.getCorps().stream()).
distinct().count();
System.out.println("total:" + count);
}
运行结果如下:
3.max&min
意义:获取流中指定元素的最大值、最小值。
案例:获取书籍中价格最高的书籍和最低的书籍价格。
Optional<Integer> max = books.stream().map(Book::getPrice).max((o1, o2) -> o1 - o2);
System.out.println(max.get());
Optional<Integer> min = books.stream().map(Book::getPrice).min((o1, o2) -> o1 - o2);
System.out.println(min.get());
运行结果如下:
4.collect
意义:将当前流转换成一个集合。
案例:将作家名称转换成一个集合。
List<String> collect = books.stream().map(Book::getAuthorName).distinct().collect(Collectors.toList());
System.out.println(collect);
运行结果如下:
案例:将作家名称转换成set集合。
Set collect = books.stream().map(Book::getAuthorName).collect(Collectors.toSet());
System.out.println(collect);
案例:将作家名称转换成mao集合。
Map<String, List<Corp>> maps = books.stream().distinct().collect(Collectors.toMap(Book::getAuthorName, Book::getCorps));
System.out.println(maps);
运行结果如下:
5.查找与匹配
1.anyMatch
意义:可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型,只要有一个记录符合,就返回true。
案例:判断流中书籍价格是否有大于100的记录
boolean b = books.stream().distinct().anyMatch(new Predicate<Book>() {
@Override
public boolean test(Book book) {
return book.getPrice() > 100;
}
});
System.out.println(b);
运行结果如下:
2.allMatch
意义:用来判断是否都符合匹配条件,都符合返回为true,否则返回false。
案例:判断所有作家的名字是否均超过两个字。
boolean b1 = books.stream().distinct().allMatch(new Predicate<Book>() {
@Override
public boolean test(Book book) {
return book.getAuthorName().length() >= 2;
}
});
System.out.println(b1);
运行结果如下:
3.noneMatch
意义:可以判断流中的元素是否都不符合匹配条件。如果都不符合则返回结果为true,否则结果为false。
案例:判断所有书籍价格是否均小于200。
boolean b2 = books.stream().distinct().noneMatch(new Predicate<Book>() {
@Override
public boolean test(Book book) {
return book.getPrice() >= 200;
}
});
System.out.println(b2);
}
运行结果如下:
4.findAny
意义:获取流中的任意一个元素。该方法无法保证获取的一定就是流中的第一个元素。
案例:获取任意一个名字大于两个字的作家。
Optional<Book> optionalBook = books.stream().distinct().filter(book -> book.getAuthorName().length() >= 2).findAny();
optionalBook.ifPresent(new Consumer<Book>() {
@Override
public void accept(Book book) {
System.out.println(book.getName());
}
});
运行结果如下:
5.findFirst
意义:获取流中的第一个元素。
6.reduce归并
意义:对流中的数据按照指定的计算方式计算出一个结果。reduce的作用是将stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和在初始化值得基础上进行计算,计算结果再和后面的元素计算。
案例:计算所有书籍总价格(重复不算)
Integer reduce = books.stream().distinct().map(Book::getPrice).reduce(0, (integer, integer2) -> integer + integer2);
System.out.println(reduce);
reduce使用方式比较灵活,max和min都是基于reduce方法实现的。
2.2.4 注意事项
1.惰性求值(如果没有终结操作,中间操作是不会得到执行的);
2.流是一次性的(一旦一个流对象经过一个终结操作后,这个流就不能再被使用);
3.不会影响原始数据(在流中可以对数据做很多处理,但是正常情况下是不会影响原来集合中的元素)。
2.3 Optional类
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。用于避免处理空指针异常(NullPointerException)。将可能为空值的变量放入Optional容器中,通过Optional容器访问对象。
2.3.1 Optional的ofNullable方法
一般使用Optional的静态ofNullable来把数据封装成一个Optional对象,无论传入参数是否为null都不会出现问题。
Book book = new Book();
Optional<Book> book1 = Optional.ofNullable(book);
2.3.2 安全获取期望值
如果使用Optional的get方法,如果传入的值为空,会抛出空指针异常,因此需要使用其它方法:orElseGet、orElseThrow。
Optional<Object> o = Optional.ofNullable(null);
System.out.println(o.get());
1.orElseGet
获取数据并设置数据为空时的默认值,如果数据不为空就能获取到该数据,如果为空则根据你传入的参数来创建对象作为默认返回。
Optional<Book> books = Optional.ofNullable(null);
Book book = books.orElseGet(() -> new Book("hello", "tom", 3, "remark", null));
System.out.println(book.getName());
运行结果如下:
2.orElseThrow
获取数据,如果数据不为空则获取到该数据,如果为空则根据你传入的参数来创建异常抛出。
Optional<Book> books = Optional.ofNullable(null);
Book book1 = books.orElseThrow(new Supplier<Throwable>() {
@Override
public Throwable get() {
return new Exception("数据不存在");
}
});
System.out.println(book1.getName());
运行结果如下:
2.3.3 数据过滤
可以使用filter方法对数据进行过滤,如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。
Optional<Book> books = Optional.ofNullable(new Book("hello", "tom", 3, "remark", null));
books.filter(book1 -> book1.getPrice() > 10).ifPresent(book1 -> System.out.println(book1.getName()));
运行结果如下:
2.3.4 判读数据是否存在
ifPresent方法可以判断是否为空,返回boolean类型。
2.3.5 数据转换
Optional提供了map对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了使用安全。
Optional<Book> books = Optional.ofNullable(new Book("hello", "tom", 3, "remark", null));
Optional<List<Corp>> corps = books.map(book -> book.getCorps());
corps.ifPresent(corps1 -> System.out.println(corps1));
2.4 常见函数式接口
2.4.1 概念
只有一个抽象方法的接口我们称之为函数式接口。JDK的函数式接口都加上了@FunctionalInterface注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
2.4.2 Consumer接口
根据其中抽象方法的参数列表和返回值类型,可以在方法中对传入的参数进行消费。
2.4.3 Function计算转换接口
根据其中抽象方法的参数列表和返回类型知道,可以在方法中对传入的参数计算或转换,把结果返回。
2.4.4 Predicate判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果。
2.4.5 Supplier生产型接口
根据其中抽象方法的参数列表和返回值类型,可以在方法中创建对象,然后将创建好的对象返回。
2.5 高级语法
2.5.1 基本数据类型优化
由于之前使用的很多Stream方法都使用了泛型,所以涉及到的参数和返回值都是引用数据类型。
即使操作是整数小数,但是实际用的都是他们的包装类。JDK5引入了自动拆箱和自动装箱使数据拆装箱更为灵活。包括mapToInt、 mapToLong、mapToDouble、flatMapToInt、flatMapToDouble等。
2.5.2 并行流
当流中有大量元素时,可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完成。如果使用Stream的话,只需要修改一个方法的调用就可以使用并行流来帮助实现。流中需要处理大量的元素时,才会使用并行流操作。大数据量的判断标准可以参考文献1。
List<Book> books =new ArrayList<>();
books.add(Book.builder().authorName("hello").name("周国破").build());
books.add(Book.builder().authorName("hello").name("北风").build());
books.parallelStream().filter(book -> book.getName().length() > 2).forEach(book -> System.out.println(book.getAuthorName()+book.getName()));
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Stream<Integer> integerStream = Arrays.stream(arr);
Optional<Integer> reduce = integerStream.parallel().peek(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(Thread.currentThread().getName() + ":" + integer);
}
}).filter(num -> num > 5).reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
});
System.out.println("result:"+reduce.get());
运行结果如下:
3.小结
1.JAVA函数式编程能够有效减少冗余代码的编写,简化代码;
2.本文案例采用了多种方式编写,包括简化的lambda表达式和实现函数接口式,主要是方便大家更好地理解内部实现;
3.并行流编程需要根据场景数据量来考虑。
4.参考文献
1.https://www.jianshu.com/p/76d91f940055
2.https://www.bilibili.com/video/BV1Gh41187uR
3.https://juejin.cn/post/7056354222689746974
5.附录
1.https://gitee.com/Marinc/springboot-demos/tree/master/stream