使用<! - ? - >参数调用通用Java函数

时间:2012-11-24 23:13:16

标签: java generics casting unchecked

以下是否有可能?

interface Foo<T> {
    public void bar(T object);
}

...

public void callBar(Foo<?> foo) {
    foo.bar("Hello world!");
}

显然,这不是类型安全的,因为假设Foo<?>实际上是Foo<String>

但不是通常的“未经检查”警告,这实际上给了我以下错误: Foo类型中的方法栏(捕获#1-of?)不适用于参数(String)

通常我可以做一些演员将这个异常变成我想要的警告,但不知何故我现在找不到...

任何想法(除了“不要这样做!”,)?

修改

好像每个人都想要讨论“不要这样做”,所以让我解释一下我试图尽可能优雅地解决的整个问题然后可能有人有一个更清洁的方式......

我正在尝试编写一个灵活的eventbus系统,我不需要为每个事件声明十亿种接口类型。

我希望有一个类EventBus,如下所示:

public class EventBus<GroupType, EventType>  {

  ...

  public void addHandler(EventHandler<GroupType, EventType, ?> handler, GroupType group, EventType... events) {
    //                                                      ^ this is the <?> !!!
    // add handler to list of handlers for group and requested event types
  }


  public <Source> void fireEvent(GroupType group, EventType event, Source source) {
    // call all handlers registered for group and event type
  }
}

接口EventHandler如下所示:

public interface EventHandler<GroupType, EventType, Source> {
  public void onEvent(GroupType group, EventType event, Source source);
}

这样,我可以简单地编写我的事件处理程序,如下所示:

public class Handler implements EventHandler<Groups, Events, TextBox> {
  public void onEvent(Groups group, Events event, TextBox source) {
    // Do something with source immediately
  }
}

其中GroupsEvents是描述可能的事件类型的枚举。

然后我用

注册它们
addHandler(myHandler, Groups.EDIT_BOXES, Events.VALUE_CHANGED, Events.CLEARED, ...);

我可以用

打电话给他们
fireEvent(Groups.EDIT_BOXES, Events.VALUE_CHANGED, myEditField);

在我的代码中,我知道在小组EDIT_BOXES中,所有来源都是TextBox类型,我不想在每个来源中输入我的生命处理程序我写。这就是为什么我希望能够在处理程序中实现特定的接口,但是使用EventBus中的一个不安全的类型转换(我只编写一次并永远隐藏)来调用它,而不是像我这样编写所有处理程序:

public class Handler implements EventHandler<Groups, Events> {
  public void onEvent(Groups group, Events event, Object source) {
    TextBox usableSource = (TextBox) source;
    // Do something with usableSource
  }
}

如果演员表错了,那么该程序将会崩溃并以任何方式刻录。即使我在处理程序中放入“instanceof”检查,我也需要以某种方式将其作为错误抛出。我可以优雅地做到这一点,但这并不重要,因为这个场景中的任何错误都是需要修复的代码中的错误而不是某些运行时用户错误,我应该优雅地告知用户。

现在,我已经看到其他库实现了一个完全类型安全的事件系统,但是这通常需要为每种可能的事件类型和每种可能类型的事件处理程序声明接口,有时甚至为事件总线本身的函数声明如果你问我,那就太痛苦了。

如果你们有任何一个干净的方式去做我想要实现的目标,我会被激怒。但我不确定它是否可行。

3 个答案:

答案 0 :(得分:4)

您建议这是一个解决方案:

public void callBar(Foo<?> foo) {
    ((Foo<String>) foo).bar("Hello world!");
}

我声称,如果此 工作,那么您的API输入有问题。

一种可能性是callBar将要调用的所有地方,foo实际上都是Foo<String>。但如果是这种情况,那么(通用)方法签名是不正确的。你应该声明为:

public void callBar(Foo<String> foo) {
    foo.bar("Hello world!");
}

另一种可能性是,当使用与泛型类型不匹配的参数调用时,bar方法实际上可以正常工作;例如

Foo<Integer> f = ...
f.bar("Hello world!");
由于"Hello world!"时间错误,

不会导致任何运行时断开。在这种情况下,您可能错误地在接口中声明了方法签名。它应该是:

public void bar(Object object);

第三种可能性是您的代码适用于Foo.bar方法的特定实现,并且您只会将其用于这些实现。但如果是这种情况,您的代码应该反映这一点:

public void callBar(Foo<?> foo) {
    if (foo instanceof SomeFooImpl) {
        foo.realBar("Hello world!");
    } else {
        throw InvalidArgument("barring the wrong kind of foo");
    }
}

public class SomeFooImpl<T> implements Foo<T> {
    ...
    public void bar(T object) {
        realBar(object);
    }

    publiC void realBar(Object object) {
        System.out.print("Breaker breaker - come in '" + object + "'"));
    }
}

(注意我们不能重载barrealBar;即给它们相同的名称。方法需要有不同的删除签名...)


但最重要的是,您提出的正确解决方案是责任导致foo方法被调用,其实际参数与基类型不匹配。这是错误的,至少从您声明它们的类型签名的角度来看。


更新以回复您的评论。

我认为最好的解决方案是改变这个:

public interface EventHandler<GroupType, EventType, Source> {
    public void onEvent(GroupType group, EventType event, Source source);
}

public interface EventHandler<GroupType, EventType> {
    public void onEvent(GroupType group, EventType event, Object source);
}

这意味着特定的处理程序需要将source对象类型转换为它所期望的类型。但这几乎是你的EventBus API的强制要求......这似乎是说处理程序注册和调度与处理程序对象的源类型无关。从功能的角度来看也很好,因为它允许单个处理程序处理来自多个源类型的事件。

(评论:您的API设计看起来可能过于关注泛型的优雅使用......以支持事件总线需要提供的功能为代价。我对事件总线的概念是它应该避免事件的供应商和消费者之间的静态依赖关系。这意味着它需要能够动态地“处理”生产者和消费者之间的类型不匹配。但是你对泛型的使用似乎(重新)引入了静态类型依赖......)

答案 1 :(得分:1)

如果您有通用接口,请不要让方法使用特定类型调用它。或者不要使用特定类型并保持通用

interface Foo<T> {
    public void bar(T object);
}

class SomeGenericClass {
    public <T> void callGenericBar(Foo<T> foo, T param) {
        foo.bar(param);
    }
    public void callStringBar(Foo<String> foo) {
        foo.bar("Hello");
    }
}

如果你这样做

public void callBar(Foo<?> foo) {
    ((Foo<String>) foo).bar("Hello world!");
}

以下代码编译正常:

interface Foo<T> {
    public void bar(T object);
}

class StringFoo implements Foo<String> {
    public void bar(String object) {
        System.out.println(object);
    }
}

class IntFoo implements Foo<Integer> {
    public void bar(Integer object) {
        System.out.println(object);
    }
}

class TestClass {
    public static void callBar(Foo<?> foo) {
        ((Foo<String>) foo).bar("Hello world!");
    }

    public static void main(String[] args) {
        StringFoo foo1 = new StringFoo();
        IntFoo foo2 = new IntFoo();
        callBar(foo1);
        callBar(foo2);
    }
}

但是:一旦你运行它就会得到

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

要求 fooFoo<String>。使用

public static void callBar(Foo<String> foo) {
    foo.bar("Hello world!");
}

如果你有多种类型的Foo使用方法重载。

答案 2 :(得分:0)

没关系......只是想通了:

public void callBar(Foo<?> foo) {
    ((Foo<String>) foo).bar("Hello world!");
}