函数式编程 (Functional Programming / FP)

  1. 什么是函数是编程
    函数式编程是一种编程方式,属于结构化编程方式的一种。主要思想是把运算过程尽量写成一系列嵌套的函数调用。

举例来说:

(1 + 2) * 3 - 4

    // 过程式编程,可能这样写:
	var a = 1 + 2;

	var b = a * 3;

	var c = b - 4; 

	// 函数式编程要求使用函数,我们可以把运算过程定义为不同的函数,然后写成下面这样:
	var result = subtract(multiply(add(1,2), 3), 4); 
   
这就是函数式编程。

它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算。而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。

函数式编程的特点:

  1. 函数是"第一等公民"
    指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。
  2. 只用"表达式",不用"语句"
    "表达式"是一个单纯的运算过程,总是有返回值;"语句"是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
    原因是函数式编程的开发动机,一开始就是为了处理运算,不考虑系统的读写。"语句"属于对系统的读写操作,所以就被排斥在外。
    实际应用中,不做读写是不可能的。因此,编程过程中,函数式编程只要求把读写限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。
  3. 没有"副作用"
    指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
    函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
  4. 不修改状态
    函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。
    在其他类型的语言中,变量往往用来保存"状态"(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。下面的代码是一个将字符串逆序排列的函数,它演示了不同的参数如何决定了运算所处的"状态"。
  5. 引用透明
    引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
    有了前面的第三点和第四点,这点是很显然的。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫"引用不透明",很不利于观察和理解程序的行为。
  6. 发展历史

函数式编程思想是一个非常古老的思想。我们简述如下:

  • 我们就从1900 年 David Hilbert 的第 10 问题(能否通过有限步骤来判定不定方程是否存在有理整数解?) 开始说起吧。
  • 1920,Schönfinkel,组合子逻辑(combinatory logic)。直到 Curry Haskell 1927 在普林斯顿大学当讲师时重新发现了 Moses Schönfinkel 关于组合子逻辑的成果。Moses Schönfinkel的成果预言了很多 Curry 在做的研究,于是他就跑去哥廷根大学与熟悉Moses Schönfinkel工作的Heinrich Behmann、Paul Bernays两人一起工作,并于 1930 年以一篇组合子逻辑的论文拿到了博士学位。Curry Brooks Haskell 整个职业生涯都在研究组合子,实际开创了这个研究领域,λ演算中用单参数函数来表示多个参数函数的方法被称为 Currying (柯里化),虽然 Curry 同学多次指出这个其实是 Schönfinkel 已经搞出来的,不过其他人都是因为他用了才知道,所以这名字就这定下来了;并且有三门编程语言以他的名字命名,分别是:Curry, Brooks, Haskell。Curry 在 1928 开始开发类型系统,他搞的是基于组合子的 polymorphic,Church 则建立了基于函数的简单类型系统。
  • 1929, 哥德尔(Kurt Gödel )完备性定理。Gödel 首先证明了一个形式系统中的所有公式都可以表示为自然数,并可以从一自然数反过来得出相应的公式。这对于今天的程序员都来说,数字编码、程序即数据计算机原理最核心、最基本的常识,在那个时代却脑洞大开的创见。
  • 1933,λ 演算。 Church 在 1933 年搞出来一套以纯λ演算为基础的逻辑,以期对数学进行形式化描述。 λ 演算和递归函数理论就是函数式编程的基础。
  • 1936,确定性问题(decision problem,德文 Entscheidungsproblem (发音 [ɛntˈʃaɪ̯dʊŋspʁoˌbleːm])。 Alan Turing 和 Alonzo Church,两人在同在1936年独立给出了否定答案。

1935-1936这个时间段上,我们有了三个有效计算模型:通用图灵机、通用递归函数、λ可定义。Rosser 1939 年正式确认这三个模型是等效的。

  • 1953-1957,FORTRAN (FORmula TRANslating ),John Backus。1952 年 Halcombe Laning 提出了直接输入数学公式的设想,并制作了 GEORGE编译器演示该想法。受这个想法启发,1953 年 IBM 的 John Backus 团队给 IBM 704 主机研发数学公式翻译系统。第一个 FORTRAN (FORmula TRANslating 的缩写)编译器 1957.4 正式发行。FORTRAN 程序的代码行数比汇编少20倍。FORTRAN 的成功,让很多人认识到直接把代数公式输入进电脑是可行的,并开始渴望能用某种形式语言直接把自己的研究内容输入到电脑里进行运算。John Backus 在1970年代搞了 FP 语言,1977 年发表。虽然这门语言并不是最早的函数式编程语言,但他是 Functional Programming 这个词儿的创造者, 1977 年他的图灵奖演讲题为[“Can Programming Be Liberated From the von Neumann Style? A Functional Style and its Algebra of Programs”]
  • 1956, LISP, John McCarthy。John McCarthy 1956年在 Dartmouth一台 IBM 704 上搞人工智能研究时,就想到要一个代数列表处理(algebraic list processing)语言。他的项目需要用某种形式语言来编写语句,以记录关于世界的信息,而他感觉列表结构这种形式挺合适,既方便编写,也方便推演。于是就创造了LISP。正因为是在 IBM 704 上开搞的,所以 LISP 的表处理函数才会有奇葩的名字: car/cdr 什么的。其实是取 IBM704 机器字的不同部分,c=content of,r=register number, a=address part, d=decrement part 。
  • 1966年,Niklaus Wirth发明了Pascal。
  • 1969年,Ken Thompson和Dennis Ritchie发明了C语言,过程式语言由于其高效和可移植性迅速崛起。
  • 1973年,Robin Milner 发明了ML(Meta Language),后来演变成了OCaml和Stardard ML。
  • 1977年,John Buckus在其图灵奖的演讲中创造了 Functional Programming 这个词。
  • 1990年,惰性求值的函数式编程语言 Haskell 1.0 发布。
  • 2014 年 3 月甲骨文公司发布了Java8正式版
  1. 和面向对象编程比有哪些优点
  2. FP天生的适合处理数据。

2.函数式最大的特点不是函数作为第一等公民,也不是各种map、filter,而是变量不可变.为什么变量不可变这么重要呢?因为一切由并发引起的问题,一切由多线程、多进程引起的问题,都是由变量可变引起的!!!如果变量不可变,那么所有的竞争问题、死锁问题、并发更新问题都将烟消云散。

面向对象的不足:

面向对象编程往往需要共享状态。对象及其行为常常会添加到同一个实体上,这样一来,如果一堆函数都要访问这个实体,而且这些函数的执行顺序不确定的话,很可能就会出乱子了,比如竞争条件(race conditions)这种现象(函数 A 依赖于实体的某个属性,但是在 A 访问属性之前,属性已经被函数 B 修改了,那么函数 A 在使用属性的时候,很可能就得不到预期的结果)。

函数式的不足:

代码如果过度利用了函数式的编程特性(如无参风格、大量方法的组合),就会影响其可读性,从而简洁度有余、易读性不足。
大部分工程师还是更熟悉面向对象编程、命令式编程,

  1. 函数式编程为什么流行
  2. 代码简洁

函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。

  1. 接近自然语言,易于理解

函数式编程的自由度很高,可以写出很接近自然语言的代码。

  1. 易于"并发编程"

函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。

6 Java 8如何实现函数式编程

6.1 Lambda表达式

//使用::关键字初始化构造函数,返回的是一个函数式接口,这个接口可以自己定义,也可以直接使用系统提供的函数式接口。现在先来定义一个函数式接口用于获取Person实例
@FunctionalInterface
public interface PersonSupply {
    Person get();
}
//接下来写一个接口的实现,如果按照常规写法,写出来的代码一般会是如下形式。
PersonSupply sp=new PersonSupply{
  public Person get(){
      Person person=new Person();
      return person;
  }
}
Person person=sp.get();//获取实例

//而自从Java8引入::关键字后,就可以简化为一行代码
PersonSupply sp=Person::new; //接口实现
Person person=sp.get();//获取实例

//当然为了使这个接口更通用,我们可以定义成如下形式
@FunctionalInterface
public interface PersonSupply<T> {
    T get();
}
PersonSupply<Person> sp=Person::new;
Person person=sp.get();

//java8自带的Supplier接口就是这样实现的,后面会做介绍。直接使用Supplier如下
Supplier<Person> sp=Person::new;
Person person=sp.get();



//这种简便写法同样支持带参构造函数,首先写一个函数式接口,由于需要参数所以get()里面输入参数,返回Person,如下。
@FunctionalInterface
public interface PersonSupply<P extends Person> {
    P get(String name, int age);
}
//常规写法如下:
PersonSupply<Person> sp=new PersonSupply<Person>{
  public Person get(String name, int age);{
      Person person=new Person(name,age);
      return person;
  }
}
Person person=sp.get("1111",20);

//简便写法如下:
PersonSupply<Person> sp=Person::new;
Person person=sp.get("1111",20);


//使用::关键字引用静态方法
//同样,写一个函数式接口,该静态方法需要参数,所以get()里传入参数,需要返回参数,所以返回String。

@FunctionalInterface
public interface PersonFactory {
    String get(String str);
}
PersonFactory pf=Person::show;
pf.get("哈哈哈");

PersonFactory pf=Person::show
等价于:
PersonFactory pf=new PersonFactory{
  public String getString str){
      return Person.show(str);
  }
}

6.2. 核心接口

6.2.1 Function

//源码
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    /**
     * @return a composed function that first applies the {@code before}
     * function and then applies this function
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    /**
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    /**
     * Returns a function that always returns its input argument.
     *
     * @param <T> the type of the input and output objects to the function
     * @return a function that always returns its input argument
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

//Function:
如何实例化?
对于这样的函数式接口,因为只有一个抽象函数,所以只要实现了该函数就能够实例化对象了。所以可以通过下面这一行代码的方式实例化一个“接口”对象

Function<Integer, Integer> fun = x -> x + 1;

其中等号(=)右边的是Function接口中的那个抽象方法的实现

首先我们已经知道了Function是一个泛型类,其中定义了两个泛型参数T和R,在Function中,T代表输入参数,R代表返回的结果

Function中没有具体的操作,具体的操作需要我们去为它指定,因此apply具体返回的结果取决于传入的lambda表达式。

 举例 我们用lambda表达式定义了一个行为使得i自增1,我们使用参数5执行apply,最后返回6
public void test(){
        Function<Integer,Integer> test= x -> x + 1;
        Integer res = test.apply(5);
        System.out.println(res);   // 6
}

在函数式编程之前我们定义一组操作首先想到的是定义一个方法,然后指定传入参数,返回我们需要的结果。
函数式编程的思想是先不去考虑具体的行为,而是先去考虑参数,具体的方法我们可以后续再设置。

compose接收一个Function参数,返回时先用传入的逻辑执行apply,然后使用当前Function的apply。

andThen跟compose正相反,先执行当前的逻辑,再执行传入的逻辑。

Function<Integer,Integer> A=i->i+1;
    Function<Integer,Integer> B=i->i*i;

    System.out.println(B.apply(A.apply(5))); //36

    System.out.println(B.compose(A).apply(5)); //36

    System.out.println(A.apply(B.apply(5)));  //26

    System.out.println(B.andThen(A).apply(5)); //26

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

6.2.2 Predicate

public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     */
    boolean test(T t);

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * AND of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code false}, then the {@code other}
     * predicate is not evaluated.
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     * Returns a predicate that represents the logical negation of this
     * predicate.
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * OR of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code true}, then the {@code other}
     * predicate is not evaluated.
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * Returns a predicate that tests if two arguments are equal according
     * to {@link Objects#equals(Object, Object)}.
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}


