Java8中Lambda表达式的理解和运用

目录:

一、简介

二、函数接口

三、Lambda表达式

四、目标类型

五、流

六、function包

七、对并发的影响

正文

一、简介

1、java中lambda的由来

开发类库的程序员使用java时,发现抽象级别还不够,尤其是面对大型数据集合时,java还欠缺高效的并行操作。为了编写这类处理批量数据的并行类库,就需要在为java增加lambda表达式。

再如,当我们定义一个线程类时,可以将该类实现Runnable接口(该接口只有一个方法run()),并实现run方法即可。但大多数情况下我们可能并不这么做,因为该线程可能只会被使用一次,此时我们一般会使用匿名内部类将线程的行为进行内联,代码举例如下:

a9befb15b030adf67df06e05bfd22b24498.jpg

但是匿名内部类存在缺陷:

  1. 语法过于冗余
  2. 匿名内部类中的this和变量名容易使人产生误解
  3. 无法捕获非final的局部变量

等等,由于上述缺陷,需要借助函数式编程得以解决。

二、函数式接口

我们对面向对象编程并不陌生,面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。现实世界中,数据和行为是并存的。

那么什么是函数式编程呢?其核心思想是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。

函数式接口定义:把只有一个抽象方法的接口称为函数式接口,可用做lambda表达式的类型。java中的函数式接口如Runnable、Comparator、Callable等。

如何自定义一个函数式接口?其实我们并需要额外的工作来声明一个接口是函数式接口,编译器会根据接口的结构自行判断,当然并非简单对接口中的方法进行计数。另外,java api提供了@FunctionalInterface注解来显示声明一个接口为函数式接口,加上该注解后,编译器就会验证该接口是否满足函数式接口的要求。

java8中增加了一个新的package:java.util.function,里面包含了常用的函数式接口。

三、Lambda表达式

lambda表达式又被称为闭包或匿名方法。

下面对lambda进行一些简单举例:

  1. (int x, int y) -> x + y    表示:接收整形参数x和y,并返回他们的和
  2. () -> 66                        表示:没有参数传入,直接返回数字66
  3. (String s) -> { System.out.println(s); }    表示:接收一个字符串型的参数,并将它输出到控制台,不返回值

由此可见,lambda表达式由参数列表、箭头符号(->)和函数体组成,其中函数体既可以是一个表达式,也可以是一个语句块。表达式函数体适用于小型lambda表达式,它省略了return关键字,使语法更简洁。

在使用lambda表达式时,可以显示声明参数的类型,如:(int x, int y) -> x + y,其实也可以省略参数类型,让编译器自己去推导出来,如:(x, y) -> x + y。

四、目标类型

1、目标类型

编译器负责推导lambda表达式的类型,它利用lambda表达式所在上下文所期待的类型进行推导,这个被期待的类型就是目标类型。lambda只能出现在目标类型为函数接口的上下文中。

lambda表达式对目标类型也是有要求的,当下面所有条件都成立时,lambda表达式才会赋给目标类型T。

  • T是一个函数式接口
  • lambda表达式的参数和T的方法参数在数量和类型上一一对应
  • lambda表达式的返回值和T的方法返回值相兼容
  • lambda表达式内所抛出的异常和T方法的throws类型相兼容

2、目标类型的上下文包括:

  • 变量声明:Callable<Integer> callable;
  • 赋值       :  callable = () -> "hello";
  • 返回语句 : public Runnable runner() { return () -> { System.out.println("hello"); }; }
  • 数组初始化器 : FileFilter[] fileFilters = new FileFilter[] { f -> f.exists(), f -> f.canRead(), f -> f.getName().startsWith("q") };
  • 方法或构造方法的参数 :
    方法或构造方法参数对目标类型的确认会涉及到其它两个语言特性:重载解析和参数类型推导。如果lambda具有显示类型(参数类型被显示指定),编译器就可以直接使用lambda表达式的返回类型;如果lambda表达式具有隐式类型(参数类型被推导而知),重载解析则会忽略lambda表达式函数体而只依赖lambda表达式参数的数量。如果在解析方法声明时存在二义性,我们就需要利用转型或显示提供lambda表达式类型。举例:
    f63e6a95250368d4ae02e42b018a0116818.jpg
  • lambda表达式函数体  : Supplier<Runnable> supplier = () -> () -> { System.out.println("hi"); };
  • 条件表达式(? :): Callable<Integer> c = true ? (() -> 23) : (() -> 42);
  • 转型表达式(cast): Runnable o = (Runnable) () -> { System.out.println("hi"); };

