如何使用Lambda表达式在调用另一个方法时将方法作为参数传递?

3
假设我有两个方法void chargeToIndividual(int amount, int account)void chargeToCompany(int amount, int account)。假设我还有另一个名为void processSale(String item, Customer c)的方法。我该如何才能将chargeToIndividualchargeToCompany作为参数传递给processSale,并如何调用它?
例如,我希望能够这样做:
if(isIndividual(someCustomer))
{
  processSale(anItem, someCustomer, chargeToIndividual)
}
else if(isCompany(someCustomer))
{
  processSale(anItem, someCustomer, chargeToCustomer)
}

那么在processSale()函数内,我应该如何调用chargeToIndividual或者chargeToCustomer()函数呢?


+1 因为有人有勇气发布这个问题。 - Kick Buttowski
你不需要解释,只需将公司/客户作为参数传递,并让面向对象选择方法的正确签名(重载)。 - Nir Alfasi
1
@KickButtowski 谢谢,我已经搜索了一段时间,人们只是半提及它并暗示它,但似乎没有带有工作代码的明确解释(例如,看看上面alfasin写得不好的评论,根本没有意义)。 - Celeritas
@alfasin 公平地说,接口不同,因此显然不是多态链的一部分。否则我会完全同意。如果您控制两个类,则应设置正确的继承树(或至少实现一致的接口),以便您可以使用多态性来解决此问题。或者,如果这些是“客户”不同的子类,则可以重载“processSale”以接受“Individual”或“Company”。 - aruisdante
你不能使用BiConsumer接口吗?http://docs.oracle.com/javase/8/docs/api/java/util/function/BiConsumer.html - Celeritas
2个回答

2
你有两个函数:
void chargeToIndividual(int amount, int account);
void chargeToCompany(int amount, int account);

他们的共同点是都接受两个 int 参数并返回 void。在 java.util.function 中没有匹配这种形状的函数接口,但我们很容易定义自己的:
interface IntIntConsumer {
    void accept(int amount, int account);
}

您需要将调用代码重写如下:
if (isIndividual(someCustomer)) {
    processSale(anItem, someCustomer, MyClass::chargeToIndividual);
} else if (isCompany(someCustomer)) {
    processSale(anItem, someCustomer, MyClass::chargeToCompany);
} else { ... }

或者如果chargeToIndividualchargeToCustomer是实例方法,可能使用this::chargeToIndividualthis::chargeToCustomer。另一种形式是将充电函数存储在本地变量中。然后您将只调用一次processSale
IntIntConsumer chargeFunc;

if (isIndividual(someCustomer)) {
    chargeFunc = this::chargeToIndividual;
} else if (isCompany(someCustomer)) {
    chargeFunc = this::chargeToCompany;
} else { ... }

processSale(anItem, someCustomer, chargeFunc);

现在,在processSale中,调用充电功能的代码如下:

void processSale(Item item, Customer customer, IntIntConsumer func) {
    ...
    func.accept(item.getAmount(), customer.getAccount());
    ...
}

当然,我假设你知道如何获取amountaccount参数,但我认为你能理解这个想法。


关于您提出的两个选项,它基本上归结为何时传递参数与何时设置成员字段(“将充电功能存储在本地变量中”)的论点?在速度方面有区别吗? - Celeritas
@Celeritas 不是成员字段而是局部变量。我怀疑速度上没有什么区别。这更多是一种风格和代码分解的问题。在第一种方法中,有两个对 processSale 的调用;有些人可能会觉得这很重复。如果你将这些调用之间的差异提取到一个局部变量中,它就可以减少到一个对 processSale 的调用,但是要想弄清楚它在做什么,你必须跟踪局部变量的赋值。我认为情况将决定哪种方法更可取。 - Stuart Marks
当使用lambda表达式来实现接口时,似乎不再需要关键字implements,例如在您的示例中,您从未使用过“implements IntIntConsumer”。 - Celeritas
@Celeritas 正确,lambda表达式会自动转换为目标类型的实例,该目标类型必须是一个函数式接口。因此,implements IntIntConsumer或其他内容是隐式的,不需要声明。但是,在接收端,如果您使用instanceof IntIntConsumer测试参数值,则结果将为true - Stuart Marks

0

由于问题涉及使用lambda表达式:

import java.util.function.Consumer;

public class PlayWithLambdas {

    public void testIsCustomer(Consumer<Chargeable> f) {
        Chargeable customer = new Customer();
        Chargeable company = new Company();
        f.accept(customer); // prints "true"
        f.accept(company);  // prints "false"
    }

    public static void main(String[] args) {
        PlayWithLambdas p = new PlayWithLambdas();
        p.testIsCustomer(chargeable -> {
            System.out.println(chargeable.getType().equals(ChargeableType.CUSTOMER));
        });
    }    
}

interface Chargeable {
    public ChargeableType getType();
}

class Customer implements Chargeable {
    @Override
    public ChargeableType getType() {
        return ChargeableType.CUSTOMER;
    }
}

class Company  implements Chargeable {
    @Override    
    public ChargeableType getType() {
        return ChargeableType.COMPANY;
    }
}

enum ChargeableType {
    CUSTOMER, COMPANY;
}

希望这个语无伦次的可怜写手能够帮到你 :)


实现getType()方法时,应该使用@override注解吗? - Celeritas
我这样问是因为Netbeans在lambda表达式中没有建议它,而在其他情况下则有。我想Netbeans只是还没有跟上Java 8的步伐。 - Celeritas
@Celeritas 发现得好!您可能需要提交错误报告 - Nir Alfasi

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