zoukankan      html  css  js  c++  java
  • Lambda与函数式接口

    一、简介

    • 什么是Lambda?

      Lambda就是一个匿名函数。

    • 为什么使用Lambda?

      对接口进行非常简洁的实现。可以说是一个语法糖。(原来要新建一个类实现接口,或使用内部类,匿名类)。

    • Lambda对接口要求?

      要求接口中定义的必须要实现的抽象方法有且只有一个。这种接口又称为函数式接口。

      java8之后接口可以包含静态方法和默认实现,这两种方法不算必须要实现的方法。

    二、基础语法

    • lambda是一个匿名方法

      方法包括:方法名,参数列表,返回值,方法体

      匿名,所以不需要名字。

      ( ):用来写参数列表

      { }:用来描述方法体

      ->:标识这是lambda表达式,读作goes to

      eg:
      (type parm ...) -> {
      	...
      }
      
      //函数式接口示例
      @FunctionalInterface
      public interface LambdaInterface {
      		int test(int a,int b);
      }
      //实现上面的接口方法
      LambdaInterface lambda1 = (int a,int b) -> {
      		return a+b;
      }
      //lambda1 实现了接口,就可以使用该接口的方法
      int ret = lambda1.test(10,20);
      
    • 语法精简

      • 参数类型可以省略,因为接口中已经定义好了;
      • 当参数只有一个时,小括号可以不写,没有参数时必须写小括号;
      • 当方法体只有一行时,大括号可以不写(Eclipse中jdk14好像不能写大括号);
      • 当方法体只有一行,并且该行语句是一条返回语句,省略大括号的同时也必须省略return。
      //上面lambda的精简写法:
      LambdaInterface lambda1 = (a,b) -> a+b;
      
    • 说白了就是对一个函数式接口进行了匿名实现,然后就可以用这个接口的方法了。

    三、使用案例

    下面是一些lambda实现函数式接口的常用例子:

    • ArrayList.sort( )

      当对我们自己定义的对象list进行排序时,sort方法需要一个参数,是一个比较器实现的参数。比较器是一个函数式接口,定义如下。

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

      可以看到我们需要实现这个接口传给sort,这就可以用lambda表达式来做。

      //假如有person类,有name,age域,希望能按照age降序排序
      ArrayList<Person> list = new ArrayList<>();
      //往list中存入一些Person对象
      //现在直接sort是不行的,不知道按照什么比较,我们需要对sort传入比较器的实现
      list.sort((o1, o2) -> o2.age - o1.age);
      

      还有Collections.sort( ),Arrays.sort( )等sort也一样的。

    • TreeSet

      自带排序的set,当存入自定义类型的对象时,我们就需要指定排序依据。通过传入比较器的构造方法构造TreeSet。

      //还是Person类,存入treeset中,希望是降序的。
      TreeSet<Person> set = new TreeSet<>((o1, o2) -> o2.age - o1.age);
      //由于TreeSet会去重,比较结果为0的,会被除去。上述方法就不能存年龄相同的对象,
      //可以修改lambda表达式的逻辑达到不去重效果
      TreeSet<Person> set = new TreeSet<>((o1, o2) -> {
      		if(o1.age >= o2.age){
      				return -1;
      		}else{
      				return 1;
      		}
      });
      
    • ActionListener

      //ActionListener是一个函数式接口,触发后的执行动作
      public interface ActionListener extends EventListener {
          /**
           * Invoked when an action occurs.
           * @param e the event to be processed
           */
          public void actionPerformed(ActionEvent e);
      }
      

      Timer定时器可以传入ActionListener的实现,定时触发事件,我们想每隔一秒打印当前时间,来看看怎么做。

      //通过新建一个类来实现接口:
      class TimePrinter implements ActionListener
      {  
         public void actionPerformed(ActionEvent event)
         {  
            System.out.println("At the tone, the time is " + new Date());
         }
      }
      //然后创建这个类的实例就可以传入
      ActionListener listener1 = new TimePrinter();
      Timer t = new Timer(1000, listener1);
      t.start();
      
      //通过lambda来做:
      //可以看到ActionListener的方法,无返回,有一个参数
      ActionListener listener2 = Event -> {
          System.out.println("At the tone, the time is " + new Date());
      };
      //然后就可以用这个实例了
      Timer t = new Timer(1000, listener2);
      t.start();
      

    四、方法引用

    方法引用是java8的新特性之一,是一种可以进一步简化lambda的方法。可以直接引用已有Java类或对象的方法或构造器,即要写的逻辑已有现成的,不需要自己再实现。

    lambda表达式可用方法引用代替的场景可以简要概括为:lambda表达式的主体仅包含一个表达式,且该表达式仅调用了一个已经存在的方法。且lambda的参数和返回值与方法的一致。

    java8方法引用有四种形式:

    • 静态方法引用: ClassName::staticMethodName
    • 构造器引用(引用构造函数): ClassName::new
    • 类的任意对象的实例方法引用: ClassName::instanceMethodName
    • 特定对象的实例方法引用: object::instanceMethodName
    eg:
    // lambda表达式使用:
    Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(s -> System.out.println(s));
    // 静态方法引用:
    Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(System.out::println);
    // 构造器引用:
    Supplier<List<String>> supplier = ArrayList<String>::new;
    // 类的任意对象的实例方法引用:
    Arrays.sort(strs,(s1,s2)->s1.compareToIgnoreCase(s2));
    	// 转成方法引用:
    	Arrays.sort(strs, String::compareToIgnoreCase);
    

    五、闭包

    lambda代码块中可以使用外部(lambda外部)定义的变量,但是有一个重要的限制,即这个变量的值是不能改变的,既不能在lambda中改变,也不能在外部改变。说白了,如果你使用了外部变量,这个变量就好像变为了一个final变量,当你改变它时就会报错。这个特性又称为“闭包”。

    lambda表达式定义的位置和它实际执行的位置往往不同,很可能延后很久才会执行。在定义时,传入的变量就将给定一个值,如果允许改变传进来的变量,在定义和执行之间又将该变量改变了,逻辑将会变得混乱,在并发执行时也将会不安全,所以禁止了这样做。

    六、标准函数式接口

    在jdk8中,引入了一个新的包java.util.function。这个包下有几十个定义好的接口,不过大致可以分为以下几类。

    lambda搭配function包提供的函数式接口,使得编程更加简洁且容易理解。

    我们可以根据自己的任务类型选用不同的接口,然后使用lambda实现即可调用。

    如:

    // 实现一个Predicate接口用于判断传入字符是不是"sb".
    Predicate<String> pd = (s) -> s.equals("sb");
    
    System.out.println("u r "+ pd.test("sb") + " sb.");
    

    此外,接口Java API还提供了基本类型的规范接口,如接口IntFunction,表示传入参数是int类型,返回R类型。使用应该优先选用规范接口,这样可以减少自动装箱。

  • 相关阅读:
    (网页)html中页面传递参数不用cookie不用缓存,js方法搞定
    (网页)table加上分页,优点可随便加样式
    (后端)分页比较好的语句
    (后端)Spring手动回滚事务
    jquery 插件开发
    mysql 触发器
    mysql 存储过程
    YaHoo 前端优化军规
    html5 离线存储
    java 网络编程
  • 原文地址:https://www.cnblogs.com/cpcpp/p/14570119.html
Copyright © 2011-2022 走看看