Java8新特性(一):Lambda表达式

标签: Java  

新特性列表

以下是Java8中的引入的部分新特性。关于Java8新特性更详细的介绍可参考这里

  • 接口默认方法和静态方法
  • Lambda 表达式
  • 函数式接口
  • 方法引用
  • Stream
  • Optional
  • Date/Time API
  • 重复注解
  • 扩展注解的支持
  • Base64
  • JavaFX
  • 其它
    • JDBC4.2规范
    • 更好的类型推测机制
    • HashMap性能提升
    • IO/NIO 的改进
    • JavaScript引擎Nashorn
    • 并发(Concurrency)
    • 类依赖分析器jdeps
    • JVM的PermGen空间被移除

一、接口默认方法和静态方法

Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。

1. 接口默认方法

默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。让我们看看下面的例子:

private interface Defaulable {
    // Interfaces now allow default methods, the implementer may or
    // may not implement (override) them.
    default String notRequired() {
        return "Default implementation";
    }
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

Defaulable接口用关键字default声明了一个默认方法notRequired()Defaulable接口的实现者之一DefaultableImpl实现了这个接口,并且让默认方法保持原样。Defaulable接口的另一个实现者OverridableImpl用自己的方法覆盖了默认方法。

(1). 多重继承的冲突说明

由于同一个方法可以从不同的接口引入,自然而然的会有冲突的现象,规则如下:

  • 一个声明在类里面的方法优先于任何默认方法
  • 优先选取最具体的实现
public interface A {

    default void hello() {
        System.out.println("Hello A");
    }

}
public interface B extends A {

    default void hello() {
        System.out.println("Hello B");
    }

}
public class C implements A, B {

    public static void main(String[] args) {
        new C().hello(); // 输出 Hello B
    }

}

(2). 优缺点

  • 优点: 可以在不破坏代码的前提下扩展原有库的功能。它通过一个很优雅的方式使得接口变得更智能,同时还避免了代码冗余,并且扩展类库。
  • 缺点: 使得接口作为协议,类作为具体实现的界限开始变得有点模糊。

(3). 接口默认方法不能重载Object类的任何方法

接口不能提供对Object类的任何方法的默认实现。简单地讲,每一个java类都是Object的子类,也都继承了它类中的equals()/hashCode()/toString()方法,那么在类的接口上包含这些默认方法是没有意义的,它们也从来不会被编译。

在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream()parallelStream()forEach()removeIf()等。尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法。

2. 接口静态方法

Java 8带来的另一个有趣的特性是接口可以声明(并且可以提供实现)静态方法。在接口中定义静态方法,使用static关键字,例如:

public interface StaticInterface {

    static void method() {
        System.out.println("这是Java8接口中的静态方法!");
    }

}

下面的一小段代码是上面静态方法的使用。

public class Main {

    public static void main(String[] args) {
        StaticInterface.method(); // 输出 这是Java8接口中的静态方法!
    }

}

Java支持一个实现类可以实现多个接口,如果多个接口中存在同样的static方法会怎么样呢?如果有两个接口中的静态方法一模一样,并且一个实现类同时实现了这两个接口,此时并不会产生错误,因为Java8中只能通过接口类调用接口中的静态方法,所以对编译器来说是可以区分的。

二、Lambda 表达式

Lambda表达式(也称为闭包)是整个Java 8发行版中最受期待的在Java语言层面上的改变,Lambda允许把函数作为一个方法的参数(即:行为参数化,函数作为参数传递进方法中)。

一个Lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。

首先看看在老版本的Java中是如何排列字符串的:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {

    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }

});

只需要给静态方法Collections.sort传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给sort方法。 在Java 8 中你就没必要使用这种传统的匿名对象的方式了,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表达式是如何在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如果没有指定,上面的代码也是对的。

Java8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在Lambda上。

Java8 API同样还提供了很多全新的函数式接口来让工作更加方便,有一些接口是来自Google Guava库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