3、词法作用域

lambda引用的是值,而不是变量。在匿名内部类中引用它所在方法的变量时,该变量必须声明为final,虽然Java8中放宽了此限制,可以引用非final变量,但是该变量在既成事实上必须是final的,即不能再匿名内部类中改变它所在方法的变量值,否则编译器会报错。既成事实上的final是指只能给该变量赋值一次,换句话说,Lambda引用的是值,而不是变量。举例如下:

45bfa50071874886cedecd09c2c501df47a.jpg       086b905241706215eb7c68b7e42ecc88e03.jpg

上面两个例子中,只要在内部类或lambda表达式中修改其所在方法中的变量都会报错。lambda表达式不支持修改外部变量的另一个原因是:我们可以使用更好的方式实现相同的效果:使用规约。java.util.function包中提供了各种规约,如sum、min、max等。

在内部类中定义和外部类中相同的变量时,其内部类中定义的变量会覆盖外部类中定义的变量。而lambda表达式基于词法作用域,在lambda函数体里面不允许定义和外面相同的变量,如果定义则会报错。举例:

b9ee50702c1249c9a9cb2d26de4229ee7bd.jpg

因此,可以说:lambda表达式对值封闭,对变量开放。

五、流

1、概念

外部迭代:通过Iterator的方式进行迭代的过程称为外部迭代。

内部迭代:通过stream的方式进行迭代的过程称为内部迭代。

流 - Stream:是用函数式编程的方式在集合类上进行复杂操作的工具。java8中为集合类和数组都提供了转为Stream的方法,由集合或数组生成的Stream不是一个新集合,而是创建新集合的配方。

惰性求值方法:只描述或刻画Stream,最终不产生新集合的方法叫惰性求值方法,如Stream的filter方法。

及早求值方法:最终会生成新集合的方法叫及早求值方法,如Stream的count方法。

说明:判断一个方法是惰性求值还是及早求值很简单:只需看它的返回值,如果返回值是Stream,那么就是惰性求值方法,如果返回的是另一个值或空,就是及早求值方法。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果。

2、常用的流操作

2.1 collect(Collector<? super T,A,R>)

作用:该方法由Stream里的值生成一个列表,是一个及早求值方法。

举例:

List<String> list = Stream.of("a", "1", "b", "2").collect(Collectors.toList());

2.2 Stream<R> map(Function<? super T, ? super R>)

作用:将一个流中的值转换成一个新流,是一个惰性求值方法。

举例:

Stream<String> stream = Stream.of("a","b","c").map(one -> one.toUpperCase());

2.3 Stream<T> filter(Predicate<? super T>)

作用:遍历流中的数据并根据条件进行过滤,形成一个新流,是一个惰性求值方法。

举例:

Stream<String> stream1 = Stream.of("abc","bcd","def").filter(one -> one.contains("bc"));

2.4 Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>>)

作用:当流中包含多个列表时,可将这多个列表中的内容打平放到一个列表下的流中,是一个惰性求值方法。

举例:

Stream<String> stream2 = Stream.of(Arrays.asList("a","b","c"), Arrays.asList("abc","bcd","def")).flatMap(one -> one.stream());

2.5 Optional<T> max(Comparator<? super T>)和Optional<T> min(Comparator<? super T>)

作用:获取一个集合中的最大值和最小值,是两个及早求值方法

举例:

Integer max = Stream.of(1,2,3).max(Comparator.comparing(one->one)).get();
Integer min = Stream.of(1,2,3).min(Comparator.comparing(one->one)).get();

2.6 long count()

作用:返回Stream中元素的数量,是一个及早求值方法

举例:

Long count = Stream.of(1,2,3).count();

2.7 Optional<T> reduce(BinaryOperator<T>)

作用:从stream的一组值中生成1个值,count、max、min都属于reduce操作,是一个及早求值方法。

举例:第1个参数one是上次函数计算的返回值,其初始值是stream中第1个值;第2个参数two是stream中的元素值,其初始值是stream中第2个值,返回类型是Optional,通过get方法获取其值

Integer count = Stream.of(1,2,3,4,5,6).reduce((one, two) -> { System.out.println(one + ":"+two);return two; }).get();

2.8 T reduce(T, BinaryOperator<T>)

