第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 流式调用