jdk13快来了,jdk8的这几点应该看看!
说明
jdk8虽然出现很久了,但是可能我们还是有很多人并不太熟悉,本文主要就是介绍说明一些jdk8相关的内容。
主要会讲解:
- lambda表达式
- 方法引用
- 默认方法
- Stream
- 用Optional取代null
- 新的日志和时间
- CompletableFuture
- 去除了永久代(PermGen) 被元空间(Metaspace)代替
我们来看看阿里规范里面涉及到jdk8相关内容:
jdk8开篇
https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html
主要有:
1:lambda表达式:一种新的语言特性,能够把函数作为方法的参数或将代码作为数据。lambda表达式使你在表示函数接口(具有单个方法的接口)的实例更加紧凑。
2:方法引用 是lambda表达式的一个简化写法,所引用的方法其实是lambda表达式的方法体实现,这样使代码更容易阅读
3:默认方法:Java 8引入default method,或者叫virtual extension method,目的是为了让接口可以事后添加新方法而无需强迫所有实现该接口的类都提供新方法的实现。也就是说它的主要使用场景可能会涉及代码演进。
4: Stream 不是 集合元素,也不是数据结构,它相当于一个 高级版本的 Iterator,不可以重复遍历里面的数据,像水一样,流过了就一去不复返。它和普通的 Iterator 不同的是,它可以并行遍历,普通的 Iterator 只能是串行,在一个线程中执行。操作包括:中间操作 和 最终操作(只能操作一遍) 串行流操作在一个线程中依次完成。并行流在多个线程中完成,主要利用了 JDK7 的 Fork/Join 框架来拆分任务和加速处理。相比串行流,并行流可以很大程度提高程序的效率
5:用Optional取代null
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
lambda
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.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.util.concurrent.Callable
- java.io.FileFilter
- java.beans.PropertyChangeListener
除此之外,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
接口的实现类。
方法引用(Method references)
如果一个Lambda表达式仅仅是调用方法的情况,那么就可以用方法引用来完成,这种情况下使用方法引用代码更易读。
方法引用语法:
目标引用放在分隔符::前,方法的名称放在后面。
names2.forEach(System.out::println);//1
names2.forEach(s->System.out.println(s));//2
第二行代码的lambda表达式仅仅就是调用方法,调用的System.out的println方法,所以可以用方法引用写成System.out::println即可。
方法引用的种类(Kinds of method references)
方法引用有很多种,它们的语法如下:
- 静态方法引用:
ClassName::methodName
- 实例上的实例方法引用:
instanceReference::methodName
- 父类的实例方法引用:
super::methodName
- 类型上的实例方法引用:
ClassName::methodName
备注:String::toString 等价于lambda表达式 (s) -> s.toString()
这里不太容易理解,实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。
- 构造方法引用:
Class::new
- 数组构造方法引用:
TypeName[]::new
个人理解:方法引用,说白了,用更好,不用也可以,如果可以尽量用!!!
Stream
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
- Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。
- Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
- 和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。
对stream的操作分为三类。
- 创建stream
- 中间操作(intermediate operations)【没有终止操作是不会执行的】
- 终止操作(terminal operations):
中间操作会返回另一个流。可以用链式编程.的形式继续调用。在没有终止操作的时候,中间操作是不会执行的。
终止操作不会返回流了,而是返回结果(比如返回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().availableProcessors()得到的。但是你可以通过系统属性 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
异步函数式编程
引入CompletableFuture原因
Future模式的缺点
- Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知的机制,我们无法得知Future什么时候完成。
- 要么使用阻塞,在future.get()的地方等待future返回的结果,这时又变成同步操作。要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。
Future 接口的局限性
future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:
- 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第
一个的结果。 - 等待 Future 集合中的所有任务都完成。
- 仅等待 Future 集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同
一个值),并返回它的结果。 - 通过编程方式完成一个 Future 任务的执行(即以手工设定异步操作结果的方式)。
- 应对 Future 的完成事件(即当 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));
}
JVM方面改变
去除了永久代(PermGen) 被元空间(Metaspace)代替 配置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m
用Optional取代null
Optional对象创建
1、 创建空对象
Optional<String> optStr = Optional.empty();
上面的示例代码调用empty()方法创建了一个空的Optional对象型。
2、**创建对象:不允许为空 **
Optional提供了方法of()用于创建非空对象,该方法要求传入的参数不能为空,否则抛NullPointException,示例如下:
Optional<String> optStr = Optional.of(str); // 当str为null的时候,将抛出NullPointException
3、**创建对象:允许为空 **
如果不能确定传入的参数是否存在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
**如果读完觉得有收获的话,欢迎点赞、关注、加公众号 [匠心零度] ,查阅更多精彩历史!!! **
jdk13快来了,jdk8的这几点应该看看!的更多相关文章
- JDK14发布了,快来看看有什么新特性吧
序言
- java JDK8 学习笔记——第16章 整合数据库
第十六章 整合数据库 16.1 JDBC入门 16.1.1 JDBC简介 1.JDBC是java联机数据库的标准规范.它定义了一组标准类与接口,标准API中的接口会有数据库厂商操作,称为JDBC驱动程 ...
- Hadoop3.0新特性介绍,比Spark快10倍的Hadoop3.0新特性
Hadoop3.0新特性介绍,比Spark快10倍的Hadoop3.0新特性 Apache hadoop 项目组最新消息,hadoop3.x以后将会调整方案架构,将Mapreduce 基于内存+io+ ...
- jdk1.8新特性,还不知道的朋友还不看看,1.9都快出来了
一.接口的默认方法 Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下:代码如下:interface Formula { ...
- JDK8新特性一览
转载自:http://blog.csdn.net/qiubabin/article/details/70256683 官方新特性说明地址 Jdk8新特性.png 下面对几个常用的特性做下重点说明. 一 ...
- JDK8的新特性——Lambda表达式
JDK8已经发布快4年的时间了,现在来谈它的新特性显得略微的有点“不合时宜”.尽管JDK8已不再“新”,但它的重要特性之一——Lambda表达式依然是不被大部分开发者所熟练运用,甚至不被开发者所熟知. ...
- (一)jdk8学习心得之遍历
一.遍历 -- 最好和stream流连用 使用jdk8提供的forEach进行遍历时,采用的是多线程的操作,理论上讲是会比原来的方法更快.但是注意,系统启动时需要加载lambda框架的内容.因此如果通 ...
- JDK8集合类源码解析 - LinkedList
linkedList主要要注意以下几点: 1构造器 2 添加add(E e) 3 获取get(int index) 4 删除 remove(E e),remove(int index) 5 判断对象 ...
- JDK源码分析(三)——HashMap 下(基于JDK8)
目录 概述 内部字段及构造方法 哈希值与索引计算 存储元素 扩容 删除元素 查找元素 总结 概述 在上文我们基于JDK7分析了HashMap的实现源码,介绍了HashMap的加载因子loadFac ...
随机推荐
- 0 推荐系统——CB和CF
一.基于内容的推荐(CB,Content-based Recommendations): 基于内容的推荐CB应该算是最早被使用的推荐方法,它根据用户过去喜欢的产品(本文统称为 item),为用户推荐和 ...
- JAVA从零学习 第一天 邮箱ych1102@163.com QQ382993199
学习编程 听说读写 寻寻渐进 要准备好长期蒙蒙的状态 延迟3个月后明白 机器语言 二级制 1010 1100 0011 汇编语言 助记词表达程序 机器如果执行就需要编译 汇编语言移植性 ...
- 【Python3爬虫】当爬虫碰到表单提交,有点意思
一.写在前面 我写爬虫已经写了一段时间了,对于那些使用GET请求或者POST请求的网页,爬取的时候都还算得心应手.不过最近遇到了一个有趣的网站,虽然爬取的难度不大,不过因为表单提交的存在,所以一开始还 ...
- Scrapy框架安装失败解决办法
安装报错信息 正常安装: pip3 install scrapy 出现报错信息如下: 两种解决办法 第一种方法 最根本得解决办法 需要我们安装 Microsoft Visual C++ 14.0 ...
- 【Android Studio】Frameworks detected: Android framework is detected in the project Configure
刚开始在 Mac 上用 Android Studio, 打开第一个项目就遇到了问题,描述如下: 上午9:: Frameworks detected: Android framework is dete ...
- Qtech 暑假未讲到的算法(不完全)
一.数据结构: 优先队列.堆.RMQ问题(区间最值问题,可以用线段树解决,还有一个Sparse-Table算法).排序二叉树.划分树.归并树..... 字符串处理: KMP.字典树.后 ...
- 用机智云做PWM占空比控制电机,物联网智能家居应用
因为是新申请的博客,所以申请了总想往里面加点东西,所以把我之前在机智云写的帖子复制了过来 (各位抱歉,由于之前上传的文件可能有错误,之前上传的文件PWM不能用,那么我又重新上传了一个文件,这个文件 ...
- Unity实现放大缩小以及相机位置平移实现拖拽效果
放大缩小功能是游戏开发中用到的功能,今天就来讲一下Unity中放大缩小怎么实现. 1.IDragHandler, IBeginDragHandler, IEndDragHandler这三个接口是Uni ...
- WebGL简易教程(二):向着色器传输数据
目录 1. 概述 2. 示例:绘制一个点(改进版) 1) attribute变量 2) uniform变量 3) varying变量 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL ...
- Element UI系列:Upload图片自定义上传
HTML部分代码 Javascript部分代码 CSS代码 样式部分可以自由调整 主要实现的原理是利用 http-request 的属性对上传进行自定义