java8新特性,你有用起来了吗?(精编)

 

2019年9月19日java13已正式发布,感叹java社区强大,经久不衰。因为国内偏保守,新东西总要放一放,让其余人踩踩坑,等稳定了才会去用。而且企业目的仍是赚钱,更不会由于一个新特性去重构代码,再开发一套程序出来。甚者国内大多传统企业还在用java4 、五、6…java


今天讲一讲 java8 的新特性,Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的日期 API,新的Stream API 等。是Java5以后一个大的版本升级,让Java语言和库仿佛得到了新生,核心新特性包含:程序员

  • Java8 函数式接口− 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,可是能够有多个非抽象方法的接口。函数式接口能够被隐式转换为 lambda 表达式。数据库

  • Lambda 表达式 − Lambda 容许把函数做为一个方法的参数(函数做为参数传递到方法中)。express

  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。编程

  • 方法引用 − 方法引用提供了很是有用的语法,能够直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可使语言的构造更紧凑简洁,减小冗余代码。api

  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。数组

  • Date Time API − 增强对日期与时间的处理。安全

  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。markdown


一. 函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,可是能够有多个非抽象方法的接口。
函数式接口能够被隐式转换为 lambda 表达式。框架

JDK 1.8 以前已有的函数式接口:

java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener

JDK 1.8 新增长的函数接口:

java.util.function

这里说一下@FunctionalInterface注解,这个注解是java8新出得一个注解。

咱们经常使用的一些接口Callable、Runnable、Comparator等在JDK8中都添加了@FunctionalInterface注解。
在这里插入图片描述

追踪源码查看@FunctionalInterface注解javadoc

在这里插入图片描述经过JDK8源码javadoc,能够知道这个注解有如下特色:

  • 该注解只能标记在”有且仅有一个抽象方法”的接口上。

  • JDK8接口中的静态方法和默认方法,都不算是抽象方法。

  • 接口默认继承Java.lang.Object,因此若是接口显示声明覆盖了Object中方法,那么也不算抽象方法。

  • 该注解不是必须的,若是一个接口符合”函数式接口”定义,那么加不加该注解都没有影响。加上该注解可以更好地让编译器进行检查。若是编写的不是函数式接口,可是加上了@FunctionInterface,那么编译器会报错。

@FunctionalInterface标记在接口上,“函数式接口”是指仅仅只包含一个抽象方法的接口。
在这里插入图片描述

若是一个接口中包含不止一个抽象方法,那么不能使用@FunctionalInterface,编译会报错。

在这里插入图片描述

好比下面这个接口就是一个正确的函数式接口:
在这里插入图片描述


二. 默认方法

简单说,默认方法就是接口能够有实现方法,并且不须要实现类去实现其方法。

咱们只需在方法名前面加个 default 关键字便可实现默认方法。

为何要有这个特性?

首先,以前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当须要修改接口时候,须要修改所有实现该接口的类,目前的 java 8 以前的集合框架没有 foreach 方法,一般能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是无法在给接口添加新方法的同时不影响已有的实现。因此引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

这里值得注意的是,咱们知道java中一个类能够实现多个接口 若是多个接口中有同名同参同返回值得默认方法须要咱们在实现类重写该方法,不然会编译报错。


三. lambda表达式

这是java8的一大重要特性,咱们知道java是面向对象语言,把行为封装成一个对象是咱们根深蒂固的java编程思想,可是lambda正好反其道而行之,是一种面向过程的编程思想。

不在赘述更多模糊,概念的含义,你们如今有这样的一个认识就能够了。下面我会用例子带入你们。
可以接收Lambda表达式的参数类型,是一个有且仅有一个抽象方法,可是能够有多个非抽象方法的接口。“函数接口”。能够顶替匿名内部类。

语法:

(parameters) -> expression或(parameters) ->{ statements; }

如下是lambda表达式的重要特征:

  • 可选类型声明:不须要声明参数类型,编译器能够统一识别参数值。

  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数须要定义圆括号。

  • 可选的大括号:若是主体包含了一个语句,就不须要使用大括号。

  • 可选的返回关键字:若是主体只有一个表达式返回值则编译器会自动返回值,大括号须要指定明表达式返回了一个数值。

