java8 系列:十分钟入门Lambda表达式

原创 esjson java基础 1877

hello world

先来个没有使用lambda表达式的例子

1.
Runnable old = new Runnable() {
    public void run() {
        System.out.println("old hello world!!!");
    }
};
new Thread(old).start();

使用第一个lambda表达式来实现

2.
Runnable helloWorld = () -> System.out.println("hello world 1");

new Thread(helloWorld).start();

是不是精简了许多,当然你可能会说直接匿名类的方式也很精简

 new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("old hello world!!!");
    }
}).start();

然而,看看下面的lambda表达式后,你会不会改观呢?嘿嘿


new Thread(()->System.out.println("hello world3")).start();

本来为了创建实际只打印hello world的线程,你必须去实例化接口对象或创建匿名类覆盖方法的方式去实现功能,代码要3、4行,而有了lambda表达式后,一行代码就能搞定。

lambda表达式特点

  • 匿名——不像普通方法一样非得有个明确的名称
  • 函数—— lambda函数不像方法那样属于特定的类。但lambda可以和方法一样有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
  • 传递性——lambda表达式可以作为参数传递给方法或存储在变量中
  • 简洁性——无需像匿名类那样写很多模板代码。
  • 参数类型、返回结果可推导

我们来看看如何简洁地定义一个Comparator对象

原先的方式:

 Comparator<Apple> sortByWeight = new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2){
        return a1.getWeight().compareTo(a2.getWeight());
    }
 };

用了lambda表达式后

Comparator<Apple> sortByWeight = (a1,a2)->a1.getWeight().compareTo(a2.getWeight());

