JDK 8 新特性总结

Java 8 简介

Java 8 (又称 jdk8) 是 Java 开发的主要版本,Oracle 公司于 2014 年 3 月 18 日发布 Java 8.

新特性

  1. Lambda 表达式
  2. 方法引用
  3. 默认方法
  4. 新工具:Nashorn引擎 jjs、 类依赖分析器jdeps
  5. Stream API
  6. Date Time API
  7. Optional 类
  8. Nashorn, JavaScript 引擎

Lambda 表达式

使用 Lambda 表达式需要注意的地方

  • Lambda 表达式主要用来定义行内执行的方法类型接口(例如,一个简单方法接口)。
  • Lambda 表达式免去了使用匿名方法的麻烦,并且给予 Java 简单但是强大的函数化的编程能力。
  • lambda 表达式只能引用标记了 final 的外层局部变量。
  • lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)。
  • 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

方法引用

什么是方法引用

  • 方法引用通过方法的名字来指向一个方法。
  • 方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 方法引用使用一对冒号 ::

不同方法的引用

  • 构造器引用
  • 静态方法引用
  • 特定类的任意对象的方法引用
  • 特定对象的方法引用

实例

// 方法引用
public class PracticeTest {

    @Test
    public void test() {
        // 构造器引用
        final Car car = Car.create(Car::new);
        final List<Car> cars = new ArrayList<>(Collections.singletonList(car));

        // 静态方法引用
        cars.forEach(Car::collide);

        // 特定类的任意对象的方法引用
        cars.forEach(Car::repair);

        // 特定对象的方法引用
        final Car police = Car.create(Car::new);
        cars.forEach(police::follow);
    }
}

class Car {

    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }

    public static void collide(final Car car) {
        System.out.println("Collide " + car.toString());
    }

    public void follow(final Car anthor) {
        System.out.println("Follow the " + anthor.toString());
    }

    public void repair() {
        System.out.println("Repaird " + this.toString());
    }
}

函数式接口

什么是函数式接口

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

JDK 1.8 新增加的函数接口:

  • java.util.function

实例

public class PracticeTest {

    @Test
    public void test() {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        eval(list, n -> n % 2 == 0);

    }

    public static void eval(List<Integer> list, Predicate<Integer> predicate) {
        for (Integer n : list){
            if (predicate.test(n)){
                System.out.println(n + " ");
            }
        }
    }
}

默认方法

什么是默认方法

简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
我们只需在方法名前面加个 default 关键字即可实现默认方法。

为什么要有这个特性?

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

实例

public class PracticeTest implements Vehicle, FourWheeler {

    @Test
    public void test() {
        this.print();
    }

    @Override
    public void print() {
        System.out.println("我是一辆四轮汽车");
        Vehicle.super.print();
        // 静态默认方法
        Vehicle.blowHorn();
        FourWheeler.super.print();
    }
}

interface Vehicle {

    default void print() {
        System.out.println("我是第一辆车");
    }

    static void blowHorn() {
        System.out.println("按喇叭");
    }
}

interface FourWheeler {
    default void print() {
        System.out.println("我是一辆四轮车");
    }
}

Stream 流

什么是 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 为集合创建并行流

实例

public class PracticeTest {

    @Test
    public void test() {
        // 计算空字符串
        List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");


        long count = strings.stream().filter(String::isEmpty).count();

        count = strings.stream().filter(i -> i.length() == 3).count();

        List<String> stringList = strings.stream().filter(i -> !i.isEmpty()).collect(Collectors.toList());

        String s = strings.stream().filter(i -> !i.isEmpty()).collect(Collectors.joining(","));

        List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
        List<Integer> numberCollect = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList());

        List<Integer> integers = Arrays.asList(1,2,13,4,15,6,17,8,19);

        IntSummaryStatistics intSummaryStatistics = integers.stream().mapToInt(i -> i).summaryStatistics();

        intSummaryStatistics.getMax();

        intSummaryStatistics.getMin();

        intSummaryStatistics.getSum();

        intSummaryStatistics.getAverage();

        Random random = new Random();
        random.ints().limit(10).sorted().forEach(System.out::println);
    }
}

Optional 类

引入 Optional 类的目的是什么?

Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回 true,调用 get() 方法会返回该对象。

Optional 是个容器:它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional 类的引入很好的解决空指针异常。

建议

  • 不建议将 Optional 作为参数,容易造成空指针和误解,这和 Optional 的目的相违背,如果是想传递某个调用,请使 Supplier.
  • 不建议将 Optional 作为属性,非要用建议使用 guava 包的 Optional 类。

实例