老版本Java中排列字符串

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
});
 

只须要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。一般作法都是建立一个匿名的比较器对象而后将其传递给sort方法。

在Java 8 中你就不必使用这种传统的匿名对象的方式了,直接上lambda

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});
 

看到了吧,代码变得更段且更具备可读性,可是实际上还能够写得更短:

Collections.sort(names, (String a, String b) -> b.compareTo(a));
 

对于函数体只有一行代码的,你能够去掉大括号{}以及return关键字,可是你还能够写得更短点:

Collections.sort(names, (a, b) -> b.compareTo(a));
 

据官方文档中介绍Java编译器能够自动推导出参数类型,能够不写,可是这里我仍是建议你们写出参数类型,方便代码被其余人阅读时候的可读性。本身反过来查看代码也有帮助。


总结:

缺点 : 学习成本稍高,刚开始接触不容易理解,并须要反复练习。

优势 : lambda表达式让咱们能够把一个方法当成参数传递进另外一个方法,顶替匿名内部类消除了样板式代码。并让咱们的代码看起来更加简洁、干净。

而且lambda表达式能够在结合不少地方使用。下面涉及我会再分析。总的来讲lambda表达式仍是值得咱们学习的。


四. stream流

Java 8 API添加了一个新的抽象称为流Stream,可让你以一种声明的方式处理数据。

Stream 使用一种相似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Stream API能够极大提升Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看做一种流, 流在管道中传输, 而且能够在管道的节点上进行处理, 好比筛选, 排序,聚合等。

元素流在管道中通过中间操做(intermediate operation)的处理,最后由最终操做(terminal operation)获得前面处理的结果。

什么是stream?

Stream(流)是一个来自数据源的元素队列并支持聚合操做。

元素是特定类型的对象,造成一个队列。Java中的Stream并不会存储元素,而是按需计算。

数据源 流的来源。能够是集合,数组,I/O channel, 产生器generator 等。

聚合操做 相似SQL语句同样的操做, 好比filter, map, reduce, find, match, sorted等。

和之前的Collection操做不一样, Stream操做还有两个基础的特征:

Pipelining: 中间操做都会返回流对象自己。这样多个操做能够串联成一个管道, 如同流式风格(fluent style)。这样作能够对操做进行优化, 好比延迟执行(laziness)和短路( short-circuiting)。

内部迭代:之前对集合遍历都是经过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫作外部迭代。Stream提供了内部迭代的方式, 经过访问者模式(Visitor)实现。

在 Java 8 中, 集合接口有两个方法来生成流:

stream() − 为集合建立串行流。

parallelStream() − 为集合建立并行流。

在这里插入图片描述

API:

forEach() Stream 提供了新的方法 ‘forEach’ 来迭代流中的每一个数据,如下代码片断使用 forEach 输出了10个随机数:

在这里插入图片描述

limit() 方法用于获取指定数量的流,如下代码片断使用 limit 方法打印出 10 条数据:

在这里插入图片描述

map() 方法用于映射每一个元素到对应的结果, 如下代码片断使用 map 输出了元素对应的平方数:
distinct() 去重 须要实现hascCode和equase方法

在这里插入图片描述

filter() 方法用于经过设置的条件过滤出元素。如下代码片断使用 filter 方法过滤出空字符串:

在这里插入图片描述

sorted 方法用于对流进行排序。如下代码片断使用 sorted 方法对输出的 10 个随机数进行排序:

在这里插入图片描述

并行(parallel)程序

parallelStream 是流并行处理程序的代替方法。如下实例咱们使用 parallelStream 来输出空字符串的数量:

在这里插入图片描述

Collectors 结合 collect()方法后使用 Collectors.joining(String 分隔符) Collectors.toList()变为集合
Collectors 类实现了不少归约操做,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
在这里插入图片描述


五. 方法引用

方法引用经过方法的名字来指向一个方法。

方法引用可使语言的构造更紧凑简洁,减小冗余代码。

方法引用使用一对冒号 :: 。