Predicate是个断言式接口其参数是<T,boolean>,也就是给一个参数T,返回boolean类型的结果。跟Function一样,Predicate的具体实现也是根据传入的lambda表达式来决定的。

看Predicate方法and,or和negate 这三个方法对应了java的三个连接符号&&、||和!

int[] numbers= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
        List<Integer> list=new ArrayList<>();
        for(int i:numbers) {
            list.add(i);
        }
        Predicate<Integer> p1=i->i>5;
        Predicate<Integer> p2=i->i<20;
        Predicate<Integer> p3=i->i%2==0;

        List test=list.stream().filter(p1.and(p2).and(p3)).collect(Collectors.toList());
        System.out.println(test.toString()); //* print:[6, 8, 10, 12, 14]

isEqual这个方法的返回类型也是Predicate,所以我们也可以把它作为函数式接口进行使用。我们可以当做==操作符来使用。

6.2.3 Cosumer

@FunctionalInterface
public interface Consumer<T> {
 
    void accept(T t);
 
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Consumer consumer = x -> {
int a = x + 2;
System.out.println(a);// 12
System.out.println(a + “”);// 12
};
consumer.accept(10);
接收一个泛型的参数T,然后调用accept,对这个参数做一系列的操作,没有返回值,主要是对入参做一些列的操作,在stream里,主要是用于forEach;内部迭代的时候,对传入的参数,做一系列的业务操作,没有返回值;

6.2.4 Supplier

该接口就一个抽象方法get方法,不用传入任何参数,直接返回一个泛型T的实例.就如同无参构造一样

package java.util.function;
 
@FunctionalInterface
public interface Supplier<T> {
 
