Java企业教程系列

Java8教程

  在本教程中主要讲解Java 8新的函数式编程功能,熟悉这些新的 API:streams, 函数接口, map扩展和新的日期API。

接口的缺省方法

Java 8让我们能够增加非抽象方法实现到一个接口中, 使用default,这个特点就是 Extension Methods.

interface Formula {

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

接口 Formula 定义了一个默认方法sqrt. 该接口实现类只要完成接口中抽象方法calculate即可,而sqrt方法可以被外部使用。

ormula formula = new Formula() {

    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

代码中formula 实现类是一个匿名类,在Java 8中有很多好的方法实现这种单个方法的匿名类。

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和一个比较器comparator,后者是为了对集合中元素排序,一般我们都是创建一个匿名的比较器对象传递集合中。

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编译器会照顾你忽略了a和b的类型String。这称为语言的类型判断。

 

函数接口

Lambda表达式是一种函数语法,与Java的类型语言是两种不同性质的语法,如同南北两个不同方向,那么Java 8的Lambda表达式如何配合Java天生的类型系统呢?每个Lambda都对应一个给定的类型,主要是一个接口类型,也称为函数接口,只能包含一个抽象方法,每个类型的Lambda表达式与这个抽象方法匹配,因为默认default方法不是抽象方法,你可以在接口中自由增加自定义的default默认方法。

我们可以使用很多接口作为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

这段代码中,我们定义了接口Converter一个抽象方法,注意虽然使用了@FunctionalInterface ,但是不使用也是可以,那么这个接口中抽象方法就可以作为Lambda表达式使用,首先,我们定义了这个接口的实现:

Converter<String, Integer> converter = (from) -> Integer.valueOf(from);

(from) -> Integer.valueOf(from)实际是抽象方法 T convert(F from)的实现具体细节,这里是将字符串转换为整数型。

然后,我们就可以直接调用这个接口:converter.convert("123")

 

方法和构造器的引用

上述代码如果使用静态方法引用static method references将会更加简化:

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

Java 8 使用符号 ::让你传递方法或构造器的引用,因为 Integer.valueOf是一个静态方法,其引用方式是 Integer::valueOf,我们还能引用一个对象的普通方法:

class Something {

    String startsWith(String s) {

        return String.valueOf(s.charAt(0));

    }

}

Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

这里引用的是类Something的方法startsWith,我们首先要创建这个类的实例,然后使用something::startsWith,这相当于实现了接口Converter的convert方法。不必像我们传统方式,通过Something implements Converter,然后在具体实现Converter的抽象方法convert。这样摆脱了子类对接口的依赖。

符号::也可以使用在构造器方法中:

class Person {

    String firstName;
    String lastName;
    Person() {}

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

    }

}

下面是创建Person的工厂接口:

interface PersonFactory<P extends Person> {

    P create(String firstName, String lastName);

}

有别于传统手工实现这个接口的方式,我们使用::将接口和实现粘合在一起。

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

我们使用Person::new创建了一个指向Person构造器的引用,Java编译器将会自动挑选匹配PersonFactory.create方法签名的构造器。

 

Lambda作用域

从Lambda表达式访问外部变量非常类似匿名对象访问外部一样,匿名对象只能访问外部访问有final定义的变量。

访问本地变量:

final int num = 1;
Converter<Integer, String> stringConverter =   (from) -> String.valueOf(from + num);
stringConverter.convert(2);   

 

这里(from) -> String.valueOf(from + num)表达式 访问了外部的变量num,区别于匿名类,这个变量不是必须加上final定义。下面也可以:

int num = 1;
Converter<Integer, String> stringConverter =  (from) -> String.valueOf(from + num);

这就是Lambda表达式比匿名类的好处。但是这不代表你可以修改num值,这里是隐式的final,下面语法是不可以的:

int num = 1;
Converter<Integer, String> stringConverter =   (from) -> String.valueOf(from + num);
num = 3;

一旦被写入了Lambda表达式的变量不能再被修改了。

访问字段和静态变量

正好和本地变量相反,我们在Lambda表达式中可以对实例的字段和静态变量进行读和写。无论这个字段或静态变量是否在lambda表达式中使用过。

class Lambda4 {

    static int outerStaticNum;
    int outerNum;

    void testScopes() {

        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

访问接口的默认方法

还记得开始的formula案例吗?接口Formula定义了一个默认方法sqrt,它可以被匿名对象访问 ,但是不能被Lambda表达式访问。

默认方法不能在Lambda表达式中访问,下面代码不会编译通过:

Formula formula = (a) -> sqrt( a * 100);

 

下页

 

使用Java8的Lambda实现的一个简单案例

使用Java8的Lambda实现模板模式

使用Java8的Lambda实现策略模式

Java8的Lambda和排序

用Java 8 lambda优化JDBC

使用Java8的Lambda实现Monda

Java 8十个lambda表达式案例

Closure Lambda和Monad

Java8的CompletableFuture

函数编程