JDK8出来已经很久了,在刚出来的时候稍微了解了一下,后面就丢弃了,现在重新拾取。虽然现在JDK11了。
本文将以持续更新模式进行。
直接看代码,在java8新特性里,我们可以这样写:
public static void main(String[] args) {
Supplier supplier = ()->1;
Consumer 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 supplier = () -> {
return 1;
};
Consumer consumer = (a) -> {
System.out.println(a);
};
System.out.println(supplier.get());
consumer.accept("test");
}
编译器并没有做这个功能,那就可能是JVM实现了Lambda的功能,后期再深入了解一下。标记1
写到这里,可以知道Lambda表达式减少了代码量,在java8之前只能用匿名内部类或者创建一个类实现该接口实现该功能。如下
Supplier supplier = new Supplier() {
@Override
public Integer get() {
return 1;
}
};
实际上,我比较好奇伴随Lambda的接口,打开源码可以看到
@FunctionalInterface
public interface Supplier {
/**
* 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 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 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 Optional ofNullable(T value) {
return value == null ? empty() : of(value);
}
若传入的参数为null则返回new Optional<>(),否则返回new Optional<>(value),防止了空指针异常。
然后看方法map,返回Optional:
public Optional map(Function super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
Function
R apply(T t);
isPresent是判断不为空,从函数式接口可以看出,传入T返回R,可能传入的是T对象,后面返回的是R对象。也就是说
new Optional<>(T)变成了new Optional<>(R),但是上面的代码在这里,仅是将字符串"a"转为"abc"
然后是filter方法,返回Optional
public Optional filter(Predicate super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
Predicate
boolean test(T t);
filter传入参数应该返回一个Boolean值,表示成功失败,成功返回原来的Optional,失败返回new Optional<>()
然后是orElseThrow,返回T
public T orElseThrow(Supplier extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
接口Supplier
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