举例:该方法比上一个方法多了一个初始值100,one仍然代表上次函数计算的返回值,其初始值为就是设定的初始值100;two仍然是stream中的元素值,其初始值是stream中的第1个值。由于该方法指定了初始值,因此该方法的返回值类型就是设定的初始值类型,即Integer。

Integer count1 = Stream.of(1,2,3,4,5,6).reduce(100, (one, two) -> { System.out.println(one + ":"+two);return two; });

2.9 Stream<T> distinct()

作用:返回由该流的不同元素组成的一个新流,是一个惰性求值方法

举例:

Stream.of(3,2,5,3,4,3).distinct().peek(one -> System.out.println(one)).count();

2.10 Stream<T> limit(int maxSize)

作用:返回前maxSize个元素组成的新流,是一个惰性求值方法

举例:

System.out.println(Stream.of(1,2,3,4,5,6).limit(3).filter(one -> {System.out.println(one);return true;}).count());

2.11 Stream<T> peek(Consumer<? super T>)

作用:返回一个由该流的元素组成的新流,并且对新流中每个元素执行指定的操作

举例:

Stream stream = Stream.of(1,2,3,4,5,6).peek(one -> System.out.println(one));

2.12 Stream<T> skip(long n)

作用:从流的第1个元素开始,跳过n个元素,把从第n+1个元素开始到最后所有的元素形成一个新流

举例:

Stream.of(1,2,3,4,5,6).skip(3).peek(one -> System.out.println(one)).count();

2.13 Stream<T> sorted()

作用:将流中的元素按照自然顺序进行排序,是一个惰性求值方法

举例:

Stream.of(6,2,5,3,4,1).peek(one-> System.out.println(one)).sorted().peek(one-> System.out.println(one)).count();

2.14 boolean allMatch(Predicate<? super T>)

作用:返回流中的所有元素是否都与条件匹配

举例:

System.out.println(Stream.of(6,2,5,3,4,1).allMatch(one -> one>3));

2.15 boolean anyMatch(Predicate<? super T>)

作用:返回流中是否存在与条件匹配的元素

举例:

System.out.println(Stream.of(6,2,5,3,4,1).anyMatch(one -> one>3));

2.16 boolean noneMatch(Predicate<? super T>)

作用:返回流中所有元素是否都不与条件匹配

举例:

System.out.println(Stream.of(6,2,5,3,4,1).noneMatch(one -> one>3));

2.17 findAny()

作用:返回描述流的一些元素的Optional对象,如果流为空,则返回一个空Optional

举例:

System.out.println(Stream.of(3,2,5,6,4,1).findAny().get());

2.18 findFirst()

作用:返回描述流的第一个元素的Optional对象,如果流为空,则返回一个空Optional

举例:

System.out.println(Stream.of(3,2,5,6,4,1).findAny().get());

2.19 void forEach(Consumer<? super T>)

作用:对流中每个元素循环进行操作

举例:

Stream.of(3,2,5,6,4,1).forEach(one -> System.out.println(one));

2.20 Object[] toArray()

作用:返回一个包含流中元素的数组

举例:

Integer[] values = (Integer[])Stream.of(3,2,5,6,4,1).toArray();

六、function包

1、Predicate<T>

抽象方法为boolean test(T),传入T类型的参数,返回一个boolean类型值。可用于流中数值判断。

举例:

Predicate<String> predicate = one -> one.length() > 0;

2、Consumer<T>

抽象方法为void accept(T),传入T类型的参数,不返回值。可用于实现消费者

举例:

Consumer<Integer> consumer = one -> System.out.println(one);

3、Supplier<T>

抽象方法为String[] value(),无参数,返回一个字符串数组。可用于实现生产者

举例:

Supplier<Integer> supplier = () -> new Integer(100);

4、Function<T, R>

抽象方法为R apply(T),传入参数T,返回结果R。

举例:

Function<String, Integer> function = one -> one.length();

5、UnaryOperator<T>

抽象方法T apply(T),接收T对象,返回T对象。

举例:

UnaryOperator<String> unaryOperator = one -> "hello:" + one;

6、BinaryOperator<T>

抽象方法为T apply(T, T),接收2个T对象,返回T对象。

举例:

BinaryOperator<Integer> binaryOperator = (one, two) -> one + two;

七、对并发的影响

待续

转载于:https://my.oschina.net/u/3272058/blog/1925359

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值