    T get();
}

 Supplier<String> s = () -> "Hello World!";
        String s1 = s.get();

Supplier和Cosumer都是功能接口。 `Supplier`表示结果的提供者,该结果返回一个对象且不接受任何参数,而`Consumer`表示一个操作,其接受单个输入参数且不返回任何结果

6.2.5 Stream

java8提供的StreamAPI可以让程序员像操作数据库一样操作集合。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序.

Stream专注于集合对象的操作,将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

Stream的三大特性:1、不存储数据;2、不改变源数据;3、延时执行。

stream的优点:1、简化代码;2、使用并行流可以利用多核特性,提升效率。

简而言之,Stream API是一个非常高效的数据处理框架

什么是流:

Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如,“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

Stream特点:

1元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。

2数据源的来源。 可以是集合,数组,I/O channel, 产生器generator 和IntStream等

3聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

4.stream上的所有操作分为两类:中间操作和结束操作,中间操作只是一种标记,只有结束操作才会触发实际计算。

中间操作最终操作: stream包含中间(intermediate operations)和最终(terminal operation)两种形式的操作。中间操作(intermediate operations)的返回值还是一个stream,因此可以通过链式调用将中间操作(intermediate operations)串联起来。最终操作(terminal operation)只能返回void或者一个非stream的结果。在上述例子中:filter, map ,sorted是中间操作,而ForEach是一个最终操作。上面例子中的链式调用也被称为操作管道流。

大多stream操作接受某种形式的lambda表达式作为参数,通过方法接口的形式指定操作的具体行为,这些方法接口的行为基本上都是无干扰(non-interfering)和无状态(stateless)。无干扰(non-interfering)的方法的定义是:该方法不修改stream的底层数据源,比如上述例子中:没有lambda表达式添加或者删除myList中的元素。无状态(stateless)方法的定义:操作的执行是独立的,比如上述例子中,没有lambda表达式在执行中依赖可能发生变化的外部变量或状态。

并行流parallelStream

提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现。简单理解就是多线程异步任务的一种实现。这可以很大程度简化我们使用并发操作.

并行流parallelStream的线程安全

由于并行流使用多线程,则一切线程安全问题都应该是需要考虑的问题,如:资源竞争、死锁、事务、可见性等等。

注意并行流:适合没有线程安全问题、较单纯的数据处理任务

返回值是还是Stream类型的操作是中间操作,返回值是void或者是非Stream类型的操作的最终操作。Stream的API不会改变原始数据

6.2.5.1 Stream API

是基于上面的函数式编程的在集合类上进行复杂操作的工具。之前都需要通过写遍历或者写迭代器相关代码对于每个元素进行筛选,但运用Stream就可以只传入规则,剩下的交给集合类去做,我们坐等结果就好了。

通过 java.util.Collection.stream() 方法用集合创建流

List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream1 = list.parallelStream();
// 顺序流转成并行流
Stream<String> parallelStream2 = stream.parallel();

stream和parallelStream的简单区分: stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:

如果流中的数据量足够大,并行流可以加快处速度。

Optional概念:

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象

6.2.5.2 筛选(filter)

筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。

例子 人员类:

class Person {
 // 姓名
 private String name; 
 // 薪资
 private int salary;
 // 年龄
 private int age;
 //性别
 private String sex;
 // 地区
 private String area;  

 

//filter 过滤 筛选员工中工资高于8000的人,并形成新的集合
  List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
  personList.add(new Person("Anni", 8200, 24, "female", "New York"));
  personList.add(new Person("Owen", 9500, 25, "male", "New York"));
  personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

