lambada表达式

news2024/11/15 17:55:37

负壹、 函数式编程

Java为什么要支持函数式编程?

  1. 代码简洁 函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。

  2. 多核友好 Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一下parallel()方法。

Java怎么实现函数式编程

  1. Lambada表达式代替函数式接口、匿名内部类、Collection 、Map等。
  2. stream api

关于 Java Lambda 表达式看这一篇就够了(强烈建议收藏)

零、Lambada编写规则

lambada表达式只关注函数接口抽象方法的入参、函数体、返回值。根据入参个数、函数体行数、函数体是否只有单行返回行来进行相应简写。

壹、Lambada使用场景

1.替代匿名内部类。

    只有函数接口才能被lambada简写,匿名内部类有的是函数接口。函数接口即@FunctionalInterface注解且只有一个抽象方法的抽象类。

(1)无参函数的简写

 (2)带参函数的简写

 (3)自定义函数接口,然后简写

自定义函数接口 @FunctionalInterface是可选的,但加上该标注编译器会帮你检查接口是否符合函数接口规范。就像加入@Override标注会检查是否重载了函数一样

@FunctionalInterface
public interface ConsumerInterface<T>{
 void accept(T t);
}


上述接口定义,就可以写出类似如下的代码:

ConsumerInterface<String> consumer = str -> System.out.println(str);

进一步的,还可以这样使用:

class MyStream<T>{
 private List<T> list;
    ...
 public void myForEach(ConsumerInterface<T> consumer){// 1
  for(T t : list){
   consumer.accept(t);
  }
 }
}
MyStream<String> stream = new MyStream<String>();
stream.myForEach(str -> System.out.println(str));// 使用自定

2.Collection和Map使用Lambada 

        之所以能够使用Lambada,是因为Collection和Map给某些方法新增了函数接口类型的形参。增加函数接口的方法如下。

        为引入Lambda表达式,Java8新增了java.util.funcion包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分函数接口,以便与Lambda表达式对接。此时调用一些方法时,如果方法的参数实现了函数接口,那么既可以使用匿名内部类也可以lambada。

      如何想到使用Collection和Map的新增方法和结合Lambada使用。记住方法和特殊函数接口形参。看到提示就能想起来。

(1)Collection新增方法,以ArrayList举例 

 ---forEach方法

       该方法的签名为void forEach(Consumer<? super E> action),作用是对容器中的每个元素执行action指定的动作,其中Consumer是个函数接口,里面只有一个待实现方法void accept(T t)(后面我们会看到,这个方法叫什么根本不重要,你甚至不需要记忆它的名字)。你可以在自定义类中的方法中,将Consumer<? super E> action作为入参,那么你也就可以使用lambada了。

如下可以明显看出forEach方法的前后变化。以前当做一个普通循环使用,后来Java8实现了函数接口Consumer,然后就既可以匿名内部类也可以lambada简写方式遍历。

// 使用曾强for循环迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(String str : list){
    if(str.length()>3)
        System.out.println(str);
}

// 使用forEach()结合匿名内部类迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer<String>(){
    @Override
    public void accept(String str){
        if(str.length()>3)
            System.out.println(str);
    }
});


// 使用forEach()结合Lambda表达式迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach( str -> {
        if(str.length()>3)
            System.out.println(str);
    });

---removeIf()

         该方法签名为boolean removeIf(Predicate<? super E> filter),作用是删除容器中所有满足filter指定条件的元素,其中Predicate是一个函数接口,里面只有一个待实现方法boolean test(T t),同样的这个方法的名字根本不重要,因为用的时候不需要书写这个名字。

---replaceAll

       该方法签名为void replaceAll(UnaryOperator<E> operator),作用是对每个元素执行operator指定的操作,并用操作结果来替换原来的元素。其中UnaryOperator是一个函数接口,里面只有一个待实现函数T apply(T t)

---sort

       该方法定义在List接口中,方法签名为void sort(Comparator<? super E> c),该方法根据c指定的比较规则对容器元素进行排序Comparator接口我们并不陌生,其中有一个方法int compare(T o1, T o2)需要实现,显然该接口是个函数接口。

