zoukankan      html  css  js  c++  java
  • Lambda表达式

    什么是Lambda表达式

    Lambda 表达式是一种匿名函数,简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。

    它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 语言的表达能力得到了提升。

    Lambda表达式的语法

    基本语法:  (parameters) -> expression  或者 (parameters) ->{ statements}

    举例说明:

    // 1. 不需要参数,返回值为 5  
    () -> 5  
      
    // 2. 接收一个参数(数字类型),返回其2倍的值  
    x -> 2 * x  
      
    // 3. 接受2个参数(数字),并返回他们的差值  
    (x, y) -> x – y  
      
    // 4. 接收2个int型整数,返回他们的和  
    (int x, int y) -> x + y  
      
    // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
    (String s) -> System.out.print(s)

    什么是函数式接口

      再对上面进行举例说明之前,必须先来理解下函数式接口,因为Lambda是建立在函数式接口的基础上的。

       (1)只包含一个抽象方法的接口,称为函数式接口。

      (2)你可以通过 Lambda 表达式来创建该接口的对象。

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

    @FunctionalInterface

     通过JDK8 源码javadoc,可以知道@FunctionalInterface有以下特点:

    1.该注解只能标记在"有且仅有一个抽象方法"的接口上,表示函数式接口。
    2.JDK8接口中的静态方法和默认方法,都不算是抽象方法。
    3.接口默认继承 java.lang.Object,所以如果接口显示声明覆盖了Object中的方法,那么也不算抽象方法。
    4.该注解不是必须的,如果一个接口符合"函数式编程"定义,那么加不加该注解都没有影响。加上该注解能够 更好地让编译器进行检查,如果编写的不是函数式接口,但是加上了@FunctionalInterface 那么编译器会报错。

    通过接口传递代码

    接口以及面向接口的编程,针对接口而非具体类型进行编程,可以降低程序的耦合性,提高灵活性,提高复用性。
    接口常被用于传递代码,比如,我们知道File有如下方法:

    public File[] listFiles(FilenameFilter filter)
    listFiles需要的其实不是FilenameFilter对象,而是它包含的如下方法:
    boolean accept(File dir, String name);
    或者说,listFiles希望接受一段方法代码作为参数,但没有办法直接传递这个方法代码本身,只能传递一个接口。

    listFiles示例

    File f = new File(".");
    //通过接口传递行为代码,就要传递一个实现了该接口的实例对象,在之前的章节中,最简洁的方式是使用匿名内部类
    //列出当前目录下的所有扩展名为.txt的文件
    File[] files = f.listFiles(new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
            if (name.endsWith(".txt")) {
                return true;
            }
            return false;
        }
    });
    //Java 8提供了一种新的紧凑的传递代码的语法:Lambda表达式。对于前面列出文件的例子,代码可以改为:
    {
        File[] files = f.listFiles((File dir, String name) -> {
            if (name.endsWith(".txt")) {
                return true;
            }
            return false;
        });
    }
    // 可以看出,相比匿名内部类,传递代码变得更为直观,不再有实现接口的模板代码,不再声明方法,也没有名字,而是直接给出了方法的实现代码。
    // Lambda表达式由->分隔为两部分,前面是方法的参数,后面{}内是方法的代码。上面的代码可以简化为:
    {
        File[] files = f.listFiles((File dir, String name) -> {
            return name.endsWith(".txt");
        });
    }
    //当主体代码只有一条语句的时候,括号和return语句也可以省略,上面的代码可以变为:
    {
        File[] files = f.listFiles((File dir, String name) -> name.endsWith(".txt"));
    }
    // 注意:没有括号的时候,主体代码是一个表达式,这个表达式的值就是函数的返回值,结尾不能加分号,也不能加return语句。
    {
        File[] files = f.listFiles((dir, name) -> name.endsWith(".txt"));
    }
    //之所以可以省略方法的参数类型,是因为Java可以自动推断出来,它知道listFiles接受的参数类型是FilenameFilter,这个接口只有一个方法accept,这个方法的两个参数类型分别是File和String。

    扩展:

    当参数只有一个的时候,参数部分的括号可以省略。比如,File还有如下方法:
    public File[] listFiles(FileFilter filter)

    FileFilter的定义为:

    public interface FileFilter { boolean accept(File pathname); }

    使用FileFilter重写上面的列举文件的例子,代码可以为:

    File[] files = f.listFiles(path -> path.getName().endsWith(".txt"));

    Comparator示例

    再如,类Collections中的很多方法都接受一个参数Comparator,比如: 
    public static <T> void sort(List<T> list, Comparator<? super T> c)
    它们需要的也不是Comparator对象,而是它包含的如下方法:
    int compare(T o1, T o2);
    但是,没有办法直接传递方法,只能传递一个接口。
    //将files按照文件名排序,代码为:
    Arrays.sort(files, new Comparator<File>() {
        @Override
        public int compare(File f1, File f2) {
            return f1.getName().compareTo(f2.getName());
        }
    });
    // 排序的代码用Lambda表达式可以写为:
    {
        Arrays.sort(files, (f1, f2) -> f1.getName().compareTo(f2.getName()));
    }

    扩展:

    Comparator中的复合方法

    Comparator接口定义了如下静态方法:

    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }
    这个方法是什么意思呢?它用于构建一个Comparator,比如,在前面的例子中,对文件按照文件名排序的代码为:
    Arrays.sort(files, (f1, f2) -> f1.getName().compareTo(f2.getName()));
    使用comparing方法,代码可以简化为:
    Arrays.sort(files, Comparator.comparing(File::getName));
    comparing方法为什么能达到这个效果呢?它构建并返回了一个符合Comparator接口的Lambda表达式,这个Comparator接受的参数类型是File,它使用了传递过来的函数代码keyExtractor将File转换为String进行比较。像comparing这样使用复合方式构建并传递代码并不容易阅读,但调用者很方便,也很容易理解。
    Comparator还有很多默认方法,我们看两个:
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res ! = 0) ? res : other.compare(c1, c2);
        };
    }
    reversed返回一个新的Comparator,按原排序逆序排。thenComparing也返回一个新的Comparator,在原排序认为两个元素排序相同的时候,使用传递的Comparator other进行比较。
    看一个使用的例子,将学生列表按照分数倒序排(高分在前),分数一样的按照名字进行排序:
    students.sort(Comparator.comparing(Student::getScore)
                              .reversed()
                              .thenComparing(Student::getName));

    ExecutorService示例

    又如,异步任务执行服务ExecutorService,提交任务的方法有:

    <T> Future<T> submit(Callable<T> task);
    Future<? > submit(Runnable task);
    Callable和Runnable接口也用于传递任务代码。 
    //使用匿名内部类
    //提交一个最简单的任务,代码为:
    ExecutorService executor = Executors.newFixedThreadPool(100);
    executor.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println("hello world");
        }
    });
    // 提交任务的代码用Lambda表达式可以写为:
    executor.submit(() -> System.out.println("hello"));
    //参数部分为空,写为()。

    扩展:

    //与匿名内部类类似,Lambda表达式也可以访问定义在主体代码外部的变量,
    // 但对于局部变量,它也只能访问final类型的变量,与匿名内部类的区别是,它不要求变量声明为final,但变量事实上不能被重新赋值。比如:
    {
        String msg = "hello world";
        executor.submit(() -> System.out.println(msg));
    }
    // 可以访问局部变量msg,但msg不能被重新赋值,如果这样写:
    {
        String msg = "hello world";
        msg = "good morning";
        // executor.submit(() -> System.out.println(msg));
    }
    //Java编译器会提示错误。
    //这个原因与匿名内部类是一样的,Java会将msg的值作为参数传递给Lambda表达式,为Lambda表达式建立一个副本,它的代码访问的是这个副本,而不是外部声明的msg变量。
    // 如果允许msg被修改,则程序员可能会误以为Lambda表达式读到修改后的值,引起更多的混淆。
    //为什么非要建立副本,直接访问外部的msg变量不行吗?不行,因为msg定义在栈中,当Lambda表达式被执行的时候,msg可能早已被释放了。
    // 如果希望能够修改值,可以将变量定义为实例变量,或者将变量定义为数组,比如:
    {
        String[] msg = new String[]{"hello world"};
        msg[0] = "good morning";
        executor.submit(() -> System.out.println(msg[0]));
    }
     参考:
    https://www.cnblogs.com/qdhxhz/p/9393724.html
    java编程的逻辑-26.1 Lambda表达式
     
  • 相关阅读:
    Power of Matrix(uva11149+矩阵快速幂)
    Training little cats(poj3735,矩阵快速幂)
    233 Matrix(hdu5015 矩阵)
    Contemplation! Algebra(矩阵快速幂,uva10655)
    Another kind of Fibonacci(矩阵)
    欢迎使用CSDN-markdown编辑器
    M斐波那契数列(矩阵快速幂+费马小定理)
    Fibonacci(矩阵)
    常系数线性递推的第n项及前n项和 (Fibonacci数列,矩阵)
    Evolution(矩阵快速幂)zoj2853
  • 原文地址:https://www.cnblogs.com/ooo0/p/13691427.html
Copyright © 2011-2022 走看看