Java8新特性你了解多少呢?

563 阅读6分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

记得面试时常会有这样的问答
面试官:“Java8新特性了解吗?能说一下吗?”
我:“嗯……了解一点”
然后巴拉巴拉说了一些。

Java8新特性,很多人应该都了解过,Java8新特性是Java一次重大的版本升级后新增的,有人认为,虽然这些新特性可以令Java开发人员提高开发效率,但同时也需要花不少精力去学习,接下来我们就来介绍下Java8新特性。

一、接口默认方法

Java 8允许我们给接口添加一个非抽象的方法实现,这里只需要使用 default关键字即可,故这个特征又叫做扩展方法,示例如下:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

解释:Formula接口在拥有calculate方法之外同时还定义了sqrt方法,实现了Formula接口的子类只需要实现一个calculate方法,默认方法sqrt将在子类上可以直接使用。

    Formula formula = new Formula() {
        @Override
        public double calculate(int a) {
            return sqrt(a * 100);
        }
    };
    formula.calculate(100); // 100.0 
    formula.sqrt(16); // 4.0

解释:以上代码中的formula被实现为一个匿名类的实例,该代码非常容易理解,6行代码实现了计算sqrt(a * 100)。在接下来,我们将会看到实现单方法接口的更简单的做法。

二、Lambda 表达式

首先来看下原先Java中是如何排列字符串的

List names = Arrays.asList("peterF", "anna", "mike", "xenia");
Collections.sort(names,newComparator() {
    @Override 
    public int compare (String a, String b){
        return b.compareTo(a);
    }
});

以上代码只需要给静态方法 Collections.sort 传入一个List对象以及一个比较器来按指定顺序排列。其中通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。
但是在Java8中你就没必要使用这种传统的匿名对象的方式了,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表达式是如何在Java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接 口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹 配到这个抽象方法。因为默认方法不算抽象方法,所以你也可以给你的函数式接口添加默认方法。我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。示例如下:

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}

Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123

需要注意@FunctionalInterface如果没有指定,上面的代码也是对的。

四、方法与构造函数引用

前一节中的代码还可以通过静态方法引用来表示,如下:

Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123"); System.out.println(converted); // 123

在Java 8中允许你使用 :: 关键字来传递方法或者构造函数引用,上面的代码展示了如何引用一个静态方法,我们也可以引用一个对象的方法,如下:

converter =something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);

接下来再看看构造函数是如何使用::关键字来引用的。首先我们定义一个包含多个构造函数的简单Person类:

class Person {
    String firstName;
    String lastName;

    Person() {
    }

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

接下来我们指定一个用来创建Person对象的对象工厂接口,代码如下:

interface PersonFactory {
    P create(String firstName, String lastName);
}

在这里我们使用构造函数是来引用将他们关联起来,而不是实现一个完整的工厂,代码如下:

PersonFactory personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

结论是我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的签名来选择合适的构造函数。

五、Stream流

Java8之前的传统编程方式,如果我们需要操作一个集合数据,那就要使用集合提供的API,通过一个循环去获取集合的元素,这种访问数据的方式会使代码显得臃肿,Java8新引入的Stream类,用于重新封装集合数据,通过使用流式Stream代替常用集合数组、list和map的遍历操作可以极大的提高效率 可以形象地理解Stream的操作是对一组粗糙的工艺品原型(即对应的 Stream 数据源)进行加工成颜色统一的工艺品(即最终得到的结果),第一步筛选出合适的原型(即对应Stream的 filter 的方法),第二步将这些筛选出来的原型工艺品上色(对应Stream的map方法),第三步取下这些上好色的工艺品(即对应Stream的 collect(toList())方法)。在取下工艺品之前进行的操作都是中间操作,可以有多个或者0个中间操作,但每个Stream数据源只能有一次终止操作,否则程序会报错。 示例: map中间操作中将一种类型的值映射为另一种类型的值,可以将 Stream 中的每个值都映射为一个新的值,最终转换为一个新的 Stream 流。例:把 Stream 中每个字符串都转换为大写的形式。

public void mapTest() {
    String[] testStrings = {"java", "react", "angular", "vue"};

    List<String> list = Stream.of(testStrings).map(test -> test.toUpperCase()).collect(Collectors.toList());

    list.forEach(test -> System.out.println(test));
}

结语

对于Java8新特性的介绍,网上的观点有很多,但是其中最常用的几个特性是Lambda表达式、方法引用和Stream流,这几个在我们日常工作的见得多用得应该也是最多的,其他就暂时不介绍了。Java8新特性用得好,开发效率会提高。