Java8实战 — Lambda表达式详解

Lambda管中窥豹

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

    何为更简介,做一个例子,使用匿名类和Lambda定义一个Comparator对象:

package cn.net.bysoft.chapter3;

import java.util.Comparator;

public class Example1 {

    public static void main(String[] args) {
        // 使用匿名类定义Comparator对象
        Comparator<Apple> byWeight = new Comparator<Apple>() {
            @Override
            public int compare(Apple a1, Apple a2) {
                return a1.getWidth().compareTo(a2.getWidth());
            }
        };
        // 使用Lambda定义Comparator对象
        // 以 -> 为界限,-> 左侧部分是方法参数, -> 右侧部分是方法主体
        Comparator<Apple> byWeight_lambda = (Apple a1, Apple a2) -> a1.getWidth().compareTo(a2.getWidth());
    }

}

    在进一步,下面给出了Java8中五个有效的Lambda表达式:

        // 具有一个String类型的参数并返回一个int,没有return语句,因为已经隐含了return
        (String s) -> s.length()
        // 具有一个Apple类型的参数并返回一个boolean
        (Apple a) -> a.getWeight() > 150
        // 具有两个int类型的参数而没有返回值
        (int x, int y) -> {
            System.out.println(x + y);
        }
        // 没有参数并返回一个int
        () -> 42
        // 有两个Apple类型的参数并返回一个int
        (Apple a1, Apple a2) -> a1.getWeight().comparaTo(a2.getWeight())

在哪里以及如何使用Lambda

    函数式接口就是只定义一个抽象方法的接口,例如Comparator和Runnable。

    Lambda表达式允许你以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。用匿名类也可以完成同样的事情,但比较笨拙。

    让我们通过一个例子来看看在哪里以及如何使用Lambda表达式。

    打开一个资源,做一些处理,关闭资源:

package cn.net.bysoft.chapter3;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Example2 {
    public static void main(String[] args) {
        try {
            String s = processFile();
            System.out.println(s);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    // 打开一个资源,读取一行,关闭资源
    // 使用了Java7中的带资源的try语句,不需要显示关闭资源
    public static String processFile() throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) {
            return br.readLine();
        }
    }
}

    上面这段代码是有局限性的,只能读文件的第一行。如果想要返回前两行,甚至更多该怎么办?

    一般来说,通过4个步骤就可以将行为抽象出来:

  1. 使用函数式接口;
  2. 行为参数化;
  3. 执行一个行为;
  4. 传递Lambda表达式;

    具体来看看这4步,首先,创建一个函数式接口:

package cn.net.bysoft.chapter3;

import java.io.BufferedReader;
import java.io.IOException;

@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader br) throws IOException;
}

    接下来,行为参数化:

public static String processFile(BufferedReaderProcessor p) throws IOException {
    ...
}

    在来,执行行为:

public static String processFile(BufferedReaderProcessor p) throws IOException {
    try(BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) {
        return p.process(br);
    }
}

    最后,传递lambda:

String oneLine = processFile((BufferedReader br) -> br.readLine());
String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine());

    测试:

