第三章(5)方法引用
1.方法引用初探
方法调用可以被看作仅仅调用特定方法的lambda表达式的一种快捷写法。如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显式地指明方法的名称,你的代码的可读性会更好。类如:
我们要在苹果列表中提取苹果的Name属性,形成另一个苹果名称列表:
在这里,我们使用了lambda表达式,我们发现这个lambda表达式仅仅就是直接调用了Apple的getName方法而已,那么我们就可以使用方法引用来简化我们的lambda表达式:
List<String> apples = Test.getApplesName(list, Apple::getName);
它是如何工作的呢?当你需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如,Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) ->a.getWeight()的快捷写法。
下面的图片给出了一些方法引用的例子,你可以依据这些例子写出自己在实际工作中想要写的方法引用:
2.如何构建方法引用
(1)指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::parseInt)。代码案例:
如果使用方法引用:
List<Integer> list = Test.getInteger(strArr, Integer::parseInt);
(2)指向任意类型实例方法的方法引用(例如String的length方法,写作String::length)。
List<Integer> list = Test.getInteger(strArr, (String s)->s.length());
如果使用方法引用:
List<Integer> list = Test.getInteger(strArr, String::length);
类似于String::length的第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda表达式(String s) -> s.toUppeCase()可以写作String::toUpperCase。也就是说只要被引用的方法是属于lambda表达式参数对象的,那么就可以使用参数对象所属类引用该方法。比如
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Integer> list = new ArrayList<>();
list.add(22);
list.add(1);
list.add(3);
list.add(54);
list.sort((Integer i1,Integer i2)->i1.compareTo(i2));
for(Integer a:list) {
System.out.println(a);
}
}
就可以改写为:
list.sort(Integer::compareTo);
(3)指向现有对象的实例方法的方法引用:那就是本篇一开始所举的Apple例子。
3.构造函数的引用
对于一个构造函数,我们可以使用classname::new来创建它的一个引用,例如,我们通过无参构造函数的lambda来获取一个Apple对象。
Supplier<Apple> s1= ()->new Apple();//Supplier为java8为我们提供
Apple apple = s1.get();
那么上面的lambda就可以直接改写为:
Supplier<Apple> s1= Apple::new;//Supplier为java8为我们提供
Apple apple = s1.get();
如果你调用一个有参数的构造函数:
Function<Integer, Apple> function = (weight)->new Apple(weight);
Apple apple = function.apply(100);
System.out.println(apple.getWeight());
同样,你也可以改写为构造函数的引用:
Function<Integer, Apple> function = Apple::new;
Apple apple = function.apply(100);
System.out.println(apple.getWeight());
在下面的代码中,一个由Integer构成的List中的每个元素都通过我们前面定义的类似的map方法传递给了Apple的构造函数,得到了一个具有不同重量苹果的List:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Apple> list = Test.getApplesName(Arrays.asList(1,2,3,4,5,6,7), Apple::new);
for(Apple apple:list) {
System.out.println(apple.getWeight());
}
}
public static <T,R>List<R> getApplesName (List<T> list,Function<T, R> f) {
List<R> list2 = new ArrayList<>();
for(T t : list) {
list2.add(f.apply(t));
}
return list2;
}
}
如果我们的构造函数有两个参数:
BiFunction<String, Integer, Apple> b1 = Apple::new;
Apple apple =b1.apply("大狗", 100);
System.out.println(apple.getName()+apple.getWeight());
目前java8提供的函数式接口只能支持到两个参数的构造函数,如果你想支持两个以上的构造函数,那么你可以自己写一个函数式接口,如下:
public interface TriFunction<T, U, V, R>{
R apply(T t, U u, V v);
}
不将构造函数实例化却能够引用它,这个功能有一些有趣的应用。例如,你可以使用Map来将构造函数映射到字符串值:
public class MyTest {
static Map<String, Function<Integer, Fruit>> map = new HashMap<>();
static {
map.put("apple", Apple::new);
map.put("orange", Orange::new);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Fruit fruit = MyTest.giveMeFruit("apple", 100);
System.out.println(fruit.getWeight());
}
public static Fruit giveMeFruit(String name,Integer weight) {
return map.get(name).apply(weight);
}
}