---spliterator()

    方法签名为Spliterator<E> spliterator(),该方法返回容器的可拆分迭代器。从名字来看该方法跟iterator()方法有点像,我们知道Iterator是用来迭代容器的,Spliterator也有类似作用,但二者有如下不同:

  1. Spliterator既可以像Iterator那样逐个迭代,也可以批量迭代。批量迭代可以降低迭代的开销。

  2. Spliterator是可拆分的,一个Spliterator可以通过调用Spliterator<T> trySplit()方法来尝试分成两个。一个是this,另一个是新返回的那个,这两个迭代器代表的元素没有重叠。

可通过(多次)调用Spliterator.trySplit()方法来分解负载,以便多线程处理。

---stream()和parallelStream()

    stream()parallelStream()分别返回该容器的Stream视图表示,不同之处在于parallelStream()返回并行的StreamStream是Java函数式编程的核心类,我们会在后面章节中学习。

(2)Map新增方法 以HashMap举例

---forEach()

      该方法签名为void forEach(BiConsumer<? super K,? super V> action),作用是Map中的每个映射执行action指定的操作,其中BiConsumer是一个函数接口,里面有一个待实现方法void accept(T t, U u)BinConsumer接口名字和accept()方法名字都不重要,请不要记忆他们。

---getOrDefault()

     该方法跟Lambda表达式没关系,但是很有用。方法签名为V getOrDefault(Object key, V defaultValue),作用是按照给定的key查询Map中对应的value,如果没有找到则返回defaultValue。使用该方法程序员可以省去查询指定键值是否存在的麻烦.

---putIfAbsent()

     该方法跟Lambda表达式没关系,但是很有用。方法签名为V putIfAbsent(K key, V value),作用是只有在不存在key值的映射或映射值为null,才将value指定的值放入到Map中,否则不对Map做更改.该方法将条件判断和赋值合二为一,使用起来更加方便.

---remove()

   我们都知道Map中有一个remove(Object key)方法,来根据指定key值删除Map中的映射关系;Java8新增了remove(Object key, Object value)方法,只有在当前Map中**key正好映射到value时**才删除该映射,否则什么也不做.

---replace()

   在Java7及以前,要想替换Map中的映射关系可通过put(K key, V value)方法实现,该方法总是会用新值替换原来的值.为了更精确的控制替换行为,Java8在Map中加入了两个replace()方法,分别如下:

  • replace(K key, V value),只有在当前Map中**key的映射存在时**才用value去替换原来的值,否则什么也不做.

  • replace(K key, V oldValue, V newValue),只有在当前Map中**key的映射存在且等于oldValue时**才用newValue去替换原来的值,否则什么也不做.

----replaceAll()

     该方法签名为replaceAll(BiFunction<? super K,? super V,? extends V> function),作用是对Map中的每个映射执行function指定的操作,并用function的执行结果替换原来的value,其中BiFunction是一个函数接口,里面有一个待实现方法R apply(T t, U u).不要被如此多的函数接口吓到,因为使用的时候根本不需要知道他们的名字.

---merge()

该方法签名为merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction),作用是:

  1. 如果Mapkey对应的映射不存在或者为null,则将value(不能是null)关联到key上;

  2. 否则执行remappingFunction,如果执行结果非null则用该结果跟key关联,否则在Map中删除key的映射.

参数中BiFunction函数接口前面已经介绍过,里面有一个待实现方法R apply(T t, U u)

merge()方法虽然语义有些复杂,但该方法的用法很明确,一个比较常见的场景是将新的错误信息拼接到原来的信息上,比如:

map.merge(key, newMsg, (v1, v2) -> v1+v2);

---compute()

     该方法签名为compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),作用是把remappingFunction的计算结果关联到key上,如果计算结果为null,则在Map中删除key的映射.

要实现上述merge()方法中错误信息拼接的例子,使用compute()代码如下:

map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg));

  使用computeIfAbsent()将条件判断和添加操作合二为一,使代码更加简洁.

---computeIfAbsent()

    该方法签名为V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction),作用是:只有在当前Map不存在key值的映射或映射值为null,才调用mappingFunction,并在mappingFunction执行结果非null时,将结果跟key关联.

