java 8 实战 (一)Lambda简介

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值