zoukankan      html  css  js  c++  java
  • 【转】Java8学习笔记(1) -- 从函数式接口说起

    http://blog.csdn.net/zxhoo/article/details/38349011

    函数式接口

    理解Functional Interface(函数式接口,以下简称FI)是学习Java8 Lambda表达式的关键所在,所以放在最开始讨论。FI的定义其实很简单:任何接口,如果只包含唯一一个抽象方法,那么它就是一个FI。为了让编译器帮助我们确保一个接口满足FI的要求(也就是说有且仅有一个抽象方法),Java8提供了@FunctionalInterface注解。举个简单的例子,Runnable接口就是一个FI,下面是它的源代码:

    [java] view plain copy
     
    1. @FunctionalInterface  
    2. public interface Runnable {  
    3.     public abstract void run();  
    4. }  

    Lambda语法糖

    为了能够方便、快捷、幽雅的创建出FI的实例,Java8提供了Lambda表达式这颗语法糖。下面我用一个例子来介绍Lambda语法。假设我们想对一个List<String>按字符串长度进行排序,那么在Java8之前,可以借助匿名内部类来实现:

    [java] view plain copy
     
    1. List<String> words = Arrays.asList("apple", "banana", "pear");  
    2. words.sort(new Comparator<String>() {  
    3.   
    4.     @Override  
    5.     public int compare(String w1, String w2) {  
    6.         return Integer.compare(w1.length(), w2.length());  
    7.     }  
    8.   
    9. });  

    上面的匿名内部类简直可以用丑陋来形容,唯一的一行逻辑被五行垃圾代码淹没。根据前面的定义(并查看Java源代码)可知,Comparator是个FI,所以,可以用Lambda表达式来实现:

    [java] view plain copy
     
    1. words.sort((String w1, String w2) -> {  
    2.     return Integer.compare(w1.length(), w2.length());  
    3. });  

    代码变短了好多!仔细观察就会发现,Lambda表达式,很像一个匿名的方法,只是圆括号内的参数列表和花括号内的代码被->分隔开了。垃圾代码写的越少,我们就有越多的时间去写真正的逻辑代码,不是吗?是的!圆括号里的参数类型是可以省略的:

    [java] view plain copy
     
    1. words.sort((w1, w2) -> {  
    2.     return Integer.compare(w1.length(), w2.length());  
    3. });  

    如果Lambda表达式的代码块只是return后面跟一个表达式,那么还可以进一步简化:

    [java] view plain copy
     
    1. words.sort(  
    2.     (w1, w2) -> Integer.compare(w1.length(), w2.length())  
    3. );  

    注意,表达式后面是没有分号的!如果只有一个参数,那么包围参数的圆括号可以省略:

    [java] view plain copy
     
    1. words.forEach(word -> {  
    2.     System.out.println(word);  
    3. });  

    如果表达式不需要参数呢?好吧,那也必须有圆括号,例如:

    [java] view plain copy
     
    1. Executors.newSingleThreadExecutor().execute(  
    2.     () -> {/* do something. */} // Runnable  
    3. );  

    方法引用

    有时候Lambda表达式的代码就只是一个简单的方法调用而已,遇到这种情况,Lambda表达式还可以进一步简化为方法引用(Method References)。一共有四种形式的方法引用,第一种引用静态方法,例如:

    [java] view plain copy
     
    1. List<Integer> ints = Arrays.asList(1, 2, 3);  
    2. ints.sort(Integer::compare);  

    第二种引用某个特定对象的实例方法,例如前面那个遍历并打印每一个word的例子可以写成这样:

    [java] view plain copy
     
    1. words.forEach(System.out::println);  

    第三种引用某个类的实例方法,例如:

    [java] view plain copy
     
    1. words.stream().map(word -> word.length()); // lambda  
    2. words.stream().map(String::length); // method reference  

    第四种引用类的构造函数,例如:

    [java] view plain copy
     
    1. // lambda  
    2. words.stream().map(word -> {  
    3.     return new StringBuilder(word);  
    4. });  
    5. // constructor reference  
    6. words.stream().map(StringBuilder::new);  

    什么时候用Lambda表达式

    既然Lambda表达式这么好用,那么,可以在哪些地方使用呢?如果你真正明白了什么是FI(很容易),应该立刻就能给出答案:任何可以接受一个FI实例的地方,都可以用Lambda表达式。比如,虽然上面给出的例子都是把Lambda表达式当作方法参数传递,但实际上你也可以定义变量:

    [java] view plain copy
     
    1. Runnable task = () -> {  
    2.     // do something  
    3. };  
    4.   
    5. Comparator<String> cmp = (s1, s2) -> {  
    6.     return Integer.compare(s1.length(), s2.length());  
    7. };  

    预定义函数式接口

    Java8除了给Runnable,Comparator等接口打上了@FunctionalInterface注解之外,还预定义了一大批新的FI。这些接口都在java.util.function包里,下面简单介绍其中的几个。

    [java] view plain copy
     
    1. @FunctionalInterface  
    2. public interface Predicate<T> {  
    3.     boolean test(T t);  
    4. }  

    Predicate用来判断一个对象是否满足某种条件,比如,单词是否由六个以上字母组成:

    [java] view plain copy
     
    1. words.stream()  
    2.     .filter(word -> word.length() > 6)  
    3.     .count();  

    Function表示接收一个参数,并产生一个结果的函数:

    [java] view plain copy
     
    1. @FunctionalInterface  
    2. public interface Function<T, R> {  
    3.     R apply(T t);  
    4. }  

    下面的例子将集合里的每一个整数都乘以2:

    [java] view plain copy
     
    1. ints.stream().map(x -> x * 2);  

    Consumer表示对单个参数进行的操作,前面例子中的forEach()方法接收的参数就是这种操作:

    [java] view plain copy
     
    1. @FunctionalInterface  
    2. public interface Consumer<T> {  
    3.     void accept(T t);  
    4. }  

    对原有API的增强

    为了充分发挥Lambda的威力,Java8对很多老的类库进行了增强,给它们配备了Lambda武器。比如前面例子中用到的forEach()方法,实际上是添加到Iterable接口中的。而多次出现的stream()方法,则是添加在了Collection接口里。用过Ruby,Scala,Groovy等语言的Java程序员,可能已经对在这些语言里很好实现的外部迭代器模式垂涎很久了。虽然Google的Guava可以在一定程度上弥补Java的这种缺陷,但是Java8的Lambda才真正让Java朝着函数式编程迈进了一大步。

    接口的默认方法

    细心的读者可能会发现一个问题,给Iterable和Collection等接口增加方法,岂不是会破坏接口的向后兼容性?是的,为了保证API的向后兼容性,Java8对接口的语法进行了较大的调整,增加了默认方法(Default Methods)。下面是forEach()方法的实现代码:

    [java] view plain copy
     
    1. public interface Iterable<T> {  
    2.     ...  
    3.     default void forEach(Consumer<? super T> action) {  
    4.         Objects.requireNonNull(action);  
    5.         for (T t : this) {  
    6.             action.accept(t);  
    7.         }  
    8.     }  
    9.     ...  
    10. }  

    接口的静态方法

    除了抽象方法和默认方法,从Java8开始,接口也可以有静态(static)方法了。有了这个语法,我们就可以把和接口相关的帮助方法(Helper Methods)直接定义在接口里了。比如Function接口就定义了一个工厂方法indentity():

    [java] view plain copy
     
    1. public interface Function<T, R> {  
    2.     ...  
    3.     /** 
    4.      * Returns a function that always returns its input argument. 
    5.      * 
    6.      * @param <T> the type of the input and output objects to the function 
    7.      * @return a function that always returns its input argument 
    8.      */  
    9.     static <T> Function<T, T> identity() {  
    10.         return t -> t;  
    11.     }  
    12. ...  
    13. }  

    变量捕获

    内部类一样,Lambda也可以访问外部(词法作用域)变量,规则基本一样。Java8之前,内部类只能访问final类型的变量,Java8放宽了这种限制,只要变量实际上不可变(effectively final)就可以。换句话说,如果你给变量加上final关键字编译器也不报错,那么去掉final关键字后,它就是effectively final的。看下面的例子:

    [java] view plain copy
     
    1. int a = 100;  
    2. Runnable x = new Runnable() {  
    3.   
    4.     @Override  
    5.     public void run() {  
    6.         System.out.println(a);  
    7.     }  
    8.       
    9. };  

    在Java8之前,a必须是final的才能被x看到。下面用Lambda表达式重写上面的例子:

    [java] view plain copy
     
    1. int a = 100;  
    2. Runnable x = () -> {  
    3.     System.out.println(a);  
    4. };  

    结论

    可以看到,为了支持Lambda表达式,Java8对Java语言做了很大的调整。但Lambda表达式并非只是Java语法糖,而是由编译器和JVM共同配合来实现的,这一点我会在下一篇文章里详细介绍。

  • 相关阅读:
    泛微云桥e-Bridge 目录遍历,任意文件读取
    (CVE-2020-8209)XenMobile-控制台存在任意文件读取漏洞
    selenium 使用初
    将HTML文件转换为MD文件
    Python对word文档进行操作
    使用java安装jar包出错,提示不是有效的JDK java主目录
    Windows server 2012安装VM tools异常解决办法
    ifconfig 命令,改变主机名,改DNS hosts、关闭selinux firewalld netfilter 、防火墙iptables规则
    iostat iotop 查看硬盘的读写、 free 查看内存的命令 、netstat 命令查看网络、tcpdump 命令
    使用w uptime vmstat top sar nload 等命令查看系统负载
  • 原文地址:https://www.cnblogs.com/exmyth/p/6091177.html
Copyright © 2011-2022 走看看