第6章  java8与并发

1 显式函数指函数与外界交换数据的唯一渠道就是参数和返回值,显式函数不会去读取或者修改函数的外部状态。这样的函数对于调试和排错是有益的。

2 函数式编程式申明式的编程方式。而命令式则喜欢大量使用可变对象和指令。如下

// 命令式编程
public static void imperative(){
    int[] iArr = {1,3,4,5,6,9,8,7,4,2};
    for(int i=0;i<iArr.length;i++){
        System.out.println(iArr[i]);
    }
}    

// 申明式编程
public static void declarative(){
    int[] iArr = {1,3,4,5,6,9,8,7,4,2};
    Arrays.stream(iArr).forEach(System.out::println);
}

3 Java8版本里增加了函数式编程。在函数式编程中,几乎所有传递的对象都不会被轻易修改,如下

package com.yxj.test.util;

import java.util.Arrays;

public class Java8Test {
    public static void main(String[] args){
        int[] arr = {1,2,3,4,5,6,7,8,9,10};
        Arrays.stream(arr).map((x)->x=x+1).forEach(System.out::println);
        System.out.println();
        Arrays.stream(arr).forEach(System.out::println);
    }
}

4 由于对象都处于不变状态,因此函数式编程更加易于并行。

5 函数式接口,就是只定义了单一抽象方法的接口。注释FunctionalInterface用于表明IntHandler接口是一个函数式接口,该接口被定义为只包含一个抽象方法handler( )。如果一个函数满足函数式接口的定义,那么即使不标注为@FunctionalInterface。

// 符合函数式接口
@FunctionalInterface
public static interface IntHandler{
    void handle(int i);
}


// 不符合函数式接口
public static interface IntHandler{
    void handle(int i);
    void handle2(int i);
}

6 函数式接口只能有一个抽象方法,而不是只能有一个方法。这分两点来说明:首先,在Java8中,接口运行存在实例方法,其次任何被java.lang.Object实现的方法,都不能视为抽象方法。如下的NoFunc接口不是函数式接口,因为equals( )函数在java.lang.Object中已经实现

interface NoFunc{
    boolean equals(Object obj);
}

而下面实现的IntHandler接口符合函数式接口要求

@FunctionalInterface
public static interface IntHandler{
    void handle(int i);
    boolean equals(Object obj);
}

7 在Java8之前的版本,接口只能包含抽象方法。但从Java8之后,接口也可以包含若干个实例方法。这一改进使得Java8拥有了类似多继承的能力。一个对象实例,将拥有来自于多个不同接口的实例方法。

public interface IHorse{
    void eat();
    default void run(){
        System.out.println("hourse run");
    }
}

在Java8中,使用default关键字,可以在接口内定义实例方法。

8 但如果多个接口都定义了同个实例方法,实现类就无法区别要使用哪个,如下

// IHorse.java
package com.yxj.test.util;

public interface IHorse {
    void eat();
    default void run(){
        System.out.println("horse run");
    }
}



// IDonkey.java
package com.yxj.test.util;

public interface IDonkey {
    void eat();
    default void run(){
        System.out.println("Donkey run");
    }
}



// Mule.java
package com.yxj.test.util;

public class Mule implements IHorse,IDonkey {
    public void eat(){
        System.out.println("Mule eat");
    }
    public static void main(String[] args){
        Mule m = new Mule();
        m.run();
        m.eat();
    }
}

此时运行则会报错 Error:(3, 8) java: 类 com.yxj.test.util.Mule从类型 com.yxj.test.util.IHorse 和 com.yxj.test.util.IDonkey 中继承了run() 的不相关默认值

此时不得不重新实现run( )方法,让编译器进行方法绑定,如下

package com.yxj.test.util;

public class Mule implements IHorse,IDonkey {
    public void run(){
        IHorse.super.run();
    }
    public void eat(){
        System.out.println("Mule eat");
    }
    public static void main(String[] args){
        Mule m = new Mule();
        m.run();
        m.eat();
    }
}

9 在Java8中,Comparator接口新增若干个默认方法,用于多个比较器的整合。其中一个默认方法如下

