java8新特性学习笔记之唠唠“匿名内部类与lambda”

负一、知道啥是匿名内部类不?

要使用lambda,我觉得你至少得明白匿名内部类是个啥。“o -> o.getName”是lambda表达式,"Book::getName"也是一个lambda表达式,表达式表达式,表达的是什么呢?当你在看到这个式子的时候不懵逼吗?你好像知道要取个什么东西的名字,但是是怎么取名字的,取出来的名字怎么处理,你真的知道嘛?懵逼不,不懂匿名内部类的时候,搁我我也懵。

所以我在准备之前还要给你介绍一下lambda为何而存在,这样你才能知道在什么时候能够用它,再然后,你才能开始学习它怎么使用。

Let's start:

举个栗子,数组排序见过吧?

List<Long> num = Lists.newArrayList(1L,2L,5L,3L,4L,30L,15L,0L,8L,2L);

num.sort(new Comparator<Long>() {
            @Override
            public int compare(Long o1, Long o2) {
                return (int)(o1-o2);
            }
        });

Comparator本来是一个接口,它有一个唯一的未实现的抽象方法,那就是compare(Long o1, Long o2)这个方法。

但是你是不是发现了我既然知道我要的是这个接口(Comparator),我也不用选我要实现的哪个抽象方法(就一个compare没实现了)

——那为啥还要我特地说一声我实现的是这个接口(Comparator)的这个方法(compare(o1,o2))?嗯?

于是这帮搞语言的懒鬼决定整一个更方便写的东西。

Lambda

怎么个方便法呢?

我们先来整理一下我们写的那一堆垃圾中,有哪些是有价值的可回收垃圾

...

是不是只有一个入参、一个方法体内容,最多再有个泛型确定方法体可以调用哪些方法处理入参。

于是懒鬼们为了方便更广大的懒鬼们,说,那你们给个入参给个处理方法,剩下的我们帮你写呗

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

(o1, o2) -> o1-o2

↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

你让我这么给我就这么给呗,其他的想都不要想,分号都没得(* ̄︶ ̄)

附上最后它的模样

List<Long> num = Lists.newArrayList(1L,2L,5L,3L,4L,30L,15L,0L,8L,2L);
num.sort((o1, o2) -> (int) (o1-o2));

 


真·一行更比五行强?!


 

(以上的场景都是我yy的)

P.S.:顺便说一句,只有一个抽象方法的接口,就叫函数式接口,你要是不能判断,可以在接口前加上@FunctionalInterface注解来校验,这个注解在程序中并不会有什么具体的作用,只是会告诉编译器更严格地检查这个接口的代码,保证此接口只有一个抽象的方法。

 

零、唠前准备:

用到哪些实体和接口呢?

1.没事儿找事儿干类:

public class JustDoSomething {
    //非静态方法
    public int justDoSomething(Object o1, Object o2){
        System.out.println(-1);
        return -1;
    }

    //静态方法
    public static int justDoSomeOtherThing(Object o1, Object o2){
        System.out.println(-2);
        return -2;
    }
}

 

没了

 

一、lambda介绍:

1.lambda的本质是?

一个可以代替特定匿名内部类(有且只有一个未实现的抽象方法)的表达式。可以用下面的代码来说明一下

/**
 * 这是刚刚的排序方法
 */
default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }

可以看到,在lambda出现的位置上,本来应该用一个实现了比较器接口的实现类来当作参数的,所以现在的情况就是lambda能够替代这个类。

我们再来看一个方法

Thread t = new Thread(() -> System.out.println(1));

这个线程的构造方法是

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

那么问题来了,在sort中我lambda可以表示Comparator,在new Thread的时候我又表示Runnable了?

莫非lambda这个狗东西见人说人话,见鬼说鬼话?

 

没错!

lambda的类型就是所谓的“目标类型”

通俗的说,就是,只要你符合函数式接口的标准,你要啥我给啥,啥姿势都行。

 

2.lambda可以表示成哪些形式?

前面已经展示过lambda表达式长什么样子了,总结来说,大部分lambda表达式可以归纳为下面的形状

(参数们) -> {对参数的操作}

这种极简的表达形式,也会导致理解上的偏差,我们可以分开来进行理解:

① "(参数)"部分:参数部分的标准形式应该是(a, b, .......),如果参数只有一个,a -> {方法体}也是可以的,其他时候括号均不可省略,包括无参与复数个参数的时候,另外,如果你想要加上参数的种类也是被允许的(int a, String b, ......),但咱本来不就是学lambda来偷懒的嘛!

