JAVA8 Lambda表达式完全解析

JAVA8 新特性

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

1、 接口的默认方法和静态方法
Java 8允许我们给接口添加一个默认方法,用default修饰即可。默认方法可以重写。

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修饰即可。

public interface IMyInterface {

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

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

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

}

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

//显式指明该接口是函数式接口
@FunctionalInterface
public interface IMyInterface {

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

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

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

}

3、方法与构造函数引用
Java 8 允许你使用::关键字来引用已有Java类或对象的方法或构造器。::的诞生和Lambda一样都是来简化匿名内部类的写法的,所以::必须配合函数式接口一起用。使用::操作符后,会返回一个函数式接口对象,这个接口可以自己定义,也可以直接使用系统提供的函数式接口,系统提供的后面会单独介绍。

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

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;
    }
}
  • 使用::关键字初始化构造函数,返回的是一个函数式接口,这个接口可以自己定义,也可以直接使用系统提供的函数式接口。现在先来定义一个函数式接口用于获取Person实例。
@FunctionalInterface
public interface PersonSupply {
    Person get();
}

接下来写一个接口的实现,如果按照常规写法,写出来的代码一般会是如下形式。

PersonSupply sp=new PersonSupply{

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

Person person=sp.get();//获取实例

而自从Java8引入::关键字后,就可以简化为一行代码,简直不要太简洁。

PersonSupply sp=Person::new; //接口实现
Person person=sp.get();//获取实例

当然为了使这个接口更通用,我们可以定义成如下形式

@FunctionalInterface
public interface PersonSupply<T> {
    T get();
}

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

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

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

这种简便写法同样支持带参构造函数,首先写一个函数式接口,由于需要参数所以get()里面输入参数,返回Person,如下。

@FunctionalInterface
public interface PersonSupply<P extends Person> {
    P get(String name, int age);
}

常规写法如下:

PersonSupply<Person> sp=new PersonSupply<Person>{

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

Person person=sp.get("maplejaw",20);

简便写法如下:

PersonSupply<Person> sp=Person::new;
Person person=sp.get("maplejaw",20);
  • 使用::关键字引用静态方法
    同样,写一个函数式接口,该静态方法需要参数,所以get()里传入参数,需要返回参数,所以返回String。
@FunctionalInterface
public interface PersonFactory {
    String get(String str);
}
PersonFactory pf=Person::show;
pf.get("哈哈哈");

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

PersonFactory pf=new PersonFactory{

  public String get(String str){
      return Person.show(str);
  }
}
  • 使用::关键字引用普通方法比较特殊。
    如果要调用的方法没有参数,可以用Class::method形式调用,但是这时需要传入一个Person实例,这时函数式接口这样写,在get()中传入Person类的实例,返回String。
@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("--->"+pf.get(person));

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

PersonFactory pf=new PersonFactory{

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

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

@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;等价于下面

Person person=new Person("maplejaw",20);
PersonFactory pf=new PersonFactory{

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

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

@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;等价于下面

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 提供者,不接受参数,有返回值
    Supplier<Person> Supplier=new Supplier<Person>() {

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

        };

    Supplier<Person> sp=Person::new;
  • Function 一个参数,一个返回值。常用于数据处理。
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 消费者,只有一个参数,没有返回值
Consumer<String> consumer=new Consumer<String>() {

            @Override
            public void accept(String t) {

            }
        };
  • Comparator 比较类
        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组合成其他复杂的逻辑(比如:与,或,非):
Predicate<String> predicate=new Predicate<String>() {

            @Override
            public boolean test(String t) {

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

            @Override
            public String apply(String s) {
                // TODO Auto-generated method stub
                return s;
            }
        };
  • BinaryOperator 接收两个参数,返回一个参数,且参数类型相同
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提供很多有用的方法,这样我们就不用显式进行空值检测。
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必须有数据源。那就给一个数据源。

    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是一个最终操作(只能放在最后)

      //遍历打印数据
      list.forEach(new Consumer<String>() {
      
                  @Override
                  public void accept(String t) {
                   System.out.println("---->"+t);
      
                  }
              });

      打印结果如下

      ---->ddd2
      ---->aaa2
      ---->bbb1
      ---->aaa1
      ---->aaa3
      ---->bbb3
      ---->ccc
      ---->bbb2
      ---->ddd1
      
    • filter 过滤

      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);
      
                  }
              });

      打印结果如下

      ---->aaa2
      ---->aaa1
      ---->aaa3
      
    • Sort 排序

       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

      
           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);
      
                  }
              });
      
      ---->aaa1--->被我处理过了
      ---->aaa2--->被我处理过了
      ---->aaa3--->被我处理过了
    • Match 匹配,是一个最终操作

      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 计数,是一个最终操作

      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 规约,最终操作

           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 提取第一个,最终操作

       Optional<String> optional = 
                      list
                          .stream()
                           .sorted()
                          .findFirst();
           System.out.println("---->"+optional.get()); //aaa1
      
  • parallelStream(并行流)
    并行化之后和之前的代码区别并不大。并且并行操作下,速度会比串行快。但是需要注意的是不用在并行流下排序,并行流做不到排序。

 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接口,再也不用写烦人的匿名内部类了,比如

    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关键字,使得语法更加简洁。

以下是一些例子

(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表达式可以用来简化内部类写法,比如

   //常规代码
   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表达式写一遍。

//一般写法
Predicate<String> predicate=new Predicate<String>() {

            @Override
            public boolean test(String t) {

                return t.startsWith("h");
            }

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

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

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

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

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

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

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

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

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

打印结果如下

aaa1被我处理过了
aaa2被我处理过了
aaa3被我处理过了
  • 显示指定目标类型
    如果Lambda表达式的目标类型是可推导的,就不用指定其类型,如果Lambda表达式的参数类型是可以推导,就不用指定参数类型。因为编译器可以根据上下文自动推导出其类型,然后进行隐式转换。但是,有些场景编译器是没法推导的。比如下面这样的,如果不显示指定类型,编译器就会提示错误
//接口一
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));

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

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

二、指定目标类型

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

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

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

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

最后

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


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

  • 19
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值