1. Comparator (比较器接口)

Comparator是老Java中的经典接口, Java 8在此之上添加了多种默认方法。源代码及使用示例如下:

@FunctionalInterface
public interface Comparator<T> {

    int compare(T o1, T o2);

}
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

2. Consumer (消费型接口)

Consumer接口表示执行在单个参数上的操作。源代码及使用示例如下:

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

}
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

更多的Consumer接口

  • BiConsumer:void accept(T t, U u);: 接受两个参数的二元函数
  • DoubleConsumer:void accept(double value);: 接受一个double参数的一元函数
  • IntConsumer:void accept(int value);: 接受一个int参数的一元函数
  • LongConsumer:void accept(long value);: 接受一个long参数的一元函数
  • ObjDoubleConsumer:void accept(T t, double value);: 接受一个泛型参数一个double参数的二元函数
  • ObjIntConsumer:void accept(T t, int value);: 接受一个泛型参数一个int参数的二元函数
  • ObjLongConsumer:void accept(T t, long value);: 接受一个泛型参数一个long参数的二元函数

3. Supplier (供应型接口)

Supplier接口是不需要参数并返回一个任意范型的值。其简洁的声明,会让人以为不是函数。这个抽象方法的声明,同Consumer相反,是一个只声明了返回值,不需要参数的函数。也就是说Supplier其实表达的不是从一个参数空间到结果空间的映射能力,而是表达一种生成能力,因为我们常见的场景中不止是要consume(Consumer)或者是简单的map(Function),还包括了new这个动作。而Supplier就表达了这种能力。源代码及使用示例如下:

@FunctionalInterface
public interface Supplier<T> {

    T get();
}
Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

更多Supplier接口

  • BooleanSupplier:boolean getAsBoolean();: 返回boolean的无参函数
  • DoubleSupplier:double getAsDouble();: 返回double的无参函数
  • IntSupplier:int getAsInt();: 返回int的无参函数
  • LongSupplier:long getAsLong();: 返回long的无参函数

4. Predicate (断言型接口)

Predicate接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:)。Streamfilter方法就是接受Predicate作为入参的。这个具体在后面使用Stream的时候再分析深入。源代码及使用示例如下:

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

}
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo");            // true
predicate.negate().test("foo");     // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

更多的Predicate接口

  • BiPredicate:boolean test(T t, U u);: 接受两个参数的二元断言函数
  • DoublePredicate:boolean test(double value);: 入参为double的断言函数
  • IntPredicate:boolean test(int value);: 入参为int的断言函数
  • LongPredicate:boolean test(long value);: 入参为long的断言函数

5. Function (功能型接口)

Function接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen)。源代码及使用示例如下:

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

}
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123");     // "123"

更多的Function接口

  • BiFunction :R apply(T t, U u);: 接受两个参数,返回一个值,代表一个二元函数;
  • DoubleFunction :R apply(double value);: 只处理double类型的一元函数;
  • IntFunction :R apply(int value);: 只处理int参数的一元函数;
  • LongFunction :R apply(long value);: 只处理long参数的一元函数;
  • ToDoubleFunction:double applyAsDouble(T value);: 返回double的一元函数;
  • ToDoubleBiFunction:double applyAsDouble(T t, U u);: 返回double的二元函数;
  • ToIntFunction:int applyAsInt(T value);: 返回int的一元函数;
  • ToIntBiFunction:int applyAsInt(T t, U u);: 返回int的二元函数;
  • ToLongFunction:long applyAsLong(T value);: 返回long的一元函数;
  • ToLongBiFunction:long applyAsLong(T t, U u);: 返回long的二元函数;
  • DoubleToIntFunction:int applyAsInt(double value);: 接受double返回int的一元函数;
  • DoubleToLongFunction:long applyAsLong(double value);: 接受double返回long的一元函数;
  • IntToDoubleFunction:double applyAsDouble(int value);: 接受int返回double的一元函数;
  • IntToLongFunction:long applyAsLong(int value);: 接受int返回long的一元函数;
  • LongToDoubleFunction:double applyAsDouble(long value);: 接受long返回double的一元函数;
  • LongToIntFunction:int applyAsInt(long value);: 接受long返回int的一元函数;