② "->"箭头部分:这个只要英文的就行了没啥好说的。记得指向的是方法体啊!

③ "{对参数的操作}"方法体部分:这一部分只要记住两点,第一点,只有复数条语句才需要加"{}",第二点,如果只有一条语句并且是形如"return a"这样的语句,那么连"return "都可以省略掉。(究极懒惰)

注:这里的参数为什么能这样子省略呢?是因为lambda表示的对应匿名内部类中的对应抽象方法只有一个,而编译器能够推导出来各个参数对应的类型,所以你不写也没问题的啦。

但是也不一定所有的lambda都是那个形状的啦。

 

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓看?↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

3.lambda的特殊形式:

如果你的表达式的方法体部分的返回值只需要调用另一个类的一个方法的返回值,同时,抽象方法的参数可以原封不动地传入调用方法中,那么你可以把lambda写成

List<Long> num = Lists.newArrayList(1L,2L,5L,3L,4L,30L,15L,0L,8L,2L);
num.sort(JustDoSomething::justDoSomeOtherThing);

这里,justDoSomeOtherThing是静态方法,所以可以直接通过类类引用,如果不是静态方法,则要先实例对象,

List<Long> num = Lists.newArrayList(1L,2L,5L,3L,4L,30L,15L,0L,8L,2L);
JustDoSomething jds = new JustDoSomething();
num.sort(jds::justDoSomething);

如果你的抽象方法是要求第一个参数传入处理对象的实例,比如这样

@FunctionalInterface
public interface Job {
    void doSomeJobThing(String s, int a, int b);
}

期望的实现类是这样的(调用第一个参数的方法,根据后面的参数做一些操作):

public class JobImpl implements Job{
    @Override
    public String doSomeJobThing(String s, int a, int b) {
        return s.substring(a,b);
    }
}

那么,你完全可以这么写:

Job job = String::substring;

是不是快很多!不过一定要注意哦,这是对参数有要求的。↓↓↓↓

接口的抽象方法的第一个参数是提供处理方法的对象。

最后一种情况,如果你的返回值只需要一个某个类的新的实例对象:

@FunctionalInterface
public interface Job {
    JobImpl getInstance();
}

我懒得改名字了↓

public class JobImpl{
}

这时候就可以:

Job job = JobImpl::new;

 

OK,懒人助手lambda工具的使用就基本讲完了~

下面整理一下lambda和匿名内部类的区别吧!

 

二、lambda和匿名内部类的区别:

 

lambda和匿名内部类异同整理
你是谁lambda匿名内部类
工作量少的一批比左边多
this关键词this指向外部类

this指向内部类自己

super关键词同上同上
可实现的接口与类只能是函数式接口不限于函数式接口,可以实现有多个抽象方法的接口与抽象类
是否能使用接口的的默认方法不能使用(因为没法用this指向接口类)可以使用(使用this关键词调用自身的方法)
对外部类成员变量的引用引用则会给外部类的成员变量加上隐性的final关键字,对该变量再赋值则会让程序报错。(此处涉及到了闭包的概念,我会在别的笔记里说)

 

在@山鬼谣me的这篇文章(原文链接见文末)中指出了使用lambda可能会遇到这样的问题

 

假设有这么一个函数式接口:

interface Task{
    public void execute();
}

和这两个同名方法

public static void doSomething(Runnable r){ r.run(); }
public static void doSomething(Task a){ a.execute(); }


如果用匿名类实现Task:

// 没有歧义
doSomething(new Task() {
    public void execute() {
        System.out.println("Danger danger!!");
    }
});



但如果用lambda表达式:

// 存在歧义,到底调用的是Runnable重载方法还是Task重载方法
doSomething(() -> System.out.println("Danger danger!!"));


解决办法:

可以通过强制转换来解决。

doSomething((Task)() -> System.out.println("Danger danger!!"));

 ———————————————— 
版权声明:本文为CSDN博主「山鬼谣me」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013066244/article/details/90644711

 

三、写在文末(怎么三就文末了):

本文的参考学习资料大部分来自疯狂java讲义(第4版),部分来源于

http://blog.oneapm.com/apm-tech/226.html#comment

这篇文章。

 

顺便,diss一下这个unfriendly网站

这个辣鸡网站
这个辣鸡网站,互相伤害呀!

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值