有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

使用<?>参数

以下是可能的吗

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

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

显然,这不是类型安全的,因为它假设在本例中Foo<?>实际上是一个Foo<String>

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

通常我可以做一些调整来将这个异常转化为我想要的警告,但不知为什么我现在找不到一个

除了“别这样!”之外还有什么想法吗,请)

编辑:

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

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

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

如果你们中的任何一个人有一个干净的方法去做我想要实现的事情,我会很兴奋的。但我不确定这是可行的任何其他方式


共 (3) 个答案

  1. # 1 楼答案

    没关系。。。我刚想出来:

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

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

    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方法在使用与泛型类型不匹配的参数调用时实际上可以正常工作;e、 g

    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对象类型转换为它所期望的类型。但这是您的EventBusAPI的基本要求。。。这似乎意味着处理程序注册和分派与处理程序对象的源类型无关。从功能角度来看,它可能也很好,因为它允许单个处理程序处理来自多个源类型的事件

    (注释:您的API设计似乎过于注重泛型的优雅使用……而忽略了支持事件总线需要提供的功能。我对事件总线的概念是,它应该避免事件的提供者和使用者之间的静态依赖关系。这意味着它需要能够mically“处理”生产者和消费者之间的类型不匹配。但是您对泛型的使用似乎(重新)引入了静态类型依赖项…)

  3. # 3 楼答案

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

    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,请使用方法重载