概述
目录:
一、简介
二、函数接口
三、Lambda表达式
四、目标类型
五、流
六、function包
七、对并发的影响
正文
一、简介
1、java中lambda的由来
开发类库的程序员使用java时,发现抽象级别还不够,尤其是面对大型数据集合时,java还欠缺高效的并行操作。为了编写这类处理批量数据的并行类库,就需要在为java增加lambda表达式。
再如,当我们定义一个线程类时,可以将该类实现Runnable接口(该接口只有一个方法run()),并实现run方法即可。但大多数情况下我们可能并不这么做,因为该线程可能只会被使用一次,此时我们一般会使用匿名内部类将线程的行为进行内联,代码举例如下:
但是匿名内部类存在缺陷:
- 语法过于冗余
- 匿名内部类中的this和变量名容易使人产生误解
- 无法捕获非final的局部变量
等等,由于上述缺陷,需要借助函数式编程得以解决。
二、函数式接口
我们对面向对象编程并不陌生,面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。现实世界中,数据和行为是并存的。
那么什么是函数式编程呢?其核心思想是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
函数式接口定义:把只有一个抽象方法的接口称为函数式接口,可用做lambda表达式的类型。java中的函数式接口如Runnable、Comparator、Callable等。
如何自定义一个函数式接口?其实我们并需要额外的工作来声明一个接口是函数式接口,编译器会根据接口的结构自行判断,当然并非简单对接口中的方法进行计数。另外,java api提供了@FunctionalInterface注解来显示声明一个接口为函数式接口,加上该注解后,编译器就会验证该接口是否满足函数式接口的要求。
java8中增加了一个新的package:java.util.function,里面包含了常用的函数式接口。
三、Lambda表达式
lambda表达式又被称为闭包或匿名方法。
下面对lambda进行一些简单举例:
- (int x, int y) -> x + y 表示:接收整形参数x和y,并返回他们的和
- () -> 66 表示:没有参数传入,直接返回数字66
- (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表达式类型。举例: - 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引用的是值,而不是变量。举例如下:
上面两个例子中,只要在内部类或lambda表达式中修改其所在方法中的变量都会报错。lambda表达式不支持修改外部变量的另一个原因是:我们可以使用更好的方式实现相同的效果:使用规约。java.util.function包中提供了各种规约,如sum、min、max等。
在内部类中定义和外部类中相同的变量时,其内部类中定义的变量会覆盖外部类中定义的变量。而lambda表达式基于词法作用域,在lambda函数体里面不允许定义和外面相同的变量,如果定义则会报错。举例:
因此,可以说: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
最后
以上就是难过抽屉为你收集整理的Java8中Lambda表达式的理解和运用的全部内容,希望文章能够帮你解决Java8中Lambda表达式的理解和运用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复