6. Operator

Operator其实就是Function,函数有时候也叫作算子。算子在Java8中接口描述更像是函数的补充,和上面的很多类型映射型函数类似。算子Operator包括:UnaryOperatorBinaryOperator。分别对应单(一)元算子和二元算子。

算子的接口声明如下:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {

    public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

    public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
    }
}

Operator只需声明一个泛型参数T即可。对应的使用示例如下:

UnaryOperator<Integer> increment = x -> x + 1;
System.out.println("递增:" + increment.apply(2)); // 输出 递增:3

BinaryOperator<Integer> add = (x, y) -> x + y;
System.out.println("相加:" + add.apply(2, 3)); // 输出 相加:5

BinaryOperator<Integer> min = BinaryOperator.minBy((o1, o2) -> o1 - o2);
System.out.println("最小值:" + min.apply(2, 3)); // 输出 最小值:2

更多的Operator接口

  • LongUnaryOperator:long applyAsLong(long operand);: 对long类型做操作的一元算子
  • IntUnaryOperator:int applyAsInt(int operand);: 对int类型做操作的一元算子
  • DoubleUnaryOperator:double applyAsDouble(double operand);: 对double类型做操作的一元算子
  • DoubleBinaryOperator:double applyAsDouble(double left, double right);: 对double类型做操作的二元算子
  • IntBinaryOperator:int applyAsInt(int left, int right);: 对int类型做操作的二元算子
  • LongBinaryOperator:long applyAsLong(long left, long right);: 对long类型做操作的二元算子

6. 其它函数式接口

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

四、方法引用

1. 概述

在学习了Lambda表达式之后,我们通常使用Lambda表达式来创建匿名方法。然而,有时候我们仅仅是调用了一个已存在的方法。如下:

Arrays.sort(strArray, (s1, s2) -> s1.compareToIgnoreCase(s2));

在Java8中,我们可以直接通过方法引用来简写Lambda表达式中已经存在的方法。

Arrays.sort(strArray, String::compareToIgnoreCase);

这种特性就叫做方法引用(Method Reference)。

方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。

注意: 方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号::

2. 分类

方法引用的标准形式是:类名::方法名。(注意:只需要写方法名,不需要写括号)

有以下四种形式的方法引用:

  • 引用静态方法: ContainingClass::staticMethodName
  • 引用某个对象的实例方法: containingObject::instanceMethodName
  • 引用某个类型的任意对象的实例方法:ContainingType::methodName
  • 引用构造方法: ClassName::new

3. 示例

使用示例如下:

public class Person {

    String name;

    LocalDate birthday;

    public Person(String name, LocalDate birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }

    @Override
    public String toString() {
        return this.name;
    }
}
public class MethodReferenceTest {

    @Test
    public static void main() {
        Person[] pArr = new Person[] {
            new Person("003", LocalDate.of(2016,9,1)),
            new Person("001", LocalDate.of(2016,2,1)),
            new Person("002", LocalDate.of(2016,3,1)),
            new Person("004", LocalDate.of(2016,12,1))
        };

        // 使用匿名类
        Arrays.sort(pArr, new Comparator<Person>() {
            @Override
            public int compare(Person a, Person b) {
                return a.getBirthday().compareTo(b.getBirthday());
            }
        });

        //使用lambda表达式
        Arrays.sort(pArr, (Person a, Person b) -> {
            return a.getBirthday().compareTo(b.getBirthday());
        });

        //使用方法引用,引用的是类的静态方法
        Arrays.sort(pArr, Person::compareByAge);
    }

}

「真诚赞赏,手留余香」

请我喝杯咖啡?

使用微信扫描二维码完成支付

相关文章