JAVA 8 Lambda表达式完全解析

JAVA8 新特性

在学习JAVA8 Lambda之前,必须先了解一下JAVA8中与Lambda相关的新特性,不然对于一些概念会感到比较陌生。

1、 接口的默认方法和静态方法
Java 8允许我们给接口添加一个默认方法,用default修饰即可。默认方法可以重写,也可以不用重写。这就是和抽象方法的区别,在用法上,没有其他区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface IMyInterface {
void onMethond(String str);//这是一个抽象方法
default String onDefalutMethod(){//这是一个默认方法
return "这是一个默认方法";
}
}
//重写默认方法
public class MyImpl1 implements IMyInterface {

@Override
public void onMethond(String str) {
// TODO Auto-generated method stub

}
@Override
public String onDefalutMethod() {
return "重写默认方法";
}

}
//不重写默认方法
public class MyImpl2 implements IMyInterface {

@Override
public void onMethond(String str) {
// TODO Auto-generated method stub
}
}

此外Java 8还允许我们给接口添加一个静态方法,用static修饰即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface IMyInterface {

void onMethond(String str);//这是一个抽象方法

default String onDefalutMethod(){//这是一个默认方法
return "这是一个默认方法";
}

static String onStaticMethod(){
return "这是一个静态方法";
}

}

2、 函数式接口(Functional Interface)
什么叫函数式接口?他和普通接口有什么区别?
“函数式接口”是指仅仅只包含一个抽象方法的接口(可以包含默认方法和静态方法),其他特征和普通接口没有任何区别,Java中Runnalbe,Callable等就是个函数式接口;。我们可以给一个符合函数式接口添加@FunctionalInterface注解,这样就显式的指明该接口是一个函数式接口,如果不是,编译器会直接提示错误。当然你也可以不用添加此注解。添加的好处在于,由于Lambda表达式只支持函数式接口,如果恰好这个接口被应用于Lambda表达式,某天你手抖不小心添加了个抽象方法,编译器会提示错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//显式指明该接口是函数式接口
@FunctionalInterface
public interface IMyInterface {

void onMethond(String str);//这是一个抽象方法

default String onDefalutMethod(){//这是一个默认方法
return "这是一个默认方法";
}

static String onStaticMethod(){
return "这是一个静态方法";
}

}

3、方法与构造函数引用
Java 8 允许你使用::关键字来引用已有Java类或对象的方法或构造器。::的诞生和Lambda一样都是来简化匿名内部类的写法的,所以::必须配合函数式接口一起用。使用::操作符后,会返回一个函数式接口对象,这个接口可以自己定义,也可以直接使用系统提供的函数式接口,系统提供的后面会单独介绍。请注意,这一节的内容会让你觉得这些用法蛋疼无比,这是因为还没有开始介绍Lambda表达式,::和Lambda结合使用才是王道,希望你还能坚持到介绍Lambda表达式^_^。

