jdk8虽然出现好久了,可是可能咱们仍是有不少人并不太熟悉,本文主要就是介绍说明一些jdk8相关的内容。html
主要会讲解:java
咱们来看看阿里规范里面涉及到jdk8相关内容:算法
https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html编程
主要有:api
1:lambda表达式:一种新的语言特性,可以把函数做为方法的参数或将代码做为数据。lambda表达式使你在表示函数接口(具备单个方法的接口)的实例更加紧凑。数组
2:方法引用 是lambda表达式的一个简化写法,所引用的方法实际上是lambda表达式的方法体实现,这样使代码更容易阅读数据结构
3:默认方法:Java 8引入default method,或者叫virtual extension method,目的是为了让接口能够过后添加新方法而无需强迫全部实现该接口的类都提供新方法的实现。也就是说它的主要使用场景可能会涉及代码演进。多线程
4: Stream 不是 集合元素,也不是数据结构,它至关于一个 高级版本的 Iterator,不能够重复遍历里面的数据,像水同样,流过了就一去不复返。它和普通的 Iterator 不一样的是,它能够并行遍历,普通的 Iterator 只能是串行,在一个线程中执行。操做包括:中间操做 和 最终操做(只能操做一遍) 串行流操做在一个线程中依次完成。并行流在多个线程中完成,主要利用了 JDK7 的 Fork/Join 框架来拆分任务和加速处理。相比串行流,并行流能够很大程度提升程序的效率并发
5:用Optional取代nulloracle
6:新的日志和时间,可使用Instant代替Date LocalDateTime代替Calendar DateTimeFormatter代替SimpleDateFormat
7:CompletableFuture:CompletableFuture提供了很是强大的Future的扩展功能,能够帮助咱们简化异步编程的复杂性,而且提供了函数式编程的能力,能够经过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。
8:去除了永久代(PermGen) 被元空间(Metaspace)代替 配置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m
JDK8最大的特性应该非lambda莫属!
IDEA工具自动提示:
lambda语法结构 : 完整的Lambda表达式由三部分组成:参数列表、箭头、声明语句;
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM;}
绝大多数状况,编译器均可以从上下文环境中推断出lambda表达式的参数类型,因此参数能够省略:
(param1,param2, ..., paramN) -> { statment1; statment2; //............. return statmentM;}
当lambda表达式的参数个数只有一个,能够省略小括号:
param1 -> { statment1; statment2; //............. return statmentM;}
当lambda表达式只包含一条语句时,能够省略大括号、return和语句结尾的分号:
param1 -> statment
在那里以及如何使用Lambda????
你能够在函数式接口上面使用Lambda表达式。
备注: JDK定义了不少如今的函数接口,实际本身也能够定义接口去作为表达式的返回,只是大多数状况下JDK定义的直接拿来就能够用了。
Java SE 7中已经存在的函数式接口:
除此以外,Java SE 8中增长了一个新的包:java.util.function
,它里面包含了经常使用的函数式接口,例如:
Predicate<T>
——接收T
对象并返回boolean
Consumer<T>
——接收T
对象,不返回值Function<T, R>
——接收T
对象,返回R
对象Supplier<T>
——提供T
对象(例如工厂),不接收值随便看几个:
Java 8 引入了新的语言特性——默认方法(Default Methods)。
Default methods enable new functionality to be added to the interfaces of libraries and ensure binary compatibility with code written for older versions of those interfaces.
默认方法容许您添加新的功能到现有库的接口中,并能确保与采用旧版本接口编写的代码的二进制兼容性。
默认方法是在接口中的方法签名前加上了 default
关键字的实现方法。
为何要有默认方法
在 java 8 以前,接口与其实现类之间的 耦合度 过高了(tightly coupled),当须要为一个接口添加方法时,全部的实现类都必须随之修改。默认方法解决了这个问题,它能够为接口添加新的方法,而不会破坏已有的接口的实现。这在 lambda 表达式做为 java 8 语言的重要特性而出现之际,为升级旧接口且保持向后兼容(backward compatibility)提供了途径。
这个 forEach
方法是 jdk 1.8 新增的接口默认方法,正是由于有了默认方法的引入,才不会由于 Iterable
接口中添加了 forEach
方法就须要修改全部 Iterable
接口的实现类。
若是一个Lambda表达式仅仅是调用方法的状况,那么就能够用方法引用来完成,这种状况下使用方法引用代码更易读。
方法引用语法:
目标引用放在分隔符::前,方法的名称放在后面。
names2.forEach(System.out::println);//1 names2.forEach(s->System.out.println(s));//2
第二行代码的lambda表达式仅仅就是调用方法,调用的System.out的println方法,因此能够用方法引用写成System.out::println便可。
方法引用有不少种,它们的语法以下:
ClassName::methodName
instanceReference::methodName
super::methodName
ClassName::methodName
**备注:**String::toString 等价于lambda表达式 (s) -> s.toString() 这里不太容易理解,实例方法要经过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。
Class::new
TypeName[]::new
我的理解:方法引用,说白了,用更好,不用也能够,若是能够尽可能用!!!
Java 8 中的 Stream 是对集合(Collection)对象功能的加强,它专一于对集合对象进行各类很是便利、高效的聚合操做(aggregate operation),或者大批量数据操做 (bulk data operation)。Stream API 借助于一样新出现的 Lambda 表达式,极大的提升编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操做,并发模式可以充分利用多核处理器的优点,使用 fork/join 并行方式来拆分任务和加速处理过程。一般编写并行代码很难并且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就能够很方便地写出高性能的并发程序。
对stream的操做分为三类。
中间操做会返回另外一个流。能够用链式编程.的形式继续调用。在没有终止操做的时候,中间操做是不会执行的。
终止操做不会返回流了,而是返回结果(好比返回void-仅仅System.out输出,好比返回总数 int,返回一个集合list等等)
例如:
3种方式建立流,普通流调用
经过Stream接口的静态工厂方法
经过Arrays方法
经过Collection接口的默认方法
//经过Stream接口的静态工厂方法 Stream stream = Stream.of("hello", "world", "hello world"); String[] strArray = new String[]{"hello", "world", "hello world"}; //经过Stream接口的静态工厂方法 Stream stream1 = Stream.of(strArray); //经过Arrays方法 Stream stream2 = Arrays.stream(strArray); List<String> list = Arrays.asList(strArray); //经过Collection接口的默认方法 Stream stream3 = list.stream();
本质都是StreamSupport.stream。
经过Collection接口的默认方法获取并行流。
或者经过stream流调用parallel
获取并行流
只须要对并行流调用sequential
方法就能够把它变成顺序流
能够经过对收集源调用parallelStream方法来把集合转换为并行流。并行流就是一个把内容分红多个数据 块,并用不一样的线程分别处理每一个数据块的流。这样一来,你就能够自动把给定操做的工做负荷分配给多核处理器的全部内核,让它们都忙起来。
并行流用的线程是从哪儿来的?有多少个?怎么自定义这个过程呢?
并行流内部使用了默认的ForkJoinPool,它默认的线程数量就是你的处理器数量,这个值是由 Runtime.getRuntime().available Processors()获得的。可是你能够经过系统属性 java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,以下所示: System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12"); 这是一个全局设置,所以它将影响代码中全部的并行流。反过来讲,目前还没法专为某个 并行流指定这个值。通常而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值, 除非你有很好的理由,不然咱们强烈建议你不要修改它
测试并行流和顺序流速度
//Sequential Sort, 采用顺序流进行排序 @Test public void sequentialSort(){ long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.err.println("count = " + count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); //sequential sort took: 1932 ms } //parallel Sort, 采用并行流进行排序 @Test public void parallelSort(){ long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.err.println("count = " + count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); //parallel sort took: 1373 ms 并行排序所花费的时间大约是顺序排序的一半。 }
class Accumlator{ public long total = 0; public void add(long value) { total += value; } } public class ParallelTest { public static void main(String[] args) { //错误使用并行流示例 System.out.println("SideEffect parallel sum done in :" + measureSumPerf(ParallelTest::sideEffectParallelSum, 1_000_000_0) + "mesecs"); System.out.println("================="); //正确应该这样的 System.out.println("SideEffect sum done in :" + measureSumPerf(ParallelTest::sideEffectSum, 1_000_000_0) + "mesecs"); } //错误使用并行流 public static long sideEffectParallelSum(long n) { Accumlator accumlator = new Accumlator(); LongStream.rangeClosed(1, n).parallel().forEach(accumlator::add); return accumlator.total; } //正确使用流 public static long sideEffectSum(long n) { Accumlator accumlator = new Accumlator(); LongStream.rangeClosed(1, n).forEach(accumlator::add); return accumlator.total; } //定义测试函数 public static long measureSumPerf(Function<Long, Long> adder, long n) { long fastest = Long.MAX_VALUE; //迭代10次 for (int i = 0; i < 2; i++) { long start=System.nanoTime(); long sum = adder.apply(n); long duration=(System.nanoTime()-start)/1_000_000; System.out.println("Result: " + sum); //取最小值 if (duration < fastest) { fastest = duration; } } return fastest; } }
本质问题在于total += value;它不是原子操做,并行调用的时候它会改变多个线程共享的对象的可变状态,从而致使错误,在使用并行流须要避免这类问题发生!
思考: 什么状况结果正常,可是并行流比顺序流慢的状况呢???
并行流中更新共享变量,若是你加入了同步,极可能会发现线程竞争抵消了并行带来的性能提高!
特别是limit和findFirst等依赖于元素顺序的操做,它们在并行流上执行的代价很是大
对于较小的数据量,选择并行流几乎历来都不是一个好的决定。并行处理少数几个元素的好处还抵不上并行化形成的额外开销。
**备注:**sort或distinct等操做接受一个流,再生成一个流(中间操做),从流中排序和删除重复项时都须要知道全部集合数据,若是集合数据很大可能会有问题(若是数据大,都放内存,内存不够就会OOM了)。
使用并行流仍是顺序流都应该应该测试,以及压测,若是在并行流正常的状况下,效率有提高就选择并行流,若是顺序流快就选择顺序流。
CompletableFuture
异步函数式编程future接口能够构建异步应用,但依然有其局限性。**它很难直接表述多个Future 结果之间的依赖性。**实际开发中,咱们常常须要达成如下目的:
新的CompletableFuture将使得这些成为可能。
CompletableFuture
提供了四个静态方法用来建立CompletableFuture对象:
方法入参和返回值有所区别。
里面有很是多的方法,返回为CompletableFuture以后能够用链式编程.的形式继续调用,最后调用一个不是返回CompletableFuture的介绍,和流式操做里面的中间操做-终止操做。
/** * 可使用Instant代替Date * LocalDateTime代替Calendar * DateTimeFormatter代替SimpleDateFormat */ public static void main(String args[]) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime now = LocalDateTime.now(); System.out.println(now.format(formatter)); //10分钟前 String d1 = now.minusMinutes(10).format(formatter); //10分钟后 String d2 = now.plusMinutes(10).format(formatter); System.out.println(d1); System.out.println(d2); LocalDateTime t5 = LocalDateTime.parse("2019-01-01 00:00:00", formatter); System.out.println(t5.format(formatter)); }
去除了永久代(PermGen) 被元空间(Metaspace)代替 配置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m
一、 建立空对象
Optional<String> optStr = Optional.empty();
上面的示例代码调用empty()方法建立了一个空的Optional<String>对象型。
二、**建立对象:不容许为空 ** Optional提供了方法of()用于建立非空对象,该方法要求传入的参数不能为空,不然抛NullPointException,示例以下:
Optional<String> optStr = Optional.of(str); // 当str为null的时候,将抛出NullPointException
三、**建立对象:容许为空 ** 若是不能肯定传入的参数是否存在null值的可能性,则能够用Optional的ofNullable()方法建立对象,若是入参为null,则建立一个空对象。示例以下:
Optional<String> optStr = Optional.ofNullable(str); // 若是str是null,则建立一个空对象
String str = null; len = Optional.ofNullable(str).map(String::length).orElse(0); //不会报NullPointerException
**若是读完以为有收获的话,欢迎点赞、关注、加公众号 [匠心零度] ,查阅更多精彩历史!!! **
原文出处:https://www.cnblogs.com/jiangxinlingdu/p/11476892.html