  List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).map(Person::getName).collect(Collectors.toList());
  System.out.print("StreamAPI 高于8000的员工姓名:" + fiterList);
//StreamAPI 高于8000的员工姓名:[Tom, Anni, Owen]
    
  List<String> personNameList = new ArrayList<>();
        for (Person person : personList) {
            if( person.getSalary() > 8000){
                personNameList.add(person.getName());
            }
        }
        System.out.println("常规方法 高于8000的员工姓名:" + personNameList);
    //常规方法 高于8000的员工姓名:[Tom, Anni, Owen]

6.2.5.3 遍历/匹配(foreach/find/match)

List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1, 3, 5, 5, 6, 10);

        //遍历输出大于6的元素
        list.stream().filter(x -> x > 6).forEach(System.out::println);
7
9
8
10
   
        for (Integer tmp : list) {
            if (tmp > 6) {
                System.out.println(tmp);
            }
        }
7
9
8
10

    
        //匹配第一个大于6的元素
        Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst();
        System.out.println("StreamAPI 匹配第一个大于6的元素:" + findFirst.get());
		//StreamAPI 匹配第一个大于6的元素:7

        Integer tmp1 = null;
        for (Integer tmp : list) {
            if (tmp > 6) {
                tmp1 = tmp;
                break;
            }
        }
        System.out.println(" 常规方法匹配第一个大于6的元素:" + tmp1);
		//StreamAPI 是否包含符合大于6的元素:true






        //是否包含符合大于6的元素
        boolean anyMatch = list.stream().anyMatch(x -> x < 6);
        System.out.println("StreamAPI 是否包含符合大于6的元素:" + anyMatch);
		//StreamAPI 是否包含符合大于6的元素:true

        boolean tmp2 = false;
        for (Integer tmp3 : list) {
            if (tmp3 > 6) {
                tmp2 = true;
            }
        }
        System.out.println("常规方法 是否包含符合大于6的元素:" + tmp2);
		//常规方法 是否包含符合大于6的元素:true

