Java8中的Lambda表达式以及相关知识点

Lambda表达式

1、基本概念

什么是Lambda表达式?
Lambda表达式可以理解为一种特殊的匿名函数,它没有名称,但有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。

Lambda的基本语法:

(parameters) -> expression
// 或者
(parameters) -> {statements; }
// 其中expression表示表达式,statement表示语句

在哪里使用Lambda表达式?
只有在接受函数式接口的地方才可以使用Lambda表达式(函数式接口就是仅仅声明一个抽象方法的接口)。Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。

2、Lambda表达式与异常

任何内置的函数式接口都不允许抛出受检异常( checked exception)。如果需要Lambda表达式抛出异常,有两种方法:1)定义一个自己的函数式接口,并声明受检异常;2)把Lambda表达式包在一个try/catch块中。示例如下:

// 第一种方法:
@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}
BufferedReaderProcessor p = (BufferedReader b) -> br.readLine();

// 第二种方法:
Function<BufferedReader, String> f = (BufferedReader b) -> {
    try {
        return b.readLine();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
};

3、类型检查与类型推断

只有在函数式接口的地方才能使用Lambda表达式,那如何判断Lambda表达式的类型和函数式接口的类型(称为目标类型)是否一致呢?

  1. 首先确定使用Lambda表达式的地方的目标类型,即函数式接口的类型;
  2. 函数式接口里面只有一个抽象方法,确定抽象方法的参数和返回值,即函数描述符;
  3. 最后判断函数描述符与Lambda表达式的签名是否匹配,如果匹配则类型检查无误。

类型推断:函数描述符可以通过目标类型来得到,也就是说使用了哪个函数式接口,就知道了应该用哪种类型的参数和返回值,因此Java编译器可以根据函数描述符来推断出适合Lambda的签名。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。示例如下:

Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
// 省略参数类型
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
// 这里的目标类型是Comparator<Apple>,
// 它的抽象方法compare的函数描述符是(Apple, Apple -> int),因此就可以推断出Lambda表达式的参数类型

函数式接口

1、含义

函数式接口是指仅仅包含一个抽象方法,但是可以有多个非抽象方法(即默认方法)的接口。

2、常用的函数式接口

1)Predicate
java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型T对象,并返回一个boolean。

2)Consumer
java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。

3)Function
java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。

使用示例:

public class BuiltInFunctionalInterface {

    // Predicate函数式接口练习,函数描述符:T -> boolean
    public 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;
    }

    // Consumer函数式接口练习,函数描述符:T -> void
    public static <T> void forEach(List<T> list, Consumer<T> c) {
        for (T t : list) {
            c.accept(t);
        }
    }

    // Function函数式接口,函数描述符:T -> R
    public 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;
    }

    public static void main(String[] args) {
        List<String> listOfStrings = Arrays.asList("Apple", "", "Banana");
        List<String> nonEmpty = filter(listOfStrings, (String s) -> !s.isEmpty());
        for (String s : nonEmpty) {
            System.out.println(s);
        }

        System.out.println("======");
        List<Integer> listOfNums = Arrays.asList(1,2,3,4,5);
        forEach(listOfNums, (Integer i) -> System.out.println(i));

        System.out.println("======");
        List<String> listOfStr = Arrays.asList("Java", "C++", "Scala");
        List<Integer> result = map(listOfStr, (String str) -> str.length());
        for (Integer i : result) {
            System.out.println(i);
        }
    }
}

方法引用

1、含义

方法引用只是Lambda表达式的一种快捷写法,因此必须先理解Lambda表达式,才能用好方法引用。

方法引用的基本思想:有些Lambda表达式中只是调用了某个方法,比如(Apple a) -> a.getWeight(),如果直接使用方法名字(getWeight方法)来调用它,而不是使用表达式或语句来描述如何调用它,就会使得代码可读性更好,上述Lambda表达式等效的方法引用就是Apple::getWeight,直接通过方法名来表示获得苹果的重量,可读性较好。

基本语法:目标引用::方法名称,注意:方法名称不需要括号,因为没有实际调用这个方法。

2、构建方法引用

方法引用主要有三类:

  1. 指向静态方法的方法引用,比如Integer::parseInt
  2. 指向任意类型实例方法的方法引用,比如String::length
  3. 指向现有对象的实例方法的方法引用

第二种和第三种的区分:第二种方法引用表示引用一个对象的方法,而这个对象是Lambda的一个参数。例如,Lambda表达式(String s) -> s.toUpperCase()可以写成String::toUpperCase。第三种方法引用表示在Lambda表达式中调用一个已经存在的外部对象中的方法。

Lambda表达式与三种引用的对应关系:

// 第一种
(args) -> ClassName.staticMethod(args) 
ClassName::staticMethod
// 第二种
(arg0, rest) -> arg0.instanceMethod(rest)  // arg0是ClassName类型的
ClassName::instanceMethod
// 第三种
(args) -> expr.instanceMethod(args)
expr::instanceMethod

3、构造函数引用

对于一个现有构造函数,可以利用它的名称和关键字new来创建它的一个引用:ClassName::new
对于无参构造函数,适用于Supplier函数式接口:

Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get(); // 调用Supplier的get方法将产生一个新的Apple

对于一个参数的构造函数Apple(Integet weight),适用于Function接口:

Function<Integer, Apple> c2 = Apple::new;
Apple a2 = c2.apple(50);

对于两个参数的构造函数Apple(String color, Integet weight),适用于BiFunction接口:

BiFunction<String, Integer, Apple> c3 = Apple::new;
Apple a3 = c3.apple("red", 50);

为什么构造函数参数数量不同,需要对应不同的函数式接口?
因为方法引用本质上就是Lambda表达式,所以Lambda表达式的签名和函数式接口的函数描述符必须要匹配。

上面的接口Supplier、Function、BiFunction都是Java内置的接口,所以可以直接使用。对于具有三个参数的构造函数,比如Color(int, int, int),语言本身没有没有提供与之匹配的函数式接口,此时可以自己创建一个,如下:

public interface TriFunction<T, U, V, R> {
    R apple(T t, U u, V v);
}
// 使用构造函数引用
TriFunction<Integer, Integer, Integer, Color> colorFactory = Color::new;

参考资料:《Java 8实战》第3章

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值