0

以下是可能的吗?

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

...

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

显然,这不是类型安全的,因为它假设Foo<?>在这种情况下实际上是一个Foo<String>.

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

通常我可以做一些转换来将此异常转换为我想要的警告,但不知何故我现在找不到一个......

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

编辑:

似乎每个人都想讨论“不要这样做”,所以让我解释一下我试图尽可能优雅地解决的整个问题,然后也许有人有更简洁的方法来做到这一点......

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

我想要一个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
  }
}

whereGroupsEvents是描述可能的事件类型的枚举。

然后我注册他们

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”检查,我也需要以某种方式将其作为错误抛出。我可以优雅地做到这一点,但这并不重要,因为这种情况下的任何错误都是代码中需要修复的错误,而不是一些运行时用户错误,我应该优雅地通知用户。

现在,我已经看到其他库实现了一个完全类型安全的事件系统,但这通常涉及必须为每种可能的事件类型和每种可能的事件处理程序类型声明接口,有时甚至为事件总线本身上的函数声明接口,其中,如果你问我,是不是比它的价值更痛苦。

如果你们中的任何人有一种干净的方式来做我想要实现的目标,我会很兴奋。但我不确定这是否可行。

4

3 回答 3

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 + "'"));
    }
}

(注意我们不能重载barand realBar; 即给它们相同的名字。这些方法需要有不同的擦除签名......)


但最重要的是,您提出的正确解决方案可能会导致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 设计看起来可能过于关注泛型的优雅使用......以支持事件总线需要提供的功能为代价。我对事件总线的概念是它应该避免静态依赖在事件的提供者和消费者之间。这意味着它需要能够动态地“处理”生产者和消费者之间的类型不匹配。但是您对泛型的使用似乎是(重新)引入静态类型依赖... )

于 2012-11-24T23:54:47.177 回答
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

要求foo一个Foo<String>. 采用

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

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

于 2012-11-24T23:38:31.057 回答
0

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

public void callBar(Foo<?> foo) {
    ((Foo<String>) foo).bar("Hello world!");
}
于 2012-11-24T23:16:24.687 回答