6.2.5.4 聚合(max/min/count)

max、min、count类似于mysql中我们常用进行数据统计。Java stream中也引入了这些概念和用法,极大地方便了对集合、数组的数据统计工作。

获取String集合中最长的元素。

List<String> list = Arrays.asList("","adnm1", "admmmt", "pot", "xbangd", "weoujgsd");

        Optional<String> max = list.stream().max(Comparator.comparing(String::length));
        System.out.println("StreamAPI 最长的字符串:" + max.get());
		// StreamAPI 最长的字符串:weoujgsd

        int length = 0;
        String tmp = null;
        for (String s : list) {
            if (s.length() > length) {
                tmp = s;
            }
        }
        System.out.println("最长的字符串:" + tmp);
 	// 最长的字符串:weoujgsd

获取员工工资最高的人。

List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Tom", 8900, 23, "male", "New York"));
        personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
        personList.add(new Person("Anni", 8200, 24, "female", "New York"));
        personList.add(new Person("Owen", 9500, 25, "male", "New York"));
        personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

        Optional<Person> max = personList.stream().max(Comparator.comparingInt(Person::getSalary));
        System.out.println("员工工资最大值:" + max.get().getSalary());
    	//员工工资最大值:9500

        int salaryTmp = 0;
        for (Person person : personList) {
            if (person.getSalary() >salaryTmp){
                salaryTmp = person.getSalary();
            }
        }
        System.out.println("员工工资最大值:" + salaryTmp);
		//员工工资最大值:9500

