十五、函数式编程
1、Lambda表达式
(1)、概述
Lambda表达式由参数列表、箭头和Lambda主体组成。
例:
①、(parameters) -> expression
②、 (parameters) -> { statements; }
a、参数 + 箭头符号 + 执行体
(2)、特点
①、可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
②、可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
③、可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
④、可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
⑤、注意事项
a、在Lambda表达式中this是指外围实例,而匿名类中的this是指匿名类实例。Java默认会把当前实例传入非静态方法,参数名为this,位置在第一个。
b、如果想在Lambda表达式里面修改外部变量的值也是可以的,可以将变量定义为非局部变量,即为实例变量或者将变量定义为数组。
c、Lambda表达式如果引用某局部变量,则直接将其视为final。
(3)、示例
public static void main(String args[]){
Test tester = new Test();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
// 运算结果
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message -> System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) -> System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
2、函数式接口
(1)、概述
①、《Java语言规范》中定义函数接口是一种只有一个抽象方法(除Object中的方法)的接口,因此代表一种单一函数契约。函数接口的抽象方法可以是从超级接口继承而来,但继承而来的方法应该是覆写等效的,这种情况,在逻辑上,代表一个方法。如果接口声明的抽象方法覆写Object类的公共方法,那这方法不算作接口的抽象方法。
②、创建函数接口实例,除了以声明和实例化类的形式这种常规过程之外,还可以使用方法引用表达式和lambda表达式创建函数接口的实例。
③、声明函数接口时,除了要声明一个抽象方法,还可以声明覆写Object类中的public方法以及default方法。
④、覆写等效释义
C接口继承接口A,B,但继承而来的fun方法是覆写等效,因此C接口也属于函数接口
// 定义A接口
interface A {
int fun(int x, int y);
}
// 定义B接口
interface B {
int fun(int x, int y);
}
// C接口继承A,B,但继承而来的fun方法是覆写等效,因此C接口也属于函数接口
interface C extends A, B{
}
(2)、@FunctionalInterface注解
@FunctionalInterface注解标记的类型表明这是一个函数接口,该注解只能应用于接口类型,并且只允许有一个抽象方法,否则就会编译出错。主要是帮助程序员避免一些低级错误,例如在接口中再增加抽象方法。
(3)、分类
java.util.function它包含了很多类,用来支持Java的函数式编程。
①、消费型接口:Consumer<T>
代表接受一个输入参数并且无返回的操作。此函数式接口在foreach循环中用到过。
②、供给型接口:Supplier<T>
无参数,返回一个结果。
③、断言型接口:Predicate<T>
接受一个输入参数,返回一个布尔值结果。
④、函数型接口:Function<T,R>
接受一个输入参数,返回一个结果。
⑤、级联表达式和柯里化
柯里化是把多个参数的函数转换为只有一个参数的函数,目的是函数标准化。
public class Test {
public static void main(String args[]){
// 函数型接口
Function<String,String> function = (str)->{
str += " function";
return str;
};
Test.functionTest(function);
// 消费型接口
Consumer<String> consumer = (str)->{
str += " consumer";
System.out.println(str);
};
Test.consumerTest(consumer);
// 供给型接口
Supplier supplier = ()->{
String str = "test supplier";
return str;
};
Test.supplierTest(supplier);
// 断言型接口
Predicate<String> predicate = (str)->{
boolean check = StringUtils.isBlank(str);
return check;
};
Test.predicateTest(predicate);
// 级联表达式实现
Function<Integer, Function<Integer, Integer>> fun1 = a -> b -> a + b;
System.out.println(fun1.apply(3).apply(5));
Function<Integer, Function<Integer, Function<Integer, Integer>>> fun2 = a -> b -> c -> a + b + c;
System.out.println(fun2.apply(2).apply(4).apply(3));
}
// 函数型接口测试打印:test function
public static void functionTest(Function<String,String> function){
System.out.println(function.apply("test"));
}
// 消费型接口测试打印:test consumer
public static void consumerTest(Consumer<String> consumer){
consumer.accept("test");
}
// 供给型接口测试打印:
public static void supplierTest(Supplier supplier){
String str = (String) supplier.get();
System.out.println(str);
}
// 断言型接口测试打印:test predicate:false
public static void predicateTest(Predicate<String> predicate){
boolean check = predicate.test("predicate");
System.out.println("test predicate:" + check);
}
}
| 分类 | 接口 | 描述 |
| Consumer | Consumer<T> | 接收一个输入参数,无返回值 |
| BiConsumer<T,U> | 接收两个输入参数,无返回值 | |
| DoubleConsumer | 接收一个double类型的输入参数,无返回值 | |
| IntConsumer | 接收一个int类型的输入参数,无返回值 | |
| LongConsumer | 接收一个long类型的输入参数,无返回值 | |
| ObjDoubleConsumer<T> | 接收一个object类型和一个double类型的输入参数,无返回值 | |
| ObjIntConsumer<T> | 接收一个object类型和一个int类型的输入参数,无返回值 | |
| ObjLongConsumer<T> | 接收一个object类型和一个long类型的输入参数,无返回值 | |
| Supplier | Supplier<T> | 无参数,返回一个结果 |
| BooleanSupplier | 无参数,返回一个boolean类型结果 | |
| DoubleSupplier | 无参数,返回一个double类型结果 | |
| IntSupplier | 无参数,返回一个int类型结果 | |
| LongSupplier | 无参数,返回一个结果long类型结果 | |
| Predicate | Predicate<T> | 接收一个输入参数,返回一个boolean类型结果 |
| BiPredicate<T,U> | 接收两个输入参数,返回一个boolean类型结果 | |
| DoublePredicate | 接收一个double类型的输入参数,返回一个boolean类型结果 | |
| IntPredicate | 接收一个int类型的输入参数,返回一个boolean类型结果 | |
| LongPredicate | 接收一个long类型的输入参数,返回一个boolean类型结果 | |
| Function | Function<T,R> | 接收一个输入参数,返回一个结果 |
| DoubleFunction<R> | 接收一个double类型的输入参数,并且返回一个结果 | |
| IntFunction<R> | 接收一个int类型的输入参数,返回一个结果 | |
| LongFunction<R> | 接收一个long类型的输入参数,返回一个结果 | |
| ToDoubleFunction<T> | 接收一个输入参数,返回一个double类型结果 | |
| ToIntFunction<T> | 接收一个输入参数,返回一个int类型结果 | |
| ToLongFunction<T> | 接收一个输入参数,返回一个long类型结果 | |
| DoubleToIntFunction | 接收一个double类型的输入参数,返回一个int类型结果 | |
| DoubleToLongFunction | 接收一个double类型的输入参数,返回一个long类型结果 | |
| IntToDoubleFunction | 接收一个int类型的输入参数,返回一个double类型结果 | |
| IntToLongFunction | 接收一个int类型的输入参数,返回一个long类型结果 | |
| LongToDoubleFunction | 接收一个long类型的输入参数,返回一个double类型结果 | |
| LongToIntFunction | 接收一个long类型的输入参数,返回一个int类型结果 | |
| ToDoubleBiFunction<T,U> | 接收两个输入参数,返回一个double类型结果 | |
| BiFunction<T,U,R> | 接收两个输入参数,并且返回一个结果 | |
| ToIntBiFunction<T,U> | 接收两个输入参数,返回一个int类型结果 | |
| ToLongBiFunction<T,U> | 接收两个输入参数,返回一个long类型结果 | |
| UnaryOperator | UnaryOperator<T> | 接收一个同类型的输入参数,返回一个同类型结果 |
| DoubleUnaryOperator | 接收一个double类型的输入参数,返回一个double类型结果 | |
| IntUnaryOperator | 接收一个int类型的输入参数,返回一个int类型结果 | |
| LongUnaryOperator | 接收一个long类型的输入参数,返回一个long类型结果 | |
| BinaryOperator<T> | 接收两个同类型的输入参数,返回一个同类型结果 | |
| DoubleBinaryOperator | 接收两个double类型的输入参数,返回一个double类型结果 | |
| IntBinaryOperator | 接收两个int类型的输入参数,返回一个int类型结果 | |
| LongBinaryOperator | 接收两个long类型的输入参数,返回一个long类型结果 |
3、Stream
(1)、概述
Stream流是从支持数据处理操作的源生成的元素序列,源可以是数组、文件、集合、函数。Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。
①、Stream是java.util包下的与java.io包里的InputStream和OutputStream是完全不同的概念。
②、Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
③、而和迭代器又不同的是,Stream可以并行化操作,迭代器只能命令式地、串行化操作。Stream的并行操作依赖于Java7中引入的Fork/Join框架(JSR166y)来拆分任务和加速处理过程。
a、当使用串行方式去遍历时,每个item读完后再读下一个item。
b、而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
④、Stream 的另外一大特点是,数据源本身可以是无限的。
(2)、流的构成
当我们使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换 → 执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
(3)、生成方式
①、利用Stream接口静态方法创建:Stream<Integer> stream1 = Stream.of(1,2,3,4,5);
②、从Collection和数组获得,集合接口有stream()和parallelStream()两个方法来生成流。
a、集合创建串行流:Collection.stream()
b、集合创建并行流:Collection.parallelStream()
c、数组:Arrays.stream(T array) or Stream.of()
③、从BufferedReader获得:java.io.BufferedReader.lines()
④、静态工厂
a、java.util.stream.IntStream.range()
b、java.nio.file.Files.walk()
⑤、自定义:java.util.Spliterator
⑥、其他
a、Random.ints()
b、BitSet.stream()
c、Pattern.splitAsStream(java.lang.CharSequence)
d、JarFile.stream()
(4)、操作类型
①、Intermediate(中间操作符)
一个流可以后面跟随零个或多个intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
a、filter:通过设置的条件过滤出元素。
Integer[] sixNums = {1, 2, 3, 4, 5, 6};
Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
Arrays.stream(evens).forEach(System.out::println);
// 打印偶数evens结果:2 4 6
b、map:map接收的参数类型为Function,这个函数会被应用到每个元素上,每个元素都按照这个规则转换成为另外一个元素。
List<String> strings = Arrays.asList("abc", "ssaad", "bc", "efg", "abcd", "jkl");
List<String> mapped = strings.stream().filter(str -> str.length() > 4).map(String::toUpperCase).collect(Collectors.toList());
// 打印mapped结果:SSAAD
c、distinct:去重,筛选出不重复的单词。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> numList = numbers.stream().distinct().collect(Collectors.toList());
// 打印numList结果:3 2 7 5
d、sorted:排序,它比数组排序的优势在于可以首先对Stream进行各类map、filter、limit、skip甚至distinct来减少元素数量后再排序,能明显缩短执行时间。
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> numList = numbers.stream().distinct().sorted().collect(Collectors.toList());
// 打印numList结果:2 3 5 7
e、peek:对元素进行遍历,并执行相应处理,可以对一个Stream进行多次Intermediate运算。forEach也是terminal操作,无法对一个Stream进行多次terminal运算。peek接收的参数类型为Consumer,Consumer接收单个输入参数T但不返回结果的操作。而map接收的参数类型为Function,Function接收一个参数并产生结果的函数,也就是有返回值的操作。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
List<String> peeked = strings.stream()
.filter(str -> str.length() > 4)
.peek(str -> System.out.println("666:" + str))
.map(String::toUpperCase)
.peek(str -> System.out.println("777:" + str))
.collect(Collectors.toList());
// 打印peeked结果:
// 666:ssaad
// 777:SSAAD
f、skip:返回一个扔掉了前n个元素的Stream。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
List<String> skiped = strings.stream().skip(3).collect(Collectors.toList());
// 打印skiped结果:jkl
g、limit:返回Stream的前面n个元素。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
List<String> limited = strings.stream().limit(1).collect(Collectors.toList());
// 打印limited结果:abc
h、flatMap:可以解决一对多映射关系,flatMap把Stream中的层级结构扁平化。map生成的是个1:1映射,每个输入元素都按照规则转换成为另外一个元素。
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1,4),
Arrays.asList(2, 3),
Arrays.asList(0, 5)
);
Stream<Integer> outputStream = inputStream
.flatMap((childList) -> childList.stream().filter( i -> i > 2))
.sorted();
// 打印outputStream结果:3 4 5
②、Terminal(终端操作符)
一个流只能有一个terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果。在对一个Stream进行多次转换操作Intermediate操作,每次都对Stream的每个元素进行转换,转换操作都是lazy的,多个转换操作只会在Terminal操作的时候融合起来,一次循环完成。
a、collect:收集器,将流转换为其他形式,例如将流转换成集合和聚合元素。
Map<String, Student> studentMap = studentList.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (k1, k2) -> k1));
// (k1, k2) -> k1 如果k1和k2的key值相同,选择k1作为那个key对应的value值。
b、forEach:forEach不能修改自己包含的本地变量值,也不能用break/return之类的关键字提前结束循环。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
strings.stream().forEach(System.out::println);
// 循环打印
c、count:返回流中元素总数。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
long n = strings.stream().count();
// 打印n结果:4
d、sum:对指定元素求和。
int sum = studentList.stream().mapToInt(Student::getAge).sum();
e、max:返回最大值。
int max = userList.stream().max(Comparator.comparingInt(User::getId)).get().getId();
f、min:返回最小值。
int min = userList.stream().min(Comparator.comparingInt(User::getId)).get().getId();
g、anyMatch:Stream中只要有一个元素符合传入的条件,返回true。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
boolean match = strings.stream().anyMatch(s -> s == "abc”);
// 打印match结果:true
h、allMatch:Stream中全部元素符合传入的条件,返回true。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
boolean match = strings.stream().anyMatch(s -> s == "abc”);
// 打印match结果:false
i、noneMatch:Stream中没有一个元素符合传入的条件,返回true。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
boolean match = strings.stream().noneMatch(s -> s == "abc”);
// 打印match结果:false
j、findFirst:返回Stream的第一个元素或者空,返回值类型Optional。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
Optional<String> str = strings.stream().findFirst();
// 打印str结果:Optional[abc]
k、findAny:将返回当前流中的任意元素,返回值类型Optional。
List<String> strings = Arrays.asList("abc", "ssaad", "abcd", "jkl");
Optional<String> str = strings.stream().findAny();
// 打印str结果:Optional[jkl]
③、Short-circuiting 操作
对于一个intermediate操作,如果它接受的是一个无限大(infinite/unbounded)的Stream,但返回一个有限的新Stream。对于一个terminal操作,如果它接受的是一个无限大的Stream,但能在有限的时间计算出结果。当操作一个无限大的Stream,而又希望在有限时间内完成操作,则在管道内拥有一个short-circuiting操作是必要非充分条件。
a、anyMatch
b、allMatch
c、noneMatch
d、findFirst
e、findAny
f、limit
4、Optional
(1)、概述
Optional 类(java.util.Optional) 是一个容器类,它可以保存类型T的值,其值可能是null或者不是null。在Java 8之前一般某个函数表示一个值不存在可能返回了null,而在Java 8以后,不推荐返回null而是返回Optional。Optional可以更好的表达这个概念,并且可以避免空指针异常。
(2)、方法
①、构建类方法
a、Optional.of(T t)
创建一个 Optional 实例,t必须非空。
b、Optional.empty()
创建一个空的 Optional 实例。
c、Optional.ofNullable(T t)
创建一个 Optional 实例,t可以为null,为null时返回Optional.empty()。
②、获取类方法
a、T get()
如果调用对象有值,返回该值,否则抛NPE异常。
b、T orElse(T other)
如果有值则将其返回,否则返回指定的other对象。
c、T orElseGet(Supplier<? extends T> other)
如果有值则将其返回,否则返回由Supplier接口实现提供的对象。
d、T orElseThrow(Supplier<? extends X> exceptionSupplier)
如果有值则将其返回,否则抛出由Supplier接口实现提供的异常。
③、判断类方法
a、boolean isPresent()
判断值对象是否存在,存在返回true。
b、void ifPresent(Consumer<? super T> consumer)
如果有值,就执行Consumer接口的实现代码,并且该值会作为参数传给它。
④、过滤类方法
a、Optional<T> filter(Predicate<? super <T> predicate)
如果值存在,并且这个值匹配给定的predicate,返回一个Optional用以描述这个值,否则返回一个空的Optional。
⑤、映射类方法
a、<U>Optional<U> map(Function<? super T,? extends U> mapper)
如果有值,则对其执行调用映射函数得到返回值。如果返回值不为null,则创建包含映射返回值的Optional作为map方法返回值,否则返回空Optional。
b、<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
如果值存在,就对该值执行提供的mapping函数调用,返回一个Optional类型的值,否则就返回一个空的Optional对象。
(3)、应用
①、场景一
public static void main(String[] args) {
// 查询用户信息,不为空把pin赋值为priceInfo对象
// 未使用Optional的if判断
UserInfo userInfo = userService.getUserInfoById(user.getId());
if (null != userInfo) {
priceInfo.setPin(userInfo.getPin());
}
// 使用Optional
Optional.ofNullable(userInfo).ifPresent(p -> priceInfo.setPin(p.getPin()));
}
②、场景二
public static void main(String[] args) {
// 商品信息查询为空或者商品状态为空,抛出下发简易模板的异常
// 未使用Optional
Product product = productService.getProductInfoById(skuId);
if (product == null || isEmpty(product.getWareStatus())) {
throw new SimpleTemplateException();
}
// 使用Optional
Optional.ofNullable(product).filter(s -> !isEmpty(product.getWareStatus())).orElseThrow(() -> new SimpleTemplateException());
}
public static boolean isEmpty(CharSequence str) {
return str == null || str.length() == 0;
}
③、场景三
public static void main(String[] args) {
// 判读传进来的多层对象是否为空
// A对象设置B对象,B对象设置C对象
// 未使用Optional
if (a != null) {
B b = a.getB();
if (b != null) {
C c = b.getC();
if (c != null) {
return c.getName();
}
}
}
throw new IllegalArgumentException("The value of param comp isn't available.");
// 使用Optional
Optional.ofNullable(a)
.map(A::getB) // 相当于p -> p.getB(),下同
.map(B::getC)
.map(C::getName)
.orElseThrow(()->new IllegalArgumentException("The value of param comp isn't available."));
}
④、场景四
public static void main(String[] args){
// 类型之间的转换,并且当没有值的时候返回一个默认值
int timeout = Optional.ofNullable(redisProperties.getTimeout())
.map(x -> Long.valueOf(x.toMillis()).intValue())
.orElse(10000);
}
本文介绍Java函数式编程,涵盖Lambda表达式、函数式接口、Stream流和Optional类。Lambda表达式有多种特点和注意事项;函数式接口有特定定义和分类;Stream流可进行并行操作,有多种生成方式和操作类型;Optional类能避免空指针异常,有构建、获取、判断等方法。

被折叠的 条评论
为什么被折叠?