Function是一个函数接口,里面有一个待实现方法R apply(T t)

computeIfAbsent()常用来对Map的某个key值建立初始化映射.比如我们要实现一个多值映射,Map的定义可能是Map<K,Set<V>>,要向Map中放入新值,

---computeIfPresent()

    该方法签名为V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),作用跟computeIfAbsent()相反,即,只有在当前Map存在key值的映射且非null,才调用remappingFunction,如果remappingFunction执行结果为null,则删除key的映射,否则使用该结果替换key原来的映射.

 贰、Lambada和匿名内部类什么区别

1.匿名内部类,生成字节码时会单独生成一个类。Lambada表达式生成字节码时会当做当前类的一个私有方法。因此Lambada表达式的this代表当前类对象,而且可以调用当前类的方法。

叁、函数式编程

1、创建steam

      stream是Java函数式编程的主角。对于Java 7来说stream完全是个陌生东西,stream并不是某种数据结构,它只是数据源的一种视图。这里的数据源可以是一个数组,Java容器或I/O channel等。正因如此要得到一个stream通常不会手动创建,而是调用对应的工具方法,比如:

  • 调用Collection.stream()或者Collection.parallelStream()方法

  • 调用Arrays.stream(T[] array)方法

stream的操作分为为两类,中间操作(intermediate operations)和结束操作(terminal operations),二者特点是:

  1. 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。

  2. 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。

如果你熟悉Apache Spark RDD,对stream的这个特点应该不陌生。

下表汇总了Stream接口的部分常见方法:

操作类型接口方法
中间操作concat() distinct() filter() flatMap() limit() map() peek()
skip() sorted() parallel() sequential() unordered()
结束操作allMatch() anyMatch() collect() count() findAny() findFirst()
forEach() forEachOrdered() max() min() noneMatch() reduce() toArray()

区分中间操作和结束操作最简单的方法,就是看方法的返回值,返回值为stream的大都是中间操作,否则是结束操作。

2. steam api

forEach()

    我们对forEach()方法并不陌生,在Collection中我们已经见过。方法签名为void forEach(Consumer<? super E> action),作用是对容器中的每个元素执行action指定的动作,也就是对元素进行遍历。

// 使用Stream.forEach()迭代
Stream<String> stream = Stream.of("I", "love", "you", "too");
stream.forEach(str -> System.out.println(str));

由于forEach()是结束方法,上述代码会立即执行,输出所有字符串。

filter()

   函数原型为Stream<T> filter(Predicate<? super T> predicate),作用是返回一个只包含满足predicate条件元素的Stream

// 保留长度等于3的字符串
Stream<String> stream= Stream.of("I", "love", "you", "too");
stream.filter(str -> str.length()==3)
    .forEach(str -> System.out.println(str));

distinct

函数原型为Stream<T> distinct(),作用是返回一个去除重复元素之后的Stream。 

sorted()

排序函数有两个,一个是用自然顺序排序,一个是使用自定义比较器排序,函数原型分别为Stream<T> sorted()Stream<T> sorted(Comparator<? super T> comparator)

 map

函数原型为<R> Stream<R> map(Function<? super T,? extends R> mapper),作用是返回一个对当前所有元素执行执行mapper之后的结果组成的Stream。直观的说,就是对每个元素按照某种操作进行转换,转换前后Stream中元素的个数不会改变,但元素的类型取决于转换之后的类型。

flatMap

函数原型为<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper),作用是对每个元素执行mapper指定的操作,并用所有mapper返回的Stream中的元素组成一个新的Stream作为最终返回结果。说起来太拗口,通俗的讲flatMap()的作用就相当于把原stream中的所有元素都"摊平"之后组成的Stream,转换前后元素的个数和类型都可能会改变。

Stream<List<Integer>> stream = Stream.of(Arrays.asList(1,2), Arrays.asList(3, 4, 5));
stream.flatMap(list -> list.stream())
    .forEach(i -> System.out.println(i));

