Java 8 中 Lambda 与 Stream 特性的介绍

15 篇文章 0 订阅
13 篇文章 0 订阅

引言 

本来早就想总结一下Lambda与Stream的使用,由于拖延症犯了,就一直拖到现在,但是我心里一直放不下这个执念,因为我觉得作为一个搞技术的,学习就是一种修行,不管怎样都要坚持,为什么要学习Lambda和Stream,我的理由很简单,好用,实现同样的功能,能少写一些代码.

Lambda表达式  

1. 语法  

1) Lambda表达式的语法其实比较简单.主要有以下2种  

(parameters) -> expression

(parameters) -> { statements; }

2) 通过实际案例理解以下就是

() -> {}  //这个Lambda没有参数,并返回void。它类似于主体为空的方法:public void run() {}。
() -> "Zach"  //这个Lambda没有参数,并返回String作为表达式。
() -> {return "Zach";} //这个Lambda没有参数,并返回String(利用显式返回语句)
(Integer) -> {return "Zach" +i;} //return是一个控制流语句,有Integer类型参数,返回一个字符串  
(String s) -> "Zach" //"Zach"是一个表达式,不是一个语句,不能加花括号,和上一条的效果一样,返回一个字符串

2. 在哪里及如何使用Lambda  

在函数式接口上使用Lambda,函数式接口即是定义了一个抽象方法的接口;

1) 自定义函数式接口案例

@FunctionalInterface  //该注解表明这是一个函数式接口
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}

/**
 * 环绕执行模式
 *
 * 1. 将读取文件这一行为参数化
 * 2. 定义一个函数接口传递行为
 * 3. 执行行为,Lambda表达式让你直接内联,为函数式接口的抽象的方法提供实现,并将
 * 整个表达式作为函数式接口的一个实例
 */
public class ExecuteAroundDemo {

    public static String processFile(BufferedReaderProcessor p) throws IOException {
        try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
            return p.process(br);
        }
    }

    public static void main(String[] args) throws IOException {
        String lines = processFile((BufferedReader br) -> br.readLine()+br.lines());
    }
}

3. java 8 中提供的常用的函数式接口 

1. Predicate

   java.util.function.Predicate<T> 定义了一个名为test的抽象化的方法,接收泛型T对象,并返回一个boolean,当你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口  

@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for(T s: list){
if(p.test(s)){
results.add(s);
}
}
return results;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);

2. Consumer  

Consumer定义一个accept()的抽象方法,处理泛型T对象,无返回值,当你需要接受一个泛型T对象并且做一些操作的时候,可以使用

@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
public static <T> void forEach(List<T> list, Consumer<T> c){
for(T i: list){
c.accept(i);
}
}
forEach(
Arrays.asList(1,2,3,4,5),
(Integer i) -> System.out.println(i)
);

3. Function

Function定义了一个抽象的apply方法,接受一个泛型T对象,并返回一个泛型R对象,如果需要定义一个Lambda,将输入的对象的信息映射到输出,可以使用这个接口;

@FunctionalInterface
public interface Function<T, R>{
R apply(T t);
}
public static <T, R> List<R> map(List<T> list,
Function<T, R> f) {
List<R> result = new ArrayList<>();
for(T s: list){
result.add(f.apply(s));
}
return result;
}
// [7, 2, 6]
List<Integer> l = map(
Arrays.asList("lambdas","in","action"),
(String s) -> s.length()
);

注意: 任何函数式接口都不允许抛出受检异常(checked exception);若需要Lambda表达式抛出异常,一是定义一个自己的函数式接口,并声明受检的异常,或者把Lambda包在try/catch块中  

4. 类型检查,类型推断及限制 

Lambda的类型是从使用Lambda的上下文推断出来的,上下文(如,接受它传递方法的参数或接受它的值得局部变量)中Lambda表达式需要的参数类型称为目标类型

List<Apple> heavierThan150g =
filter(inventory, (Apple a) -> a.getWeight() > 150);

   1) 首先,你要找出filter方法的声明。

    2) 第二,要求它是Predicate<Apple>(目标类型)对象的第二个正式参数。

   3) Predicate<Apple>是一个函数式接口,定义了一个叫作test的抽象方法。

   4)test方法描述了一个函数描述符,它可以接受一个Apple,并返回一个boolean

   5)  filter的任何实际参数都必须匹配这个要求

 