**public class PracticeTest {

    @Test
    public void test() {
        Integer value1 = null;
        Integer value2 = 10;

        System.out.println(this.sum(value1, value2));
    }

    public Integer sum(Integer a, Integer b) {

        // 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
        Optional<Integer> ao = Optional.ofNullable(a);
        Optional<Integer> bo = Optional.ofNullable(b);

        // Optional.isPresent - 判断值是否存在
        System.out.println("第一个参数值存在:" + ao.isPresent());
        System.out.println("第二个参数值存在:" + bo.isPresent());

        // Optional.orElse - 如果值存在,返回它,否则返回默认值
        Integer value1 = ao.orElse(0);
        Integer value2 = bo.orElse(0);

        //Optional.get - 获取值,值需要存在
        return value1 + value2;

    }
}**

Nashorn JavaScript

N a s h o r n J a v a S c r i p t E n g i n e 在 J a v a 15 已经不可用了。 \color{red}{Nashorn JavaScript Engine 在 Java 15 已经不可用了。} NashornJavaScriptEngineJava15已经不可用了。

日期时间 API

在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:

  • 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是 Java 日期类最大的问题之一。
  • 设计很差 − Java 的日期/时间类的定义并不一致,在 java.util 和 java.sql 的包中都有日期类,此外用于格式化和解析的类在 java.text 包中定义。java.util.Date 同时包含日期和时间,而 java.sql.Date 仅包含日期,将其纳入 java.sql 包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此 Java 引入了 java.util.Calendar 和 java.util.TimeZone 类,但他们同样存在上述所有的问题。

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

  • Local(本地) − 简化了日期时间的处理,没有时区的问题。
  • Zoned(时区) − 通过指定的时区处理日期时间。

新的 java.time 包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

本地化日期时间 API

LocalDate/LocalTime 和 LocalDateTime 类可以在处理时区不是必须的情况。

实例

public class PracticeTest {

    @Test
    public void test() {
        // 获取当前的日期时间
        LocalDateTime currentTime = LocalDateTime.now();
        System.out.println("当前时间:" + currentTime);

        LocalDate date1 = currentTime.toLocalDate();
        System.out.println("date1:" + date1);

        Month month = currentTime.getMonth();
        int day = currentTime.getDayOfMonth();
        int second = currentTime.getSecond();

        System.out.println("月:" + month + ",日:" + day + ",秒:" + second);

        LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
        System.out.println("date2:" + date2);

        LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
        System.out.println("date3:" + date3);

        LocalTime date4 = LocalTime.of(22, 15);
        System.out.println("date4:" + date4);

        // 解析字符串
        LocalTime date5 = LocalTime.parse("20:15:30");
        System.out.println("date5:" + date5);

        LocalDateTime date6 = LocalDateTime.parse("2012-07-10 14:05:45", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("date6:" + date6);
    }
}

使用时区的日期时间API

需要考虑到时区,就可以使用时区的日期时间 API

实例

public class PracticeTest {

    @Test
    public void test() {
        // 获取当前的日期时间
        ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
        System.out.println("date1:" + date1);

        ZoneId id = ZoneId.of("Europe/Paris");
        System.out.println("ZoneId:" + id);

        ZoneId currentZone = ZoneId.systemDefault();
        System.out.println("当前时区:" + currentZone);
    }
}

Base64

在 Java 8 中,Base64 编码已经成为 Java 类库的标准。

Java 8 内置了 Base64 编码的编码器和解码器。

Base64 工具类提供了一套静态方法获取下面三种 BASE64 编解码器:

  • 基本:输出被映射到一组字符 A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
  • URL:输出映射到一组字符 A-Za-z0-9+_,输出是URL和文件。
  • MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用’\r’并跟随’\n’作为分割。编码输出最后没有行分割。

实例

public class PracticeTest {

    @Test
    public void test() {
        // 获取当前的日期时间
        String base64encodeString = Base64.getEncoder().encodeToString("runoob?java8".getBytes(StandardCharsets.UTF_8));
        System.out.println("Base64 编码字符串(基本):" + base64encodeString);

        byte[] base64decodeBytes = Base64.getDecoder().decode(base64encodeString);

        System.out.println("原始字符串: " + new String(base64decodeBytes));
        base64encodeString = Base64.getUrlEncoder().encodeToString("runoob?java8".getBytes(StandardCharsets.UTF_8));
        System.out.println("Base64 编码字符串 (URL):" + base64encodeString);

        // StringBuilder 是线程不安全的,不能同步访问
        // 使用 StringBuffer 时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新对象
        // 由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder。
        StringBuilder stringBuilder = new StringBuilder();

        for (int i = 0; i < 10; i++) {
            stringBuilder.append(UUID.randomUUID().toString());
        }

        byte[] mimeBytes = stringBuilder.toString().getBytes(StandardCharsets.UTF_8);
        String mimeEncodeString = Base64.getMimeEncoder().encodeToString(mimeBytes);
        System.out.println("Base64 编码字符串(mime):" + mimeEncodeString);
    }
}

参考文献

Oracle 官网
菜鸟教程

你可能感兴趣的:(后端,java,开发语言)