JDK 1.8 新特性

  • What’s New in JDK 8
  • JDK8允许内部类使用的外部变量可以不用声明其为final,但实际上还是final类型。

一、改进的类型推断

泛型的最大优点是提供了程序的类型安全同时可以向后兼容,但尴尬的是每次定义时都要写明泛型的类型,这样在显示上有些冗长,最主要是很多程序员不熟悉泛型,因此很多时候不能够给出正确的类型参数,现在通过编译器自动推断泛型的参数类型,能够减少这样的情况,并提高代码可读性。

Java 8 里面泛型的目标类型推断主要有 2 个方面:

  1. 支持通过方法上下文推断泛型目标类型
  2. 支持在方法调用链路当中,泛型类型推断传递到最后一个方法

Java SE 7在创建泛型实例时的类型推断是有限制的;只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。下面的代码在 JDK1.7下无法通过:

List<String> list = new ArrayList<>(); 
list.add("A");
list.addAll(Arrays.asList()); 

但这段代码在 JDK 1.8 中可以正常执行,若要在JDK 1.7中要想执行则必须修正如下:

List<String> stringList = new ArrayList<>();
stringList.add("A");
stringList.addAll(Arrays.<String>asList());

addAll 的目标类型是Collection<?ExtendedString>Arrays.asList返回一个List<T>实例。在本例中,JavaSE 8 编译器可以推断类型变量T的值是字符串。编译器从目标类型Collection<?ExtensionString>推断这一点。

官网的例子:

class List<E> {   
   static <Z> List<Z> nil() { ... };   
   static <Z> List<Z> cons(Z head, List<Z> tail) { ... };   
   E head() { ... }   
}  

在调用上面方法的时候可以这样写:

//通过方法赋值的目标参数来自动推断泛型的类型   
List<String> l = List.nil();   
//而不是显示的指定类型   
//List<String> l = List.<String>nil();   
//通过前面方法参数类型推断泛型的类型   
List.cons(42, List.nil());   
//而不是显示的指定类型   
//List.cons(42, List.<Integer>nil());  

二、Lambda表达式

三、接口中的默认方法静态方法

默认方法

  • Default Methods

      Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法。该非抽象的方法实现可以在子类中直接调用,子类只需要实现所有的抽象方法即可。

public interface Formula {
    public double calculate(int a);
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

测试方法:

public class Ceshi {
    public static void main(String[] args) {
        Formula formula = new Formula() {
            @Override
            public double calculate(int a) {
                return sqrt(a * 100);
            }
        };

        System.out.println(formula.calculate(100));// 100.0
        System.out.println(formula.sqrt(16));// 4.0
    }
}

注意:由于 Java 的接口是多继承的 , 就可能存在一个类实现多个接口 , 如果多个接口中的多个默认方法不冲突(方法签名不冲突) , 则互不影响 , 若存在方法签名一样的默认方法 , 则要求子类必须显式实现并重写默认方法 。

静态方法

静态方法比较简单,使用static修饰方法即可。

接口中的默认方法和静态方法的区别在于:

