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的连接操作中。通过理解函数式接口可以更好地理解其原理。