【问题标题】:specifying an object method as a parameter in java在java中将对象方法指定为参数
【发布时间】:2013-10-31 08:49:24
【问题描述】:

我想创建一些实用方法,并且我希望它能够在不同的对象上工作,只要它们可以提供可读/写的 Integer 属性。

我知道“标准”方式是:

  1. 声明一些接口IProvidesAccessToInteger 具有setIntegergetInteger
  2. 声明MyUtility.doSomethingWonderful(IProvidesAccessToInteger obj)
  3. 致电obj.setIntegerobj.getInteger

但这有一个非常严重的缺点,它需要我希望 MyUtility 与之合作的所有班级的合作,不仅如此,即使某些班级想要合作 MyUtility 仍然只能@ 987654329@ 到该类的单个预定字段。

我正在寻找一些语法,它允许像MyUtility.doSomethingWonderful(T obj, Method<Integer> getter, Method<Integer,Integer> setter) 这样的东西,可能以某种方式使用泛型来指定obj 有两个方法的要求,这些方法可以设置并设置一个 Integer 并有一些方法可以在实例上调用它们obj.

用不需要对象的静态方法做类似的事情也可能很有趣。

更新: 当我提到反射时,我想澄清一下,我知道可以使用反射来完成密切的事情。

但是,由于我不需要在运行时解析实际接口,我希望 JAVA 有某种方式来指定某种“接口实现映射”,这样如果我的要求是一个具有两种方法的对象@ 987654333@ 和 void ?(int) 我可以指定类似.doSomethingWonderful(?<int getter(),void setter(int)> obj) 的内容,并使用具有int getInt()void setInt(int) 的某个object1 调用它一次,然后使用具有int getIntValue()void setIntValue(int) 的其他object2 调用一次,方法是在调用中指定即object 满足getInteger 的要求getInt 满足setInteger 的要求setInt 等等。可能使用像 `.doSomethingWonderful((?)object1)

这样的调用语法

至少在理论上,我认为应该可以在编译时完成所有操作,并且不需要任何运行时反射。

也许这个名字的正确名称应该是匿名接口。

也就是说,我接受通过反射的运行时解决方案也可能是一种解决方案。

【问题讨论】:

  • 你刚刚描述了反射。现在谷歌它并阅读!
  • 我知道什么是反射,但我不是在寻找在 RUNTIME 解决 getter 和 setter 的能力,所以我(希望)寻找一种更具声明性(和类型安全)的方法来做到这一点。
  • 不,我认为 (1) 使用接口(你说你不想这样做)和 (2) Boris 的出色解决方案确实是你唯一的选择。

标签: java generics methods delegates


【解决方案1】:

最接近您描述的将是 Java 8。您仍然需要接口,但调用者不需要关心它们,更好的是,有很多用于典型任务的默认接口。例如,您可以定义这样的方法:

static void increment(IntSupplier in, IntConsumer out)
{
  out.accept(in.getAsInt()+1);
}

并像这样使用它来访问对象的不同属性:

class ClassWithInProperties {
  int a, b;

  public int getA() {
    return a;
  }
  public void setA(int a) {
    this.a = a;
  }
  public int getB() {
    return b;
  }
  public void setB(int b) {
    this.b = b;
  }
  @Override
  public String toString() {
    return "a="+a+", b="+b;
  }
}

ClassWithInProperties obj=new ClassWithInProperties();
increment(obj::getA, obj::setA);
increment(obj::getA, obj::setA);
increment(obj::getB, obj::setB);
System.out.println(obj);

或使用静态方法:

public class Test {
    static int DATA = 42;
    static int getData() {
        return DATA;
    }
    static void setData(int i) {
        DATA=i;
    }
}

increment(Test::getData, Test::setData);
System.out.println(DATA);

【讨论】:

  • 那么IntSupplierIntConsumergetAsIntaccept 来自哪里?是什么让obj::getA 适合用作 IntSupplier ?如果我想获得一个任务两个 Integers 和一个 String 的方法怎么办?
  • @epeleg 这都是 Java 8。IntSupplierIntConsumer 是 Java 8 API 的一部分。 obj::getA 是 Java 8 Lambda 的一个示例。
  • @epeleg 说它们是在 Java 8 中预定义的,并且方法引用是合适的,因为它的签名匹配。对于像您的三参数方法这样的特殊情况,您需要定义自己的接口,但是当引用的方法具有您的接口的正确签名时,方法引用的工作方式相同。 download.java.net/jdk8/docs/api/java/lang/…
【解决方案2】:

您的 getter 和 setter 是一种很好的反思范式。但这会带来很大的风险并失去性能。面向接口的编程是处理这种情况的非常常见的“标准”。

【讨论】:

    【解决方案3】:

    你不能用泛型做到这一点。

    您可以通过反射来做到这一点。使用诸如BeanUtils 之类的实用程序当然会更容易,但您也可以手写。

    public void doSomethingWonderful(final Object in, final String fieldName) 
            throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        final String upperCased = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        final Method getter = in.getClass().getMethod("get" + upperCased);
        final Method setter = in.getClass().getMethod("set" + upperCased, Integer.TYPE);
    
        //to invoke getter
        final int val = (int) getter.invoke(in);
    
        //to invoke setter
        setter.invoke(in, val);
    }
    

    我假设您使用的是int 而不是Integer,在后一种情况下您需要稍微更改代码。

    您可以看到它抛出了大量异常,我建议将它们全部包装在自定义异常类型中以简化客户端代码。

    编辑

    Op 想要将方法分解为三个重载方法:

    public void doSomethingWonderful(final Object in, final String fieldName)
            throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        final String upperCased = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        doSomethingWonderful(in, "get" + upperCased, "set" + upperCased);
    }
    
    public void doSomethingWonderful(final Object in, final String getterName, final String setterName)
            throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        final Method getter = in.getClass().getMethod(getterName);
        final Method setter = in.getClass().getMethod(setterName);
        doSomethingWonderful(in, getter, setter);
    }
    
    public void doSomethingWonderful(final Object in, final Method getter, final Method setter)
            throws NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        //to invoke getter
        final int val = (int) getter.invoke(in);
    
        //to invoke setter
        setter.invoke(in, val);
    }
    

    【讨论】:

    • 即使我确实采用了反射方式,为什么不将Method getter 和Method setter 作为参数传递呢?或者使用String getterMethodNameString setterMethodName ?
    • @epeleg 为什么不传递方法?因为您需要在某处获取它们——这将是重复的代码,为什么不把它放在一个地方。为什么不传递 getter 和 setter 名称?因为这是三个方法参数,其中两个可以合并为一个 - 名称是相对于字段名称定义的。当然,这取决于您的用例,但这是编写通用 bean 访问器的最标准方式。
    • 我明白了——这是因为我说我想“在不同的对象上工作,只要它们可以提供读/写的整数属性”。基于相同的登录 如果我想保持从两个不同位置读取和写入的灵活性,那么您将传递方法名称......在经常使用此实用程序的场景中,能够在性能方面更好让调用者获取方法并缓存它们以供多次调用 - 所以可能提供一个使用方法名称的版本和getMethod 它们并调用已经获取方法的版本,也可以直接调用。
    • @epeleg 我已经添加了一个使用重载的建议示例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    • 1970-01-01
    • 1970-01-01
    • 2014-07-23
    • 1970-01-01
    • 2018-04-13
    • 2013-12-20
    相关资源
    最近更新 更多