【JAVA8新特性】- Lambda表达式(二)(转载)

继续上一篇

三 lambda表达式的类型

我们都知道,Java是一种强类型语言。所有的方法参数都有类型,那么lambda表达式是一种什么类型呢?

View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //...
    }
};
        
button.setOnClickListener(listener);

如上所示,以往我们是通过使用单一方法的接口来代表一个方法并且重用它。
在lambda表达式中,仍使用的和之前一样的形式。我们叫做函数式接口(functional interface)。如我们之前button的点击响应事件使用的View.OnClickListener就是一个函数式接口。

public class View implements Drawable.Callback, KeyEvent.Callback,
    AccessibilityEventSource {
    ...
    
    public interface OnClickListener {
        void onClick(View v);
    }
    ...
}

那究竟什么样的接口是函数式接口?
函数式接口是只有一个抽象方法的接口,用作表示lambda表达式的类型。 比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。java 8提供 @FunctionalInterface作为注解,这个注解是非必须的,只要接口符合函数式接口的标准(即只包含一个方法的接口),虚拟机会自动判断,但最好在接口上使用注解@FunctionalInterface进行声明,以免团队的其他人员错误地往接口中添加新的方法。举例如下:

@FunctionalInterface
public interface Runnable { void run(); }

public interface Callable<V> { V call() throws Exception; }

public interface ActionListener { void actionPerformed(ActionEvent e); }

public interface Comparator<T> { 
    int compare(T o1, T o2); 
    
    boolean equals(Object obj); 
}

注意最后这个Comparator接口。它里面声明了两个方法,貌似不符合函数接口的定义,但它的确是函数接口。这是因为equals方法是Object的,所有的接口都会声明Object的public方法——虽然大多是隐式的。所以,Comparator显式的声明了equals不影响它依然是个函数接口。

Java中的lambda无法单独出现,它需要一个函数式接口来盛放,lambda表达式方法体其实就是函数接口的实现。即Lambda表达式不能脱离目标类型存在,这个目标类型就是函数式接口
看下面的例子:

String []datas = new String[] {"peng","zhao","li"};
Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length());
Arrays.sort(datas,comp);
Stream.of(datas).forEach(param -> {System.out.println(param);}); 

根据上面代码,Lambda表达式被赋值给了comp函数接口变量。

你可以用一个lambda表达式为一个函数接口赋值:

Runnable r1 = () -> {System.out.println("Hello Lambda!");};

然后再赋值给一个Object:

Object obj = r1;

但却不能这么干

Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!

必须显式的转型成一个函数接口才可以:

Object o = (Runnable) () -> { System.out.println("hi"); }; // correct

一个lambda表达式只有在转型成一个函数接口后才能被当做Object使用。所以下面这句也不能编译:

 System.out.println( () -> {} ); //错误! 目标类型不明

必须先转型

System.out.println( (Runnable)() -> {} ); // 正确

假设你自己写了一个函数接口,长的跟Runnable一模一样:

@FunctionalInterface
public interface MyRunnable {
    public void run();
}

那么

Runnable r1 =    () -> {System.out.println("Hello Lambda!");};
MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};

都是正确的写法。这说明一个lambda表达式可以有多个目标类型(函数接口),只要函数匹配成功即可。但需注意一个lambda表达式必须至少有一个目标类型
JDK预定义了很多函数接口以避免用户重复定义。最典型的是Function:

@FunctionalInterface
public interface Function<T, R> {  
    R apply(T t);
}

还有一个Predicate,用来判断某项条件是否满足。经常用来进行筛滤操作:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

综上所述,一个lambda表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。

lambda表达式可使用的变量

举例

@Test
public void test1(){
    //将为列表中的字符串添加前缀字符串
    String waibu = "lambda :";
    List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
    List<String>execStrs = proStrs.stream().map(chuandi -> {
        Long zidingyi = System.currentTimeMillis();
        return waibu + chuandi + " -----:" + zidingyi;
    }).collect(Collectors.toList());
    
    execStrs.forEach(System.out::println);
}

输出

lambda :Ni -----:1498722594781
lambda :Hao -----:1498722594781
lambda :Lambda -----:1498722594781

变量waibu :外部变量

变量chuandi :传递变量

变量zidingyi :内部自定义变量

lambda表达式可以访问给它传递的变量,访问自己内部定义的变量,同时也能访问它外部的变量。不过lambda表达式访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)

当在表达式内部修改waibu = waibu + " ";时,IDE就会提示你:

Local variable waibu defined in an enclosing scope must be final or effectively final

编译时会报错。因为变量waibu被lambda表达式引用,所以编译器会隐式的把其当成final来处理。

以前Java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。现在java8对这个限制做了优化,可以不用显示使用final修饰,但是编译器隐式当成final来处理

lambda表达式作用域

总体来说,Lambda表达式的变量作用域与内部类非常相似,只是条件相对来说,放宽了些,以前内部类要想引用外部类的变量,必须像下面这样

final String[] datas = new String[] { "peng", "Zhao", "li" };
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(datas);
    }
}).start();

将变量声明为final类型的,现在不用了,在Java 8中可以这样写代码

String []datas = new String[] {"peng","Zhao","li"};
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(datas);
    }
}).start();

也可以这样写

new Thread(() -> System.out.println(datas)).start();

看了上面的两段代码,能够发现一个显著的不同,就是Java 8中内部类或者Lambda表达式对外部类变量的引用条件放松了,不要求强制的加上final关键字了,但是Java 8中要求这个变量是effectively final。What is effectively final?

Effectively final就是有效只读变量,意思是这个变量可以不加final关键字,但是这个变量必须是只读变量,即一旦定义后,在后面就不能再随意修改,如下代码会编译出错,如下
···
String []datas = new String[] {"peng","Zhao","li"};
datas = null;
new Thread(() -> System.out.println(datas)).start();
···
Java中内部类以及Lambda表达式中也不允许修改外部类中的变量,这是为了避免多线程情况下的race condition

lamdba 表达式中的this概念

在lambda中,this不是指向lambda表达式产生的那个对象,而是声明它的外部对象。
例如:

package com.demo;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class WhatThis {
    
    public void whatThis(){
        //转全小写
         List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
        List<String> execStrs = proStrs.stream().map(str -> {
             System.out.println(this.getClass().getName());
             return str.toLowerCase();
        }).collect(Collectors.toList());
        
        execStrs.forEach(System.out::println);
    }
    
    public static void main(String[] args) {
        WhatThis wt = new WhatThis();
        wt.whatThis();
    }

}

输出:

com.wzg.test.WhatThis
com.wzg.test.WhatThis
com.wzg.test.WhatThis
ni
hao
lambda
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容