jdk13快来了,jdk8的这几点应该看看!

说明

jdk8虽然出现好久了,可是可能咱们仍是有不少人并不太熟悉,本文主要就是介绍说明一些jdk8相关的内容。html

主要会讲解:java

  • lambda表达式
  • 方法引用
  • 默认方法
  • Stream
  • 用Optional取代null
  • 新的日志和时间
  • CompletableFuture
  • 去除了永久代(PermGen) 被元空间(Metaspace)代替

咱们来看看阿里规范里面涉及到jdk8相关内容:算法

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

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 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的操做分为三类。

  1. 建立stream
  2. 中间操做(intermediate operations)【没有终止操做是不会执行的】
  3. 终止操做(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().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异步函数式编程

引入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对象建立

一、 建立空对象

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

相关文章
相关标签/搜索