上述代码中,原来的stream中有两个元素,分别是两个List<Integer>,执行flatMap()之后,将每个List都“摊平”成了一个个的数字,所以会新产生一个由5个数字组成的Stream。所以最终将输出1~5这5个数字。

 

规约操作(reduction operation)又被称作折叠操作(fold),是通过某个连接动作将所有元素汇总成一个汇总结果的过程。元素求和、求最大值或最小值、求出元素总个数、将所有元素转换成一个列表或集合,都属于规约操作。Stream类库有两个通用的规约操作reduce()collect(),也有一些为简化书写而设计的专用规约操作,比如sum()max()min()count()等。

最大或最小值这类规约操作很好理解(至少方法语义上是这样),我们着重介绍reduce()collect(),这是比较有魔法的地方。

多面手reduce()

reduce操作可以实现从一组元素中生成一个值,sum()max()min()count()等都是reduce操作,将他们单独设为函数只是因为常用。reduce()的方法定义有三种重写形式:

  • Optional<T> reduce(BinaryOperator<T> accumulator)

  • T reduce(T identity, BinaryOperator<T> accumulator)

  • <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

虽然函数定义越来越长,但语义不曾改变,多的参数只是为了指明初始值(参数identity),或者是指定并行执行时多个部分结果的合并方式(参数combiner)。reduce()最常用的场景就是从一堆值中生成一个值。用这么复杂的函数去求一个最大或最小值,你是不是觉得设计者有病。其实不然,因为“大”和“小”或者“求和"有时会有不同的语义。

需求:从一组单词中找出最长的单词。这里“大”的含义就是“长”。

// 找出最长的单词
Stream<String> stream = Stream.of("I", "love", "you", "too");
Optional<String> longest = stream.reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2);
//Optional<String> longest = stream.max((s1, s2) -> s1.length()-s2.length());
System.out.println(longest.get());

上述代码会选出最长的单词love,其中Optional是(一个)值的容器,使用它可以避免null值的麻烦。当然可以使用Stream.max(Comparator<? super T> comparator)方法来达到同等效果,但reduce()自有其存在的理由。

需求:求出一组单词的长度之和。这是个“求和”操作,操作对象输入类型是String,而结果类型是Integer

// 求单词长度之和
Stream<String> stream = Stream.of("I", "love", "you", "too");
Integer lengthSum = stream.reduce(0, // 初始值 // (1)
        (sum, str) -> sum+str.length(), // 累加器 // (2)
        (a, b) -> a+b); // 部分和拼接器,并行执行时才会用到 // (3)
// int lengthSum = stream.mapToInt(str -> str.length()).sum();
System.out.println(lengthSum);

上述代码标号(2)处将i. 字符串映射成长度,ii. 并和当前累加和相加。这显然是两步操作,使用reduce()函数将这两步合二为一,更有助于提升性能。如果想要使用map()sum()组合来达到上述目的,也是可以的。

3.collect 详解

reduce()擅长的是生成一个值,如果想要从Stream生成一个集合或者Map等复杂的对象该怎么办呢?终极武器collect()横空出世!

 

夸张的讲,如果你发现某个功能在Stream接口中没找到,十有八九可通过以collect()方法实现。collect()Stream接口方法中最灵活的一个,学会它才算真正入门Java函数式编程。先看几个热身的小例子:

// 将Stream转换成容器或Map
Stream<String> stream = Stream.of("I", "love", "you", "too");
List<String> list = stream.collect(Collectors.toList()); // (1)
// Set<String> set = stream.collect(Collectors.toSet()); // (2)
// Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length)); // (3)

上述代码分别列举了如何将Stream转换成ListSetMap。虽然代码语义很明确,可是我们仍然会有几个疑问:

  1. Function.identity()是干什么的?

  2. String::length是什么意思?

  3. Collectors是个什么东西?

Java新增的接口的静态方法和默认方法

Function是一个接口,那么Function.identity()是什么意思呢?这要从两方面解释:

  1. Java 8允许在接口中加入具体方法。接口中的具体方法有两种,default方法和static方法,identity()就是Function接口的一个静态方法。

  2. Function.identity()返回一个输出跟输入一样的Lambda表达式对象,等价于形如t -> t形式的Lambda表达式。

