什么是方法引用?
方法引用可用于在不调用方法的情况下引用方法。它将方法视为Lambda表达式。它们只能作为语法糖来减少一些lambdas的冗长。在方法引用中,将包含方法的对象(或类)放在::运算符之前,将方法的名称放在不带参数的方法之后。例如:
Object :: methodName
为什么会出现方法引用?
在Java中,我们可以通过创建新对象来使用对象的引用,或者引用已有的对象,例如:
//创建新对象来引用该对象
List list = new ArrayList();
dosth(list);
//引用已有的对象
List list2 = list;
dosth(list2);
如果我们只在另一个方法中使用对象的方法,我们仍然必须将完整的对象作为参数传递。那么将方法作为参数传递不是更有效吗?
方法引用这种语法糖不能用于所有的方法,它们只能用于只有一个方法的lambda表达式
//lambda转方法引用
Consumer<String> c = s -> System.out.println(s);
Consumer<String> c = System.out::println;
换句话说,可以使用lambda表达式去代替一个匿名类,如果这个匿名类只有一个方法,就可以用更简短的方法引用来代替lambda表达式。
静态方法引用
from
(args) -> Class.staticMethod(args)
to
Class::staticMethod
如上所述,我们不需要显式的传递参数,方法引用中会自动传递参数
Consumer<String> c1 = (s) -> System.out.println(s);
Consumer<String> c2 = System.out::println;
c2.accept("Hello");
当我们所使用的lambda表达式仅仅调用了一个静态方法,我们就可以使用静态方法引用。如下所示,将lambda表达式(匿名类)中的值判断提取成isMoreThanFifty这个静态方法,就可以使用静态方法引用这种语法糖。
public class Numbers {
//将值判断提取成静态方法
public static boolean isMoreThanFifty(int n1, int n2) {
return (n1 + n2) > 50;
}
//公共接口方法
public static List<Integer> findNumbers(List<Integer> l, BiPredicate<Integer, Integer> p) {
List<Integer> newList = new ArrayList<>();
for (Integer i : l) {
if (p.test(i, i + 10)) {
newList.add(i);
}
}
return newList;
}
public static void main(String[] args) {
List<Integer> list = Arrays.asList(12, 5, 45, 18, 33, 24, 40);
// Using an anonymous class
List<Integer> res = Numbers.findNumbers(list, new BiPredicate<Integer, Integer>() {
@Override
public boolean test(Integer integer, Integer integer2) {
return Numbers.isMoreThanFifty(integer, integer2);
}
});
res.stream().forEach(System.out :: println);
// res.stream().peek(System.out :: println).collect(Collectors.toList());
// Using a lambda expression
Numbers.findNumbers(list, (i1, i2) -> Numbers.isMoreThanFifty(i1, i2));
// Using a method reference
Numbers.findNumbers(list, Numbers::isMoreThanFifty);
}
}
特定类型的对象的实例方法的方法引用
from
(obj, args) -> obj.instanceMethod(args)
to
ObjectType::instanceMethod
- 我们使用实例的类型而非实例本身
- 参数隐式传递
public class People {
public double calculateWeight() {
double weight = 0;
// Calculate weight
return weight;
}
public List<Double> calculateAllWeight(List<People> l, Function<People, Double> f) {
List<Double> results = new ArrayList<>();
for (People s : l) {
results.add(f.apply(s));
}
return results;
}
}
class PeopleClient {
public static void main(String[] args) {
List<People> list = new ArrayList<>();
People p = new People();
// Using an anonymous class
p.calculateAllWeight(list, new Function<People, Double>() {
@Override
public Double apply(People people) {// The object
return people.calculateWeight();// The method
}
});
// Using a lambda expression
p.calculateAllWeight(list, people -> people.calculateWeight());
// Using a method reference
p.calculateAllWeight(list, People::calculateWeight);
}
}
在这个例子中,我们没有给方法传递任何参数,关键在于对象的实例是lambda表达式的一个参数,我们通过实例的类型来完成对实例方法的引用。下面是另一个例子,在这个例子中,我们向方法引用中传递了两个参数。
//java中有一个Function接口可以接收一个参数,BiFunction接口接受两个参数,没有接受三个参数的,所以我们自定义一个TriFunction
interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
//然后定义一个类,接收两个参数,并且有一个返回值
class Sum {
Integer doSum(String s1, String s2) {
return Integer.parseInt(s1) + Integer.parseInt(s1);
}
}
//用匿名类实现TriFunction,来包装doSum()
TriFunction<Sum, String, String, Integer> anonymous =
new TriFunction<Sum, String, String, Integer>() {
@Override
public Integer apply(Sum s, String arg1, String arg2) {
return s.doSum(arg1, arg2);
}
};
System.out.println(anonymous.apply(new Sum(), "1", "4"));
//使用lambda表达式来包装
TriFunction<Sum, String, String, Integer> lambda =
(Sum s, String arg1, String arg2) -> s.doSum(arg1, arg2);
System.out.println(lambda.apply(new Sum(), "1", "4"));
//使用方法引用
TriFunction<Sum, String, String, Integer> mRef = Sum::doSum;
System.out.println(mRef.apply(new Sum(), "1", "4"));
- 第一个参数是要执行的方法的实例对象
- 第二、三个参数是传递的其他参数
- 最后一个参数是要执行的方法的返回值类型
已有对象的实例方法引用
from
(args) -> obj.instanceMethod(args)
to
obj::instanceMethod
直接引用已经在其他地方实例化的对象的一个方法,本身不需要再实现一次
class Car {
private int id;
private String color;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class Mechanic {
public void fix(Car c) {
System.out.println("Fixing car " + c.getId());
}
}
public class InstanceRef {
public static void main(String[] args) {
InstanceRef instanceRef = new InstanceRef();
final Mechanic mechanic = new Mechanic();
Car car = new Car();
// Using an anonymous class
instanceRef.execute(car, new Consumer<Car>() {
public void accept(Car c) {
mechanic.fix(c);
}
});
// Using a lambda expression
instanceRef.execute(car, car1 -> mechanic.fix(car1));
//Using a method reference
instanceRef.execute(car, mechanic::fix);
}
private void execute(Car car, Consumer<Car> c) {
c.accept(car);
}
}
构造方法引用
from
(args) -> new ClassName(args)
to
ClassName::new
这个lambda表达式唯一能做的就是创建一个新对象,我们通过关键字new来引用类的构造函数。与其他情况一样,参数(如果有)不会在方法引用中传递。
无参构造
// Using an anonymous class
Supplier<List<String>> s = new Supplier() {
public List<String> get() {
return new ArrayList<String>();
}
};
List<String> l = s.get();
// Using a lambda expression
Supplier<List<String>> s = () -> new ArrayList<String>();
List<String> l = s.get();
// Using a method reference
Supplier<List<String>> s = ArrayList::new;
List<String> l = s.get();
带参构造
// Using a anonymous class
BiFunction<String, String, Locale> f = new BiFunction<String, String, Locale>() {
public Locale apply(String lang, String country) {
return new Locale(lang, country);
}
};
Locale loc = f.apply("en","UK");
// Using a lambda expression
BiFunction<String, String, Locale> f = (lang, country) -> new Locale(lang, country);
Locale loc = f.apply("en","UK");
// Using a method reference
BiFunction<String, String, Locale> f = Locale::new;
Locale loc = f.apply("en","UK");
如果有三个及以上的参数的构造函数,则必须创建自己的函数接口。引用构造函数与引用静态方法非常相似,区别在于构造函数“方法名称”是new。
总结
如果使用了方法引用之后能让代码变的更加整洁,就使用它。实际使用中,一种使用方式是将代码包裹在一个方法中,而非使用一个单独的类或者lambda表达式,然后使用方法引用的方式进行调用。方法引用常用于java8的另一种新特性Streams中,而基于方法引用的设计模式也会更加具有拓展性。