假如有个Person类如下,以下的例子都以这个Person类为基础讲解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Person {
private String name;
private int age;

public Person(){

}
public Person(String name,int age){
this.name=name;
this.age=age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public static String show(String str){
System.out.println("----->输入"+str);
return "----->返回"+str;
}
}

  • 使用::关键字初始化构造函数,返回的是一个函数式接口,这个接口可以自己定义,也可以直接使用系统提供的函数式接口。现在先来定义一个函数式接口
    1
    2
    3
    4
    5
    6
    @FunctionalInterface
    public interface PersonSupply {
    Person get();
    }
    PersonSupply sp=Person::new;
    Person person=sp.get();

PersonSupply sp=Person::new;等价于下面

1
2
3
4
5
6
7
PersonSupply sp=new PersonSupply{

public Person get(){
Person person=new Person();
return person;
}
}

怎么定义这个函数式接口呢?首先我们要看::后面,可知,默认构造不需要参数,所以我们的get()定义为无参,但是我们需要Person对象,所以返回值为Person。
当然为了使这个接口更通用,我们可以定义成如下形式

1
2
3
4
5
6
7
@FunctionalInterface
public interface PersonSupply<T> {
T get();
}

PersonSupply<Person> sp=Person::new;
Person person=sp.get();

java自带Supplier的接口就是这样的,后面会做介绍。直接使用Supplier如下

1
2
Supplier<Person> sp=Person::new;
Person person=sp.get();

怎么初始化带参构造函数?
首先写一个函数式接口,由于需要参数所以get()里面输入参数,返回Person,如下。

1
2
3
4
5
6
7
8
@FunctionalInterface
public interface PersonSupply<P extends Person> {
P get(String name, int age);
}


PersonSupply<Person> sp=Person::new;
Person person=sp.get("maplejaw",20);

PersonSupply<Person> sp=Person::new;等价于下面

1
2
3
4
5
6
7
PersonSupply<Person> sp=new PersonSupply<Person>{

public Person get(String name, int age);{
Person person=new Person(name,age);
return person;
}
}

  • 使用::关键字引用静态方法
    同样,写一个函数式接口,该静态方法需要参数,所以get()里传入参数,需要返回参数,所以返回String。
    1
    2
    3
    4
    5
    6
    @FunctionalInterface
    public interface PersonFactory {
    String get(String str);
    }
    PersonFactory pf=Person::show;
    pf.show("哈哈哈");

PersonFactory pf=Person::show;等价于下面

1
2
3
4
5
6
PersonFactory pf=new PersonFactory{

public String get(String str){
return Person.show(str);
}
}

  • 使用::关键字引用普通方法比较特殊。
    如果要调用的方法没有参数,可以用Class::method形式调用,但是这时需要传入一个Person实例,这时函数式接口这样写,在get()中传入Person类的实例,返回String。
    1
    2
    3
    4
    5
    6
    7
    8
    @FunctionalInterface
    public interface PersonFactory {
    String get(Person person);
    }
    PersonSupply<Person> sp=Person::new;
    Person person=sp.get("maplejaw", 20);
    PersonFactory pf=Person::getName;
    System.out.println("--->"+sp.get(penson));

PersonFactory pf=Person::getName;等价于下面

1
2
3
4
5
6
PersonFactory pf=new PersonFactory{

public String get(Person person){
return person.getName();
}
}

也可以以instance::method形式调用。这时get()不需要传参。返回String。

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface PersonFactory {
String get();
}

PersonSupply<Person> sp=Person::new;
Person person=sp.get("maplejaw", 20);
PersonFactory pf=person::getName;
System.out.println("--->"+pf.get());

PersonFactory pf=person::getName;等价于下面

1
2
3
4
5
6
7
Person person=new Person("maplejaw",20);
PersonFactory pf=new PersonFactory{

public String get( ){
return person.getName();
}
}

如果要调用的方法有参数,必须用instance::method形式调用,这时函数式接口这样写,set传入参数。

1
2
3
4
5
6
7
8
9
@FunctionalInterface
public interface PersonFactory {
void set(String name);
}

PersonSupply<Person> sp=Person::new;
Person person=sp.get("maplejaw", 20);
PersonFactory pf=person::setName;
pf.set("maplejaw");

PersonFactory pf=person::setName;等价于下面

1
2
3
4
5
6
7
Person person=new Person("maplejaw",20);
PersonFactory pf=new PersonFactory{

public void set(String name){
return person.setName(name);
}
}

4、JAVA8 API内建的函数式接口
还记得前面提到的Supplier函数式接口吗,它就是API内建的函数式接口之一,下面将介绍一些常用的内建函数式接口。

  • Supplier
    Supplier 提供者,不接受参数,有返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Supplier<Person> Supplier=new Supplier<Person>() {

    @Override
    public Person get() {
    return new Person();
    }

    };

    Supplier<Person> sp=Person::new;
  • Function 一个参数,一个返回值。常用于数据处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Function<String, Integer> function=new Function<String, Integer>(){

    @Override
    public Integer apply(String s) {

    return Integer.parseInt(s);
    }

    } ;
    Function<String, Integer> function=Integer::parseInt;
  • Consumer 消费者,只有一个参数,没有返回值

    1
    2
    3
    4
    5
    6
    7
    Consumer<String> consumer=new Consumer<String>() {

    @Override
    public void accept(String t) {

    }
    };
  • Comparator 比较类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Comparator<Integer> comparator=new Comparator<Integer>() {

    @Override
    public int compare(Integer o1, Integer o2) {
    // TODO Auto-generated method stub
    return o1-o2;
    }


    };
  • Predicate
    Predicate 接口,抽象方法只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Predicate<String> predicate=new Predicate<String>() {

    @Override
    public boolean test(String t) {

    return t.startsWith("h");
    }
    };
    boolean b=predicate.test("hahaha");//判断是否符合条件
1
2
Predicate<String> predicate = String::isEmpty;
boolean b=predicate.test("hahaha");//判断是否符合条件
  • UnaryOperator 接收一个参数,返回一个参数,且参数类型相同

    1
    2
    3
    4
    5
    6
    7
    8
    UnaryOperator<String> unaryOperator=new UnaryOperator<String>() {

    @Override
    public String apply(String s) {
    // TODO Auto-generated method stub
    return s;
    }
    };
  • BinaryOperator 接收两个参数,返回一个参数,且参数类型相同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    BinaryOperator<String> binaryOperator=new BinaryOperator<String>() {


    @Override
    public String apply(String t, String u) {
    // TODO Auto-generated method stub
    return t+u;
    }
    };

5、三个API

  • Optional
    Optional 这是个用来防止NullPointerException异常而引入的。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

    1
    2
    3
    4
    Optional<String> optional = Optional.of("给你一个值");
    optional.isPresent(); //判断是否为空值
    optional.get(); //获取值 ,如果空值直接抛异常。
    optional.orElse("返回空值"); //获取值 ,如果空值返回指定的值。
  • Stream(流)
    最新添加的Stream API(java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
    接触过RxJava的可能对下面的代码风格比较眼熟,Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。
    怎么用Stream?Stream必须有数据源。那就给一个数据源。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    List<String> list = new ArrayList<>();
    list.add("ddd2");
    list.add("aaa2");
    list.add("bbb1");
    list.add("aaa1");
    list.add("aaa3");
    list.add("bbb3");
    list.add("ccc");
    list.add("bbb2");
    list.add("ddd1");
    • forEach list新增的for循环方法,forEach是一个最终操作(只能放在最后)

      1
      2
      3
      4
      5
      6
      7
      8
      9
        //遍历打印数据
      list.forEach(new Consumer<String>() {

      @Override
      public void accept(String t) {
      System.out.println("---->"+t);

      }
      });

      打印结果如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
       ---->ddd2
      ---->aaa2
      ---->bbb1
      ---->aaa1
      ---->aaa3
      ---->bbb3
      ---->ccc
      ---->bbb2
      ---->ddd1
    • filter 过滤

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      list.stream()
      .filter(new Predicate<String>() {

      @Override
      public boolean test(String t) {

      return t.startsWith("a");
      }
      })
      .forEach(new Consumer<String>() {

      @Override
      public void accept(String t) {
      System.out.println("---->"+t);

      }
      });
      打印结果如下
      
      1
      2
      3
      ---->aaa2
      ---->aaa1
      ---->aaa3
    • Sort 排序

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
         list.stream()
      .sorted()//排序,如果不实现Comparator接口,则按默认规则排序
      .filter(new Predicate<String>() {

      @Override
      public boolean test(String t) {

      return t.startsWith("a");
      }
      })
      .forEach(new Consumer<String>() {

      @Override
      public void accept(String t) {
      System.out.println("---->"+t);

      }
      });

      list.stream()
      .sorted(new Comparator<String>() {

      @Override
      public int compare(String o1, String o2) {
      // TODO Auto-generated method stub
      return o1.compareTo(o2);
      }
      })
      .filter(new Predicate<String>() {

      @Override
      public boolean test(String t) {

      return t.startsWith("a");
      }
      })
      .forEach(new Consumer<String>() {

      @Override
      public void accept(String t) {
      System.out.println("---->"+t);

      }
      });
  • map

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    list.stream()
    .sorted(new Comparator<String>() {

    @Override
    public int compare(String o1, String o2) {
    // TODO Auto-generated method stub
    return o1.compareTo(o2);
    }
    })
    .filter(new Predicate<String>() {

    @Override
    public boolean test(String t) {

    return t.startsWith("a");
    }
    })
    .map(new Function<String, String>() {

    @Override
    public String apply(String t) {
    // TODO Auto-generated method stub
    return t+"--->被我处理过了";
    }
    })
    .forEach(new Consumer<String>() {

    @Override
    public void accept(String t) {
    System.out.println("---->"+t);

    }
    });
    1
    2
    3
    ---->aaa1--->被我处理过了
    ---->aaa2--->被我处理过了
    ---->aaa3--->被我处理过了
  • Match 匹配,是一个最终操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
      boolean b= list.stream()
    .anyMatch(new Predicate<String>() {

    @Override
    public boolean test(String t) {
    // TODO Auto-generated method stub
    return t.startsWith("a");
    }
    });

    System.out.println("----->"+b);//true
    boolean b= list.stream()
    .allMatch(new Predicate<String>() {

    @Override
    public boolean test(String t) {
    // TODO Auto-generated method stub
    return t.startsWith("a");
    }
    });

    System.out.println("----->"+b);//false
  • Count 计数,是一个最终操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     long count = list.stream()
    .filter(new Predicate<String>() {

    @Override
    public boolean test(String t) {

    return t.startsWith("a");
    }
    })
    .count();
    System.out.println(count); // 3
  • Reduce 规约,最终操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      	 Optional<String> optional = 
    list
    .stream()
    .sorted()
    .reduce(new BinaryOperator<String>() {

    @Override
    public String apply(String t, String u) {

    return t+u;
    }
    });
    System.out.println("---->"+optional.get());
    //---->aaa1aaa2aaa3bbb1bbb2bbb3cccddd1ddd2
  • findFirst 提取第一个,最终操作
    1
    2
    3
    4
    5
    6
      Optional<String> optional = 
    list
    .stream()
    .sorted()
    .findFirst();
    System.out.println("---->"+optional.get()); //aaa1
  • parallelStream(并行流)
    并行化之后和之前的代码区别并不大。并且并行操作下,速度会比串行快。但是需要注意的是不用在并行流下排序,并行流做不到排序。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    list.parallelStream()
    .filter(new Predicate<String>() {

    @Override
    public boolean test(String t) {
    // TODO Auto-generated method stub
    return t.startsWith("a");
    }
    })
    .forEach(new Consumer<String>() {

    @Override
    public void accept(String t) {
    // TODO Auto-generated method stub
    System.out.println("--->"+t);
    }
    });

    list.parallelStream()
    .sorted()
    .forEach(new Consumer<String>() {

    @Override
    public void accept(String t) {
    // TODO Auto-generated method stub
    System.out.println("--->"+t);
    }
    });
    //打印出来的并没有排序

Lambda 表达式

在上面你是不是觉得::有时候挺好用的?可以不用再new接口,再也不用写烦人的匿名内部类了,比如

1
Supplier<Person> sp=Person::new;

但是::的使用场景还是比较有限的。Lambda表达式的诞生就是为了解决匿名内部类中饱受诟病的问题的。

  • 什么是Lambda表达式
    Lambda表达式是Java8的一个新特性,它提供了一种更加清晰和简明的方式使用函数式接口(以前被叫作单一方法接口)。使用Lambda表达式能够更加方便和简单的使用匿名内部类,比如对于集合的遍历、比较、过滤等等。

  • Lambda表达式格式
    (type1 arg1, type2 arg2…) -> { body }
    每个lambda都包括以下三个部分:
    参数列表:(type1 arg1, type2 arg2…)
    箭头: ->
    方法体:{ body }

    方法体既可以是一个表达式,也可以是一个语句块:

    • 表达式:表达式会被执行然后返回执行结果。
    • 语句块:语句块中的语句会被依次执行,就像方法中的语句一样,return语句会把控制权交给匿名方法的调用者。
      表达式函数体适合小型lambda表达式,它消除了return关键字,使得语法更加简洁。

以下是一些例子

1
2
3
4
5
(int a, int b) -> {  return a + b; }
( a, b) -> { return a + b; }
( a, b) -> a+b
() -> System.out.println("s")
(String s) -> { System.out.println(s); }

  • 一个 Lambda 表达式可以有零个或多个参数
  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同
  • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b)
  • 空圆括号代表参数集为空。例如:()->{System.out.println(“s”);};
  • 当只有一个参数,且其类型可推导时,圆括号()可省略。
  • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略,且不能以分号结尾。
  • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中,每条语句必须以分号结尾。

Lambda表达式可以用来简化内部类写法,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  //常规代码
Runnable runable=new Runnable() {

@Override
public void run() {

System.out.println("--->");
}
};

//Lambda表达式
Runnable runable=()->{System.out.println("--->");};
//启动一个线程
new Thread(()->{System.out.println("--->");}).start();

还记得前面介绍的Predicate接口吗?现在我们再来用Lambda表达式写一遍。

1
2
3
4
5
6
7
8
9
10
11
12
//一般写法
Predicate<String> predicate=new Predicate<String>() {

@Override
public boolean test(String t) {

return t.startsWith("h");
}

};
//Lambda写法
Predicate<String> predicate=(String s)->{return s.startsWith("h");};

你现在是不是觉得Lambda表达式太神奇了?居然可以写出这么简洁的代码。
还记得内部类使用局部变量时需要把变量声明为final吗,Lambda表达式则不需要。不过虽然不用声明final,但也不允许改变值。

1
2
3
4
String s="sss";
new Thread(()->{
System.out.println(s);
}).start();

此外,内部类引用外部类也不用使用MainActivity.this,这种操蛋的代码了。
在Lambda表达式中this,指的就是外部类,因为根本就没有内部类的概念啊。

1
2
3
btn.setOnClickListener(()->{
Toast.makeText(this,"xxx",Toast.LENGTH_SHORT).show();
});

现在回过头来把前面Stream中的代码用Lambda表达式再写一遍。

1
2
3
4
5
  list.stream()
.sorted((s1,s2)->s1.compareTo(s2))
.filter((s)->s.startsWith("a"))
.map((s)->s+"被我处理过了")
.forEach(s->System.out.println(s));

代码简洁的简直让人窒息。但是能不能更简洁一点呢?当然是可以的,首先我们检查一下哪里可以替换成::关键字,然后作如下替换,是不是更简洁了。关于替换规则,请看前面的介绍。

1
2
3
4
5
list.stream()
.sorted((s1,s2)->s1.compareTo(s2))
.filter((s)->s.startsWith("a"))
.map((s)->s+"被我处理过了")
.forEach(System.out::println);

打印结果如下

1
2
3
aaa1被我处理过了
aaa2被我处理过了
aaa3被我处理过了

  • 显示指定目标类型
    如果Lambda表达式的目标类型是可推导的,就不用指定其类型,如果Lambda表达式的参数类型是可以推导,就不用指定参数类型。因为编译器可以根据上下文自动推导出其类型,然后进行隐式转换。但是,有些场景编译器是没法推导的。比如下面这样的,如果不显示指定类型,编译器就会提示错误
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //接口一
    public interface IMyInterface1 {
    void opreate(String str);
    }
    //接口二
    public interface IMyInterface2 {
    void opreate(int i);
    }
    //Person类
    public class Person{
    private String name;
    private int age;

    public Test(String name, int age) {
    this.name = name;
    this.age = age;
    }

    public void opreate(IMyInterface1 inter){
    inter.opreate(name);
    }

    public void opreate(IMyInterface2 inter){
    inter.opreate(age);
    }
    }


    //这样写是错误的,因为编译器无法推导出其目标类型
    new Test("maplejaw",20).opreate((name)->System.out.println(name));

解决办法有两个
一、指定参数类型,但是如果两个接口的参数类型是一样的,就只能显示指定目标类型。

1
new Test("maplejaw",20).opreate((String name)->System.out.println(name));

二、指定目标类型

1
2
new Test("maplejaw",20).opreate((IMyInterface1) (s)->System.out.println(s));
new Test("maplejaw",20).opreate((IMyInterface1) System.out::println);

由于目标类型必须是函数式接口,所以如果想赋值给Object对象时,也必须显示转型。

1
Object runnable=(Runnable)()->{System.out.print("--->");};

关于Lambda表达式的介绍到此为止,想更深入了解推荐【深入理解Java 8 Lambda】这篇文章。

最后

当初学习Lambda表达式的时候,由于网上的资料比较零散,且直接用了JAVA8的新API来做演示,由于对新API不是很熟导致学习的时候走了一些弯路,看得一头雾水。所以决定把我的学习路线给记录下来,或许可以帮助部分人。
Lambda表达式是把双刃剑,让代码简洁的同时,降低了代码的可读性。但是作为程序员,追逐新技术的脚步不能停下。


本文参考了【深入理解Java 8 Lambda】【JAVA8 十大新特性详解】两篇文章