下面,咱们在 Car 类中定义了 4 个方法做为例子来区分 Java 中 4 种不一样方法的引用。

在这里插入图片描述

构造器引用:它的语法是Class::new,或者更通常的Class< T >::new实例以下:

finalCarcar=Car.create(Car::new);

      finalList<Car>cars=Arrays.asList(car);
 

静态方法引用:它的语法是Class::static_method,实例以下:

cars.forEach(Car::collide);
 

特定类的任意对象的方法引用:它的语法是Class::method实例以下:

cars.forEach(Car::repair);
 

特定对象的方法引用:它的语法是instance::method实例以下:

finalCarpolice = Car.create(Car::new);

             cars.forEach(police::follow);
 

方法引用实例
在 Java8Tester.java 文件输入如下代码:
在这里插入图片描述

实例中咱们将 System.out::println 方法做为静态方法来引用。

执行以上脚本,输出结果为:

在这里插入图片描述


六. 日期时间 API

Java 8经过发布新的Date-Time API (JSR 310)来进一步增强对日期与时间的处理。

由于Java的DateCalendar类型使用起来并非很方便,并且Date类(听说)有着线程不安全等诸多弊端。同时若不进行封装,会在每次使用时特别麻烦。因而Java8推出了线程安全、简易、高可靠的时间包。而且数据库中也支持LocalDateTime类型,在数据存储时候使时间变得简单。Java8此次新推出的包括三个相关的时间类型:LocalDateTime年月日十分秒;LocalDate日期;LocalTime时间;三个包的方法都差很少。

列出经常使用api,详细的使用网上大片,你们自行查找:

//获取当前时间的LocalDateTime对象
//LocalDateTime.now();

//根据年月日构建LocalDateTime
//LocalDateTime.of(); 

//比较日期前后
//LocalDateTime.now().isBefore(),
//LocalDateTime.now().isAfter(),
 

七. Optional 类

Optional不是对null关键字的一种替代,而是对于null断定提供了一种更加优雅的实现。

NullPointException能够说是全部java程序员都遇到过的一个异常,虽然java从设计之初就力图让程序员脱离指针的苦海,可是指针确实是实际存在的,而java设计者也只能是让指针在java语言中变得更加简单、易用,而不能彻底的将其剔除,因此才有了咱们平常所见到的关键字null。

空指针异常是一个运行时异常,对于这一类异常,若是没有明确的处理策略,那么最佳实践在于让程序早点挂掉,可是不少场景下,不是开发人员没有具体的处理策略,而是根本没有意识到空指针异常的存在。

当异常真的发生的时候,处理策略也很简单,在存在异常的地方添加一个if语句断定便可,可是这样的应对策略会让咱们的程序出现愈来愈多的null断定,咱们知道一个良好的程序设计,应该让代码中尽可能少出现null关键字,而java8所提供的Optional类则在减小NullPointException的同时,也提高了代码的美观度。但首先咱们须要明确的是,它并 不是对null关键字的一种替代,而是对于null断定提供了一种更加优雅的实现,从而避免NullPointException。

1). 直观感觉

假设咱们须要返回一个字符串的长度,若是不借助第三方工具类,咱们须要调用str.length()方法:

if(null == str) { // 空指针断定

return 0;

}

return str.length();
 

若是采用Optional类,实现以下:

return Optional.ofNullable(str).map(String::length).orElse(0)

Optional的代码相对更加简洁,当代码量较大时,咱们很容易忘记进行null断定,可是使用Optional类则会避免这类问题。

2). 基本使用
1.对象建立
Optional<String> optStr = Optional.empty();

上面的示例代码调用empty()方法建立了一个空的Optional对象型。

建立对象:不容许为空
Optional提供了方法of()用于建立非空对象,该方法要求传入的参数不能为空,不然抛NullPointException,示例以下:

// 当str为null的时候,将抛出NullPointException
Optional<String> optStr = Optional.of(str);
2. 建立对象:容许为空
若是不能肯定传入的参数是否存在null值的可能性,则能够用Optional的ofNullable()方法建立对象,若是入参为null,则建立一个空对象。示例以下:
// 若是str是null,则建立一个空对象
Optional<String> optStr = Optional.ofNullable(str);
3.流式处理
流式处理也是java8给咱们带来的一个重量级新特性,让咱们对集合的操做变得更加简洁和高效。