  • 静态方法只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用;
  • default方法,只能通过接口实现类的对象来调用。

不错的博文可查看:《Java 8 新特性:接口的静态方法和默认方法

四、Stream

五、重复注解和类型注解

六、方法参数反射

方法参数反射【Method Parameter Reflection】【待补充】

七、Optional

Java 开发中最常见的 bug 就是空值异常【NullPointerException】。在Java 8之前,Google Guava引入了Optionals 类来解决 NullPointerException,从而避免源码被各种 null 检查污染,以便开发者写出更加整洁的代码。Java 8也将 Optional 加入了官方库。

null 并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。这会导致问题,原因是当这个变量被传递到系统中的另一个部分后,你将无法获知这个 null 变量最初的赋值到底是什么类型。

Optionals类是一个封装类,当其封装的类的引用变量存在时,Optional类知识对类简单封装,而引用变量不存在时,缺失的值会被建模成一个空的Optional对象,有方法Optional.empty()返回。

我们在声明变量时使用的是Optional<Car>类型,而不是 Car 类型,这句声明非常清楚地表明了这里发生变量缺失是允许的。与此相反,使用Car这样的类型,可能将变量赋值为null,这意味着你需要独立面对这些,你只能依赖你对业务模型的理解,判断一个null是否属于该变量的有效范畴。

强调:引入Optional类的意图并非要消除每一个null引用。与此相反,它的目标是帮助我们更好地设计出普适的API,让程序员看到方法签名,就能了解它是否接受一个Optional的值。这种强制会让你更积极地将变量从Optional中解包出来,直面缺失的变量值

方法大全

注意:不推荐使用基础类型的Optional对象(OptionalIntOptionalLongOptionalDouble),对于Stream对象包含了大量元素,出于性能的考量,使用基础类型是不错的选择,但对Optional对象而言,这个理由就不成立了,因为Optional对象最多包含一个值。而且基础类型的Optional不支持mapflatMap以及filter方法。

创建Optional对象

Optional类为我们提供了三种静态方法来创建Optional对象:

  • empty() 返回一个空的 Optional 实例。
  • of(T value) 返回具有指定的当前非空值的 Optional
  • ofNullable(T value) 如果指定值为非空则返回一个 Optional ,否则则返回一个空的 Optional

使用map从Optional对象中提取和转换中值

//JDK源码
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper); // 检查参数是否为空
    if (!isPresent())           // 如果不存在则返回一个空的Optional实例
        return empty();
    else {
        //如果存在则进行转换并将结果封装到Optional实例中
        return Optional.ofNullable(mapper.apply(value));
    }
}

相比之下,Optionalmap方法 和 Stream中的map方法相差无几。流中的map操作会将提供的函数应用于流的每个元素。我们可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。如果Optional包含一个值,那函数就将该值作为参数传递给map,对该值进行转换;如果Optional为空,就什么也不做。

流和Optional中map对比

注意:Optionalmap方法将其中包含的元素进行转换,然后再包装到Optional中。

// 省略get和set方法
public class Person {
    private Optional<Car> car;
}

public class Car {
    private Optional<Insurance> insurance;
}

public class Insurance {
    private String name;
}
// 然后调用如下
Optional<Person> optPerson = Optional.of(person);
optPerson.map(Person::getCar)
         .map(Car::getInsurance)
         .map(Insurance::getName);
/*
 * 没有使用Optional之前我们的代码可书写为:
 * person.getCar().getInsurance().getName();
 */

然而上面的调用在编译器就无法通过,原因map(Person::getCar)返回的是Optional<Optional<Car>>类型的对象。因此,他对getInsurance的调用是非法的。明白了原因了,那么我们就可以想方法解决:

optPerson.map(Person::getCar).get()
         .map(Car::getInsurance).get()
         .map(Insurance::getName);

在流中有一个流的扁平化操作,同样Optional也提供了类似的方法。

使用flatMap链接Optional对象

使用流时,flatMap方法接受一个函数作为参数,这个函数的返回值是另一个流。这个方法应用到流中的每一个元素,最终形成一个新的流的流。但是flagMap会用流的内容替换每个新生成的流。换句话说,由方法生成的各个流会被合并或者扁平化为一个单一的流。

flatMap对比

上面的图中,传递给流的flatMap方法会将每个正方形转换为另一个流中的两个三角形。那么map操作的结果就包含有三个新的流,每一个流包含两个三角形,但flatMap方法会将这种两层的流合并为一个包含留个三角形的单一流。类似地,传递给optionalflatMap方法的函数会将原始包含正方形的optional对象转换为包含三角形的optional对象。如果将该方法传递个map方法,结果会是一个Optional对象,而这个Optional对象中包含了三角形;但flatMap方法会将这种两层的Optional对象转换为包含三角形的单一Optional对象。