package cn.net.bysoft.chapter3;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Example2 {
    public static void main(String[] args) {
        // 1.行为参数化
        try {
            // 4.传递Lambda
            String oneLine = processFile((BufferedReader br) -> br.readLine());
            System.out.println(oneLine);
            String twoLine = processFile((BufferedReader br) -> br.readLine() + br.readLine());
            System.out.println(twoLine);
            
            /**
             * output: 
             * aaa
             * aaabbb
             * */
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    // 2.传递行为
    public static String processFile(BufferedReaderProcessor p) throws IOException {
        // 3.调用行为
        try(BufferedReader br = new BufferedReader(new FileReader("c://data.txt"))) {
            return p.process(br);
        }
    }
}

使用Java8中的函数式接口

    Java API中已经有了一些函数式接口,比如Comparable、Runnable和Callable等。

    Java8的设计师帮你在java.util.function包中引入了几个新的函数式接口:

函数式接口函数描述符
Predicate<T>T -> boolean
Consumer<T>T -> void
Function<T,R>T -> R
Supplier<T>() -> T
UnaryOperator<T>(T) -> T
BinaryOperator<T>(T,T) -> T
BiPredicate<L,R>(L,R) -> boolean
BiConsumer<T,U>(T,U) -> void
BiFunction<T,U,R>(T,U) -> R

    写个例子使用几个常用的函数式接口:

package cn.net.bysoft.chapter3;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class Example3 {
    public static void main(String[] args) {
        
        // 使用Predicate<T>
        Apples apples = new Apples();

        List<Apple> green_apples = filter(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor()));

        // 使用Consumer<T>
        System.out.println("绿苹果有:");
        forEach(green_apples, (Apple apple) -> System.out.println(apple.getId()));

        // 使用Function<T>
        // 将字符串的长度放到一个List<Integer>中
        System.out.println("数组中的字符长度:");
        List<Integer> list = map(Arrays.asList("lambdas", "in", "action"), (String s) -> s.length());
        forEach(list, (Integer i) -> System.out.println(i));
        
    }

    private static <T> List<T> filter(List<T> list, Predicate<T> p) {
        List<T> result = new ArrayList<>();
        for (T t : list)
            if (p.test(t))
                result.add(t);
        return result;
    }

    private static <T> void forEach(List<T> list, Consumer<T> c) {
        for (T t : list)
            c.accept(t);
    }

    private static <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;
    }
}

Lambda的类型检查、类型推断以及限制

类型检查

    Lambda的类型检查是从使用Lambda的类型上下文推断出来的。上下文中需要的类型称为目标类型。例如:

filter(apples.getApples(), (Apple apple) -> "green".equals(apple.getColor()));
  1. 检查filter的方法声明;
  2. 发现目标类型是Predicate<Apple>
  3. 发现Predicate<Apple>是一个函数式接口,定义了一个test()方法;
  4. test方法描述了一个函数描述符,接收一个Apple,返回一个boolean;
  5. filter的任何实际参数都必须匹配这个要求;

类型推断

    Java编译器会从上下文推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适用Lambda的签名,因为函数描述符可以通过目标类型来得到。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型,例如:

Comparator<Apple> byWeight_lambda = (a1, a2) -> a1.getWidth().compareTo(a2.getWidth());

    有时候显示写出类型更容易读,有时候去掉他们更容易读。

使用局部变量

    Lambda允许使用自由变量,它们被称作捕获Lambda,例如:

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
r.run();

    Lambda可以没有限制地捕获实例变量和敬爱变量。但局部变量必须显示声明为final,或事实上是final。例如,下面这段代码就无法编译:

int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);  // error
portNumber = 1327;
r.run();

方法引用

简单描述

    方法引用让你可以重复使用现有的方法,并像Lambda一样传递它们,例如:

inventory.sort(comparing(Apple::getWidth));
List<String> str = Arrays.asList("a", "b", "A", "B");
str.sort(String::compareToIgnoreCase);
for(String s : str) System.out.println(s);

    可以把方法引用看作针对仅仅涉及单一方法的语法糖,因为表达同样的事情时要写的代码更少了,例如:

(Apple a) -> a.getWeight()Apple::getWeight
() -> Thread.currentThread().dumpStack()Thread.currentThread()::dumpStack
(str, i) -> str.substring(i)String::substring
(String s) -> System.out.println(s)System.out::println

    方法引用主要有三类:

  1. 指向静态方法的方法引用,Integer的parseInt写作Integer::parseInt;
  2. 指向任意类型实力方法的引用,String的length写作String::length;
  3. 指向现有对象的实例方法的引用,局部变量evn的实例方法getValue写作evn::getValue;

 构造函数引用

    可以利用类的名称和关键字new来创建一个类,写作ClassName::new。

    加入一个构造函数没有参数,它适合Supplier的签名,() -> Apple,可以这样做:

Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get();

    如果有带参数的构造函数,则可以使用BiFunction:

BiFunction<Integer, String, Apple> c2 = Apple::new;
Apple a2 = c2.apply(1, "greed");

 

转载于:https://my.oschina.net/u/2450666/blog/812819

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值