这里Optional也提供了两个基本的流失处理:映射和过滤。

为了演示,咱们设计了一个User类,以下:

public class User {


/** 用户编号 */

private long id;

private String name;

private int age;

private Optional<Long> phone;

private Optional<String> email;

public User(String name, int age) {

this.name = name;

this.age = age;

}


// 省略setter和getter

}
 

手机和邮箱不是一我的的必须有的,因此咱们利用Optional定义。

映射:map与flatMap

映射是将输入转换成另一种形式的输出的操做

好比前面例子中,咱们输入字符串,而输出的是字符串的长度,这就是一种隐射,咱们利用方法map()得以实现。假设咱们但愿得到一我的的姓名,那么咱们能够以下实现:

String name = Optional.ofNullable(user).map(User::getName).orElse("no name");
 

这样当入参user不为空的时候则返回其name,不然返回no name

若是咱们但愿经过上面方式获得phone或email,利用上面的方式则行不通了,由于map以后返回的是Optional,咱们把这种称为Optional嵌套,咱们必须在map一次才能拿到咱们想要的结果:

long phone = optUser.map(User::getPhone).map(Optional::get).orElse(-1L);
 

其实这个时候,更好的方式是利用flatMap,一步拿到咱们想要的结果:

long phone = optUser.flatMap(User::getPhone).orElse(-1L);
过滤:fliter

iliter,顾名思义是过滤的操做,咱们能够将过滤操做作为参数传递给该方法,从而实现过滤目的

假如咱们但愿筛选18周岁以上的成年人,则能够实现以下:

optUser.filter(u -> u.getAge() >= 18).ifPresent(u -> System.out.println("Adult:" + u));
4.默认行为

默认行为是当Optional为不知足条件时所执行的操做,好比在上面的例子中咱们使用的orElse()就是一个默认操做,用于在Optional对象为空时执行特定操做,固然也有一些默认操做是当知足条件的对象存在时执行的操做。

get()

get用于获取变量的值,可是当变量不存在时则会抛出NoSuchElementException,因此若是不肯定变量是否存在,则不建议使用


orElse(Tother)

当Optional的变量不知足给定条件时,则执行orElse,好比前面当str为null时,返回0。


orElseGet(Supplier<? extends X> expectionSupplier)

若是条件不成立时,须要执行相对复杂的逻辑,而不是简单的返回操做,则可使用orElseGet实现:

long phone = optUser.map(User::getPhone).map(Optional::get).orElseGet(() -> {

// do something here

return -1L;

});

 orElseThrow(Supplier<? extends X> expectionSupplier)

与get()方法相似,都是在不知足条件时返回异常,不过这里咱们能够指定返回的异常类型。


ifPresent(Consumer<? super T>)

当知足条件时执行传入的参数化操做。


3). 注意事项

Optional是一个final类,未实现任何接口,因此当咱们在利用该类包装定义类的属性的时候,若是咱们定义的类有序列化的需求,那么由于Optional没有实现Serializable接口,这个时候执行序列化操做就会有问题:

public class User implements Serializable{


/** 用户编号 */

private long id;

private String name;

private int age;

 private Optional<Long> phone;  // 不能序列化

private Optional<String> email;  // 不能序列化

不过咱们能够采用以下替换策略:

private long phone;

public Optional<Long> getPhone() {

return Optional.ofNullable(this.phone);

}

今天分享到此结束,经过本篇文章了解java8的新特性不只强大并且感受不少地方都能立刻使用起来,好比经过stream流处理集合数据结合lambda写出高效、干净、简洁的代码,经过Optional类优雅的处理NPE。rd们装X的最高境界就是写一手其余rd们看不懂又以为很高大上的代码了吧。哈哈哈。。。本着学习的态度,若是文章内有出入地方请指出,看到留言后我会和你们讨论学习。

相关文章
相关标签/搜索