Java 8中的方法引用

14
public class Car {

    private int maxSpeed;

    public Car(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }
}

我们可以按照以下方式对汽车列表进行排序:
    Car carX = new Car(155);
    Car carY = new Car(140);

    List<Car> cars = new ArrayList<>();
    cars.add(carX);
    cars.add(carY);

    cars.sort(Comparator.comparing(Car::getMaxSpeed));

如果我们查看方法Comparator.comparing的签名,输入参数类型为Function<? super T, ? extends U>

在上面的例子中,Car::getMaxSpeed如何被强制转换为Function<? super T, ? extends U>,而以下内容无法编译?

  Function<Void, Integer> function = Car::getMaxSpeed;

2
我复制/粘贴了你的代码,在我的电脑上可以运行,看起来可能是IDE的问题。 - Nicolas Filotto
请将该信息编辑到您的问题中。 - Mark Rotteveel
3
我对使用 Eclipse Mars.2 和 JDK 1.8.0_51 没有任何错误。此外,你可以使用 Comparator.comparingInt 来避免装箱。 - Tunaki
1
这里似乎存在一些误解。您最初的问题是关于 carX::getMaxSpeed,这与 Car::getMaxSpeed非常不同。大多数答案都涉及到第一版而非所有版本,这一点很令人困惑:您的编辑使得部分答案无效了... - Tunaki
2
@HopefullyHelpful:你可以声明一个类型为 Void 的参数,即期望一个 java.lang.Void 实例,但你不太可能看到除了 null 之外的其他东西,所以它没有太多用处。关键是,Void 不是 void 的包装类型,它只是 Void.TYPE 的占位符,后者持有代表 void.classClass 对象。 - Holger
显示剩余3条评论
5个回答

11

这是因为getMaxSpeed方法是一个Function<Car, Integer>

也就是说:

<Car, Integer> Comparator<Car> java.util.Comparator.comparing(
    Function<? super Car, ? extends Integer> keyExtractor
)

注意

为了使用::表达式从Car的一个实例中引用getMaxSpeed方法,你需要声明一个带有如下签名的Car方法:getMaxSpeed(Car car)


1
但是OP正在询问carX::getMaxSpeed,而不是Car::getMaxSpeed,即已绑定到实例的方法。 - tobias_k
尽管getMaxSpeed()不需要参数,但它被转换为Function<Car, Integer> - saravana_pc
4
如果getMaxSpeed是一个成员函数,那么要使用Car::getMaxSpeedCar必须声明一个成员函数getMaxSpeed()(没有参数)或者一个静态函数getMaxSpeed(Car) - Hank D
2
@Saravana_pc 当方法引用实例方法时,编写方式会将接收器添加为第一个参数。Car::getMaxSpeed 相当于 car -> car.getMaxSpeed() - Louis Wasserman
@saravana_pc 这条评论对谈话没有太多帮助,但我认为有一个更简单的方法来解释这个问题:如何让“getMaxSpeed”知道要返回哪辆车的速度?如果你想要一个“获取空值并返回其速度”的方法引用...好吧,那不合理,类型错误就在那里。它应该使用哪辆车?当你写“new Car().getMaxSpeed()”时,函数肯定知道应该使用哪辆车。但是Car :: getMaxSpeed不知道。 - Ricardo Pieper

10

如果你想为一个不需要参数的方法创建一个方法引用,比如已经绑定到实例的方法,你应该使用 Supplier,而不是 Function

Function<Car, Integer> f1 = Car::getMaxSpeed;

Car carx = new Car(42);
Supplier<Integer> f2 = carx::getMaxSpeed; 
在方法引用carX::getMaxSpeed中,函数的“隐式”this参数已经绑定到carx,因此你会得到一个无需参数的函数(顺便说一下,它不能用于Comparator),在Java 8中,无需参数的函数就是一个Supplier
同样地,如果你有一个返回void的方法,你最终得到的是一个Consumer
Consumer<Integer> f3 = carx::setMaxSpeed;

1
我同意。然后问题就是你不能在 Comparator.comparing 调用中使用它。编辑实际上,鉴于上下文,这可能不是问题。 - Mena

4
一个没有参数的成员函数实际上有一个隐藏的参数,即 this 引用。形如 ClassName::memberFunction 的方法引用总是使用函数类型的第一个参数作为类实例,即实例的隐藏的 this 参数。因此,在 Car.getMaxSpeed() 的情况下,它在内部与 static Integer getMaxSpeed(Car car) 具有相同的参数。因此,Car::getMaxSpeed 将适合 functional type Function<Car,Integer>,就像 static Integer getMaxSpeed(Car car) 一样。
某些带有一个参数的成员函数发生类似的情况-它们适合 BiFunction 函数类型,其中第一个参数是类实例。

