java 8 双冒号操作

本文我们讨论java 8 中的双冒号(::)操作以及其使用场景。

从lambda表达式到双冒号(::)操作

我们知道使用lambda表达式可以让代码非常简洁。举例,创建比较器,使用下面语法:

Comparator c = (Computer c1, Computer c2) -> c1.getAge().compareTo(c2.getAge());

使用类型推断,可以简写为:

Comparator c = (c1, c2) -> c1.getAge().compareTo(c2.getAge());

为了使上面代码更可读,我们使用下面语法:

Comparator c = Comparator.comparing(Computer::getAge);

我们使用::操作简化lambda表达式调用特定方法,让我们的代码更有表现力。

了解原理

简单地说,当我们使用方法引用时,目标引用在::之前,方法名称在其值后,举例:

Computer::getAge;

上面方法引用标识调用Computer类的getAge方法。也可以Function一起使用:

Function<Computer, Integer> getAge = Computer::getAge;
Integer computerAge = getAge.apply(c1);

我们使用函数引用,然后给其正确的参数执行。

方法引用

我们可以在多个地方使用::操作符。

静态方法

下面示例调用静态工具方法:

List inventory = Arrays.asList(
  new Computer( 2015, "white", 35), new Computer(2009, "black", 65));
inventory.forEach(ComputerUtils::repair);

现有对象的实例方法

下面看有趣的应用场景——调用现有实例对象方法。我们使用System.out变量——PrintStream类型对象,有print方法:

Computer c1 = new Computer(2015, "white");
Computer c2 = new Computer(2009, "black");
Computer c3 = new Computer(2014, "black");
Arrays.asList(c1, c2, c3).forEach(System.out::print);

特定类型的任意对象的实例方法

Computer c1 = new Computer(2015, "white", 100);
Computer c2 = new MacbookPro(2009, "black", 100);
List inventory = Arrays.asList(c1, c2);
inventory.forEach(Computer::turnOnPc);

上面代码没有在特定实例引用turnOnPc方法,而是在类自身。第四行代码将在inventory中每个实例上调用turnOnPc方法。也就是说——调用Computer实例c1的turnOnPc方法,然后调用Computer实例c2的turnOnPc方法.

特定对象超类方法

假设在Computer超类中有下面方法:

public Double calculateValue(Double initialValue) {
    return initialValue/1.50;
}

MacbookPro 子类定义方法:

@Override
public Double calculateValue(Double initialValue){
    Function<Double, Double> function = super::calculateValue;
    Double pcValue = function.apply(initialValue);
    return pcValue + (initialValue/10) ;
}

在MacbookPro 实例上调用calculateValue方法:

macbookPro.calculateValue(999.99);

也产生对Computer父类的calculateValue方法的调用。

构造器应用

创建新的实例

引用构造器实例化对象可以简化为:

@FunctionalInterface
public interface InterfaceComputer {
    Computer create();
}
 
InterfaceComputer c = Computer::new;
Computer computer = c.create();

如何构造器有两个参数:

BiFunction<Integer, String, Computer> c4Function = Computer::new; 
Computer c4 = c4Function.apply(2013, "white");

如果有三个或更多参数,需要定义新的函数接口:

@FunctionalInterface
interface TriFunction<A, B, C, R> { 
    R apply(A a, B b, C c); 
    default <V> TriFunction<A, B, C, V> andThen( Function<? super R, ? extends V> after) { 
        Objects.requireNonNull(after); 
        return (A a, B b, C c) -> after.apply(apply(a, b, c)); 
    } 
}

初始化对象代码:

TriFunction <Integer, String, Integer, Computer> c6Function = Computer::new;
Computer c3 = c6Function.apply(2008, "black", 90);

创建数组

最后,我们看看如何创建5个Computer对象数组:

Function <Integer, Computer[]> computerCreator = Computer[]::new;
Computer[] computerArray = computerCreator.apply(5);

总结

java8 引入双冒号操作,在一些场景中非常有用,特别在stream的连接操作中。通过理解函数式接口可以更好地理解其原理。