龙空技术网

第3章 Lambda表达式

惯看春风 29

前言:

眼前我们对“lambda表达式教程”大体比较关怀,朋友们都需要分析一些“lambda表达式教程”的相关内容。那么小编在网络上汇集了一些有关“lambda表达式教程””的相关知识,希望各位老铁们能喜欢,我们快快来了解一下吧!

Lambda表达式,可以很简洁地表示一个行为或者传递代码,可以把Lambda表达式看成匿名函数,它基本上就是没有声明名称的方法,和匿名类一样,它也可以作为参数传递给一个方法。

Lambda管中窥豹

可以把Lambda表达式理解为一种简洁的可传递匿名函数:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个抛出异常列表。

匿名:它不像普通方法那样有一个明确的名称函数:说它是一种函数,是因为Lambda表达式不像方法那样属于某个特定的类。但和方法一样,Lambda表达式有参数列表、函数主体、返回类型,还可能有抛出异常列表。传递:Lambda表达式可以作为参数传递给方法或者存储在变量中。简洁:你无须像匿名类那样写很多模板代码。

例如:比较两个苹果的重量

A:匿名类

Comparator<Apple> byWeight = new Comparator<Apple>(){    @Override    public int compare(Apple o1, Apple o2) {        return o1.getWeight().compareTo(o2.getWeight());    }};

B:Lambda表达式

Comparator<Apple> byWeight2 = (Apple o1, Apple o2) -> o1.getWeight().compareTo(o2.getWeight());

Lambda表达式组成

参数:采用Comparator接口中的compare方发的参数,两个Apple。箭头:箭头->把参数与Lambda主体分开Lambda主体:比较两个Apple的重量。在那里以及如何使用Lambda

1、函数式接口

只定义一个抽象方法的接口是函数式接口,如下列接口都是函数式接口:

public interface Comparator<T> {    int compare(T o1, T o2);}public interface Runnable {    void run();}public interface Callable<V> {    V call() throws Exception;}

函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

//Lambda表达式Runnable r1 = () -> System.out.println("Hello world 1");//匿名类Runnable r2 = new Runnable() {    @Override    public void run() {        System.out.println("Hello world 2");    }};public static void process(Runnable r){    r.run();}process(r1);process(r2);

2、函数描述符

函数式接口的抽象方法的签名基本上是Lambda表达式的签名,我们将这种抽象方法的签名叫作函数描述符。() -> void 代表参数列表为空,且返回void的函数。

Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法。Lambda表达式的签名要和函数式接口的抽象方法一样。

public static void process(Runnable r){    r.run();}process(() -> System.out.println("This is awesome!!"));
应用Lambda的步骤

资源处理时,常见的模式是打开一个资源,做一些处理,然后关闭资源。如:打开一个文件,读取文件内容,然后关闭文件。

第一步:行为参数化

String result = processFile((BufferedReader br) -> br.readLine());

第二步:使用函数式接口来传递行为

public interface BufferedReaderProcessor {

String process(BufferedReader b) throws IOException;

}

public static String processFile(BufferedReaderProcessor p) throws IOException {

try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){

return p.process(br);

}

}

第三步:执行一个行为

public static String processFile(BufferedReaderProcessor p) throws IOException {

try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){

return p.process(br);

}

}

第四步:传递Lambda表达式

String result = processFile((BufferedReader br) -> br.readLine());

使用函数式接口

函数式接口定义且只定义了一个抽象方法。函数式接口的抽象方法签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。

Java8库设计师在java.util.function包中引入了几个新的函数式接口:Predicate、Consumer、Function。

1、Predicate

java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,她接受泛型T对象,并且返回一个boolean。在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。

public interface Predicate<T> {    boolean test(T t);}    public static <T> List<T> filter(List<T> list, Predicate<T> p) {        List<T> results = new ArrayList<>();        for (T t : list){            if(p.test(t)){                results.add(t);            }        }        return results;    }	List<Apple> redApples = filter(list, (Apple a) -> Color.RED.equals(a.getColor()));

2、Consumer