2

任务:

Function<Void, Integer> function = carX::getMaxSpeed;

由于它是一个 Supplier<Integer> 而不是一个 Function,因此无法编译。

那么,为什么这个可以编译?:

Comparator.comparing(Car::getMaxSpeed)

Java 8允许在需要Function<T, U>的地方提供一个是Supplier<U>的实例方法引用,编译器会将getter方法有效地转换为函数。

为了弄清楚为什么这是可能的,让我们看一下如何使用反射调用getter方法:

System.out.println(Car.class.getMethod("getMaxSpeed").invoke(carX)); // "155"

当调用实例方法的 invoke() 方法时,我们将实例传递给 getter 的 Methodinvoke() 方法 - 这里有一个隐含的实例类型参数。从这个角度来看,我们可以看到,在底层,getter 实际上是通过 invoke() 方法实现为 Function<T, U>


最后一句话的参考或示例是什么? - HopefullyHelpful
1
@HopefullyHelpful 参考 https://dev59.com/xmEh5IYBdhLWcg3wWySr#22516423 和 Stuart Marks 的 这条评论。这个答案是正确的。 - Tunaki
@HopefullyHelpful 请查看已编辑的答案,其中包含为什么它有效的反思机制。 - Bohemian
2
说“它是一个Supplier<Integer>是不正确的,因为方法引用本身没有类型。carX::getMaxSpeedSupplier<Integer>兼容,并且可以在期望Supplier<Integer>的地方使用。但它也可以在期望Callable<Integer>IntSupplier的地方使用。这些接口具有相同的函数签名,但是在其他方面没有关联。 - Holger
2
此外,Reflection示例是误导性的,因为Method.invoke有两个不同的参数用于接收器和参数,因此,在调用static方法时需要将null作为第一个参数传递。此外,Method.invoke根本没有参与过程。如果您想窥视其中的内容,MethodHandle在接收器和参数之间没有区别,并且确实在底层用于指定目标方法。 - Holger
显示剩余6条评论

0

让我们详细看一下函数

Interface Function<T,R> {

    default <V> Function<T,V>   andThen(Function<? super R,? extends V> after){}

    R   apply(T t);

    default <V> Function<V,R>   compose(Function<? super V,? extends T> before){}

    static <T> Function<T,T>    identity();

}

请注意 R apply(T t); 将此函数应用于给定的参数。

Function<Void, Integer> function = Void::?????;
Void voidInstance = null;
function.apply(voidInstance);

这没有任何意义。你想传递一个Void对象以便应用Void的函数?

以下是一些编译为函数的说明性示例

请注意,如果该方法是实例方法,则c->c.getMaxSpeed()Car::getMaxSpeed在语法上是等效的。对于非静态方法,第一个参数是从使用方法的类型中推断出来的,并且需要稍后提供(作为将要执行/应用方法的实例)。

public class Car {

    private int maxSpeed;

    public Car(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    public int getMaxSpeed() {
        return this.maxSpeed;
    }
    public Void setMaxSpeed() {
        this.maxSpeed = 12;
        return null;
    }
    public static int intStaticFunction(Void v) {
        return new Random().nextInt();
    }
    public static Void voidStaticFunction(Void v) {
        return null;
    }
    public static void main(String[] args) {
        final Car carX = new Car(155);
        final Car carY = new Car(140);

        final List<Car> cars = new ArrayList<>();
        cars.add(carX);
        cars.add(carY);

        cars.sort(Comparator.comparing(Car::getMaxSpeed));
        final Function<Car, Integer> function1 = c->c.getMaxSpeed();
        final Function<Car, Integer> function2 = Car::getMaxSpeed;
        final Function<Car, Void> function3 = Car::setMaxSpeed;
        final Function<Void, Void> function4 = n->n;
        final Function<Void, Integer> function5 = n->5;
        final Function<Void, Integer> function6 = Car::intStaticFunction;
        final Function<Void, Void> function7 = Car::voidStaticFunction;
        final Function<Car, Integer> function8 = function1::apply;
        final Function<Car, Integer> function9 = function2::apply;
        System.out.println(function1.apply(carX));
        System.out.println(function2.apply(carX));
        System.out.println(function8.apply(carX));
        System.out.println(function9.apply(carX));
        System.out.println(function3.apply(carX));
        System.out.println(function1.apply(carX));
        System.out.println(function2.apply(carX));
        System.out.println(function8.apply(carX));
        System.out.println(function9.apply(carX));
        System.out.println();
        System.out.println(function4.apply(null));
        System.out.println(function5.apply(null));
        System.out.println(function6.apply(null));
        System.out.println(function7.apply(null));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接