java 8 实战 (一)Lambda简介
最近看了一本书 java 8 in action, 随手记录。发现以前看过书,大多都是看过就忘了,比如说java多线程之中Latch云云,好记性不如烂笔头,这也是大佬们为什么推荐写博客的原因之一吧。
一、引例
1.
现在有苹果类,用户需求:找出一堆苹果中红色的;找出一堆苹果中重量大于250g的
public class Apple(){
private String color;
private int weight
}
实现需求
public List<Apple> getRedApples(List<Apple> appleList){
List<Apple> result = new ArrayList<Apple>();
for(Apple a : applist){
if(a.getColor().equal("red")){
result.add(a);
}
}
return result;
}
嗯,现在实现了一个功能。诶,发现第二个功能和第一个差不多,那就复制相同部分的代码,easy实现第二个功能。(软件开发,切忌复制代码,违背DRY原则,比如一旦遍历逻辑改变,那么要修改多初代码)
public List<Apple> getWeightApples(List<Apple> appleList, int weight){
List<Apple> result = new ArrayList<Apple>();
for(Apple a : applist){
if(a.getWeight() > weight){
result.add(a);
}
}
return result;
}
2. 客户需求增加
客户现在需要在一堆苹果中找到,既是红色的,重量又要大于250g的
好吧,那就接着复制代码
public List<Apple> getWeightAndRedApples(List<Apple> appleList, int weight){
List<Apple> result = new ArrayList<Apple>();
for(Apple a : applist){
if(a.getWeight() > weight && a.getColor().equal("red")){
result.add(a);
}
}
return result;
}
终于,完成了客户需求,那么如果客户明天接着添加需求,怎么办?即使使用上面的技术解决了用户需求,代码的维护要怎么处理,到处都是相似的代码。
3. 增强
既然有很多相同的代码,那么使用行为参数化怎么样
public interface ApplePredicate(){
public boolean test(Apple a);
}
public List<Apple> filteApples(List<Apple> apples, ApplePridicate ap){
List<Apple> result = new ArrayList<Apple>();
for(Apple a : apples){
if(ap.test(a)){
result.add(a);
}
}
return result;
}
public class RedApple implements ApplePredicate{
public boolean test(Apple a){
if(a.getColor().equals("red")){
return true;
}
return false;
}
/*调用*/
List<Apple> result = filteApples(apples, new RedApple());
嗯,看起来不错,接下来就要应对客户的需求实现多个interface了。但是在应对客户巨多的需求时,interface ApplePredicate的实现就会很多,维护较难
注:读者也可以用匿名类进行优化
Lambda表达式
1.代码分析
参考上述前三段代码,其实大概的代码都是一样的,只是 if()
判断条件不同,那能不能,抽象出其他的代码,只是向一个“容器”中传入不同的if条件(代码段),就能让“容器”产生不同的功能呢?
2. Lambda
2.1 使用Lambda实现需求
所以,所以java 8就加入了Lambda表达式,实现代码传递,来解决这个问题。
/* 实现找到红色的Apple */
List<Apple> redApples = filteApples(apples, (Apple a) -> a.getColr().equals("red"));
只需要 (Apple a) -> a.getColr().equals(“red”) 即可,简直不要太简单
2.2 抽象化
让我们看一下接口定义和fileApple()定义
public interface ApplePredicate(){
public boolean test(Apple a);
}
public List<Apple> filteApples(List<Apple> apples, ApplePridicate ap)
如果把Apple类也抽象化,即
public interface Predicate(){
public boolean test(<T> a);
}
public List<T> filte(List<T> fruits, Pridicate predicate)
这样岂不是可以对任何水果进行任何条件的检索
2.3 另一个例子 : 使用Comparator排序
不使用Lambda表达式
Comparator<Apple> compareWeight = new Comparator<Apple>(){
@Override
public int compare(Apple a, Apple b){
return a.getWeight().compareTo(b.getWeight());
}
}
使用Lambda表达式
Comparator<Apple> compareWeight =
(Apple a, Apple b) -> a.getWeight().compareTo(b.getWeight());
2.4 Lambda表达式分析
看一下上文中的例子:
(Apple a) -> a.getColr().equals("red")
|参数| |箭头| | 函数体 |
参数:不需要参数时,保留()
箭头:分割并标识参数与函数体
函数体:执行多个语句或者存在返回值时,使用{}和;分行
举例:
() -> System.out.println("example")
(int a, int b) -> {return a + b;}
2.5 定义
函数式接口 : 只定义一个抽象函数的接口。在java 8中的interface中存在default函数实现,这些default函数不是抽象函数。函数式接口举例,Runnable, ActionListener
函数签名 : 即为函数的返回值与接收参数的组合,例如class Thread中的run函数,它的函数签名就是(void, void)
思考 (Apple a) -> a.getColr().equals("red")
的签名是什么,答案(Apple, boolean)
2.6 java 函数式接口API
2.6.1 java.util.function.Predicate
接口定义: 定义一个抽象函数,接受T对象,返回boolean
public interface Predicate<T>{
Public boolean test(T);
}
使用举例:
//定义一个filter函数
public static <T> List<T> filter(List<T> list, Predicate<T>){
List<T> result = new ArrayList<T>();
for(T s : list){
if(p.test(s){
result.add(s);
}
}
return result;
}
//调用方法
//创建一个Predicate<T>实例
Predicate<String> predicate = (String s) -> s.equals("test");
List<String> testStrings = filter(listOfStrings, predicate);
2.6.2 java.util.function.Consumer
接口定义: 定义一个函数抽象,接受接受参数T,返回void
@FunctionalInterface
public interface Consumer<T>{
void accpet(T);
}
使用举例:
public static <T> void forEach(List<T> list, Consumer<T> c){
for(T t : list){
c.accept(t);
}
}
//调用
forEach(Array.asList(1,2,3), (Integer i) -> System.out.println(i));
2.6.3 java.util.function.Consumer
接口定义 : 定义一个函数抽象,接受T类型的参数,返回R类型的返回值
@FunctionalInterface
public interface Function<T, R>{
R apply(T t);
}
应用实例:
public static <T, R> List<R> map(List<T> lsit, Function<T, R> func){
List<R> result = new ArrayList<R>();
for(T t : list){
result.add(t);
}
return result;
}
//调用
List<Integer> result = map(
Array.asList("lambda", "in", "action"),
(String s) -> s.length()
);
2.6.4 注解@FunctionalInterface
注释interface,表明该interface中存在默认函数实现,于java 8中引入。
该注解只能标记在”有且仅有一个抽象方法”的接口(即函数式接口)上。
接口中的静态方法和默认方法,都不算是抽象方法。
该注解不是必须的,如果一个接口符合”函数式接口”定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
2.7 流的装箱拆箱
可以注意到上面的三个接口,无一不使用了泛型T,那么,那么对于基本的数据类型在使用时,必须就要使用Integer,Double,Long等等,但是这不仅与我们的初衷,而且其中的拆箱装箱过程也会在大数据量的情况下变得十分耗时。
所以就出现了DoublePredicate, IntConsumer等等对原有的函数式接口的参数进行特化,参数特化参考
2.7 Lambda类型检查、类型推断以及限制
2.7.1 类型检查
Lsit<Apple> redApples = filter(appleList, (Apple a) -> a.getColor().equals("red"));
我们来看一下filter中的lambda是如何进行工作的
首先看一下filter的定义:
filter(List<Apple> list, Predicate<Apple> p)
那么泛型具体指定为Apple
接着查看Predicate的抽象函数:
boolean test(Apple apple)
这个函数接受一个Apple类型的参数,返回boolean,那么确定filter函数的第二个Lambda的输入类型为Apple,输出类型为boolean
对(Apple a) -> a.getColor().equals("red")
的输入输出类型进行验证,恰好符合上述类型组合,类型检查正确
2.7.2 同样的Lambda,不同的函数式接口
Callable c = () -> System.out.println("meaningless string");
Runable r = () -> System.out.println("meaningless string");
因为Callable和Runable都是函数式接口,而且都定义了run()函数,run函数的参数和返回类型都是一样的(接受的参数:void, 返回值类型:void)
接着查看上文中的Lambda表达式,(接受的参数:void, 返回值类型:void)
所以同样的Lambda表达式可以“赋值”不同的函数式接口
2.7.3 类型推断
//filter定义
filter(List<Apple> list, Predicate<Apple> p);
//Predicate<T>定义的抽象函数
boolean test(T t);
根据filter()的定义,我们就可以知道正确的Lambda表达式的输入参数类型是Apple,那么Lambda还可以继续简化,称之为 类型推断
a -> a.getColor().equals("red");
2.7.4 局部变量
int number = 4;
Runable r = () -> System.out.println(number);
局部变量的引用并不是线程安全的
2.8 方法引用
以前我们要调用对象的函数,我们要 str.getBytes()
, 但是现在,我们可以使用 str::getBytes 进行调用。如此,我们的lambda表达式可以更加精简
| 原始Lambda | 等价Lambd
|(str, i) -> str.subString(i) | String::subString
| (String s) -> System.out.println(s) | System.out::println
| a -> a.getWeight() | Apple::getWeight
当然对于无参构造函数,也可已使用className::new创建
Supplier<Apple> s = Apple::new
//Apple必须含有无参构造函数
class Apple(){
public Apple(){}
}
2.9 比较器复合
java8 API 已经为我们提供了List.sort(Comparator c)用来排序,我们不必关心如何实现sort,只需要提供一个Compartor比较器即可
Comparator<Apple> c = Comparator.comparing(Apple.getWeight);
使用Comparator提供的默认函数,我们可以轻松的构建一个比较器。显然上述的代码构造了一个比较依据为”苹果重量大小”的比较器
排序就很容易实现了:appleList.sort(comparing(Apple::getWeight))
为什么如此编写,参考2**.**7
如果要进行逆序排序呢,实现另一个Comparator?不会的,Compartor提供了reversed()对流进行逆序处理appleList.sort(comparing(Apple::getWeight).reversed())
如果要对相同重量的苹果再根据苹果的出产国进行排序呢,Comparator提供了thenComparing():appleList.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getContry))
像这种comparing(xxx).reversed().thenComparing(xxx)
称之为比较器链
2.10 谓词复合
谓词接口包括三个函数and,or,negate(与或非)。
返回现有Predicate的非
Predicate<Apple> notRedApple = redApplePred.negate()
合并两个Predicate
Predicate<Apple> p = redApplePred.and(a -> a.getWeight() > 150)
“要么满足条件a,要么满足条件b”
//要么是红色,要么重量大于150
Predicate<Apple> p = readApplePred.or(a -> a.getWeight() >150)