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类型。使用应该优先选用规范接口,这样可以减少自动装箱。

原文地址:https://www.cnblogs.com/cpcpp/p/14570119.html