Java8学习笔记(1) -- 从函数式接口说起

来源:互联网 发布:生e经 店铺数据分析 编辑:程序博客网 时间:2024/05/22 08:17

希望本文能够成为Java8 Lambda表达式的快速入门指南。

函数式接口

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

@FunctionalInterfacepublic interface Runnable {    public abstract void run();}

Lambda语法糖

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

List<String> words = Arrays.asList("apple", "banana", "pear");words.sort(new Comparator<String>() {    @Override    public int compare(String w1, String w2) {        return Integer.compare(w1.length(), w2.length());    }});

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

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

words.sort((w1, w2) -> {    return Integer.compare(w1.length(), w2.length());});
如果Lambda表达式的代码块只是return后面跟一个表达式,那么还可以进一步简化:

words.sort(    (w1, w2) -> Integer.compare(w1.length(), w2.length()));
注意,表达式后面是没有分号的!如果只有一个参数,那么包围参数的圆括号可以省略:

words.forEach(word -> {    System.out.println(word);});
如果表达式不需要参数呢?好吧,那也必须有圆括号,例如:

Executors.newSingleThreadExecutor().execute(    () -> {/* do something. */} // Runnable);

方法引用

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

List<Integer> ints = Arrays.asList(1, 2, 3);ints.sort(Integer::compare);
第二种引用某个特定对象的实例方法,例如前面那个遍历并打印每一个word的例子可以写成这样:

words.forEach(System.out::println);
第三种引用某个类的实例方法,例如:

words.stream().map(word -> word.length()); // lambdawords.stream().map(String::length); // method reference

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

// lambdawords.stream().map(word -> {    return new StringBuilder(word);});// constructor referencewords.stream().map(StringBuilder::new);

什么时候用Lambda表达式

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

Runnable task = () -> {    // do something};Comparator<String> cmp = (s1, s2) -> {    return Integer.compare(s1.length(), s2.length());};

预定义函数式接口

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

@FunctionalInterfacepublic interface Predicate<T> {    boolean test(T t);}
Predicate用来判断一个对象是否满足某种条件,比如,单词是否由六个以上字母组成:

words.stream()    .filter(word -> word.length() > 6)    .count();

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

@FunctionalInterfacepublic interface Function<T, R> {    R apply(T t);}
下面的例子将集合里的每一个整数都乘以2:

ints.stream().map(x -> x * 2);

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

@FunctionalInterfacepublic interface Consumer<T> {    void accept(T t);}

对原有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()方法的实现代码:

public interface Iterable<T> {    ...    default void forEach(Consumer<? super T> action) {        Objects.requireNonNull(action);        for (T t : this) {            action.accept(t);        }    }    ...}

接口的静态方法

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

public interface Function<T, R> {    ...    /**     * Returns a function that always returns its input argument.     *     * @param <T> the type of the input and output objects to the function     * @return a function that always returns its input argument     */    static <T> Function<T, T> identity() {        return t -> t;    }...}

变量捕获

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

int a = 100;Runnable x = new Runnable() {    @Override    public void run() {        System.out.println(a);    }    };
在Java8之前,a必须是final的才能被x看到。下面用Lambda表达式重写上面的例子:

int a = 100;Runnable x = () -> {    System.out.println(a);};

结论

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

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 滴滴订单未支付怎么办 在天猫买了假货怎么办 13岁有痛经怎么办 新店排名被降怎么办 淘宝店铺跳失率过大怎么办 国外打印很贵怎么办 店铺访客被拒怎么办 steam锁支付后怎么办 芦荟茎太长了怎么办 网店加盟被骗怎么办 分期乐忘记账号怎么办 贴墙纸遇到插头怎么办 用了屈臣氏过敏怎么办 商品房内电箱不符合标准怎么办 淘宝上恶意退货怎么办 退货率高了怎么办 淘宝店被关了钱怎么办 买家不申请退款怎么办 被买家恶意投诉怎么办 淘宝投诉后退款怎么办 天猫投诉不成功怎么办 苹果手机打不开流量怎么办 苹果6流量打不开怎么办 苹果笔记本电脑打不开软件怎么办 苹果软件蜂窝打不开怎么办 苹果手机wifi打不开怎么办 苹果app变成韩语怎么办 ipad键盘变成英文怎么办 苹果8商店打不开怎么办 苹果手机不能下载怎么办 天猫魔盒遥控器丢了怎么办 电视盒子声音小怎么办 宽带los亮红灯怎么办 猫的lan灯不亮怎么办 光猫los灯不亮怎么办 移动宽带红灯亮怎么办 ie打不开网页怎么办win10 win10系统ie打不开怎么办 笔记本电脑玩lol卡怎么办 火狐浏览器电脑版打不开怎么办 手机淘宝没密码怎么办