JDK13又怎么样?JDK8才是王道,这些特性必须掌握!
说明
-
lambda表达式 -
方法引用 -
默认方法 -
Stream -
用Optional取代null -
新的日志和时间 -
CompletableFuture -
去除了永久代(PermGen) 被元空间(Metaspace)代替
我们来看看阿里规范里面涉及到jdk8相关内容:
jdk8开篇
https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html
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.lang.Runnable -
java.util.concurrent.Callable -
java.security.PrivilegedAction -
java.util.Comparator -
java.util.concurrent.Callable -
java.io.FileFilter -
java.beans.PropertyChangeListener
java.util.function
,它里面包含了常用的函数式接口,例如:
-
Predicate
——接收T
对象并返回boolean
-
Consumer
——接收T
对象,不返回值 -
Function
——接收T
对象,返回R
对象 -
Supplier
——提供T
对象(例如工厂),不接收值
默认方法
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
关键字的实现方法。
forEach
方法是 jdk 1.8 新增的接口默认方法,正是因为有了默认方法的引入,才不会因为
Iterable
接口中添加了
forEach
方法就需要修改所有
Iterable
接口的实现类。
方法引用(Method references)
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
-
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。 -
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。 -
和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。
-
创建stream -
中间操作(intermediate operations)【没有终止操作是不会执行的】 -
终止操作(terminal operations):
流的创建
-
通过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();
parallel
获取并行流
sequential
方法就可以把它变成顺序流
中间操作
终止操作
并行流
块,并用不同的线程分别处理每个数据块的流。这样一来,你就可以自动把给定操作的工作负荷分配给多核处理器的所有内核,让它们都忙起来。
并行流内部使用了默认的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;
}
}
思考: 什么情况结果正常,但是并行流比顺序流慢的情况呢???
备注:sort或distinct等操作接受一个流,再生成一个流(中间操作),从流中排序和删除重复项时都需要知道所有集合数据,如果集合数据很大可能会有问题(如果数据大,都放内存,内存不够就会OOM了)。
`CompletableFuture`异步函数式编程
引入CompletableFuture原因
Future模式的缺点
-
Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知的机制,我们无法得知Future什么时候完成。 -
要么使用阻塞,在future.get()的地方等待future返回的结果,这时又变成同步操作。 要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。
Future 接口的局限性
-
将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第
一个的结果。 -
等待 Future 集合中的所有任务都完成。 -
仅等待 Future 集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同
一个值),并返回它的结果。 -
通过编程方式完成一个 Future 任务的执行(即以手工设定异步操作结果的方式)。 -
应对 Future 的完成事件(即当 Future 的完成事件发生时会收到通知,并能使用 Future
计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)
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方面改变
用Optional取代null
Optional对象创建
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
END
↓↓↓↓