上面的解释是不是让你疑问更多?不要问我为什么接口中可以有具体方法,也不要告诉我你觉得t -> tidentity()方法更直观。我会告诉你接口中的default方法是一个无奈之举,在Java 7及之前要想在定义好的接口中加入新的抽象方法是很困难甚至不可能的,因为所有实现了该接口的类都要重新实现。试想在Collection接口中加入一个stream()抽象方法会怎样?default方法就是用来解决这个尴尬问题的,直接在接口中实现新加入的方法。既然已经引入了default方法,为何不再加入static方法来避免专门的工具类呢!

方法引用

诸如String::length的语法形式叫做方法引用(method references),这种语法用来替代某些特定形式Lambda表达式。如果Lambda表达式的全部内容就是调用一个已有的方法,那么可以用方法引用来替代Lambda表达式。方法引用可以细分为四类:

方法引用类别举例
引用静态方法Integer::sum
引用某个对象的方法list::add
引用某个类的方法String::length
引用构造方法HashMap::new

我们会在后面的例子中使用方法引用。

Collector接口

相信前面繁琐的内容已彻底打消了你学习Java函数式编程的热情,不过很遗憾,下面的内容更繁琐。但这不能怪Stream类库,因为要实现的功能本身很复杂。

 

Collector(收集器)是为Stream.collect()方法量身打造的工具接口,主要作为collect的形参使用。

考虑一下将一个Stream转换成一个容器(或者Map)需要做哪些工作?我们至少需要两样东西:

  1. 目标容器是什么?是ArrayList还是HashSet,或者是个TreeMap

  2. 新元素如何添加到容器中?是List.add()还是Map.put()

        如果并行的进行规约,还需要告诉collect() 3. 多个部分结果如何合并成一个。

       

         结合以上分析,collect()方法定义为<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner),三个参数依次对应上述三条分析。不过每次调用collect()都要传入这三个参数太麻烦,收集器Collector就是对这三个参数的简单封装【这样只需要填写Collector的静态方法,就不再需要手动填写三个参数】,所以collect()的另一定义为<R,A> R collect(Collector<? super T,A,R> collector)

        Collectors工具类可通过静态方法生成各种常用的Collector。举例来说,如果要将Stream规约成List可以通过如下两种方式实现:

// 将Stream规约成List
Stream<String> stream = Stream.of("I", "love", "you", "too");
List<String> list = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);// 方式1
//List<String> list = stream.collect(Collectors.toList());// 方式2 收集器Collector就是对这三个参数的简单封装
System.out.println(list);

      通常情况下我们不需要手动指定collect()的三个参数,而是调用collect(Collector<? super T,A,R> collector)方法,并且参数中的Collector对象大都是直接通过Collectors工具类获得。

使用collect()生成Collection

前面已经提到通过collect()方法将Stream转换成容器的方法,这里再汇总一下。将Stream转换成ListSet是比较常见的操作,所以Collectors工具已经为我们提供了对应的收集器,通过如下代码即可完成:

// 将Stream转换成List或Set
Stream<String> stream = Stream.of("I", "love", "you", "too");
List<String> list = stream.collect(Collectors.toList()); // (1)
Set<String> set = stream.collect(Collectors.toSet()); // (2)

上述代码能够满足大部分需求,但由于返回结果是接口类型,我们并不知道类库实际选择的容器类型是什么,有时候我们可能会想要人为指定容器的实际类型,这个需求可通过Collectors.toCollection(Supplier<C> collectionFactory)方法完成。

// 使用toCollection()指定规约容器的类型
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));// (3)
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));// (4)

上述代码(3)处指定规约结果是ArrayList,而(4)处指定规约结果为HashSet。一切如你所愿。

使用collect()生成Map

前面已经说过Stream背后依赖于某种数据源,数据源可以是数组、容器等,但不能是Map。反过来从Stream生成Map是可以的,但我们要想清楚Mapkeyvalue分别代表什么,根本原因是我们要想清楚要干什么。通常在三种情况下collect()的结果会是Map

  1. 使用Collectors.toMap()生成的收集器,用户需要指定如何生成Mapkeyvalue

  2. 使用Collectors.partitioningBy()生成的收集器,对元素进行二分区操作时用到。

  3. 使用Collectors.groupingBy()生成的收集器,对元素做group操作时用到。