java.util.function.Consumer<T>接口定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。如,你可以用它来创建一个forEach方法,接受一个列表,并对其中每个元素执行操作。

public interface Consumer<T> {    void accept(T t);}    public <T> void forEach(List<T> list, Consumer<T> c) {        for (T t : list){            c.accept(t);        }    }forEach(appleList, (Apple a) -> System.out.println(a.getColor()));

3、Function

java.util.function.Function<T, R>接口定义了一个叫作apply的抽象方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口。如:将一个String列表映射到包含每个Sring长度的Integer列表。

public interface Function<T, R> {    R apply(T t);}    public <T, R> List<R> map(List<T> list, Function<T, R> f) {        List<R> result = new ArrayList<>();        for (T t : list){            result.add(f.apply(t));        }        return result;    }List<Integer> mapList = map(Arrays.asList("good", "glad", "wool", "desk"), (String s) -> s.length());

基本类型特化

Java类型要么是引用类型(Byte、Integer、Object、List),要么是基本类型(int、double、byte、char)。当时泛型只能绑定到引用类型,这是由泛型内部的实现方式造成的。因此,在Java里有一个将基本类型转换为对应的引用类型的机制,这个机制叫作装箱,相反的操作,也就是将引用类型转换为对应的基本类型,叫作拆箱,Java中有一个自动装箱机制来帮助程序员执行这一任务:装箱和拆箱操作都是自动完成的。

List<Integer> list = new ArrayList<>();for(int i = 300; i < 400, i++){    list.add(i);}

自动装箱和拆箱,在性能方面是要付出代价的,装箱后的值本质上就是把基本类型包裹起来,并保持在堆里,因此,装箱后的值需要更多的内存,并需要额外的内存来搜索获取被包裹的基本值。

一般来说,针对专门输入参数类型的函数式接口的名称都要加上对应的基本类型前缀,比如DoublePredicat、IntConsumer、LongBinaryOperator、IntFunction等。Function接口还有针对输出参数类型的变种:ToIntFunction<T>、IntToDoubleFunction等。

(T,U) -> R展示的是一个函数式描述符,箭头左侧表示参数的类型,右侧代表了返回结果的类型。

函数式接口

函数描述符

基本类型特化

Predicate<T>

T -> boolean

IntPredicate

LongPredicate

DoublePredicate

Consumer<T>

T -> void

IntConsumer

LongConsumer

DoubleConsumer

Function<T, R>

T -> R

IntFunction<R>

IntToDoubleFunction

IntToLongFunction

LongFunction<R>

LongToDoubleFunction

LongToIntFunction

DoubleFunction<R>

DoubleToIntFunction

DoubleToLongFunction

ToIntFunction<T>

ToDoubleFunction<T>

ToLongFunction<T>

Supplier<T>

() -> T

BooleanSupplier

IntSupplier

LongSupplier

DoubleSupplier

UnaryOperator<T>

T -> T

IntUnaryOperator

LongUnaryOperator

DoubleOperator

BinaryOperator<T>

(T, T) -> T

IntBinaryOperator

LongBinaryOperator

DoubleBinaryOperator

BiPredicate<T, U>

(T, U) -> boolean

BiConsumer<T, U>

(T, U) -> void

ObjIntConsumer<T>

ObjLongConsumer<T>

ObjDoubleConsumer<T>

BiFunction<T, U, R>

(T, U) -> R

ToIntBiFunction<T, U>

ToLongBiFunction<T, U>

ToDoubleBiFunction<T, U>

类型检查