default Comparator<T> thenComparing(Comparator<? super T> other){
    Objects.requireNonNull(other);
    return (Comparator<T> & Serializable) (c1 , c2) -> {
        int res = compare(c1,c2);
        return (res != 0)?res:other.compare(c1,c2);
}

有了这个默认方法,在进行排序时,就可以非常方便地进行元素多条件排序,如下

Comparator<String> cmp = Comparator.comparingInt(String::length).thenComparing(String.CASE_INSENSITIVE_ORDER);

10 lambda表达式可以算是函数式编程的核心。lambda表达式即匿名函数,是一段没有函数名的函数体,可以作为参数直接传递给相关调用者。

List<Integer> numbers = Arrays.asList(1,2,3,4,5,6);
numbers.forEach((Integer value)->System.out.println(value));
Function<Integer,Integer> stringConverter = (from)->from*num;
System.out.println(stringConverter.apply(3));

其中的num必须要声明为final,这样才能保证在lambda表达式中合法的访问它

11 即使num不定义为final,Java 8会自动地在lambda表达式中使用地变量视为final

int num = 2;   // 等同于final int num = 2;
Function<Integer,Integer> stringConverter = (from)->from*num;
num ++;    // 会报错,因为num为final变量
System.out.println(stringConverter.apply(3));

12 方法引用是Java 8中提出的用来简化lambda表达式的一种手段。它通过类名和方法名来定位到一个静态方法或者实例方法。分为如下几种

  • 静态方法引用:ClassName::methodName
  • 实例上的实例方法引用:instanceReference::methodName
  • 超类上的实例方法引用:super::methodName
  • 类型上的实例方法引用:ClassName::methodName
  • 构造方法引用:Class::new
  • 数组构造方法引用:TypeName[ ]::new
public class Mule{
    public static void main(String[] args){
        List<User> users = new ArrayList<User>();
        for(int i=1;i<10;i++){
            users.add(new User(i,"billy"+Integer.toString(i)));
        }
        users.stream().map(User::getName).forEach(System.out::println);
    }
}

User::getName表示调用User类的getName( )函数。

一般来说,如果使用的是静态方法,或者调用目标明确,那么流内的元素会自动作为参数使用。如果函数引用表示实例方法,并且不存在调用目标,那么流内元素就会自动作为调用目标。

13 

//
package com.yxj.test.util;

import java.util.Arrays;
import java.util.function.IntConsumer;

public class Mule{
    static int[] arr = {1,3,4,5,6,7,8,9,10};
    public static void main(String[] args){
        Arrays.stream(arr).forEach(new IntConsumer(){
            public void accept(int value){
                System.out.println(value);
            }
        });
    }
}


//
Arrays.stream(arr).forEach((x)->System.out.println(x));


//
Arrays.stream(arr).forEach(System.out::println);

14 

package com.yxj.test.util;

import java.util.Arrays;
import java.util.function.IntConsumer;

public class Mule{
    static int[] arr = {1,3,4,5,6,7,8,9,10};
    public static void main(String[] args){
        IntConsumer outprintln = System.out::println;
        IntConsumer errprintln = System.err::println;
        Arrays.stream(arr).forEach(outprintln.andThen(errprintln));
    }
}

15 Java 8中,可以在接口不变的情况下,将流改为并行老刘。这样就可以很自然地使用多线程进行集中的数据处理

16 使用并行流过滤数据。parallel( )函数得到一个并行流,接着,在并行流上进行过滤。此时Mule.isPrime( )函数会被多线程并发调用,应用于流中的所有元素。

package com.yxj.test.util;

import java.util.stream.IntStream;

public class Mule{
    public static boolean isPrime(int number){
        int tmp = number;
        if(tmp < 2)
            return false;
        for(int i=2;Math.sqrt(tmp) >= i;i++){
            if(tmp % i == 0)
                return false;
        }
        return true;
    }

    public static void main(String[] args){
        long n = IntStream.range(1,100000).filter(Mule::isPrime).count();
        System.out.println(n);

        // 使用parallel( )并行处理 
       IntStream.range(1,100000).parallel().filter(Mule::isPrime).count();
        System.out.println(n);

    }
}

17 从集合得到并行流

List<Student> ss = new ArrayList<Student>();
double ave = ss.stream().mapToInt(s->s.score).average().getAsDouble();

如果希望并行化,可以使用parallelStream( )函数

double ave = ss.parallelStream().mapToInt(s->score).average().getAsDouble();

18 并行排序。Java 8中提供了简单的并行功能。比如使用新增的Arrays.parallelSort( )函数直接使用并行排序

int[] arr = new int[10000000];
Arrays.parallelSort(arr);

19 CompletableFuture是Java 8新增的一个超大型工具类。它不仅实现了Future接口,更重要它也实现了CompletionStage接口。CompletionStage接口拥有多达约40种方法,是为了函数式编程中的流式调用准备的。

stage.thenApply(x->square(x)).thenAccept(x->System.out.print(x)).thenRun(()->System.out.println());

20 向CompletableFuture请求一个数据,如果数据还没准备好,请求线程就会等待。但通过CompletableFuture,可以手动设置CompletableFuture的完成状态。

package com.yxj.test.util;

import java.util.concurrent.CompletableFuture;

public class AskThread implements Runnable {
    CompletableFuture<Integer> re = null;
    public AskThread(CompletableFuture<Integer> re){
        this.re = re;
    }
    public void run(){
        int myRe = 0;
        try{
            myRe = re.get()*re.get();
        }catch (Exception e){

        }
        System.out.println(myRe);
    }
    public static void main(String[] args) throws InterruptedException{
        final CompletableFuture<Integer> future = new CompletableFuture<>();
        new Thread(new AskThread(future)).start();
        Thread.sleep(1000);
        future.complete(60);
    }
}

当AskThread执行到myRe = re.get( )*re.get( )会阻塞,因为CompletableFuture中根本没有它所需要的数据,整个CompletableFuture处于未完成状态。但sleep( )结束后,将最终数据载入CompletableFuture,并标记为完成状态。

21 异步执行任务

package com.yxj.test.util;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class AskThread{
    public static Integer calc(Integer para){
        try{
            Thread.sleep(1000);
        }catch (Exception e){

        }
        return para*para;
    }
    public static void main(String[] args) throws ExecutionException,InterruptedException{
        final CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->calc(50));
        System.out.println(future.get());
    }
}

在supplyAsync( )函数中,它会在一个新的线程中,执行传入的参数。

22 在CompletableFuture中,类似的工厂方法有以下几个:

static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) ;

static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier , Executor executor) ;

static CompletableFuture<Void> runAsync(Runnable runnable) ;

static CompletableFuture<Void> runAsync(Runnable runnable , Executor executor) ;

其中supplyAsync( )函数用于那些需要有返回值的场景。而runAsync( )函数用于没有返回值的场景。

23 在Java 8中,新增ForkJoinPool.commonPool( )函数,它可以获得一个公共的ForkJoin线程池。这个公共线程池中的所有线程都是Daemon线程。

24 流式调用