情况1:使用toMap()生成的收集器,这种情况是最直接的,前面例子中已提到,这是和Collectors.toCollection()并列的方法。如下代码展示将学生列表转换成由<学生,GPA>组成的Map。非常直观,无需多言。

// 使用toMap()统计学生GPA
Map<Student, Double> studentToGPA =
     students.stream().collect(Collectors.toMap(Function.identity(),// 如何生成key
                                     student -> computeGPA(student)));// 如何生成value

情况2:使用partitioningBy()生成的收集器,这种情况适用于将Stream中的元素依据某个二值逻辑(满足条件,或不满足)分成互补相交的两部分,比如男女性别、成绩及格与否等。下列代码展示将学生分成成绩及格或不及格的两部分。

// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream()
         .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));

情况3:使用groupingBy()生成的收集器,这是比较灵活的一种情况。跟SQL中的group by语句类似,这里的groupingBy()也是按照某个属性对数据进行分组,属性相同的元素会被对应到Map的同一个key上。下列代码展示将员工按照部门进行分组:

// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
            .collect(Collectors.groupingBy(Employee::getDepartment));

以上只是分组的最基本用法,有些时候仅仅分组是不够的。在SQL中使用group by是为了协助其他查询,比如1. 先将员工按照部门分组,2. 然后统计每个部门员工的人数。Java类库设计者也考虑到了这种情况,增强版的groupingBy()能够满足这种需求。增强版的groupingBy()允许我们对元素分组之后再执行某种运算,比如求和、计数、平均值、类型转换等。这种先将元素分组的收集器叫做上游收集器,之后执行其他运算的收集器叫做下游收集器(downstream Collector)。

// 使用下游收集器统计每个部门的人数
Map<Department, Integer> totalByDept = employees.stream()
                    .collect(Collectors.groupingBy(Employee::getDepartment,
                                                   Collectors.counting()));// 下游收集器

上面代码的逻辑是不是越看越像SQL?高度非结构化。还有更狠的,下游收集器还可以包含更下游的收集器,这绝不是为了炫技而增加的把戏,而是实际场景需要。考虑将员工按照部门分组的场景,如果我们想得到每个员工的名字(字符串),而不是一个个Employee对象,可通过如下方式做到:

// 按照部门对员工分布组,并只保留员工的名字
Map<Department, List<String>> byDept = employees.stream()
                .collect(Collectors.groupingBy(Employee::getDepartment,
                        Collectors.mapping(Employee::getName,// 下游收集器
                                Collectors.toList())));// 更下游的收集器

如果看到这里你还没有对Java函数式编程失去信心,恭喜你,你已经顺利成为Java函数式编程大师了。

使用collect()做字符串join

这个肯定是大家喜闻乐见的功能,字符串拼接时使用Collectors.joining()生成的收集器,从此告别for循环。Collectors.joining()方法有三种重写形式,分别对应三种不同的拼接方式。无需多言,代码过目难忘。

// 使用Collectors.joining()拼接字符串
Stream<String> stream = Stream.of("I", "love", "you");
//String joined = stream.collect(Collectors.joining());// "Iloveyou"
//String joined = stream.collect(Collectors.joining(","));// "I,love,you"
String joined = stream.collect(Collectors.joining(",", "{", "}"));// "{I,love,you}"

collect()还可以做更多

除了可以使用Collectors工具类已经封装好的收集器,我们还可以自定义收集器,或者直接调用collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)方法,收集任何形式你想要的信息。不过Collectors工具类应该能满足我们的绝大部分需求,手动实现之间请先看看文档。

 

3.stream怎么用

4.stream原理是啥

深入理解Java8Lambada表达式 - 代码先锋网

记笔记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/382380.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ -- STL简介、string的使用