6.2.5.5 映射(map/flatMap)

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:

  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
    // 将员工的薪资全部增加1000。
    public static void main(String[] args) {
    List personList = new ArrayList();
    personList.add(new Person(“Tom”, 8900, 23, “male”, “New York”));
    personList.add(new Person(“Jack”, 7000, 25, “male”, “Washington”));
    personList.add(new Person(“Lily”, 7800, 21, “female”, “Washington”));
    personList.add(new Person(“Anni”, 8200, 24, “female”, “New York”));
    personList.add(new Person(“Owen”, 9500, 25, “male”, “New York”));
    personList.add(new Person(“Alisa”, 7900, 26, “female”, “New York”));
List<Person> personListNew2 = personList.stream().map(person -> {
          person.setSalary(person.getSalary() + 10000);
          return person;
      }).collect(Collectors.toList());
  
  for (Person person : personList) {
          person.setSalary(person.getSalary() + 1000);
      }

Person(name=Tom, salary=18900, age=23, sex=male, area=New York)
Person(name=Jack, salary=17000, age=25, sex=male, area=Washington)
Person(name=Lily, salary=17800, age=21, sex=female, area=Washington)
Person(name=Anni, salary=18200, age=24, sex=female, area=New York)
Person(name=Owen, salary=19500, age=25, sex=male, area=New York)
Person(name=Alisa, salary=17900, age=26, sex=female, area=New York)

6.2.5.6 归约(reduce)

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

//求Integer集合的元素之和、乘积和最大值。
 List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
  // 求和方式1
  Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
  // 求和方式2
  Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
  // 求和方式3
  Integer sum3 = list.stream().reduce(0, Integer::sum);
  
  // 求乘积
  Optional<Integer> product = list.stream().reduce((x, y) -> x * y);
 
  // 求最大值方式1
  Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
  // 求最大值写法2
  Integer max2 = list.stream().reduce(1, Integer::max);
 
  System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);  //list求和:29,29,29
  System.out.println("list求积:" + product.get());    //list求积:2112 
  System.out.println("list求和:" + max.get() + "," + max2); //list求和:11,11




//求所有员工的工资之和和最高工资
List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
  personList.add(new Person("Anni", 8200, 24, "female", "New York"));
  personList.add(new Person("Owen", 9500, 25, "male", "New York"));
  personList.add(new Person("Alisa", 7900, 26, "female", "New York"));
 
  // 求工资之和方式1:
  Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);
  // 求工资之和方式2:
  Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),
    (sum1, sum2) -> sum1 + sum2);
  // 求工资之和方式3:
  Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum);
 
  // 求最高工资方式1:
  Integer maxSalary = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
    Integer::max);
  // 求最高工资方式2:
  Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
    (max1, max2) -> max1 > max2 ? max1 : max2);
 
  System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3);
  System.out.println("最高工资:" + maxSalary + "," + maxSalary2);

6.2.5.7 归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toList、toSet和toMap比较常用,另外还有toCollection、toConcurrentMap等复杂一些的用法。

List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
  List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
  Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());
 
  List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
  personList.add(new Person("Anni", 8200, 24, "female", "New York"));
  
  Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
    .collect(Collectors.toMap(Person::getName, p -> p));
  System.out.println("toList:" + listNew);
  System.out.println("toSet:" + set);
  System.out.println("toMap:" + map);

