JDK8特性深入学习笔记-Lambda表达式(1)

什么是Lambda表达式

在编程语言中,lambda指的是一种运算符,用来表示:

  • closures(闭包)
  • anonymous functions(匿名函数)

旧的语法通过传递数据来完成,而Lambda表达式更注重于行为。在java中,Lambda表达式是个对象,它们必须依附于一类特别的对象类型 —— 函数式接口(functional interface)

为什么需要Lambda表达式

  • 在java中,我们无法将函数作为参数传递的方法,也无法声明返回一个函数的方法。
  • javascript中,函数参数是一个函数,返回值是另一个函数的情况是非常常见的。

Lamdba表达式的作用

  • 传递行为,而不仅仅是值

    • 提升抽象层次
    • API重用性更好
    • 更加灵活

Lambda表达式的基本结构

(type1 arg1, type2 arg2, ..., typeN argN) -> { body }

  • Lambda示例

    • (int a, int b) -> {return a + b;}
    • () -> System.out.println("Hello World");
    • (String s) -> {System.out.println(s);}
    • () -> 42
    • () -> {return 4};
  • 一个Lambda表达式可以有零个或多个参数
  • 参数类型可以明确声明,也可以让编译器通过上下文推断。例如:(int a) 与 (a) 效果相同
  • 所有参数需要包含在圆括号内,参数之间用逗号相隔。例如:(a, b)或(int a, int b)或 (String a, int b, double c)
  • 圆空括号表示参数集合为空。例如() -> 42
  • 当只有一个参数,且其类型可以推断时,圆括号()可以省略。例如: a -> return a*a
  • Lambda表达式的主题可包含零条或多条语句
  • 如果Lambda表达式的主题只有一条语句,花括号{}可以省略。匿名函数的返回类型与该主体表达式一致。
  • 如果Lambda表达式的主题包含一条以上的语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。

函数式接口

通过Lambda表达式遍历集合

List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

list.forEach(new Consumer(){
    @Override
    public void accept(Integer integer){
        System.out.println(integer);
    }
});

Consumer接口

@FunctionalInterface
public interface Consumer {

    void accept(T t);

    default Consumer andThen(Consumer after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

关于函数式接口 注解

@FunctionalInterface:凡是接口上带上这个注解,即为函数式接口。一个函数式接口有且只有一个精确的抽象方法。函数式接口的实例可以通过Lamdba表达式的方法引用构造方法引用来去创建。
如果使用了@FunctionalInterface注解,但却不满足以下两个条件,编译器会返回编译异常:

  • 这个类型必须是Interface接口类型
  • 这个注解类型必须满足函数式接口的要求:一个函数式接口有且只有一个精确的抽象方法

如果这个抽象方法复写了或者重写了Object类里的public方法,那么这个接口也不属于函数式接口。因为java的任意实现,一定都会有一个来自java.lang.Object的实现。

@FunctionalInterface
public interface MyInterface{
    // 抽象方法
    void test();
    // Object的抽象方法
    String toString();
}

toString()是Object的抽象方法,所以编译器认为MyInterface依然是一个函数式接口。

public class UsingFunctionInterface{
    public void myUsingInterface(MyInterface interface){
        System.out.println("start");
        interface.test();
        System.out.println("end");
    }

    public static void main(String[] args){
        UsingFunctionInterface u = new UsingFunctionInterface();

        // 写法1
        u.myUsingInterface(() -> {
             System.out.println("test print");
        });

        //写法2
        MyInterface myInterface = () -> {
             System.out.println("test print");
        };

        u.myUsingInterface(myInterface);
    }
}

Lamdba表达式弱化了抽象方法名的意义,将方法实现(即->右边的部分,直接作为函数式接口的实现)。

关于函数式接口的特点

  • 如果一个接口只有一个抽象方法,那么这个接口就是函数式接口
  • 如果我们在某个接口上声明了@FunctionalInterface,那么编译器就会按照函数式接口的定义来要求该接口
  • 如果某个接口只有一个抽象方法,但我们并没有给该接口声明@FunctionalInterface,那么编译器依旧会将该接口看作是函数式接口。
  • 函数式接口的实例,可以通过Lamdba表达式方法的实例构造方法的实例来创建。

上述的总结:@FunctionalInterface只是帮助编译器检查函数式接口是否合法。

如果一个接口需要用作函数式接口,那么最好加上 @FunctionalInterface

foreach方法

list.foreach()

foreach的实现

default void forEach(Consumer action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

foreach最特殊的就是用了default关键字。jdk1.8后,接口里也可以有方法实现。一旦在接口中使用了default关键字做方法的实现,称之为默认方法(default Method)
默认方法的出现一方面保证了jdk8的新特性的加入,另一方面保证了jdk8可以向上兼容。

Consumer

Consumer是一个操作,这个操作接受一个单个的输入元素,并且不返回结果。与其他函数式接口不同的是,它可能会修改接收到的操作
Consumer的语义是消费者,所以顾名思义,这个类中的抽象方法void accept(T t)的语义为:接受一个类,并将其消费掉,并不关心结果

迭代

外部迭代:指的是利用外部的迭代器,对一个集合进行迭代。
内部迭代:通过集合本身,利用Lambda表达式进行迭代。

List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 外部迭代
for(Integer i : numbers){
    System.out.println(i);
}
// 内部迭代 : 函数式接口
numbers.forEach(new Consumer{
    public void accept(){
        System.out.println(i);
    }
});
// 内部迭代: 函数式接口 + Lambda表达式
numbers.forEach(i -> System.out.println(i) )
// 内部迭代: 方法引用
numbers.forEach(System.out::println)

使用stream做迭代

stream的实现

default Stream stream() {
        return StreamSupport.stream(spliterator(), false);
    }

stream是在Collection接口的默认方法,返回一个串行流。
spliterator(分割迭代器)方法不能返回一个分割迭代器时(不可变的、延迟的、并发的分割迭代器),stream方法需要被重写。

stream中的map()

Stream map(Function mapper); ,map本身的含义是映射,将一个给定的元素映射为另一个元素并返回一个新的Stream

List list = Arrays.asList("a", "b", "c");
List list2 = new ArrayList<>();
// Lamdba表达式实现
list.stream().map(i -> i.toUpperCase()).forEach(i -> list2.add(i));
// 方法引用实现
list.stream().map(String::toUpperCase).forEach(list2::add);

你可能感兴趣的:(java,jdk8,lambda,函数式编程,后端开发)