什么是STL STL(standard template libaray-标准模板库)&#xff1a;是C标准库的重要组成部分&#xff0c;不仅是一个可复用的组件库&#xff0c;而且是一个包罗数据结构与算法的软件框架。 STL的版本 原始版本&#xff1a;Alexander Stepanov、Meng Lee 在惠普实验室完成的原…

Person p=new student()是什么意思

记住&#xff1a;父类引用子类对象 Student t new Student(); 实例化一个Student的对象&#xff0c;这个不难理解。但当我这样定义时&#xff1a;Person p new Student(); 这代表什么意思呢&#xff1f; 很简单&#xff0c;它表示我定义了一个Person类型的引用&#xff0c;指…

内大892复试真题16年

内大892复试真题16年 1. 输出三个数中较大数2. 求两个数最大公约数与最小公倍数3. 统计字符串中得字符个数4. 输出菱形5. 迭代法求平方根6. 处理字符串(逆序、进制转换)7. 寻找中位数8. 输入十进制输出n进制1. 输出三个数中较大数 问题 代码 #include <iostream>usin…

如何保护页面端的企业邮箱

作为日常工作交流中不可或缺的工具&#xff0c;电子邮件的安全性一直是备受关注的话题。页面是企业邮箱用户日常邮箱使用的重要接入终端&#xff0c;企业邮箱承载着企业的重要业务内容和关键数据。这个环节的安全问题会对企业产生深远的影响&#xff0c;信息安全建设的重要性不…

CharGPT无法替代的职场发展底层逻辑

​ 什么是职场发展的两大底层逻辑&#xff1f; 一为对事&#xff0c;一为对人&#xff0c;二者不可偏废&#xff0c;否则就会成为“职场上的瘸子”。 01 在刚开始工作的很长一段时间里&#xff0c;我遇到了同一个问题 - 虽然我对自己的工作认真负责&#xff0c;但职业发展却并…

移动硬盘文件或目录损坏且无法读取?分享恢复数据的方法

案例&#xff1a;移动硬盘提示无法访问&#xff0c;文件或目录损坏且无法读取&#xff1f; “这个移动硬盘两年没用了&#xff0c;今天拿出来找文件&#xff0c;插上电脑后移动硬盘打不开&#xff0c;提示无法访问&#xff0c;文件或目录损坏且无法读取。硬盘里有重要文件&…

在字节跳动做了5年软件测试,12月无情被辞,想给划水的兄弟提个醒

先简单交代一下背景吧&#xff0c;某不知名 985 的本硕&#xff0c;17 年毕业加入字节&#xff0c;以“人员优化”的名义无情被裁员&#xff0c;之后跳槽到了有赞&#xff0c;一直从事软件测试的工作。之前没有实习经历&#xff0c;算是5年的工作经验吧。 这5年之间完成了一次…

SpringBoot异常处理

目录 一、 错误处理 1. 默认规则 2. 定制错误处理逻辑 二、自定义异常处理 1. 实现 ErrorController 2. RestControllerAdvice/ControllerAdvice ExceptionHandler 实现自定义异常 3. 新建 UserController.class 测试 3 种不同异常的处理 4. 最终效果如下 补充 1. 参…

【数据结构】八大经典排序总结

文章目录一、排序的概念及其运用1.排序的概念2.常见排序的分类3.排序的运用二、常见排序算法的实现1.直接插入排序1.1排序思想1.2代码实现1.3复杂度及稳定性1.4特性总结2.希尔排序2.1排序思想2.3复杂度及稳定性2.4特性总结3.直接选择排序3.1排序思想3.2代码实现3.3复杂度及稳定…

《数据治理行业实践白皮书》正式发布,开辟数据治理新范式(附下载)

近日&#xff0c;作为首届未来数商大会协办单位之一&#xff0c;袋鼠云承办“首届未来数商大会——业数融合创新论坛”&#xff0c;与参会嘉宾共同探讨数据驱动企业业务增长提效的新思路。袋鼠云联合创始人、易知微CEO 宁海元发表主题演讲《数智视融合&#xff0c;构建数字产业…

Docker安装Cassandra数据库,在SpringBoot中连接Cassandra