Lambda的类型是从使用Lambda的上下文推断出来的。上下文(比如,接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。

public List<Apple> filter(List<Apple> inventory, Predicate<Apple> p){    List<Apple> result = new ArrayList<>();    for(Apple apple : inventory){        if(p.test(apple)){            result.add(apple);        }    }    return result;}List<Apple> heavierThan150g = filter(appleList, (Apple a) -> a.getWeight() > 150);

类型检查分析过程:

第一:找出filter方法的声明

第二:filter方法声明的第二个参数,要求是Predicate<Apple>类型(目标类型)

第三:Predicate<Apple>是一个函数式接口,定义了一个叫做test的抽象方法

第四:test方法,它接受一个Apple,并返回一个boolean

第五:filter的任何实际参数都必须匹配这个要求

类型推断

Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。

//没有类型推断Comparator<Apple> c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());//有类型推断Comparator<Apple> c2 = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
使用局部变量

Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样,被称作捕获Lambda。

int portNumber = 1337;Runnable r = () -> System.out.println(portNumber);//捕获变量portNumber

Lambda可以没有限制的捕获实列变量和静态变量,当时局部变量必须显示声明为final,或者事实上式final。下面的代码无法编译,因为portNumber变量被复制两次:

int portNumber = 1337;Runnable r = () -> System.out.println(portNumber);portNumber = 1858;
方法引用

方法应用让你可以重复使用现有的方法定义,让你根据已有的方法实现来创建Lambda表达式,当你使用方法应用时,目标应用放在分隔符::前,方法的名称放在后面。

appleList.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));//方法应用appleList.sort(Comparator.comparing(Apple::getWeight));

方法应用主要有三类:

指向静态方法的方法应用,例如:Integer的parseInt方法,写作Integer::parseInt指向任意类型实例方法的方法应用,例如:String的length方法,写作String::length指向现存对象或表达式实列方法的方法应用构造方法应用

对于一个构造方法,你可以利用它的名称和关键字new来创建它的一个应用:ClassName::new。

1、不带参数的构造方法

Supplier<Apple> c1 = Apple::new;Apple a1 = c1.get();//等价于Supplier<Apple> c1 = () -> new Apple();Apple a1 = c1.get();

2、带参数的构造方法

Function<Integer, Apple> c2 = Apple::new;Apple a2 = c2.apply(100);//等价于Function<Integer, Apple> c2 = (weight) -> new Apple(weight);Apple a2 = c2.apply(100);
复合Lambda表达式

许多函数式接口,比如用于传递Lambda表达式的Comparator、Function、Predicate都提供了很多进行符合的方法,可以把多个简单的Lambda表达式符合成复制的表达式,比如:可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,还可以让一个函数的结果称为另一个函数的输入。

1、比较器符合

根据苹果重量的升序排列

Comparator<Apple> c = Comparator.comparing(Apple::getWeight);

逆序

appleList.sort(Comparator.comparing(Apple::getWeight).reversed());

比较器连接

appleList.sort(Comparator.comparing(Apple::getWeight)                .reversed()                .thenComparing(Apple::getColor));

2、谓词符合

谓词接口包含三个方法:negate、and、or,可以重用已有的Predicate来创建更复杂的谓词。

//筛选出红苹果Predicate<Apple> redApple = (Apple a1) -> Color.RED.equals(a1.getColor());//筛选出非红苹果Predicate<Apple> notRedApple = redApple.negate();//筛选出红色,并且重量大于150克的苹果Predicate<Apple> redAndHeightApple = redApple.and((Apple a1) -> a1.getWeight() > 150);//筛选出红色,或者绿色的苹果Predicate<Apple> redAndGreenApple = redApple.or((Apple a1) -> Color.GREEN.equals(a1.getColor()));
函数符合

可以把Function接口所代表的Lambda表达式符合起来。Function接口为此配了andThen和compose两个默认方法,他们都会返回Function的一个实列。

andThen方法会返回一个函数,先对输入应用一个给定函数,再对输出应用另个一个函数。函数f给数字加1(x -> x + 1);函数g给数字乘以2(x -> x*2)。

Function<Integer, Integer> f = x -> x + 1;Function<Integer, Integer> g = x -> x * 2;Function<Integer, Integer> h = f.andThen(g);//数学表达式 h = g(f(x))int result = h.apply(1);//result=4

compose方法,先把给定的函数用作compose的参数里面给的那个函数,然后再把函数本身用于结果。

Function<Integer, Integer> f = x -> x + 1;Function<Integer, Integer> g = x -> x * 2;Function<Integer, Integer> h = f.compose(g);//数学表达式 h = f(g(x))int result = h.apply(1);//result=3

标签: #lambda表达式教程