一般网站建设多少钱便宜的网站好吗,进腾讯做游戏视频网站,wap网页游戏枭雄,手机网站怎样做JDK8知识点梳理 一、lambda表达式1.标准格式2.实现原理3.省略模式4.前提条件 二、函数式接口1.函数式接口#xff1a;FunctionalInterface2.接口默认方法3.接口静态方法4.供给型接口#xff1a;Supplier5.消费型接口#xff1a;Consumer6.消费供给型接口#xff1a;Functio… JDK8知识点梳理 一、lambda表达式1.标准格式2.实现原理3.省略模式4.前提条件 二、函数式接口1.函数式接口FunctionalInterface2.接口默认方法3.接口静态方法4.供给型接口Supplier5.消费型接口Consumer6.消费供给型接口Function7.判断型接口Predicate 三、方法引用1.对象名引用成员方法2.类名引用静态方法3.类名引用构造方法 四、stream流1.stream流的两种方式惰性求值、及早求值2.forEach方法及早求值3.count方法及早求值4.filter方法惰性求值5.limit方法惰性求值6.skip方法惰性求值7.map,flatMap方法惰性求值8.sorted方法惰性求值9.distinct方法惰性求值10.match方法及早求值11.find方法及早求值12.max/min方法及早求值13.reduce方法及早求值14.map和reduce的组合使用及早求值15.mapToInt(long,double)方法惰性求值16.concat方法惰性求值17.collect方法18.对流数据进行聚合计算19.对流数据进行分组20.对流数据进行分区21.对流数据进行拼接22.并行流23.并行流的线程安全问题 五、Optional1.静态方法Optional.of()2.静态方法Optional.ofNullable()3.静态方法Optional.empty()4.isPresent5.ifPresent(ConsumerT consumer)6.filter(Predicate? super T predicate)7.map(Function? super T, ? extends U mapper)8.flatMap(Function? super T, OptionalU mapper)9.get()10.orElse(T other)11.orElseGet(Supplier? extends T other)12.orElseThrow(Supplier? extends X exceptionSupplier)13.注意事项 六、新的日期时间API1.LocalDate日期2.LocalTime时间3.LocalDateTime日期时间4.日期的格式化与解析DateTimeFormatter5.Instant类6.计算日期差值Duration / Period6.时间矫正7.日期时区 七、并发编程与任务编排CompletableFuture1.1.开启异步任务runAsync() 两个方法1.2.开启异步任务supplyAsync() 两个方法2.1 获取任务结果get() 两个方法2.2 获取任务结果getNow()2.3 获取任务结果join()3.1 设置任务结果complete()3.2 设置任务结果completeExceptionally()4.1 多任务依赖执行thenApply()、thenApplyAsync()4.2 多任务依赖执行thenAccept()、thenAcceptAsync()4.3 多任务依赖执行thenRun()、thenRunAsync4.4 多任务依赖执行whenComplete、whenCompleteAsync4.5 多任务依赖执行handle()、handleAsync()5.1 双任务组合处理thenCombine() 三个方法5.2 双任务组合处理thenAcceptBoth() 三个方法5.3 双任务组合处理runAfterBoth() 三个方法5.4 双任务组合处理applyToEither() 三个方法5.5 双任务组合处理acceptEither 三个方法5.6 双任务组合处理runAfterEither 三个方法6.1 多任务组合处理allOf()6.2 多任务组合处理anyOf() 一、lambda表达式
lambda可以说是带给了java在编码上巨大的变化。
1.标准格式
包含参数类型的参数列表 - { } 这里的-是固定写法大括号内部写业务逻辑其实可以看成是一个省去了方法名和返回类型以及访问修饰符的方法。
String str,Object obj - {
// 这里处理业务逻辑
}代码很简单带来的却是书写上巨大的变化下面做一个线程代码传统方式与lamdba的书写对比。 感觉java的很多设计在偷学前端的设计比如lambda表达式这种在前端叫箭头函数很早就有了而他的使用vue里也是有的vue中有插槽的概念插槽是子组件进行定义使用父组件真正去实现。其实和lambda很像。lambda也是定义使用的不实现而是有调用方进行实现具体的逻辑。 /**1. author pcc*/
public class TestLambda {public static void main(String[] args) {TestLambda.testByLambda();TestLambda.testByNormal();}/*** 这是lambda写法*/public static void testByLambda(){Runnable r () - System.out.println(Hello World);r.run();}/*** 这是普通写法*/public static void testByNormal(){Runnable r new Runnable() {Overridepublic void run() {System.out.println(Hello World);}};r.run();}
}可以看到lambda写法很是简洁但是若是刚使用时肯定会感觉写法陌生不好理解但新事物都是这样只要你使用习惯就会发现他的好处。
2.实现原理
那lambda的实现原理是什么呢需要先说下匿名内部类的实现原理lambda的作用其实就是简化匿名内部类的书写所以可以使用lambda书写的代码使用匿名内部类写完全没有问题。匿名内部类在编译时会自动生成一个实现接口的类重写接口方法程序真正执行时调用的就是这个系统帮我们生成的类。而lambda的原理其实和匿名内部类差不多。lambda表达式会在编译阶段生成一个私有的静态方法这个方法存放lambda表达式中的业务逻辑然后在运行阶段生成一个内部类调用在编译阶段生成的私有的静态方法。这就是他的原理
3.省略模式
其实在第一个的例子中已经使用了省略模式下面是省略的原则
参数类型可省略程序可以自动推导出因为传参对应的接口确定故参数类型可知若程序只有一句则return可省略中括号也可以省略 代码参照第一块
4.前提条件
lambda虽然书写起来比较优雅但是使用也是有条件的接口中只有一个抽象方法方可使用lambda。 当只有一个抽象方法时我们可以自动为该方法映射到lambda表达式上当有多个抽象方法时并补能自动确定使用哪个方法来进行映射当然也可以根据返回类型和传参等来进一步推断但是目前的java是不支持这么做的。
二、函数式接口
上面说了lambda的使用有一个条件必须是接口中只有一个抽象方法其实这种接口就叫做函数式接口。
1.函数式接口FunctionalInterface
只包含一个抽象方法的接口是函数式接口只要这一个条件不过我们可以发现java原生的函数式接口都有一个注解FunctionalInterface 比如说上面的Runnable接口规范中要求我们若是写函数式接口必须加上FunctionalInterface该注解可以帮助我们对代码进行检查是否符合函数式接口的规范如不符合会进行提示如下更有利于编码的规范和代码重构时对代码作用的推断
2.接口默认方法
接口默认方法是JDK8新增的一个点。之前接口是只能有抽象方法的而默认方法是可以有方法实现的顾名思义就是为方法提供默认实现但同时实现类也可以自定义新的实现。他的主要特点有
1.使用default关键字2.默认方法实现类可以有使用而不去实现3.实现类可以对默认方法进行重写实现类若重写则调用的是重写后的方法
/*** author pcc*/
FunctionalInterface
public interface TestFuncationalInterface {void test();public default void test1() {System.out.println(test1);}
}默认方法比较突出的优点是可以对已经使用很久的接口进行无缝改造不会影响其他功能实现类重不重写该方法都不会报错需要的类再重写即可。比如Map中的forEach方法就是默认方法
3.接口静态方法
接口静态方法与普通类的静态方法基本没有区别需要说的是新增静态方法并不影响函数式接口且静态方法不作为函数接口中唯一抽象方法的判定。他的典型特点有
1.静态方法不可继承普通类的静态方法也是不可继承的2.静态方法不可重写普通类也是如此3.可以通过接口.静态方法进行调用这点和普通类也是一直4.静态方法和默认方法都不参与对函数式接口中唯一方法的判定判断函数式接口时会忽略静态方法和默认方法
4.供给型接口Supplier
Supplier供应商的意思他只有一个抽象方法get不接收任何参数只做返回这种使用场景没有消费型接口使用场景较多不过在进行无需传参直接获取的场景上还是可以使用的。
FunctionalInterface
public interface SupplierT {/*** Gets a result.** return a result*/T get();
}简单使用
import java.util.function.Supplier;/*** author pcc*/
public class TestSupplier {public static void main(String[] args) {TestSupplier.testSupplier(()-{return hello;});}public static void testSupplier(SupplierString supplier){System.out.println(supplier.get());}
}5.消费型接口Consumer
Consumer消费者他是一个消费型接口接收一个参数进行处理但没有返回。不过他有两个方法一个是基本的accept方法还有一个是andThen方法这是一个默认方法如下
package java.util.function;import java.util.Objects;FunctionalInterface
public interface ConsumerT {void accept(T t);default ConsumerT andThen(Consumer? super T after) {Objects.requireNonNull(after);return (T t) - { accept(t); after.accept(t); };}
}第一个方法很好理解第二个看着有点绕呢下面是参照Consumer源码模仿的写法
FunctionalInterface
public interface LikeConsumerT {void accept(T t);default LikeConsumer? super T andThen(LikeConsumer? super T t2){return (t)-{accept(t);t2.accept(t);};}
}如果不理解那就多写几遍多模仿几遍渐渐就会理解了。 上面andThen代码意思其实是返回一个LikeConsumer接口因为该接口是函数接口所以返回的相当于这个函数式接口的方法实现也就是说andThen返回了一个新的LikeConsumer接口他的实现就是调用了两个accept。那使用时怎么用呢
/*** author pcc*/
public class TestLikeConsumer {public static void main(String[] args) {String str Hello Consumer;TestLikeConsumer.test(s-{System.out.println(s.toUpperCase());},s2-{System.out.println(s2.toLowerCase());},str);}static void test(LikeConsumerString consumer,LikeConsumerString consumer1,String str){consumer.andThen(consumer1).accept(str);}
}调用的时候可能会唯一感觉绕的是这里consumer.andThen(consumer1).accept(str);上面已经说了anThen其实是返回了一个LikeConsumer的acccpt的实现那怎么调用这个实现呢其实还是得通过accept来触发所以还是需要accept的调用。
6.消费供给型接口Function
这个函数接口是结合了上面两个接口既接收参数又返回参数。下面是仿写的Function接口
/*** author pcc*/
public interface LikeFunctionT,R {R apply(T t);// 仿写compose方法他的作用是传入一个前置执行的Function对象将他的返回结果作为参数传递给// 当前的apply所以compose传入的Function的返回参数必须是T类型入参则无所谓,// 另外因为apply的返回值是R类型所以默认方法的返回值必须是R类型// 返回函数的传入类型是V类型是因为compose接收的函数的传入是V类型这是一个泛型方法声明了泛型为V也就是传入// 的function的入参类型V而这个默认方法的返回就是一个组合函数他是需要知道真正的入参的类型的所以入参必须// 是V。// 这个方法总结一句话就是传入一个Function使用他的入参然后将其结果作为apply的入参最后返回R.default V LikeFunctionV,R compose(LikeFunction? super V,? extends T before){return (V v) -apply(before.apply(v));}// 这里与上面逻辑差不多接收一个后置的Function将本身的apply的执行结果作为参数传递给after所以// after他的入参必须是R而他的返回值是自己定义的泛型方法的泛型V所以andThen的返回函数的返回类型必须是V// 而原apply方法的入参是t所以新返回的Function的入参必须是t这个t也必须是泛型接口的泛型对象所以//这里只传入一个tdefault V LikeFunctionT,V andThen(LikeFunction? super R,? extends V after){return (T t)-after.apply(apply(t));}}这里需要说说的就是compose与andThen方法了andThen方法在Consumer中也是有的好像消费型接口都有用法上没有区别都是先执行当前的方法在执行andThen中的接口方法真正是他们俩共同构成了一个新的函数式接口调用开始执行的方法apply(T t)需要注意的是传入的接口的入参必须是上一个接口函数的返回值这里类型必须是一致的其他则没有要求了。 而对于compose来说和andThen正好是相反的function.compose(function2)其实相等于function2.andThen(function)。所以一般使用一个即可andThen更易于记忆。
/*** author pcc*/
public class TestLikeFunction {public static void main(String[] args) {TestLikeFunction.testCompost(Integer::parseInt, String::valueOf);}public static void testCompost(LikeFunctionString,Integer function, LikeFunctionInteger,String function2) {String apply function.andThen(function2).apply(123);String apply1 function2.compose(function).apply(123);// 上面两个操作是等价的System.out.println(apply.equals(apply1));}}上面是完全等价的因为都是先执行function的apply在以其结果传入function2中进行执行。
7.判断型接口Predicate
这个函数接口也是比较常用的他的主要用作布尔类型的判断程序中经常需要做true或false的判断其实是可以使用他的这样写起来应该会更优雅但是如果只有一行代码还是别使用了徒增麻烦。 他比较常用的方法是test、and、or、negate、isEquals等都比较简单。下面是源码:
FunctionalInterface
public interface PredicateT {boolean test(T t);default PredicateT and(Predicate? super T other) {Objects.requireNonNull(other);return (t) - test(t) other.test(t);}default PredicateT negate() {return (t) - !test(t);}default PredicateT or(Predicate? super T other) {Objects.requireNonNull(other);return (t) - test(t) || other.test(t);}// 这里的object是test中传入的对象真正使用时这么写Predicate.isEquals(张三).test(李四)// 这里的李四就是object张三则是targetRef了。static T PredicateT isEqual(Object targetRef) {return (null targetRef)? Objects::isNull: object - targetRef.equals(object);}}三、方法引用
lambda的出现简化了匿名内部类的实现方法引用的出现就是为了简化lambda表达式的写法。那以什么规则进行简化lambda的书写呢
1.只要是方法的入参和出参保持和函数式接口的入参和出参一致函数接口可能是泛型但必须有那我们就可以使用方法引用来简化lambda。这种适合比较简单的操作这种操作在工作中还是比较常见的。
1.对象名引用成员方法
这种是引用的普通的成员方法或者叫实例方法 下面代码将字符转大写输出 public static void main(String[] args) {ListString strings Arrays.asList(zhangsan,lisi,wangwu);strings.stream().map(String::toUpperCase).forEach(System.out::println);}2.类名引用静态方法
和上面使用没有区别 public static void main(String[] args) {ListString strings Arrays.asList(zhangsan,lisi,wangwu);strings.stream().map(AppDataController::testStaticFun).forEach(System.out::println);}static String testStaticFun(String str){return str.toUpperCase();}3.类名引用构造方法
这个有区别必须用new关键字其他规则一样 public static void main(String[] args) {ListString strings Arrays.asList(zhangsan,lisi,wangwu);strings.stream().map(StringBuilder::new).forEach(System.out::println);}四、stream流
这个小节的方法比较多得花费点时间了。这里需要记住一个大前提
1.stream流操作的数据原始值不会变stream流操作后会形成一个新的数据
1.stream流的两种方式惰性求值、及早求值
stream的本质是对数据进行流式计算我们要的最终结果肯定是一个确定的数组或者集合或者某个对象。所以根据这个规则可以将stream的烦方法分为两类一类是返回stream的方法支持我们进行链式调用这类方法叫做惰性求值方法一类是返回非stream的结果也就是我们这次流式操作已经达到目的后返回的最终结果这种叫及早求值方法。
2.forEach方法及早求值
这是一个比较常用的方法可以看到他不返回stream所以他是一个及早求值方法里面传入消费型接口所以他的处理是接收一个参数做逻辑处理然后无返回。 void forEach(Consumer? super T action);3.count方法及早求值
及早求值方法返回流中元素的长度。太简单不贴了
4.filter方法惰性求值
惰性求值传入Predicate函数接口保留true场景下的元素进行返回其他过滤掉也是比较简单 StreamT filter(Predicate? super T predicate);下面保留长度大于2的元素的值
ListString strings Arrays.asList(zhangsan,lisi,wangwu);
strings.stream().filter(str-str.length()2);5.limit方法惰性求值
这个是截取和sql里的limit一个意思。不过他只能传递一个参数long类型所以不出意外的只能从最左侧开始截取指定长度的数据
ListString strings Arrays.asList(zhangsan,lisi,wangwu);
// zhangsan
strings.stream().limit(1).forEach(System.out::println);6.skip方法惰性求值
和limit类似只能接受一个long类型参数。上面场景说了limit无法像sql里那样从指定位置开始截取只能从下标为0的位置开始其实搭配skip就可以实现类似于sql里的limit index count 这种操作了。skip的作用是跳过前多少个从下一个开始截取。
ListString strings Arrays.asList(zhangsan,lisi,wangwu);
// lisi wangwu
strings.stream().skip(1).limit(2).forEach(System.out::println);7.map,flatMap方法惰性求值
映射转化的作用接收一个Funcation作为入参。他可以将流中的A对象转化成B对象实现流中类型的转变这个还是很有用的比如在参数请求中通常需要做类型间的转换他还是很好用的。
R StreamR map(Function? super T, ? extends R mapper);使用
ListString strings Arrays.asList(zhangsan,lisi,wangwu);
// 8 4 6
strings.stream().map(String::length).forEach(System.out::println);flatMap还是很好用的他可以做多层级元素的扁平话处理如下
ListString strings Arrays.asList(zhangsan,zhang,lisi,zhangsi,liwu);
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
ListListString str3 new ArrayList();
str3.add(strs);
str3.add(strings);str3.stream().flatMap(x-x.stream()).forEach(System.out::println);这里会将返回流中的元素进行扁平化处理。不过这里好像只能处理到二级流当然如果更多级别可以使用嵌套来处理
8.sorted方法惰性求值
惰性求值默认调用对象的compareTo方法对象得实现Camparable接口才可以默认使用他的compareTo方法官方解释叫自然排序这里的自然讲的就是对象的默认排序规则了。string的默认排序规则是跟随每个字符的ascii码进行排序小的靠前大的靠后。所以顺序是升序。
ListString strings Arrays.asList(zhangsan,lisi,wangwu);
// lisi wangwu zhangsan
strings.stream().sorted().forEach(System.out::println);当然也可以不使用默认的排序方法使用自定义也是可以的下面是仿写的String的排序不过顺序反了如下
ListString strings Arrays.asList(zhangsan,lisi,wangwu);
// zhangsan wangwu lisi
strings.stream().sorted((a, b)-{int aLength a.length();int bLength b.length();int min Math.min(aLength, bLength);char[] aChar b.toCharArray();char[] bChar b.toCharArray();int i 0;while(imin){if(aChar[i] ! bChar[i]){return bChar[i] -aChar[i];}i;}return bLength-aLength;}).forEach(System.out::println);想要自己写排序规则记住两句话即可
1.传入的ab参数中a代表相邻元素的后位置元素b代表相邻元素的前位置元素2.相减结果为正元素位置不变相减结果为负元素调换位置
9.distinct方法惰性求值
这个方法也是比较简单直接调用hashcode和equals方法进行比较然后判断是否相等的
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);
// zhangsan zhang lisi wangwu
strings.stream().distinct().forEach(System.out::println);10.match方法及早求值
match方法有三个allMatch、anyMatch、noneMatch 他们三个传入都是Predicate接口,Predicate接收一个参数返回一个boolean类型的对象。 anyMatch方法使用如下这里需要特别注意anyMatch使用的是对象方法引用当使用对象方法引用时默认对象就是入参。这里相当于获取两个集合的交集。
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);
// 求交集 zhangsan zhang lisi
strs.stream().filter(str - strings.stream().anyMatch(str::equals)).forEach(System.out::println);allMatch方法使用如下只有Predicate全部为true才会返回true很明显没有任何一个元素能满足这个条件所以输出为空
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);// 空
strs.stream().filter(str - strings.stream().allMatch(str::equals)).forEach(System.out::println);noneMatch使用如下只有所有Predicate返回全部false才返回true。通俗点说就是不包含的元素返回true包含的返回false
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);
// zhaoliu ,这个可以用来求strs集合中删除的元素strings相当于有删有增strs是原始集合
strs.stream().filter(str - strings.stream().noneMatch(str::equals)).forEach(System.out::println);11.find方法及早求值
find有两个方法findFirst和findAny他们都会返回一个Optional对象他们使用上乍看没有区别findFirst肯定是返回流中的第一个元素而findAny则可能不返回第一个元素返回的值是任意的但是测试时都是返回第一个。若是想要结果转为Optional可以考虑使用该方法。
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);
// zhangsan zhangsan
strs.stream().findFirst().ifPresent(System.out::println);
strs.stream().findAny().ifPresent(System.out::println);12.max/min方法及早求值
传入一个比较器先对流中的元素进行排序max取排序后最大值min取排序后最小值。以下的写法都是可以的
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
ListInteger ints Arrays.asList(1,2,3,4,5);
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);//5 zhangsan
ints.stream().max(Integer::compareTo).ifPresent(System.out::println);
strs.stream().max(Comparator.comparing(String::length)).ifPresent(System.out::println);13.reduce方法及早求值
reduce有三个方法
方法一OptionalT reduce(BinaryOperatorT accumulator);
方法二T reduce(T identity, BinaryOperatorT accumulator);
方法三U U reduce(U identity,BiFunctionU, ? super T, U accumulator, BinaryOperatorU combiner);方法一和二其实传入的是根本接口是BiFunctionT,T,T方法一的作用是将每两个相邻的匀速进行计算然后将结果在与后面的结果进行计算这样依次类推直到最后一个元素。那方法二只多了一个identity是什么用呢方法一的起始值是下标为0的元素那若是我们想要一个值和下标为0的元素一起计算要怎么办呢起始identity就是这个作用使用了itentity就相当于先将其和第一个元素计算得到的结果再和第二个元素计算。方法一和二常用与求和等操作注意方法一返回的是Optional对象。reduce方法可以用来做聚合、排序、最大、最小等操作
ListInteger ints Arrays.asList(1,2,3,4,5);
// 15
ints.stream().reduce((x, y) - {System.out.println(x: x);System.out.println(y: y);return x y;
}).ifPresent(System.out::println);14.map和reduce的组合使用及早求值
map的作用是转化reduce的作用是递归操作其实可以使用他们的组合做很多事情下面是简单的例子
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);
// 27
strings.stream().map(String::length).reduce(Integer::sum).ifPresent(System.out::println);15.mapToInt(long,double)方法惰性求值
他们三个都差不多作用都是将数据转化为基本数据类型来进行处理转为基本数据类型可以减少占用的内存减少自动装箱和封箱的操作。
ListInteger ints Arrays.asList(1,2,3,4,5);
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);// 15
ints.stream().mapToInt(Integer::intValue).reduce(Integer::sum).ifPresent(System.out::println);16.concat方法惰性求值
顾名思义就是合并不同的流的方法他是Stream的静态方法可以直接调用不过一次只能合并两个流合并结果就是取两个集合的所有值不去重。特别需要注意的是使用concat合并过的流将不存在后面是无法使用的了这点一定切记。。。但是不影响原集合的信息。
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);// zhangsan,zhang,lisi,zhaoliu,zhangsan,zhang,lisi,wangwu,lisi
Stream.concat(strs.stream(),strings.stream()).forEach(System.out::println);
System.out.println(---------------------------------);
strs.stream().forEach(System.out::println);17.collect方法
将流中集合进行输出常用方法有如下
ListString strings Arrays.asList(zhangsan,zhang,lisi,wangwu,lisi);// zhangsan,zhang,lisi,wangwu,lisi
strings.stream().collect(Collectors.toCollection(ArrayList::new)).forEach(System.out::println);
// zhangsan,zhang,lisi,wangwu
strings.stream().collect(Collectors.toSet()).forEach(System.out::println);
// zhangsan,zhang,lisi,wangwu,lisi
strings.stream().collect(Collectors.toList()).forEach(System.out::println);
// zhangsanzhanglisiwangwulisi
System.out.println(strings.stream().collect(Collectors.joining()));18.对流数据进行聚合计算
这里的集合计算包括最大值、最小值、求和、平均值等操作。 最大值
ListInteger ints Arrays.asList(1,2,3,4,5);// 5
ints.stream().collect(Collectors.maxBy((x, y) - {return x - y;
})).ifPresent(System.out::println);最小值可以直接使用上面的将x-y改为y-x得到的就是最小值不过这种不利于阅读建议不用这么干
ListInteger ints Arrays.asList(1,2,3,4,5);// 1
ints.stream().collect(Collectors.minBy((x, y)-{return x-y;
})).ifPresent(System.out::println);平均值
ListInteger ints Arrays.asList(1,2,3,4,5);// 平均值3.0 注意这里的averagingInt 还有其他的averagingLong等方法
Double collect ints.stream().collect(Collectors.averagingInt(Integer::intValue));
System.out.println(平均值collect);总和
ListInteger ints Arrays.asList(1,2,3,4,5);// 总和15 ,这里也有long等其他类型的求和
Integer collect1 ints.stream().collect(Collectors.summingInt(Integer::intValue));
System.out.println(总和collect1);统计个数
ListInteger ints Arrays.asList(1,2,3,4,5);// 个数5
Long collect2 ints.stream().collect(Collectors.counting());
System.out.println(个数collect2);19.对流数据进行分组 单次分组 根据流中元素进行分组 ListInteger ints Arrays.asList(1,2,3,4,5);// 这里需要注意的是分组后的结果是一个Mapk,ListT集合
ints.stream().collect(Collectors.groupingBy(x-x)).forEach((k,v)-{System.out.println(kv);
});根据自定义条件进行分组 ListInteger ints Arrays.asList(1,2,3,4,5);// 这里返回的值就是我们Map的键也就是分组的组名这里的分组并不会减少数据sql分完组每组默认取一条ints.stream().collect(Collectors.groupingBy(x-{if(x3){return 大于3;}else{return 小于3;}})).forEach((k,v)-{System.out.println(kv);});2多级分组 一般若是对象会有多级分组的场景比如先根据性别分组再根据爱好分组等下面使用string的流进行演示 ListString strings Arrays.asList(zhangsan,zhang,lisi,zhangsi,liwu);// 第一次分组根据姓不姓zhang来分第二次根据名字的长度来分。这里都是分为两组也可以多写几种场景分为多个组
strings.stream().collect(Collectors.groupingBy(x-{if(x.startsWith(zhang)){return 姓zhang;}return 其他;
},Collectors.groupingBy(x-{if(x.length()5){return 大于5;}return 小于等于5;
}))).forEach((k,v)-{System.out.println(k);v.forEach((k1,v1)-{System.out.println(k1----v1);});
});需要注意groupingBy的第二个参数是Collectors.groupingBy()
20.对流数据进行分区
分区只能分为两个区因为分区传入的是Predicate接口他只有true和false两个场景所以分区只能分为两个区且分区名固定为true或者false。感觉分区能解决的问题分区肯定能解决
ListString strings Arrays.asList(zhangsan,zhang,lisi,zhangsi,liwu);strings.stream().collect(Collectors.partitioningBy(x-x.startsWith(zhang))).forEach((k,v)-{System.out.println(k::v);});21.对流数据进行拼接
数据拼接的方法之前已经列举了这里再说下吧
ListString strings Arrays.asList(zhangsan,zhang,lisi,zhangsi,liwu);// zhangsanzhanglisizhangsiliwu
System.out.println(strings.stream().collect(Collectors.joining()));22.并行流
并行流底层使用的是Fork join 框架的多线程并不是java的这种多线程支持对任务进行拆分计算所以可以提高效率他是将一个大任务拆分为了若干个小任务进行执行且线程之间还可以对任务进行抢占尽最大可能利用了线程资源。 获取并行流的两种方式
1.list.parallelStream() 使用集合直接点parallelStream即可常用2.stream对象.parallel()如果已经得到了流可以使用这种方式 并行流相较于单线程流会快很多操作演示
ListString strings Arrays.asList(zhangsan,zhang,lisi,zhangsi,liwu);strings.parallelStream().collect(Collectors.groupingBy(String::length)).forEach((k,v)-{System.out.println(k::v);
});23.并行流的线程安全问题
1.同步代码块 在可能产生异常的地方使用同步代码块2.可能产生线程安全问题的场景使用线程安全集合 比如ConcurrentHashMap等一些线程安全的集合3.Collections.synchronizedList(new ArrayList()) 这个底层其实还是使用的synchronized他是相当于接收一个集合把他放到自己的集合上然后自己集合都加了synchronized 若是并行时设计到集合操作或者时计算等很可能会出现线程安全问题此时是需要使用线程安全策略的。
五、Optional
Optional更像是一个容器他是一个final类类似String都是不可继承的。他的作用主要用来做非空判断的省略我们的ifnull等操作可以让代码看起来更优雅但里面的一些方法仍需注意也有可能带来空指针。
1.静态方法Optional.of()
of中支持传入任何类型进行返回一个Optional对象其实就是将对象装入到Optional中。不过这里不允许填充null否则报空指针
ListString list new ArrayList();
Optional.of(list);2.静态方法Optional.ofNullable()
ofNullable和of的区别就是允许装载null进入到Optional所以若是返回结果可能为null最好使用这个如下这种场景是不会报任何错误的只是不满足存在将没有任何动作而已。同时避免了ifnull的判断看起来更优雅。
ListString str3 null;
Optional.ofNullable(str3).ifPresent(System.out::println);3.静态方法Optional.empty()
他的作用就是清空一个Optional,或者初始状态调用返回一个空的Optional
// 什么也不会输出
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
Optional.ofNullable(strs).empty().ifPresent(System.out::println);// 返回一个空的Optional
Optional.empty();4.isPresent
及早方法这类类比stream里的方法返回Optional对象的为惰性方法否则是及早方法返回boolean用于判断Optional中是否存储了对象。
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
boolean present Optional.ofNullable(strs).empty().isPresent();
System.out.println(present);5.ifPresent(Consumer consumer)
及早求值不过没有返回值因为传入的是Consumer这个消费型接口参数其实就是Optional中的对象若是有则会执行Consumer的逻辑若是没有则不会执行。
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);// Optional中有对象就会执行ifPresent中的内容
Optional.ofNullable(strs).ifPresent(System.out::println);6.filter(Predicate? super T predicate)
这是惰性求值方法返回还是一个Optional他的作用和stream里的filter一样都是对内部元素进行过滤使用的
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);
Optional.ofNullable(strs).filter(x-x.size()0).ifPresent(System.out::println);7.map(Function? super T, ? extends U mapper)
惰性求值合理的map方法和Stream的map其实很像都是做内部元素转换的。
ListString strs Arrays.asList(zhangsan,zhang,lisi,zhaoliu);Optional.ofNullable(strs).map(x-{return x.stream().map(String::toUpperCase).collect(Collectors.toList());
}).ifPresent(System.out::println);8.flatMap(Function? super T, Optional mapper)
与map类似用起来差不多应该是可以类比Stream的flatMap对于map的差异。
ListString str3 null;
// 无输出map也是支持这个操作的
Optional.ofNullable(str3).flatMap(x-x.stream().findFirst()).ifPresent(System.out::println);9.get()
及早求值若是Optional中没有值使用get方法会报空指针所以一般需要配合isPresent一起使用但是这么写起来又比较麻烦没有直接使用ifPresent好用。
10.orElse(T other)
及早求值如果Optional有值就取出返回如果没有值则使用orElse传入的值相当于给了一个默认值
ListString str3 null;
Optional.ofNullable(str3).orElse(new ArrayList());11.orElseGet(Supplier? extends T other)
这个和上面差不多上面是直接传入的默认值这里的默认值是通过supplier接口生产出来的
ListString str3 null;
// 使用Supplier生产出一个默认值
Optional.ofNullable(str3).orElseGet(()-{return Arrays.asList(zhangsan,zhang,lisi,zhaoliu);}).stream().forEach(System.out::println);12.orElseThrow(Supplier? extends X exceptionSupplier)
这个也是类似上面只不过这里是生产一个异常出来当然也可以不生产异常但是一般还是希望按照接口的定义来操作。
ListString str3 null;
Optional.ofNullable(str3).orElseThrow(()- new RuntimeException(str3 is null));13.注意事项
可能空使用ofNullable不要使用of可能为空时要先判空再get不然可能空指针异常
六、新的日期时间API
老日期APIDate首先是基于1900开始计算然后就是各种线程不安全问题。所以才有了新的时间API 新的日期API
1.LocalDate日期
下面是些常用的API
System.out.println(LocalDate.now());
System.out.println(LocalDate.of(2019,10,1));
System.out.println(LocalDate.of(2019, Month.OCTOBER.getValue(), 1));
System.out.println(LocalDate.of(2019, Month.OCTOBER.getValue(), 1));
System.out.println(LocalDate.parse(2023-11-12));
System.out.println(LocalDate.parse(2023年12月13日, DateTimeFormatter.ofPattern(yyyy年MM月dd日)));
//以上所有方法返回的都是LocalDate
System.out.println(LocalDate.now().isAfter(LocalDate.of(2019, 10, 1)));
System.out.println(LocalDate.now().isBefore(LocalDate.of(2019, 10, 1)));
System.out.println(LocalDate.now().isEqual(LocalDate.of(2019, 10, 1)));
//已上是比较
System.out.println(LocalDate.now().getDayOfMonth()); // 获取日子
System.out.println(LocalDate.now().plusDays(1)); // 增加1天
System.out.println(LocalDate.now().minusDays(1)); // 减少一天
System.out.println(LocalDate.now().plusMonths(1)); // 增加1个月
System.out.println(LocalDate.now().withMonth(12)); // 修改月份2.LocalTime时间
这是时间的API大部分方法和上面是没有区别的都是类似的包含获取、判断、增加、截取某一段等
System.out.println(LocalTime.now());
System.out.println(LocalTime.of(12, 59, 59));
System.out.println(LocalTime.parse(12:59:59));System.out.println(LocalTime.now().isAfter(LocalTime.of(12, 59, 59)));
System.out.println(LocalTime.now().isBefore(LocalTime.of(12, 59, 59)));System.out.println(LocalTime.now().plusHours(1));
System.out.println(LocalTime.now().plusSeconds(20));
System.out.println(LocalTime.now().minusMinutes(12));System.out.println(LocalTime.now().getHour());
System.out.println(LocalTime.now().atDate(LocalDate.now()));3.LocalDateTime日期时间
方法其实都差不多
System.out.println(LocalDateTime.now());
System.out.println(LocalDateTime.of(LocalDate.now(), LocalTime.now()));
System.out.println(LocalDateTime.of(2019, 12, 31, 23, 59, 59));
System.out.println(LocalDateTime.parse(2007-12-03T10:15:30));
System.out.println(LocalDateTime.parse(2017年12月31日 23时59分59秒, DateTimeFormatter.ofPattern(yyyy年MM月dd日 HH时mm分ss秒)));System.out.println(LocalDateTime.now().isAfter(LocalDateTime.of(2019, 12, 31, 23, 59, 59)));
System.out.println(LocalDateTime.now().isBefore(LocalDateTime.of(2019, 12, 31, 23, 59, 59)));
System.out.println(LocalDateTime.now().isEqual(LocalDateTime.of(2019, 12, 31, 23, 59, 59)));System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyy年MM月dd日 HH时mm分ss秒)));
System.out.println(LocalDateTime.now().plusDays(1));
System.out.println(LocalDateTime.now().minusDays(1));
System.out.println(LocalDateTime.now().plusHours(1));
System.out.println(LocalDateTime.now().getDayOfMonth());
System.out.println(LocalDateTime.now().getDayOfWeek());4.日期的格式化与解析DateTimeFormatter
可以格式化日期、时间、日期时间等所有的新时间API其实上面已经涉及到了日期的格式化了。主要就是将日期以我们想要的格式进行展示和使用。
System.out.println(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss, Locale.CHINA).format(LocalDateTime.now()));
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); // 和上面的操作一模一样System.out.println(DateTimeFormatter.ofPattern(yyyy-MM-dd).format(LocalDate.now()));
System.out.println(LocalDate.now().format(DateTimeFormatter.ofPattern(yyyy-MM-dd)));System.out.println(DateTimeFormatter.ofPattern(yyyy-MM-dd).parse(2019-12-31));5.Instant类
这也是一个日期类不过主要用来处理时间戳会比较方便不过上面有的API他很多也都有但我感觉用不到可以使用他的下面这个用处会比System.cunrrentTime()效率高些。
System.out.println(Instant.now());
System.out.println(Instant.now().toEpochMilli()System.currentTimeMillis());6.计算日期差值Duration / Period
Duration用来计算时间的距离既可以算LocalTime也可以算LocalDateTime。/djuˈreɪʃn/ 第二个参数应该是较大的时间
System.out.println(Duration.between(LocalDateTime.of(2023, 8, 17, 20, 0, 0),LocalDateTime.now()).toMinutes()分钟);
System.out.println(Duration.between(LocalDateTime.of(2023, 8, 17, 20, 0, 0),LocalDateTime.now()).getSeconds()秒);
System.out.println(Duration.between(LocalDateTime.of(2023, 8, 17, 20, 0, 0), LocalDateTime.now()).toHours()时);Period用来计算日期的距离/ˈpɪəriəd/
System.out.println(Period.between(LocalDate.of(2023, 8, 16), LocalDate.now()).getDays());
System.out.println(Period.between(LocalDate.of(2023, 8, 16), LocalDate.now()).getMonths());6.时间矫正
TemporalAdjusters 可以将时间调整到我们想要的位置感觉没啥用呢他能做到的前面的也能做到但是他的操作会简单一些是真的
System.out.println(LocalDateTime.now().with(temporal - {LocalDateTime localDateTime (LocalDateTime) temporal;return LocalDateTime.of(localDateTime.getYear(), 1, 1, 0, 0, 0);}));// 上面代码和这里逻辑、结果一致
TemporalAdjuster temporalAdjuster localDateTime1 -{LocalDateTime localDateTime (LocalDateTime) localDateTime1;return LocalDateTime.of(localDateTime.getYear(), 1, 1, 0, 0, 0);
};
System.out.println(LocalDateTime.now().with(temporalAdjuster));7.日期时区
这里提供了时区时间、日期的API很少可以用到但挺有意思的
// 获取所有时区地区/城市
ZoneId.getAvailableZoneIds().forEach(System.out::println); System.out.println(ZonedDateTime.now(Clock.systemUTC()));
System.out.println(ZonedDateTime.now());这里大部分API没有列出来
七、并发编程与任务编排CompletableFuture
CompletableFuture是JKD8新增的支持异步调用与任务编排的新的API他对任务编排的支持比较友好相关API也比较丰富。当然底层依然是线程池相关技术。而且在操作时是支持我们自定义线程池的若是不定义线程池则默认使用Forkjoin.commonpool线程池这个线程池是一个守护线程池若是直接使用CompletableFuture会有隐患若是使用CompletableFuture执行异步任务且对异步任务结果不做获取获取结果自然就会等待线程执行完毕且子任务执行时间超过主线程此时因为子线程是守护线程会造成子线程意外终止。因此我们可以使用自定义的线程池注意高并发系统绝对禁止使用默认线程池并发不高时可以使用默认。
1.1.开启异步任务runAsync() 两个方法
与另个开启异步方法的区别是没有返回值只负责开启一个异步任务。他有两个方法一个是直接传入Runnable对象另一个支持多传入一个线程池不传入线程池默认使用ForkJoin的线程池
CompletableFuture.runAsync(()-{System.out.println(runAsync子任务执行了: Thread.currentThread().getName());
});System.out.println(主线程执行结束了: Thread.currentThread().getName());传入线程池的话自己新建即可一般推荐使用ThreadPoolExecutor,需要对他的七个参数都有了解再去创建这里不细说线程基础知识可以看这里 四万字爆肝总结多线程 并发夺命23问
1.2.开启异步任务supplyAsync() 两个方法
他与上面的区别是他有返回值他的返回对象是CompletableFutureT 这个T是有任务里写的return推导出来的无需我们去声明他也支持多传入一个线程池
CompletableFuture.supplyAsync(()-{System.out.println(supplyAsync子任务执行了: Thread.currentThread().getName());return Hello;
});System.out.println(主线程执行结束了: Thread.currentThread().getName());2.1 获取任务结果get() 两个方法
针对有返回值的异步任务我们是需要获取结果的第2部分则来说一下这3种获取结果的区别网上查阅说get不会阻塞主线程join则会阻塞主线程。我们知道Thread里的join方法是会导致线程阻塞的这里确实会导致阻塞但是笔者亲测get也会阻塞。下面展示结果主要是看是否会阻塞
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});System.out.println(子任务1的执行结果stringCompletableFuture.get());CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync2子任务执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return world;
});System.out.println(子任务2的执行结果stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());上面代码无论执行多少遍子任务1获取结果是子任务2都是处于等待状态第三个join其实和这个一样没有区别。
get还有一个方法支持传入超时时间也及时说等待多少时间还是获取不到值就会抛出超时异常这里注意get方法可能会抛出阻塞异常InterruptedException
System.out.println(stringCompletableFuture.get(1, TimeUnit.SECONDS));2.2 获取任务结果getNow()
这个方法比较简单就是取现在的子任务的执行结果取到了就返回获取的结果取不到就使用getNow传入的值作为默认值下面方法有个延时所以代码执行时肯定取不到所以就会返回默认值。
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});
System.out.println(stringCompletableFuture.getNow(我是默认值));
System.out.println(主线程执行结束了: Thread.currentThread().getName());2.3 获取任务结果join()
使用起来和get没发现有任何区别不过他只有一个方法。
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});
System.out.println(stringCompletableFuture.join());3.1 设置任务结果complete()
可以手动设置CompletableFuture的执行结果设置成功返回true失败返回False。目前没感觉到特别有用的用途。设置之后后面使用get、join等获取的结果都是使用complete设置的结果了而且设置之后CompletableFuture就会立即有结果而不必等其执行结束当然这个结果也不是CompletableFuture的真正执行结果
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});
stringCompletableFuture.complete(我是使用complete设置的值);
System.out.println(stringCompletableFuture.join());
System.out.println(stringCompletableFuture.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());看上图可以发现设置完之后子任务的返回值就是我们使用complete设置的值了而且这里还有一个问题因为get、join直接获取到了结果因此主线程在此并没有阻塞直接执行结束了此时子线程没有执行完毕就被杀掉了因为子线程都是守护线程。感觉可以用于在特定阶段对返回值的手动指定。
3.2 设置任务结果completeExceptionally()
这个与上面类似只是设置的是异常设置完成程序会手动抛一个异常出来。且子线程也是无法执行完毕的
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});
stringCompletableFuture.completeExceptionally(new RuntimeException(我是异常));
System.out.println(stringCompletableFuture.join());
System.out.println(stringCompletableFuture.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());4.1 多任务依赖执行thenApply()、thenApplyAsync()
上面说的都是单个任务的执行CompletableFuture好用就好用在对于多任务的编排的API很丰富支持我们各种可以想象到的操作这一节用来总结多任务依赖执行时的API。 thenApply() 是有入参有返回入参是上一个任务的执行结果返回还是一个CompletableFuture。 thenApplyAsync与上面相同只是他执行时是新起子线程且也支持线程池的传入同时因为返回都是CompletableFuture所以支持一直链式调用。不过需要说的是这种链式调用效率不会太高类似于阻塞式调用了因为下个任务总要等待上个任务的结果。
// 使用thenApply
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 stringCompletableFuture.thenApply(str-{System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());return str.toUpperCase();
});System.out.println(stringCompletableFuture.get());
System.out.println(stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());可以设想下执行结果没看结果之前我认为应该是先打印“supplyAsync子任务【2】执行了 ”再打印“Hello”可是结果不是这里我也比较疑惑暂时没搞明白以后遇到大佬再请教下吧需要特别注意的是thenApply用的是上一个任务的线程他们共用一个线程
下面是thenApplyAsync与上面没多大区别区别就是任务2也是异步了但是用的还是之前的线程除非两个任务用的线程池不是一个是一个的话还是会优先使用功能原线程的。这样做是合理的因为任务2是在任务1结束之后才执行使用原线程没有问题。
// 使用thenApplyAsync
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 stringCompletableFuture.thenApplyAsync(str-{System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return str.toUpperCase();
});System.out.println(stringCompletableFuture.get());
System.out.println(stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());4.2 多任务依赖执行thenAccept()、thenAcceptAsync()
他们俩的特点是接收一个参数不返回信息所以里面是Consumer类型的接口不过需要注意这里的不返回信息指的是CompletableFuture的泛型没有信息返回处理的参数是Consumer的入参之类不返回但不影响整体返回CompletableFuture但是CompletableFuture的返回类型应该是CompletableFutureVoid
// thenAccept
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureVoid stringCompletableFuture2 stringCompletableFuture.thenAccept(str-{System.out.println(thenAccept子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}
// return str.toUpperCase();
});System.out.println(stringCompletableFuture.get());
System.out.println(stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());这里thenAccept不返回结果所以获取时是null
下面是thenAcceptAsync
// thenAcceptAsync
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureVoid stringCompletableFuture2 stringCompletableFuture.thenAcceptAsync(str-{System.out.println(thenAccept子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}
// return str.toUpperCase();
});System.out.println(stringCompletableFuture.get());
System.out.println(stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());4.3 多任务依赖执行thenRun()、thenRunAsync
这里与上面的区别是不接收参数也不返回参数其他没啥区别了这些API都是支持自定义线程池的。
// thenRun()
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureVoid stringCompletableFuture2 stringCompletableFuture.thenRun(()-{System.out.println(thenAccept子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}
// return str.toUpperCase();
});System.out.println(stringCompletableFuture.get());
System.out.println(stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());下面是thenRunAsync
// thenRunAsync()
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureVoid stringCompletableFuture2 stringCompletableFuture.thenRunAsync(()-{System.out.println(thenAccept子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}
// return str.toUpperCase();
});System.out.println(stringCompletableFuture.get());
System.out.println(stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());4.4 多任务依赖执行whenComplete、whenCompleteAsync
注意这俩方法和completecompleteExceptionally没有关系这俩方法也是处理多任务依赖执行的第四节整个都是多任务依赖执行的方法whenComplete、whenCompleteAsync与上面的区别在于接收参数无返回可以拿到子任务1中的异常。注意若是多任务依赖执行时使用whenComplete那么前面任务若是catch住了异常他是拿不到的还有若是有异常且使用了whenComplete则异常信息只会在任务2中显示在我们定义的位置不会重复出现在任务1中下面模拟一个异常任务
// 使用whenComplete
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);int i 1/0;} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 stringCompletableFuture.whenComplete((str,thro)-{System.out.println(thenAccept子任务【2】执行了: Thread.currentThread().getName());//先判断是否有异常Optional.ofNullable(thro).ifPresent(e-{System.out.println(异常逻辑执行异常信息: e.getMessage());// do someThing()});// 执行到这说明没有异常System.out.println(无异常逻辑执行,执行结果: str.toUpperCase());
});System.out.println(stringCompletableFuture.get());
System.out.println(stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());可以设想下运行结果因为这里肯定有异常所以任务2会进入到异常逻辑块输出异常信息而正确的信息应该会执行不到因为这里会抛出异常 至于whenCompleteAsync就是方法名不同同时支持传入线程池其他没区别上面几个API也是如此这里就不重复列举了
4.5 多任务依赖执行handle()、handleAsync()
与上面的whenComplete区别在于handle可以接收参数接收的参数是上个任务的结果和异常也支持返回参数。而whenComplete是不支持的这是他们的区别此外用法一致。
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);int i 1/0;} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 stringCompletableFuture.handle((s,thro)-{System.out.println(handle子任务【2】执行了: Thread.currentThread().getName());if(null thro){System.out.println(handle子任务【2】异常信息: thro.getMessage());// doSomeThing()return 异常了;}return s.toUpperCase();
});System.out.println(stringCompletableFuture.get());
System.out.println(stringCompletableFuture2.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());可以设想下结果肯定是打印了异常注意一旦异常无论是whenComplete或者是handle获取到异常之后的代码都是无法执行的了只会执行到异常信息的地方。
5.1 双任务组合处理thenCombine() 三个方法
第四部分的API会有一个显著的问题那就是任务调用链基本是阻塞式的后面的任务必须依赖前面任务的结果当然这也不能算是问题若是业务如此则我们必须这么做倘若无需关心上一个任务的结果也无需等待上一个任务执行结束我们就可以不使用thenRun的API可以考虑使用多任务组合处理的API第一个要说的是thenComBine。第四部分的API都是传入一个函数式接口其实底层都是转到了Runnable上但是第五部分的API传递的都是CompletableFuture这是与上面的区别。其实很好理解因为他要做的是任务的并行自然需要的是多个CompletableFuture。 thenCombine的三个方法还有一个异步的还有一个支持传递线程池的API下面几个也是一样。
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return World;
});CompletableFutureString stringCompletableFuture3 stringCompletableFuture.thenCombine(stringCompletableFuture2, (result1, result2) - {System.out.println(任务1执行结果 result1 , 任务2执行结果 result2);return (result1 result2).toUpperCase();
});System.out.println(获取任务1结果stringCompletableFuture.get());
System.out.println(获取任务2结果stringCompletableFuture2.get());
System.out.println(获取任务1和任务2合并结果stringCompletableFuture3.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());
这里需要注意CompletableFuture有另个父级接口CompletableState、Future。这里我用任务1thenCombine时其实是需要传入CompletableState的所以CompletableState是可以传入的。第二个对象是一个BIFunction所以传入两个参数返回一个传入的两个参数就是任务1和任务2的处理结果。
5.2 双任务组合处理thenAcceptBoth() 三个方法
这个API与之前的accept的类似都是接收参数不返回的其他的和thenCombine类似同样
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return World;
});CompletableFutureVoid stringCompletableFuture3 stringCompletableFuture.thenAcceptBoth(stringCompletableFuture2, (result1, result2) - {System.out.println(任务1执行结果 result1 , 任务2执行结果 result2);
// return (result1 result2).toUpperCase();
});System.out.println(获取任务1结果stringCompletableFuture.get());
System.out.println(获取任务2结果stringCompletableFuture2.get());
System.out.println(获取任务1和任务2合并结果stringCompletableFuture3.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());可以预想到任务1和任务2的组合结果是null其他和上一个API没有区别
5.3 双任务组合处理runAfterBoth() 三个方法
这个和其他run也一样不接受返回结果也不返回结果只是等待前两个任务处理结束就行这里其中任务一个都是可以任务链的当前前面的也是一样。
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return World;
});CompletableFutureVoid stringCompletableFuture3 stringCompletableFuture.runAfterBothAsync(stringCompletableFuture2, () - {System.out.println(两个任务都执行完毕了: Thread.currentThread().getName());
});System.out.println(获取任务1结果stringCompletableFuture.get());
System.out.println(获取任务2结果stringCompletableFuture2.get());
System.out.println(获取任务1和任务2合并结果stringCompletableFuture3.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());5.4 双任务组合处理applyToEither() 三个方法
这个和后面的API都是一个类型的他们是当两个任务有任何一个执行完毕时都会触发谁先执行结束获取到的数据就是谁的。applyToEither支持传入和返回。同时它也有三个方法还有一个是异步的一个是支持异步传入线程池的x下面是一个异步的。
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return World;
});CompletableFutureString stringCompletableFuture3 stringCompletableFuture.applyToEitherAsync(stringCompletableFuture2, s - {System.out.println(两个任务有一个执行完毕了: Thread.currentThread().getName());System.out.println(执行结果是: s);return s.toUpperCase();
});System.out.println(获取任务1结果stringCompletableFuture.get());
System.out.println(获取任务2结果stringCompletableFuture2.get());
System.out.println(获取任务1和任务2合并结果stringCompletableFuture3.get());System.out.println(主线程执行结束了: Thread.currentThread().getName()); 5.5 双任务组合处理acceptEither 三个方法
这个只接受不返回其他和同类API类似
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return World;
});CompletableFutureVoid stringCompletableFuture3 stringCompletableFuture.acceptEither(stringCompletableFuture2, s - {System.out.println(两个任务有一个执行完毕了: Thread.currentThread().getName());System.out.println(执行结果是: s);
// return s.toUpperCase();
});System.out.println(获取任务1结果stringCompletableFuture.get());
System.out.println(获取任务2结果stringCompletableFuture2.get());
System.out.println(获取任务1和任务2合并结果stringCompletableFuture3.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());5.6 双任务组合处理runAfterEither 三个方法
不接收不返回他们都是有三个方法如下
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return World;
});CompletableFutureVoid stringCompletableFuture3 stringCompletableFuture.runAfterEither(stringCompletableFuture2, () - {System.out.println(两个任务有一个执行完毕了: Thread.currentThread().getName());
// System.out.println(执行结果是: s);
// return s.toUpperCase();
});System.out.println(获取任务1结果stringCompletableFuture.get());
System.out.println(获取任务2结果stringCompletableFuture2.get());
System.out.println(获取任务1和任务2合并结果stringCompletableFuture3.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());6.1 多任务组合处理allOf()
上面的API可以处理双任务的组合处理但是多余两个就没办法了所以还有allOf这个方法当传入的任务全部执行完毕后会进行触发该方法。如下这里需要特别注意的是若是最后的组合处理时间较慢主线程已经结束则可能会造成子任务未执行。这里无论是使用同步还是异步处理都会有这个问题因为无论是同步还是异步都会优先使用线程池中的线程其实都是类似于异步的。而使用了线程池中的线程又没有阻塞等待结果主线程就会执行完毕导致最终的任务没有被执行这个很好复现只需要把下面代码最后的的get方法调用去掉即可。 CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return World;
});CompletableFutureString stringCompletableFuture3 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【3】执行了: Thread.currentThread().getName());try {Thread.sleep(2000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return CompletableFuture;
});CompletableFuture.allOf(stringCompletableFuture, stringCompletableFuture2, stringCompletableFuture3).thenRunAsync(()-{System.out.println(所有任务都结束了: Thread.currentThread().getName());// 执行到这里所有任务肯定都结束了所以我们是可以手动获取到结果的try {System.out.println(获取任务1结果stringCompletableFuture.get());System.out.println(获取任务2结果stringCompletableFuture2.get());System.out.println(获取任务3结果stringCompletableFuture3.get());} catch (Exception e) {throw new RuntimeException(e);}});// TODO 这个很重要若是不对最终结果进行阻塞可能会造成最后的任务不执行
System.out.println(voidCompletableFuture.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());6.2 多任务组合处理anyOf()
与上面类似只不过是任意一个执行结束就会触发不过极端情况下也可能会出现上面同样的问题所以对最后一个任务的结果进行阻塞是有必要的。
CompletableFutureString stringCompletableFuture CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【1】执行了: Thread.currentThread().getName());try {Thread.sleep(3000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return Hello;
});CompletableFutureString stringCompletableFuture2 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【2】执行了: Thread.currentThread().getName());try {Thread.sleep(1000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return World;
});CompletableFutureString stringCompletableFuture3 CompletableFuture.supplyAsync(() - {System.out.println(supplyAsync子任务【3】执行了: Thread.currentThread().getName());try {Thread.sleep(2000L);} catch (InterruptedException e) {throw new RuntimeException(e);}return CompletableFuture;
});CompletableFutureVoid voidCompletableFuture CompletableFuture.anyOf(stringCompletableFuture, stringCompletableFuture2, stringCompletableFuture3).thenRun(() - {System.out.println(存在任务都结束了: Thread.currentThread().getName());// 执行到这里所有任务肯定都结束了所以我们是可以手动获取到结果的try {// 这里获取结果相当于阻塞了任务的执行可以保障所有任务都执行结束了System.out.println(获取任务1结果 stringCompletableFuture.get());System.out.println(获取任务2结果 stringCompletableFuture2.get());System.out.println(获取任务3结果 stringCompletableFuture3.get());} catch (Exception e) {throw new RuntimeException(e);}});// TODO 这个很重要若是不对最终结果进行阻塞可能会造成最后的任务不执行
System.out.println(voidCompletableFuture.get());System.out.println(主线程执行结束了: Thread.currentThread().getName());