简介 Apache Cassandra是一个高度可扩展的高性能分布式数据库&#xff0c;旨在处理许多商用服务器上的大量数据&#xff0c;提供高可用性而没有单点故障。它是NoSQL数据库的一种。首先让我们了解一下NoSQL数据库的作用。 NoSQL 数据库 NoSQL数据库&#xff08;有时称为“Not …

YOLO5-V7.0的python代码转成exe,方便离线部署

思路&#xff1a;用Pyinstaller打包&#xff0c;但有一些坑&#xff0c;踩完坑后成功运行&#xff0c;写个踩坑指南分享下。 前提&#xff1a;已经在conda 环境下安装完可以运行的yolo5代码&#xff0c;例如你的虚拟python环境叫yolo5&#xff0c;主代码是XXX.py&#xff08;这…

数字IC手撕代码--低功耗设计 Clock Gating

背景介绍芯片功耗组成中&#xff0c;有高达 40%甚至更多是由时钟树消耗掉的。这个结果的原因也很直观&#xff0c;因 为这些时钟树在系统中具有最高的切换频率&#xff0c;而且有很多时钟 buffer&#xff0c;而且为了最小化时钟 延时&#xff0c;它们通常具有很高的驱动强度。 …

《分布式技术原理与算法解析》学习笔记Day26

流量控制 什么是流量控制&#xff1f; 网络传输中的流量控制就是让发送方发送数据的速度不要太快&#xff0c;这样可以让接收方来得及接收数据&#xff0c;通常使用滑动窗口的方式来实现。 滑动窗口是指在任意时刻&#xff0c;发送方都维持一个连续的允许发送的数据大小&…

全国CSM敏捷教练认证将于2023年3月25-26开班,报名从速!

CSM&#xff0c;即Certified Scrum Master&#xff0c;是Scrum联盟发起的Scrum认证。 CSM可以帮助团队正确使用Scrum&#xff0c;从而提高项目整体成功的可能性。 CSM深刻理解Scrum的价值观、实践以及Scrum框架。 CSM是“服务型领导”&#xff0c;帮助Scrum团队一起紧密合作。 …

QT案例 Qt Creator 使用QuaZIP加密压缩解压ZIP文件

QT开发中部分项目可能会涉及对项目数据的一些指定文件的打包压缩以及指定目录下的解压&#xff0c;此时也需要对数据数据进行加密以确保数据的安全性。此时就可以使用Quazip实现相关功能。 QuaZIP是使用Qt&#xff0c;C对ZLIB进行简单封装的用于压缩ZIP以及解压缩ZIP的开源库。…

Hive表-----数据清洗

以下内容所需要的环境 &#xff1a;hive 、beeline、Zeppelin&#xff08;可视化界面如何操作表格&#xff09; 一、准备表格 1、上传csv表格至linux目录中 百度网盘自取&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1xd5MdXiBDLBUtP07kpgl5Q?pwd2ema 提取码&…

python+appium+夜神模拟器(app抓包爬虫)

安装模块 pip install appium-python-client 安装andriodSDK 官网下载&#xff1a;https://android-sdk.en.softonic.com/download 自动下载一个压缩包&#xff0c;解压后就是一个文件夹放各种需要的文件&#xff0c;将解压的路径配置到环境变量中。 然后添加到path中。 下…

带你了解“函数递归”

目录 1. 什么是递归&#xff1f; 2. 函数递归的必要条件 2.1 接收一个整型值&#xff08;无符号&#xff09;&#xff0c;按照顺序打印它的每一位。 代码如下&#xff1a; 2.2 编写一个函数&#xff0c;不用临时变量求字符串长度 代码如下&#xff1a; 2.3 递归与迭代 …

爬虫之Selenium,Phantomjs,Chrome handless

爬虫之模拟浏览器前言1. Selenium1.1 Selenium介绍1.2 安装selenium1.3 Selenium访问京东1.4 Selenium元素定位1.5 seleniu访问元素信息1.6 selenium交互2. Phantomjs2.1 介绍Phantomjs2.1 使用Phantomjs3. Chrome handless3.1 Chrome handless的系统要求3.2 Chrome handless的…