//toList:[6, 4, 6, 6, 20]
//toSet:[4, 20, 6]
//toMap:{Tom=Person(name=Tom, salary=8900, age=23, sex=male, area=New York), Anni=Person(name=Anni, salary=8200, age=24, sex=female, area=New York)}

6.2.5.8 统计(count/averaging)

Collectors提供了一系列用于数据统计的静态方法:

计数:count

平均值:averagingInt、averagingLong、averagingDouble

最值:maxBy、minBy

求和:summingInt、summingLong、summingDouble

统计以上所有:summarizingInt、summarizingLong、summarizingDouble

//统计员工人数、平均工资、工资总额、最高工资。

List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
 
  // 求总数
  Long count = personList.stream().collect(Collectors.counting());
  // 求平均工资
  Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
  // 求最高工资
  Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
  // 求工资之和
  Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
  // 一次性统计所有信息
  DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
 
  System.out.println("员工总数:" + count);
  System.out.println("员工平均工资:" + average);
  System.out.println("员工工资总和:" + sum);
  System.out.println("员工工资所有统计:" + collect);

员工总数:3
员工平均工资:7900.0
员工工资总和:23700
员工工资所有统计:DoubleSummaryStatistics{count=3, sum=23700.000000, min=7000.000000, average=7900.000000, max=8900.000000}

6.2.5.9 分组(partitioningBy/groupingBy)

  • 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
  • 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
    //将员工按薪资是否高于8000分为两部分;将员工按性别和地区分组
    List personList = new ArrayList();
    personList.add(new Person(“Tom”, 8900, 23, “male”, “New York”));
    personList.add(new Person(“Jack”, 7000, 25, “male”, “Washington”));
    personList.add(new Person(“Lily”, 800, 21, “female”, “Washington”));
    personList.add(new Person(“Tom1”, 9900, 23, “male”, “New York”));
    personList.add(new Person(“Jack1”, 70660, 25, “male”, “Washington”));
    personList.add(new Person(“Lily1”, 100, 21, “female”, “Washington”));
// 将员工按薪资是否高于8000分组
      Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
      // 将员工按性别分组
      Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
      // 将员工先按性别分组,再按地区分组
      Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
      System.out.println("员工按薪资是否大于8000分组情况:" + part);
      System.out.println("员工按性别分组情况:" + group);
      System.out.println("员工按性别、地区:" + group2)
          
          
     员工按薪资是否大于8000分组情况:{false=[Person(name=Jack, salary=7000, age=25, sex=male, area=Washington), Person(name=Lily, salary=800, age=21, sex=female, area=Washington), Person(name=Lily1, salary=100, age=21, sex=female, area=Washington)], true=[Person(name=Tom, salary=8900, age=23, sex=male, area=New York), Person(name=Tom1, salary=9900, age=23, sex=male, area=New York), Person(name=Jack1, salary=70660, age=25, sex=male, area=Washington)]}

员工按性别分组情况:{female=[Person(name=Lily, salary=800, age=21, sex=female, area=Washington), Person(name=Lily1, salary=100, age=21, sex=female, area=Washington)], male=[Person(name=Tom, salary=8900, age=23, sex=male, area=New York), Person(name=Jack, salary=7000, age=25, sex=male, area=Washington), Person(name=Tom1, salary=9900, age=23, sex=male, area=New York), Person(name=Jack1, salary=70660, age=25, sex=male, area=Washington)]}

员工按性别、地区:{female={Washington=[Person(name=Lily, salary=800, age=21, sex=female, area=Washington), Person(name=Lily1, salary=100, age=21, sex=female, area=Washington)]}, male={New York=[Person(name=Tom, salary=8900, age=23, sex=male, area=New York), Person(name=Tom1, salary=9900, age=23, sex=male, area=New York)], Washington=[Person(name=Jack, salary=7000, age=25, sex=male, area=Washington), Person(name=Jack1, salary=70660, age=25, sex=male, area=Washington)]}}

6.2.5.10 接合(joining)

joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。