这个lambda表达式有三个部分

  1. 参数列表~这里采用了Comparator中compare方法的参数,两个Apple,因为参数类型可推导,可以省略Apple类型。
  2. 箭头——箭头->把参数列表与Lambda主体分隔开,
  3. Lambda主体——比较两个苹果重量的逻辑,即代码a1.getWeight().compareTo(a2.getWeight();

lambda表达式例子

(1)

(String a) -> a.length()

表示具有一个String类型的参数并返回一个int.lambda没有return语句,因为已经隐含了return

(2)

(Apple a) -> a.getWeight() > 150

表示具有一个Apple类型的参数并返回一个boolean

(3)

(int x, int y) -> {
    System.out.println("Result:");
    System.out.println(x+y);
}

表示具有两个int类型的参数而没有返回值(void返回)。表明Lambda表达式可以包含多行语句,这里是两行。

(4)

() -> 10

表示表达式没有参数,返回一个int

(5)

() -> new Apple(10)

表示返回一个Apple对象

(6)

(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())

表示具有两个Apple类型的参数,返回一个int:比较两个Apple的重量

小结下:Lambda表达式的基本语法是
 (parameters) -> expression
 或
 (parameters) -> {expression;}

错误的Lambda表达式例子

(1)

(Integer i) -> return "xxx" + i;

return是一个控制流语句。要使此Lambda有效,需要使花括号,如下所示:
(Integer i) -> {return “xxx” + i;}

(2)

(String s) -> {"xxx";}

“xxx”是一个表达式,不是一个语句。要使此Lambda有效,你可以去除花括号
和分号,如下所示:
(String s) -> “xxx”。
或者可以使用显式返回语句,如下所示:
(String s)->{return “xxx”;}。

哪里可以使用Lambda表达式?

函数式接口

函数式接口就是只定义一个抽象方法的接口。如之前提到的Comparator和Runnable等等。


public interface Runnable {
    public abstract void run();
}


public interface Comparator<T> {
    int compare(T o1, T o2);
}

public interface ActionListener extends EventListener{
    void actionPerformed(ActionEvent e);
}


public interface Callable<V>{
    V call();
}


public interface PrivilegedAction<V>{
    V run();
}

接口现在还可以拥有默认方法(即在类没有对方法进行实现时,
其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。

思考下:用函数式接口可以干什么呢?

Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。你用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。

函数描述符是什么?

函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。这种抽象方法称为函数描述符。Runnable接口可以看作一个什么也不接受什么也不返回(void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void)。Runnable接口的签名为 ()->void。

一句话概括:函数描述符就是描述一种简单的行为 ,有什么参数(可能没有参数),以及有没有返回结果,结果是什么。

来些例子,看看哪些可以使用Lambda表达式:

(1)

execute(() -> {});
public void execute(Runnable r){
    r.run();
}

(2)

public Callable<String> fetch() {
    return () -> "Tricky example ;-)";
}

(3)

Predicate<Apple> p = (Apple a) -> a.getWeight();

答案:只有1和2是有效的。
<br>第一个例子有效,是因为Lambda() -> {}具有签名() -> void,这和Runnable中的抽象方法run的签名相匹配。请注意,此代码运行后什么都不会做,因为Lambda是空的!<br/>
第二个例子也是有效的。事实上,fetch方法的返回类型是Callable<String>。Callable<String>基本上就定义了一个方法,签名是()->String,其中T被String代替
了。因为Lambda() -> “Trickyexample;-)”的签名是() -> String,所以在这个上下文
中可以使用Lambda。<br/>
第三个例子无效,因为Lambda表达式(Apple a) -> a.getWeight()的签名是(Apple) ->
Integer,这和Predicate<Apple>:(Apple) -> boolean中定义的test方法的签名不同。

java8中的函数式接口

java8在java.util.function包中新加入了许多通用的函数式接口,这允许我们在大部分场景可以直接使用,而不用自己去定义函数式接口,减少自己开发的工作量。

几个常见的函数式接口

  • Predicate

    @FunctionalInterface
    public interface Predicate<T> {
    
      boolean test(T t);
    }
    

    描述了 接收泛型T对象,并返回一个boolean

使用场景:对传入的对象做判断,判断逻辑可自行传入。示例代码如下:

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

Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
  • Consumer

    @FunctionalInterface
    public interface Consumer<T> {
    
      /**
       * Performs this operation on the given argument.
       *
       * @param t the input argument
       */
      void accept(T t);
    

描述了接收一个泛型参数对象,没有返回结果

使用场景:你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。
示例代码如下:

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

forEach(Arrays.asList(1,2,3,4,5),
        (Integer i) -> System.out.println(i) );
  • Function
    @FunctionalInterface
    public interface Function<T, R>{
      R apply(T t);
    }
    

    接口定义了一个叫作apply的方法,它接受一个
    泛型T的对象,并返回一个泛型R的对象。

使用场景:如果你需要定义一个Lambda,将输入对象的信息映射
到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。示例代码:

public static <T, R> List<R> map(List<T> list,
                                     Function<T, R> f) {
    List<R> result = new ArrayList<>();
    for(T s: list){
         result.add(f.apply(s));
    }
    return result;
}

List<Integer> l = map(
             Arrays.asList("who","am","i"),
        (String s) -> s.length()
);// 将返回 [3,2,1]

注意:这些接口用的都是泛型,接收的都是引用类型,如果要对原始类型进行操作的话,这些接口要用相应的带有原始类型的前缀,比如DoublePredicate、IntConsumer、LongBinaryOperator、IntFunction等。这样能避免拆装箱操作,减少性能损耗和内存占用

更多函数式接口详见java.util.function包

lambda表达式异常处理

任何函数式接口都不允许抛出受检异常(checked exception)。如果你需要Lambda
表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个try/catch块中。

@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}
BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();

但是你可能是在使用一个接受函数式接口的API,比如Function<T, R>,没有办法自己创建一个。这种情况下,你可以显式捕捉受检异常:

Function<BufferedReader, String> f = (BufferedReader b) -> {
        try {
            return b.readLine();
        }
        catch(IOException e) {
            throw new RuntimeException(e);
        }
    };

总结:

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

  • Lambda表达式让你可以简洁地传递代码。

  • 函数式接口就是仅仅声明了一个抽象方法的接口。
  • 只有在接受函数式接口的地方才可以使用Lambda表达式。
  • Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
  • Java 8自带一些常用的函数式接口,放在java.util.function包里,包括Predicate<T>、Function<T,R>、Supplier<T>、Consumer<T>和BinaryOperator<T>,
Lambda java8 原创

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

关于我

过所爱的生活,爱所过的生活,快乐的生活,才能生活快乐,快乐的工作,才有快乐人生,生活的理想其实就是理想的生活!