【java8】Lambda表达式简单介绍
开始
上大学的时候就知道java8,lambda表达式。记得有段时间在写匿名内部类的时候,编译器会提示可以使用lambda表达式进行优化。虽然对Java8有一丢丢了解,但是工作至今还没有真正的使用过java8的东西。于是现在读了一下《java8实战》学习了一下,现在先简单总结一下lambda表达式。
了解Lambda表达式
可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但他有参数列表、函数主题、返回类型。使用Lambda可以让代码变得更简洁、更灵活。在以前使用匿名内部类的地方可以使用Lambda来进行优化处理。
Lambda的基本语法是:
(parameters) -> expression
或者是
(parameters) -> { expression; }
以下是五个Lambda表达式的示例:
示例1:有一个String类型的参数,并返回一个int。表达式没有retuern语句,因为已经隐含了return。
(String s) -> s.length()
示例2:有个Apple类型的参数,并返回一个boolean。
(Apple a) -> a.getWeight() > 150;
示例3:有两个int类型的参数,没有返回值。大括号包围可以写多行。
(int x, int y) -> {
System.out.println("Result:");
System.out.println(x + y);
}
示例4:没有参数,有一个int的返回值。
() -> 42
示例5:有两个Apple的参数,有一个boolean返回值。
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())
使用场景以及如何使用Lambda表达式
首先给一个完整的Lambda的使用示例。
筛选函数:
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T e : list) {
if (p.test(e)) {
result.add(e);
}
}
return result;
}
调用代码:
List<Apple> redApples = filter(inventory, (Apple apple) -> "red".equals(apple.getColor()));
调用代码的第二个参数传入了一段lambda表达式。需要注意的是第二个参数的类型是Predicate<t style="margin: 0px; padding: 0px;">,这是一个函数式接口。现在就回答小节题目中的一个问题了,可以在函数式接口上使用Lambda表达式。</t>
函数式接口
函数式接口是只定义一个抽象方法的接口。 Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。(简单说来,Lambda表达式是函数式接口的实例)。用匿名内部类也能完成同样的事情,但是会笨重点,这也是Lambda可以做优化的一个点。
像我们平常用Runnable复合函数式接口的定义,所以也是一个函数式接口。为了使用方便Java也提供了一些常用的函数式接口,大家也可以自己定义。关于函数式接口后面还会详细介绍。
函数描述符
函数描述符要表达的是一个抽象方法接受什么样的参数,有什么样的返回值的一个概念。例如Runnable接口函数描述符是什么也不接受什么也返回。因为他唯一的抽象方法run,什么也不接受什么也不返回。
Lambda表达式可以接受参数并有返回值,必然就需要这个Lambda的函数的签名(接受参数和返回值)要和函数式接口相匹配。
例如下面代码,Runnable的签名是什么都不接受什么都不返回。
public void process(Runnable r) {
r.run();
}
调用代码,传入的lambda表达式的签名也是什么都不接受,什么都不返回。所以它们可以匹配起来。
process(()-> System.out.println("funny新青年"));
小提示:
建议给函数式接口加@FunctionalInterface的注解,如果该接口不符合函数式接口的定义的时候编译器将会报错。
函数式接口
首先温习下上节的关键定义。函数式接口是只定义一个抽象方法的接口。 函数式接口的抽象方法的签名成为函数描述符。
为了适应不同的Lambda表达式,需要有一套能够描述常见函数描述符的函数式接口。Java Api中已经提供了一些常用的,免的每次使用Lambda表达式还需要自己定义函数式接口。
Java8新提供的函数式接口在java.util.function包下。具体可以查看java8 api doc(https://docs.oracle.com/javase/8/docs/api/)。
Predicate
java.util.function.Predicate<t style="margin: 0px; padding: 0px;">接口定义了一个名叫test的抽象方法,他接受泛型T,返回boolean。</t>
Consumer
java.util.function.Predicate<t style="margin: 0px; padding: 0px;">接口定义了一个名叫accept的抽象方法,它接受一个泛型T的对象,没有返回。算是消费一个对象。</t>
Function
java.util.function.Function<t, r="" style="margin: 0px; padding: 0px;">接口定义了一个名叫apply的抽象方法,它接受一个泛型T的对象,返回一个泛型R对象。是将输入经过计算得到一个输出接口。</t,>
Supplier
定义了一个名为get的抽象方法,代表的函数描述符是() -> T。
因为函数式接口中的泛型只能绑定到引用类型,如果想使用基本类型可以使用基本类型对应的引用类型,然后采用自动装箱,但是这会导致一些性能损耗。所以Java还给对应的函数式接口提供了基础类型的特殊处理。比如给Predicate接口提供了IntPredicate、LongPredicate、DoublePredicate,分别是传入int、long、double返回boolean。其他函数式接口也同样有这样的原始类型特化处理。
以上提供的函数式接口仅允许传入一个参数,为了更灵活一些。java还提供了对应两个参数的函数式接口有BiPredicate<l, r="" style="margin: 0px; padding: 0px;">、BiConsumer<t, u="" style="margin: 0px; padding: 0px;">、BiFunction<t, r="" style="margin: 0px; padding: 0px;">。</t,></t,></l,>
全部的函数式接口自己去java8 api doc(https://docs.oracle.com/javase/8/docs/api/) 找吧。
方法引用
方法引用可以让你使用已有的方法实现Lambda表达式,将目标引用放在分隔符::之前,方法名放在::后面。方法引用可以视为仅仅涉及单一方法的Lambda的语法糖,优点是代码能写的更少,更清晰一点。
那么如何构建方法引用呢?方法引用有三类。
-
指向静态方法的方法引用。(例如Integer的parseInt方法,方法引用可以写作Integer::parseInt,对用的Lambda表达式为(String s)-> Integer.parseInt(s))。
-
指向任意类型方法的方法引用。(Lambda要调用参数中的一个方法,例如String的length方法,方法引用可以写作String::length,对应的Lambda表达式为 (String s) -> s.length())。
-
指向现有对象的实例方法引用。(Lambda要调用一个外部的对象的方法,例如外部有一个expr对象,要调用他的一个方法,方法应用可以写成expr::instanceMethod,对应的Lambda可以写作(args) -> expr.instanceMethod(args)。
构造函数引用
构造函数的引用是方法引用的一个特殊方法。
对于一个现有的构造函数可以使用类名称和new关键字来创建他的一个引用:className::new。他的功能与静态方法的引用类似。
构造函数的引用的返回值肯定是确定的。就是className,问题在于有几个参数。对于参数数量的不同我们可以选择不同的签名。
无参可以用Supplier,一个参数可以用Function,两个参数可以用BiFunction,三个四五个就需要你自己来实现一些函数式接口了。
复合Lambda表达式
java8允许你将简单的Lambda表达式复合成更复杂的表达式。下面讲会比较常用的两种谓词复合和函数符合。
谓词复合
谓词接口包含三个方法:negate、and和or。你可以重用Predicate来创建更复杂的谓词。
比如你原有一个谓词一个谓词是从一堆苹果中取出红色的那些。现在想要取出不是红色的那些,只需要使用negate接口就可以了。
Predicate<T> notRedApple = redApple.negate();
类似的,如果你想得到又红又大的苹果,可以将筛选红色苹果的谓词和重量更大的苹果的谓词用and连接起来。
Predicate<T> notRedApple = redApple.and(a -> a.getWeight > 150);
函数复合
你还可以把Function接口所代表的Lambda表达式组合起来,有两个对应的默认方法andThen和compose。
andThen是先执行完前面的函数再执行后面的,compose则相反。例如有两个函数,f(x)和g(x)。f.andThen(g)相当于g(f(x)),f.compose(g)相当于f(g(x))。
最后
本文内容基本是根据《java8实现》第三章编写的,将自己认为重要的内容拿出来说了一说。值得一提的是写一篇文章比单纯的看书理解到的内容多多了。打完收工!
image欢迎关注~~~
欢迎关注【Funny 新青年】微信公众号。