List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
 
  String names = personList.stream().map(p ->   p.getName()).collect(Collectors.joining(","));
  System.out.println("所有员工的姓名:" + names);
  List<String> list = Arrays.asList("A", "B", "C");
  String string = list.stream().collect(Collectors.joining("-"));
  System.out.println("拼接后的字符串:" + string);


//所有员工的姓名:Tom,Jack,Lily
//拼接后的字符串:A-B-C

6.2.5.11 排序(sorted)

sorted,中间操作。有两种排序:

  • sorted():自然排序,流中元素需实现Comparable接口
  • sorted(Comparator com):Comparator排序器自定义排序
    //将员工按工资由高到低(工资一样则按年龄由大到小)排序
    List personList = new ArrayList();
personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
personList.add(new Person("Lily", 8800, 26, "male", "New York"));
personList.add(new Person("Alisa", 9000, 26, "female", "New York"));

// 按工资增序排序
List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
  .collect(Collectors.toList());
// 按工资倒序排序
List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
  .map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄自然排序(从小到大)
List<String> newList3 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
  .map(Person::getName).collect(Collectors.toList());
// 先按工资再按年龄自定义排序(从大到小)
List<String> newList4 = personList.stream().sorted((p1, p2) -> {
 if (p1.getSalary() == p2.getSalary()) {
  return p2.getAge() - p1.getAge();
 } else {
  return p2.getSalary() - p1.getSalary();
 }
}).map(Person::getName).collect(Collectors.toList());

System.out.println("按工资自然排序:" + newList);
System.out.println("按工资降序排序:" + newList2);
System.out.println("先按工资再按年龄自然排序:" + newList3);
System.out.println("先按工资再按年龄自定义降序排序:" + newList4);

//按工资自然排序:[Lily, Tom, Sherry, Jack, Alisa]
//按工资降序排序:[Sherry, Jack, Alisa, Tom, Lily]
//先按工资再按年龄自然排序:[Sherry, Jack, Alisa, Tom, Lily]
//先按工资再按年龄自定义降序排序:[Alisa, Jack, Sherry, Tom, Lily]

6.2.5.12 提取/组合

流也可以进行合并、去重、限制、跳过等操作。

String[] arr1 = { "a", "b", "c", "d" };
  String[] arr2 = { "d", "e", "f", "g" };
 
  Stream<String> stream1 = Stream.of(arr1);
  Stream<String> stream2 = Stream.of(arr2);
  // concat:合并两个流 distinct:去重
  List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
  // limit:限制从流中获得前n个数据
  List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
  // skip:跳过前n个数据
  List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
 
  System.out.println("流合并:" + newList);
  System.out.println("limit:" + collect);
  System.out.println("skip:" + collect2);

//流合并:[a, b, c, d, e, f, g]
//limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
//skip:[3, 5, 7, 9, 11]

6.3 扩展类

架构体系

一般函数: 输入T,输出R
Function: 接收T对象,返回R对象
IntFunction:
IntToDoubleFunction
IntToLongFunction
LongFunction:
LongToDoubleFunction
LongToIntFunction
DoubleFunction:
DoubleToIntFunction
DoubleToLongFunction
ToDoubleFunction
ToIntFunction
ToLongFunction
UnaryOperator: 接收T对象,返回T对象,返回类型与传入参数类型相同
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BiFunction:传入一个参数,返回一个结果
ToDoubleBiFunction
ToIntBiFunction
ToLongBiFunction
BinaryOperator:接收两个T对象,返回T对象,返回类型与传入的两个参数类型相同
IntBinaryOperator
LongBinaryOperator
DoubleOperator
谓词函数: 输出布尔类型
Predicate:接收T对象并返回boolean
IntPredicate
LongPredicate
DoublePredicate
BiPredicate
消费者: 无返回值
Consumer:接收T对象,不返回值
IntConsumer
LongConsumer
DoubleConsumer
BiConsumer:
ObjIntConsumer
ObjLongConsumer
ObjDoubleConsumer
供应者: 无参数,只有返回值
Supplier:提供T对象(例如工厂),不接收值
IntSupplier
LongSupplier
DoubleSupplier
BooleanSupplier