Stream 

1. 定义: 

流是从支持数据操作的源生成的元素序列;

2. 名词解释: 

  1) 元素序列: 就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序 值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元 素(如ArrayList 与 LinkedList)。但流的目的在于表达计算

  2) 源: 流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集 合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。

  3) 流水线: 很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大 的流水线

  4) 内部迭代: 与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的  

3. 流与集合的比较   

1) 两者都提供了接口配合代表元素型有序值得数据接口,所谓有序即按顺序取用值

2) 流与集合的差异就在于什么时候计算,集合是一个内存中数据结构,集合中的每个元素都必须先算出来才能添加到集合中(也可以删除),而流则是概念上固定的数据结构,不能添加或删除元素,其元素则是按需计算的,流就像是一个延迟创建的集合,只在消费者需要时才会计算值;

3) 流只能遍历一次,之后这个流就被消费掉了 ,即流只能消费一次

4) 流是内部迭代,而集合是外部迭代,Stream库的内部迭代可以自动选择一种适合你硬件的数据表示和并行实现

4. Stream中常用的操作  

Filtering  

Filter 接受predicate参数,返回一个流,支持distinct()操作,去除重复的元素  

// Filtering with predicate
        List<Dish> vegetarianMenu =
            menu.stream()
                .filter(Dish::isVegetarian)
                .collect(toList());

        vegetarianMenu.forEach(System.out::println);

        // Filtering unique elements
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        numbers.stream()
               .filter(i -> i % 2 == 0)
               .distinct()
               .forEach(System.out::println);

        // Truncating a stream
        List<Dish> dishesLimit3 =
            menu.stream()
                .filter(d -> d.getCalories() > 300)
                .limit(3)
                .collect(toList());

        dishesLimit3.forEach(System.out::println);

        // Skipping elements
        List<Dish> dishesSkip2 =
            menu.stream()
                .filter(d -> d.getCalories() > 300)
                .skip(2)
                .collect(toList());

        dishesSkip2.forEach(System.out::println);

Mapping 

map()方法接收function作为参数, function被应用在每个元素上,将其映射为新的元素

  List<String> collect = menu.stream().map(Dish::getName)
                .collect(Collectors.toList());

流的扁平化 

对于一个words集合如何返回一张列表,列出所有不同的字符?如: words["Hello","World"],你将要返回列表["H","e","l","o","W","r","d"]; 首先想到的是:

  List<String> collect = menu.stream().map(Dish::getName)
                .collect(Collectors.toList());

这个方法会为每个单词返回一个String[] (String列表),故map返回的流实际上是Stream<String[]>类型的,而我们需要的是Stream<String> 来表示一个字符流

我们需要一个字符流,而不是数组流,但是Arrays.Stream()方法可以接受一个数组并产生一个流,

String[] arrayOfWords = {"Goodbye", "World"};
Stream<String> streamOfwords = Arrays.stream(arrayOfWords);



//在之前的例子中使用,返回的是List<Stream<String>> 
words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.distinct()
.collect(toList());

使用flatMap

flatMap()方法的作用,各个数组并不是分别映射成一个流,而是映射成流的内容所有使用map(Arrays::stream)时生成的单个流都被合并起来,扁平化为一个流

    List<String> list = words.stream()
                .map(w -> w.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .collect(Collectors.toList());

案例题

 /**
    * 给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应
    * 该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。为简单起见,你可以用有两个元素的数组来代
     * 表数对。
      */
        List<int[]> pairs = numbers1.stream()
                .flatMap(i -> numbers2.stream()
                        .map(j-> new int[]{i,j})
                )
                .collect(toList());

 /**
   * 如何扩展前一个例子,只返回总和能被3整除的数对呢?例如(2, 4)和(3, 3)是可以的
    */
        List<int[]> pairs2 = numbers1.stream().flatMap(i -> numbers2.stream()
        .filter( j-> (i +j) % 3 == 0)
        .map(j -> new int[] {i,j})
        ).collect(toList());

 

5. 总结流的中间操作和终端操作

 

6. 参考资料

《Java 8 in Action》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值