• Java8-Lambda表达式


    Java8-Lambda

    1. Lambda表达式

    1.1 Lambda表达式介绍

    1.1.1 lambda表达式作用

    • lambda表达式是Java8的一个新特性,当我们在需要使用实现了某些接口的实例时,即便是该实例只在某一处使用,我们也得为它新建一个实现类(最起码也得使用匿名类来创建该实例)
    • 自JDK8开始,提供了lambda表达式语法特性,能够极大地简化代码量,在线程创建,集合Stream操作等有着广泛应用

    1.2 函数式接口

    1.2.1 函数式接口介绍

    (1)什么是函数式接口?

    • 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口
      • 在JDK8中,接口中提供了default方法,使用default接口修饰的方法在接口定义中有默认的实现,表示该接口的实现类中可以不去实现该方法而使用接口的默认实现;
      • 要是用lambda表达式,需要接口的某一个方法必须要靠类去实现,被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用
    • 函数式接口可以被隐式转换为 lambda 表达式。

    (2)通过注解表示函数式接口

    • 可以在任意函数式接口上使用 @FunctionalInterface注解,这样做可以检测它是否是一个函数式接口,同时 javadoc也会包含一条声明,说明这个接口是一个函数式接口。

      • 当我们在接口中使用@FunctionalInterface,编译器会自动检查该接口是否符合定义,如果不符合函数式接口定义会报错

      • 在实际开发者有两个比较常见的函数式接口:Runnable接口,Comparator接口

        @FunctionalInterface
        public interface Runnable {
            ...
        }
        

    1.3 lambda表达式的使用

    1.3.1 lambda表达式与传统做法相比

    • 在介绍使用lambda表达式之前,我们先来看看原先做法是怎样的,进一步体会到lambda表达式的优点

    • 自定义函数式接口

      @FunctionalInterface
      interface GreetingService {
          void sayMessage(String message);
      }
      

    (1)创建实现该接口的类

    • 传统实现方式:通过创建类来实现接口

      class GreetingServiceImpl implements GreetingService{
          
          @Override
          public void sayMessage(String message) {
              System.out.println("Hello "+message);
          }
          
      }
      
    • 实例化该接口并使用

      public class LamdaTest {
          
          public static void main(String[] args) {
              
              GreetingService greetingService = new GreetingServiceImpl();
              greetingService.sayMessage("Ni187");//Hello Ni187
          
          }
      }
      

    (2)使用静态内部类

    • 通过在使用该接口的类中创建一个静态内部类内部,实现接口方法,进而使用内部类的方法

    • 实际上代码量并没有比传统方法少,因为它只是将类的声明位置换到内部而已,但是由于我们创建的接口会只在这里使用,所以使用静态内部类会提高代码的可读性

      public class LambdaTest {
          
          static class GreetingServiceImpl02 implements GreetingService{
              
              @Override
              public void sayMessage(String message){
                  System.out.println("Hello "+message);
              }
              
          }
          
          public static void main(String[] args) {
              GreetingService greetingService = new GreetingServiceImpl();
              greetingService.sayMessage("Ni187");
              //Hello Ni187
          }
      }
      

    (3)使用局部内部类

    • 在通过使用静态内部类时,如果只是仅仅某一个方法中需要接口的实例化对象,那么我们可以通过创建局部内部类来实例化接口,进一步提高代码的可读性

      public class LambdaTest {
      
          public static void main(String[] args) {
              
              class GreetingServiceImpl02 implements GreetingService{
                  @Override
                  public void sayMessage(String message){
                      System.out.println("Hello "+message);
                  }
              }
              
              GreetingService greetingService = new GreetingServiceImpl02();
              greetingService.sayMessage("Ni187");
              //Hello Ni187
          }
      }
      

    (4)使用匿名类

    • 什么是匿名类?

      • 当我们需要实现某个接口,或者在继承关系中需要重写父类的某个或某些方法时,我们可以在new 父类名或接口名{内容}以这种方式来简化类的声明写法,从而提高我们编码的效率

      • 匿名类的内部书写规则就如同普通类的写法,通过new之后的类名或接口名表示它是某一类的子类或者是接口的实现类,我们可以继承原来类的方法,属性等,也可以重写原来方法,或者添加某些方法,只是这个类没有具体的类名而已

        • 通过反射获取匿名类Class对象

          Class r = new Runnable {
                @Override
                public void run() {}
                public void run2() {}
          }.getClass();
          System.out.println(r);//class com.niss.LambdaTest$1
          
        • 通过反射获取局部类Class对象

          Class RunImpl implements Runnable(){
               @Override
                public void run() {}
          }
          System.out.println(RunImpl.class);//class com.niss.LambdaTest$1RunImpl
          
    • 匿名类的使用方法如下:

      public class LambdaTest {
      
          public static void main(String[] args) {
              GreetingService greetingService = new GreetingService() {
                  @Override
                  public void sayMessage(String message) {
                      System.out.println("Hello "+message);
                  }
              };
              greetingService.sayMessage("Ni187");
              //Hello Ni187
          }
      }
      
    • 可以看出当我们只在某些特定场景使用接口时,完全可以通过创建匿名类的方式来简化编码流程

    (2)lambda表达式

    • 以下是使用lambda语法进行接口的实现

      public class LambdaTest {
          public static void main(String[] args) {
              GreetingService greetingService = message->System.out.println("Hello "+message);
              greetingService.sayMessage("Ni187 ");//Hello Ni187
          }
      }
      
    • 可以看出当我们使用lambda表达式时,代码只保留最核心的部分,注重实现的逻辑,其余统统被精简掉,这就是lambda表达式的一个优势所在


    1.4 lambda表达式语法

    1.4.1 Lambda表达式基本语法

    基本语法:

    • (parameters) ->{ statements };
    • 参数只有一个且不声明类型时可以省略()
    • 当后边方法体只有一条语句时可以省略{}
      • 当该语句为返回结果语句时,不可以加return关键字,除非不省略{}
    • 参数类型可以省略,但是必须全部省略或全部保留
    • 当引用的类方法或对象方法支持泛型时,可以在引用时加上加上泛型约束

    1.4.2 使用示例

    (1)常规使用

    • 接口定义

      interface GreetingService {
          String sayMessage(String message,int age);
      }
      
    • 以下表示方法都等价

      GreetingService greetingService = (message,age)->"Hello "+message+age;
      
      GreetingService greetingService = (String message,int age)->"Hello "+message+age;
      
      GreetingService greetingService = (String message,int age)->{return "Hello "+message+age;};
      

    (2)常用接口通过lambda表达式进行简化构造

    • 通过Lambda表达式实现Comparator接口进行排序

      //定义的person类
      class Person{
          private int id;
          private String name;
          
          构造方法,getter,setter
      
          @Override
          public String toString() {...}
      }
      
      public class LambdaTest {
          
          public static void main(String[] args) {
              
              LinkedList<Person> persons = new LinkedList();
              persons.add(new Person(10,"Tom"));
              persons.add(new Person(20,"Bob"));
              persons.add(new Person(15,"Com"));
              persons.add(new Person(20,"Alice"));
      
              //通过使用lambda表达式,实例化Comparator接口,完成排序工作
              persons.sort((person1, person2)->{
                  if(person1.getId()!=person2.getId()){
                      return person1.getId()-person2.getId();
                  }else{
                      return person1.getName().compareTo(person2.getName());
                  }
              });
              
              for(Person person:persons){
                  System.out.println(person);
              }
          }
      }
      
      结果:
      Person{id=10, name='Tom'}
      Person{id=15, name='Com'}
      Person{id=20, name='Alice'}
      Person{id=20, name='Bob'}
      

    1.5 方法的引用

    1.5.1方法的引用介绍

    • 当我们需要使用接口的方法时,如果我们之前实现过类似的功能方法或者接口,我们可以利用函数引用的方式传入该方法,从而使用该方法来完成我们所要实现的接口方法功能

    • 方法引用的唯一用途是支持Lambda的简写

    • 例如以下

      persons.forEach(value-> System.out.println(value));
      
      persons.forEach(System.out::println);
      
    • 方法引用要求

      • 参数数量和类型要与需要传入接口中定义的一致
      • 接口的抽象方法没有返回值,引用的方法可以有返回值也可以没有
      • 返回值类型要与需要传入接口中定义的一致
      • 对于泛型方法泛型的声明与正常方式相同
        • 静态方法:类名::<泛型声明>静态方法
        • 成员方法:类名::<泛型声明>静态方法
        • 构造方法:类名<泛型声明>::new

    1.5.2 方法引用分类

    类别 使用形式
    静态方法引用 类名 :: 静态方法名
    实例方法引用 对象名(引用名) :: 实例方法名
    类方法引用 类名 :: 实例方法名
    构造方法引用 类名 :: new

    (1)静态方法引用

    • 静态方法引用:引用比较的静态方法,代替comparator接口方法

      public class LambdaTest {
          
          public static int comparePerson(Person person1, Person person2){
              if(person1.getId()!=person2.getId()){
                  return person1.getId()-person2.getId();
              }else{
                  return person1.getName().compareTo(person2.getName());
              }
          }
          
          public static void main(String[] args) {
              LinkedList<Person> persons = new LinkedList();
              ...
              persons.sort(LambdaTest02::comparePerson);
              ...
              persons.forEach(value-> System.out.println(value));
              
          }
      }
      
    • 引用静态方法创建线程

      new Thread(pojo::say).start();
      
    • 注意引用静态方法,在调用该接口时也会对类产生影响,相当于同时调用了该类的静态方法

      public class LambdaTest {
      
          static class Pojo{
              private String name;
      
              static String str = "初始静态变量";
      
              public Pojo(String name) {
                  this.name = name;
              }
      
              public Pojo(){
                  this("初始名字");
              }
      
              String getName(){
                  return this.name;
              }
      
              void setName(String name){
                  this.name = name;
              }
      
              void setAs(Pojo pojo){
                  this.name = pojo.name;
              }
      
              void say(){
                  System.out.println(name+":哈哈");
              }
      
              public static String sayStaticValue(){
                  System.out.println("Pojo:"+Pojo.str);
                  return Pojo.str;
              }
              
              public static void setStaticValue(String str){
                  Pojo.str = str;
              }
          }
      
          public static void main(String[] args) {
              Pojo pojo = new Pojo("");
              GreetingService greetingService = Pojo::setStaticValue;
              greetingService.say("引用了pojo的setStaticValue方法");//引用了pojo的set方法
              System.out.println("===");
              System.out.println(Pojo.str);
              new Thread(Pojo::sayStaticValue).start();//Pojo:引用了pojo的setStaticValue方法
          }
      }
      
      等价于》》》GreetingService greetingService = (str)->Pojo.setStaticValue(str);
      

    (2)实例方法引用

    • 通过引用实例的方法来实现接口的功能

      public class LambdaTest {
          
          public static int comparePerson(Person person1, Person person2){
              if(person1.getId()!=person2.getId()){
                  return person1.getId()-person2.getId();
              }else{
                  return person1.getName().compareTo(person2.getName());
              }
          }
          
          public static void main(String[] args) {
              LinkedList<Person> persons = new LinkedList();
              ...
              persons.sort(new PersonComparator()::comparePerson);
              //实际上persons.sort((p1,p2)->new PersonComparator().comparePerson(p1,p2))
              ...
              persons.forEach(value-> System.out.println(value));
      
          }
      }
      
      class PersonComparator{
          public int comparePerson(Person person1, Person person2){
              if(person1.getId()!=person2.getId()){
                  return person1.getId()-person2.getId();
              }else{
                  return person1.getName().compareTo(person2.getName());
              }
          }
      }
      
    • 注意引用对象实例的方法之后,在调用该接口时也会对实例产生影响,相当于同时调用了该对象的方法

      public class LambdaTest {
          
          ...
              
          public static void main(String[] args) {
      
              Pojo pojo = new Pojo();
              GreetingService greetingService = pojo::setName;
              greetingService.say("引用了pojo的set方法");
              System.out.println("===");
              System.out.println(pojo.getName());//引用了pojo的set方法
          }
      }
      
      等价于》》》GreetingService greetingService = msg->pojo.setName(msg);
      
    • 如果在类中成员方法内容上可以使用this关键字与super关键字进行引用

      • this:表示使用该对象的方法进行引用

        public class MethodRefTest {
        
            public void say(){
                System.out.println("hello.");
            }
        
            public void startThread(){
                new Thread(this::say).start();
            }
        
            public static void main(String[] args) {
                new MethodRefTest().startThread();//hello.
            }
        
        }
        
      • super:表示使用父类的方法进行引用

        class Son extends MethodRefTest{
        
            public void startThreadBySon(){
                new Thread(super::say).start();
            }
        
            public static void main(String[] args) {
               new Son().startThreadBySon();
            }
        }
        

    (3)类方法的引用

    • 这里使用的是:类名::实例方法名

    • 对象方法引用

      class Person{
          ...
          public int compareToAnother(Person person2){
              if(this.getId()!=person2.getId()){
                  return this.getId()-person2.getId();
              }else{
                  return this.getName().compareTo(person2.getName());
              }
          }
         ...
      }
      
      public class LambdaTest02 {
        
          public static void main(String[] args) {
              LinkedList<Person> persons = new LinkedList();
              ...
              persons.sort(Person::compareToAnother);
              //实质上为persns.sort((Person person1, Person person2) -> person1.compareToAnother(perosn2) );
              persons.forEach(value-> System.out.println(value));
      
          }
      }
      
      • 首先要说明的是,方法引用不是方法调用。compareToAnother一定是某个实例调用的,该实例就被作为lambda表达式的第一个参数,然后lambda表达式剩下的参数作为 compareToAnother的参数,这样compareToAnother正好符合lambda表达式的定义
      • 或者也可以这样理解:(Person person1, Person person2) -> { person1.compareToAnother(perosn2) } 需要当前对象调用,然后与另外一个对象比较,并且返回一个int值。可以理解为lambda表达式的第一个参数 person1 赋值给当前对象, 然后person2赋值给 other对象,然后返回int值。

    (4)构造方法引用

    • 构造方法的引用,实质上是调用了构造方法

      interface CreateInter{
          Pojo create(String name);
      }
      
      public class LambdaTest {
          
          public static void main(String[] args) {
              CreateInter createInter = Pojo::new;
              Pojo pojo = createInter.create("这是我的新名字");
              System.out.println(pojo.getName());//这是我的新名字
          }
      }
      
      • 该示例代码调用了public Pojo(String name)构造方法
    • 构造数组对象

      interface StringArrCreatable {
          public String[] createStringArr(int length);
      }
      
      class Test {
          public static void main(String[] args) {
              StringArrCreatable t2 = String[]::new;
              //实际上 StringArrCreatable t2 = (length) -> new String[length];
              String[] arr = t2.createStringArr(5);
              System.out.println(arr.length);//
          }
      }
      

    1.6 函数式接口使用

    1.6.1 常用函数式接口

    • jdk中提供了非常多的函数式接口,大多封装在java.util.funcation

    (1)Supplier接口

    • 该接口只包含一个无参方法:T get()用于获得一个对象,源码:

      @FunctionalInterface
      public interface Supplier<T> {
      
          /**
           * Gets a result.
           *
           * @return a result
           */
          T get();
      }
      
    • 该接口用来获取一个泛型参数指定类型的对象数据

      public class SupplierTest {
      
          public static<T> T getString(Supplier<T> supplier){
              return supplier.get();
          }
      
          public static void main(String[] args) {
              String str = getString(()->"Hello,"+"world.");
      //        实际上相当于:
      //        String str = getString(new Supplier<String>() {
      //            @Override
      //            public String get() {
      //                return "Hello,"+"world.";
      //            }
      //        });
              System.out.println(str);//Hello,"+"world.
          }
      }
      
      
      

    (2)Consumer接口

    • 该接口包含一个accept方法用于接收泛型指定的类型对象,然后使用该对象;还包含一个andThen默认方法用于下一步操作,实现组合操作的过程,源码:

      @FunctionalInterface
      public interface Consumer<T> {
          /**
           * Performs this operation on the given argument.
           * @param t the input argument
           */
          void accept(T t);
      
          default Consumer<T> andThen(Consumer<? super T> after) {
              Objects.requireNonNull(after);
              return (T t) -> { accept(t); after.accept(t); };
          }
      }
      
    • 使用accept方法

      public class ConsumerTest {
          public static void display(String name, Consumer<String> consumer) {
              consumer.accept(name);
          }
      
          public static void main(String[] args) {
              display("你好,世界", System.out::println);//你好,世界
              //实际上相当于:
              display("你好,世界", (String o)->{
                  System.out.println(o);
              });
          }
      }
      
    • 使用andThen方法

      public class ConsumerTest {
          public static void method(String name, Consumer<String> first,Consumer<String> second) {
              first.andThen(second).accept(name);
              //实际上相当于:
              //frist.accpet(name);
              //second.accept(name);
          }
      
          public static void main(String[] args) {
              method("Java:", s-> System.out.println(s.toUpperCase()), s-> System.out.println(s.toLowerCase()));
          }
      }
      

    (3)Predicate接口

    • Predicate接口为断言式接口,主要用来进行条件判断,源码:

      @FunctionalInterface
      public interface Predicate<T> {
      
          boolean test(T t);
      
          default Predicate<T> and(Predicate<? super T> other) {
              Objects.requireNonNull(other);
              return (t) -> test(t) && other.test(t);
          }
      
          default Predicate<T> negate() {
              return (t) -> !test(t);
          }
      
          default Predicate<T> or(Predicate<? super T> other) {
              Objects.requireNonNull(other);
              return (t) -> test(t) || other.test(t);
          }
      
          static <T> Predicate<T> isEqual(Object targetRef) {
              return (null == targetRef)
                      ? Objects::isNull
                      : object -> targetRef.equals(object);
          }
      
          @SuppressWarnings("unchecked")
          static <T> Predicate<T> not(Predicate<? super T> target) {
              Objects.requireNonNull(target);
              return (Predicate<T>)target.negate();
          }
      }
      
    • test方法为测试方法,根据传入参数与方法的实现返回测试布尔数据结果

      Predicate<Integer> bigInteger = (n)->n.compareTo(1000000)>0;
      System.out.println(bigInteger.test(10));//false
      System.out.println(bigInteger.test(1000000000));//true
      
    • andnegateor方法用于传入两个Predicate接口实例,进行条件判断(分别对应&&!,|

      Predicate<Integer> bigInteger = (n) -> n.compareTo(100) > 0;
      Predicate<Integer> evenInteger = (n) -> n % 2 == 0;
      
      evenInteger.and(bigInteger).test(1000);//true
      evenInteger.and(bigInteger).test(100);//false
      evenInteger.and(bigInteger).test(1001);//false
      evenInteger.negate().and(bigInteger).test(1001)//true
      evenInteger.or(bigInteger.negate()).test(102)//true
      
    • isEqual静态方法提供了对传入对象的相等判断,如果传入对象为空,返回测试参数是否为空,否则调用传入对象的equals方法

      Predicate.isEqual("String").test("String")//true
      Predicate.isEqual(null).test(null);//true
      Predicate.isEqual(null).test("String");//false
      Predicate.isEqual("String").test(null)//false
      
    • not静态方法用于根据传入的Predicate实例创造‘否命题’,并返回新的Predicate实例

    (4)Function接口

    • Function接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件

      @FunctionalInterface
      public interface Function<T, R> {
      
          R apply(T t);
      
          default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
              Objects.requireNonNull(before);
              return (V v) -> apply(before.apply(v));
          }
      
          default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
              Objects.requireNonNull(after);
              return (T t) -> after.apply(apply(t));
          }
      
          static <T> Function<T, T> identity() {
              return t -> t;
          }
      }
      
    • apply方法用于将传入的对象,转换为其他类型的对象

      Function<Integer,String> integerToString = (integer)->"String:"+String.valueOf(integer);
      System.out.println(integerToString.apply(256));//String:256
      
    • andThen方法将当前接口实例与传入的Function接口组合操作

      Function<Integer,String> integerToString = (integer)->String.valueOf(integer);
      Function<String,Double> stringToDouble = (str)->Double.valueOf(str);
      System.out.println(integerToString.andThen(stringToDouble).apply(1));//1.0
      
    • compose方法与andThen相反,用于先用传入的Function接口进行转换再使用本接口进行转换操作

      System.out.println(stringToDouble.compose(integerToString).apply(1));
      
  • 相关阅读:
    方差分析
    Rust 指定安装目录
    perl 子函数传入多个数组
    perl 获取目录信息
    R绘图布局包 customLayout
    C语言 自定义函数按行读入文件2
    C语言 自定义函数按行读入文件
    C语言按行读入文件
    sed删除指定行
    mybatis 批量更新
  • 原文地址:https://www.cnblogs.com/nishoushun/p/12631272.html
Copyright © 2020-2023  润新知