接受两种不同类型作为参数的方法

Posted

技术标签:

【中文标题】接受两种不同类型作为参数的方法【英文标题】:Method accepting two different types as parameter 【发布时间】:2012-06-02 09:24:26 【问题描述】:

我正在编写一个方法,它应该接受两个类型之一的对象作为其参数,这两种类型的对象除了 Object 之外不共享父类型。例如,类型是 Dreams 和 Garlic。您可以同时使用dreams.crush()garlic.crush()。我想要一个方法utterlyDestroy(parameter),它可以接受 Dreams 和 Garlic 作为其参数。

utterlyDestroy(parameter) 
    parameter.crush()

Garlic 和 Dreams 都是某个库的一部分,因此不能让它们实现 ICrushable 接口(以便我可以编写 utterlyDestroy(ICrushable parameter))。

我的方法体很长,所以重载意味着重复代码。丑陋的。 我确信我可以使用反射并进行一些类黑客攻击。丑。

我尝试使用泛型,但显然我不能写类似的东西

utterlyDestroy(<T instanceof Dreams || T instanceof Garlic> parameter)

是否可以将 Garlic 类型转换为 Dreams?

utterlyDestroy(Object parameter) 
    ((Dreams)parameter).crush()

但这仍然很丑陋。我还有哪些其他选择?处理这种情况的首选方法是什么?

【问题讨论】:

【参考方案1】:

这个怎么样:

interface ICrushable 
    void crush();


utterlyDestroy(ICrushable parameter) 
    // Very long crushing process goes here
    parameter.crush()


utterlyDestroy(Dreams parameter) 
    utterlyDestroy(new ICrushable()  crush() parameter.crush(););


utterlyDestroy(Garlic parameter) 
    utterlyDestroy(new ICrushable()  crush() parameter.crush(););

新开发应该实现 ICrushable 接口,但对于现有的类,参数被包装在 ICrushable 中并传递给完成所有工作的 utterlyDestroy(ICrushable)。

【讨论】:

呃,那天好像有人有一种破坏性的情绪。【参考方案2】:

这么简单的事情怎么样?

utterlyDestroy(Object parameter) 
    if (parameter instanceof Dreams) 
        Dream dream = (Dreams) parameter;
        dream.crush();

        // Here you can use a Dream
     else if (parameter instanceof Garlic) 
        Garlic garlic = (Garlic) parameter;
        garlic.crush();

        // Here you can use a Garlic

    

如果utterlyDestroy 太复杂和太大,而您只想调用crush,那么这就是您想要的。

【讨论】:

我自然想到了,但是使用 Object 作为参数似乎是错误的。 你不是在重复同样的代码吗?对我来说没有意义。【参考方案3】:

您可以在 Java 中实现 Haskell 式的 Either 类;像这样:

class Either<L,R>

    private Object value;

    public static enum Side LEFT, RIGHT

    public Either(L left)  value = left;
    public Either(R right) value = right;

    public Side getSide() return value instanceof L ? Side.LEFT : Side.RIGHT;

    // Both return null if the correct side isn't contained.
    public L getLeft() return value instanceof L ? (L) value : null;
    public R getRight() return value instanceof R ? (R) value : null;

然后让该方法采用Either&lt;Dreams, Garlic&gt; 类型的东西。

【讨论】:

【参考方案4】:

你可以使用一个接口和adapt你的类型。

界面:

public interface Crushable 
  public void crush();

调用示例:

public class Crusher 
  public static void crush(Crushable crushable) 
    crushable.crush();
  

适配器工厂方法示例:

public final class Dreams 
  public static Crushable asCrushable(final Dream dream) 
    class DreamCrusher implements Crushable 
      @Override
      public void crush() 
        dream.crush();
      
    
    return new DreamCrusher();
  

  private Dreams() 

消费者代码如下所示:

  Dream dream = new Dream();
  Crushable crushable = Dreams.asCrushable(dream);
  Crusher.crush(crushable);

如果你有很多类型需要适应,你可以考虑反射。这是一个使用 Proxy 类型的(未优化的)适配器工厂:

public final class Crushables 
  private static final Class<?>[] INTERFACES =  Crushable.class ;

  public static Crushable adapt(final Object crushable) 
    class Handler implements InvocationHandler 
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable 
        return crushable.getClass()
            .getMethod(method.getName(), method.getParameterTypes())
            .invoke(crushable, args);
      
    

    ClassLoader loader = Thread.currentThread()
        .getContextClassLoader();
    return (Crushable) Proxy.newProxyInstance(loader, INTERFACES, new Handler());
  

  private Crushables() 

对于 API 使用者来说,这并不难看:

  Dream dream = new Dream();
  Crushable crushable = Crushables.adapt(dream);
  Crusher.crush(crushable);

但是,与反射一样,您牺牲了编译时类型检查。

【讨论】:

【参考方案5】:

如果您要在项目的许多地方以相同的方式对待它们,我建议将它们包装在一个类中,比如适配器。

【讨论】:

不,它只在一个地方。不过感谢您的回答。【参考方案6】:

只需使用方法重载。

public void utterlyDestroy(Dreams parameter) 
    parameter.crush();


public void utterlyDestroy(Garlic parameter) 
    parameter.crush();

如果您想以相同的方式支持多于这两种类型,您可以为它们都定义一个通用接口并使用泛型。

【讨论】:

正如我所说,我的方法很长,这意味着重复代码。 @JohnEye - 那么只在两个类中调用接口。如果您无法正确构建类层次结构,另请参阅 AadvarkSoup 的答案。【参考方案7】:

创建 Interface Crushable 似乎是最干净的方法。是否可以选择 Garlic 或 Dreams 子类型,并将您的界面添加到子类型中?

除此之外,您可以将公共代码放在私有方法中,并让两个版本的 utterlyDestroy 在调用公共代码之前对各个对象执行它们必须执行的操作。如果您的方法体很长,则可能无论如何都需要将其分解为私有方法。不过,我猜您已经想到了这一点,因为它比添加接口更为明显。

您可以将参数作为对象引入,然后进行强制转换。这就是你所说的反射吗?即,

public void utterlyCrush(Object crushable) 
    if (crushable instanceOf Dream) 
         ...
    
    if (curshable instanceOf Garlic) 
         ...
    

但考虑到一个不是另一个的子类型,从 Garlic 转换为 Dream 不是一种选择。

【讨论】:

不,通过反思,我的意思是使用一些漂亮的功能来找到一个名为 crush() 的方法并执行它或类似的东西。我写这些只是为了阻止“反射巨魔”回答。【参考方案8】:

因为我正在使用:

void fooFunction(Object o)
Type1 foo=null;
if(o instanceof Type1) foo=(Type1)o;
if(o instanceof Type2) foo=((Type2)o).toType1();
// code

但这只有在 Type2 可以转换为 Type1 时才有效

【讨论】:

以上是关于接受两种不同类型作为参数的方法的主要内容,如果未能解决你的问题,请参考以下文章

如何将不同类型的结构体作为一个函数的参数?

将类型函数作为Dart中的参数传递

thymeleaf 接受字符串类型model值作为js方法参数报错500

更好地允许“id”的对象类型或为方法的两种不同类型提供两个参数?

接口的重载方法不能接受联合类型:“没有重载匹配此调用”

JAVA基础-多态