开荒JAVA8,Lambda表达式

闲聊:

JDK8出来已经很久了,在刚出来的时候稍微了解了一下,后面就丢弃了,现在重新拾取。虽然现在JDK11了。

本文将以持续更新模式进行。

 

正文:

直接看代码,在java8新特性里,我们可以这样写:

public static void main(String[] args) {
        Supplier<Integer> supplier = ()->1;
        Consumer<String> consumer = (a)-> System.out.println(a);
        System.out.println(supplier.get());
        consumer.accept("test");
    }

输出:

1
test

Process finished with exit code 0

很洋气。如此简洁的代码,你行吗,对就是说的你java7

首先盗一张图过来,说明一下Lambda

也就是说,Lambda表达式,创建了一个对应接口的实现,然后赋值给变量,是不是利用编译器?查看编译后的代码如下:

public static void main(String[] args) {
        Supplier<Integer> supplier = () -> {
            return 1;
        };
        Consumer<String> consumer = (a) -> {
            System.out.println(a);
        };
        System.out.println(supplier.get());
        consumer.accept("test");
    }

编译器并没有做这个功能,那就可能是JVM实现了Lambda的功能,后期再深入了解一下。标记1

写到这里,可以知道Lambda表达式减少了代码量,在java8之前只能用匿名内部类或者创建一个类实现该接口实现该功能。如下

Supplier<Integer> supplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return 1;
            }
        };

实际上,我比较好奇伴随Lambda的接口,打开源码可以看到

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
对应的接口都添加了注解FunctionalInterface,那么不要这个注解是否能实现Lambda表达式,我做了一些测试,发现不需要注解也可以实现,目前来看注解只有两个功能 
1、表示这个接口是函数式接口 2、帮助编译器识别,接口不能有多个方法。
在有多个方法的时候会提示Multiple non-overriding abstract methods found in interface

现在我们可以通过FunctionalInterface来查找在JDK8里面有哪些接口是函数式接口

我从里面选一两个比较热门的来测试,学习

哦,先打断一下刚看文章看到一段很重要的话(在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface

我选的是 ArrayList和Optional两个类,都是很常用的类。

2018-07-31 18:01


进入ArrayList的forEach源码

@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

首先forEach使用Consumer接口作为参数,Consumer是一个函数式接口,定义了一个方法

void accept(T t);

在forEach方法中,直接遍历了当前容器的数据,然后调用Consumer的accept方法。在1.8之前,我们也可以这样写代码,但是如果没有Lambda表达式,依然有以前的问题,1、需要写一个匿名内部类,一大段代码;2、写一个实现类,重复的类。在现在,我们可以直接写:

list.forEach(a-> System.out.println(a));

大大减少了代码量,可以看出来,Lambda的目标很明确。

此外,在forEach方法里,有一个modCount值得注意,点开这个变量,发现是父类AbstractList的变量。在ArrayList里面,modCount 出现次数达到了90,仔细查看了一下代码,在容器的数据发生变化的时候,modCount就会变化。modCount的作用是防止list数据发生变化以后,遍历list发生错误。所以forEach方法里有这样的代码

if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
}

 

然后,我在使用forEach的时候,这样写了代码

String b = null;
List<String> list = new ArrayList<>();
list.forEach(a-> {
    b=a;
});

与匿名内部类同样的问题,这里需要把b设为final

否则编译器也会报错:Variable used in lambda expression should be final or effectively fianl

然而final又不能改变b的值,所以只能

final String[] b = {null};
List<String> list = new ArrayList<>();
list.forEach(a-> {
    b[0] =a;
});

如果有这样的需求,那就特别难用。

回想一下刚才看的forEach代码,

for (int i=0; modCount == expectedModCount && i < size; i++) {
    action.accept(elementData[i]);
}

在for循环调用了Consumer接口实现对象的accept方法,那么我们在accept方法里面能写continue、break吗?

编译器给了答案:Break outside switch or loop、Continue outside switch or loop

也就是说用Lambda表达式不能continue、break

 

ArrayList就到此,下面来看Optional,一个我们同事用的欲罢不能的类。

看了Optional的源码,让我想到了StringBuilder,建造者模式

stringBuilder.append("a").append("b").append("c")

写一个Optional的例子:

public static void main(String[] args) {
    String a = "a";
    System.out.println(Optional.ofNullable(a).orElse("c").contains("a"));
}

输出结果:true

这一句话翻译过来就是

if(a==null){
    a = "c";
}
System.out.println(a.contains("a"));

看起来好像没什么区别,其实用起来感觉不一样,用Optional返回的最后结果不会为空

然后继续使用Optional的其他方法

String a = "a";
Optional
.ofNullable(a)
.map(k->k+"bc")
.filter(StringUtil::hasMultibyte)
.orElseThrow(()->new Exception("错误"));

以上双冒号的用法,不在本文深入了解,标记2(在上面StringUtil::hasMultibyte的效果等于x->StringUtil.hasMultibyte(x))

为了理解上面的代码,进入Optional的源码,方法ofNullable

public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value);
} 

若传入的参数为null则返回new Optional<>(),否则返回new Optional<>(value),防止了空指针异常。

然后看方法map,返回Optional:

public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}

Function<T, R>接口:

R apply(T t);

isPresent是判断不为空,从函数式接口可以看出,传入T返回R,可能传入的是T对象,后面返回的是R对象。也就是说

new Optional<>(T)变成了new Optional<>(R),但是上面的代码在这里,仅是将字符串"a"转为"abc"

 

然后是filter方法,返回Optional

public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

Predicate<T>接口:

boolean test(T t);

filter传入参数应该返回一个Boolean值,表示成功失败,成功返回原来的Optional,失败返回new Optional<>()

然后是orElseThrow,返回T

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}

接口Supplier<T>

T get();

如果value为空,则抛出传入的异常,否则返回value,value的值来自于构造方法

private Optional() {
    this.value = null;
}
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}

若以上代码翻译为1.8之前的代码,则为:

if(a!=null){
    a+="bc";
    if(!StringUtil.hasMultibyte(a)){
        throw new Exception("错误");
    }
}

虽然代码量变多了,但是Optional的思路很明确,最终的返回的结果不为空让开发者不需要考虑繁杂的空指针异常。

2018-08-01 17:45


在工作的时候,使用了Optional,发现一开始我对Optional的理解不那么正确,一开始我是认为Optional是为了防止空指针同时代替if else的工具类。如果那样使用了,你会发现不管怎么样最后Optional都会返回一个值,而如果我们想要完成一段功能是:

if(a!=null && a==1){
    System.out.printf("业务代码");
}

那么Optional是这样的

Integer integer = Optional.ofNullable(a)
        .filter(x -> x == 1)
        .filter(x->{
            System.out.printf("业务代码"); 
            return x==1;
        })
        .orElse(0);

Optional并没有提供一个方法orElse(Consumer),传入类似Consumer函数式接口,直接执行某段代码。所以这样做是不符合Optional的设计思想的。

Optional最终是要得到一个结果,在这个过程中去防止空指针、过滤条件、修改返回结果。

 

 

2018-08-03 20:49

End


 

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值