// JDK源码
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
    if (!isPresent())
        return empty();
    else {
        return Objects.requireNonNull(mapper.apply(value));
    }
}

其实我们查看源码不难看出Map方法是对其封装的元素进行转换然后再将结果封装到Optional中,而flatMap方法是对其封装的元素进行转换然后直接返回而已!

因此我们可以将上面的代码优化如下:

optPerson.flatMap(Person::getCar)
         .flatMap(Car::getInsurance)
         .map(Insurance::getName)
         .orElse("UnKnown");

使用filter剔除特定的值

// JDK源码
public Optional<T> filter(Predicate<? super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent())
        return this;
    else
        return predicate.test(value) ? this : empty();
}

其实逻辑不难理解,如果为空则直接返回,否则判断封装的元素是否匹配谓词,如果匹配返回否则返回一个空的Optional

使用Optional封装null和异常

现存的Java API几乎都是返回一个null来表示需要的值得缺失,而我们可能希望返回一个Optional对象。我们无法修改方法的签名,但我们很容易用Optional对这些方法的返回值进行封装。

我们希望安全地对潜在为null的对象进行转换,将其替换为Optional对象时,可采取如下方式:

Optional<Object> value = Optional.ofNullable(map.get("key"));

Java API中无法返回某个值的时候,除了返回null,还可能抛出一个异常。这时我们可以用空的Optional对象对其进行建模,如下:

public Optional<Integer> stringToInt(String s){
    try {
        return Optional.of(Integer.parseInt(s));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}

八、新的日期和时间的API

Java 8 引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题,java.util.Date 和后来的 java.util.Calendar 一直没有解决这个问题(甚至令开发者更加迷茫)。

因为上面这些原因,诞生了第三方库Joda-Time,可以替代 Java 的时间管理API。Java 8 中新的时间和日期管理API深受 Joda-Time 影响,并吸收了很多 Joda-Time 的精华。新的 java.time 包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。

《暂时不打算写,后期会追加一个详细的博文》—-》》》》》》》》》

重要的类:

  • LocalTime:提供简单的时间,并不包含年月日信息。
  • LocalDate:提供简单的日期,并不包含当天的时间信息。
  • LocalDateTime:LocalTime和LocalDate的合体,同时表明日期和时间。
  • Instant:以Unix元年时间开始所经历的秒数进行计算。
  • 时间间隔
    • Duration:以秒和纳秒为单位
    • Period:以年月日为时间单位
  • TemporalAdjuster接口:日期调整
    • TemporalAdjusters类的为我们提供了大量预定义的TemporalAdjuster。

重要知识点:

  • java.time.temporal接口定义了如何读取和操纵为时间建模的对象【temporal object】的值,如日期、时间、偏移量或它们的某种组合。
  • LocalDateTime和Instant视为不同的目的而设计的,前者是为了便于人阅读使用,后者是为了便于及其处理。

九、紧凑API配置文件

JDK 8新增的此项功能将API库的子集组织成所谓的紧凑配置文件。他们分别叫做compact1、compact2 和 compact3。每个配置文件包含库的一个子集。此外,compact2包含整个compact1,compact3包含整个compact2。因此,每个配置文件以前一个配置文件为基础。紧凑配置文件的优势在于,用不到完整库的应用程序不需要下载整个库。使用紧凑配置文件减小了库的大小,从而让一些Java应用程序能够在无法支持完整Java API的设备上运行。使用紧凑配置文件还降低了加载程序所需的事件。Java API文档说明了每个API元素所属的配置文件。

编译程序时,使用-profile选项可以确定程序是否使用了紧凑配置文件中的定义的API。具体形式如下所示:

javac -profile profileName programName

其中,profileName指定了配置文件,必须是compact1、compact2 或 compact3。

参考资料:

赞赏

猜你喜欢

转载自blog.csdn.net/fanxiaobin577328725/article/details/82189330