Question

Is the following possible somehow?

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

...

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

Clearly, this is not type-safe as it is assuming that Foo<?> in this case actually is a Foo<String>.

But rather than the usual "unchecked" warning, this actually gives me the following error: The method bar(capture#1-of ?) in the type Foo is not applicable for the arguments (String)

Usually there's some casting I can do to turn this exception into the warning I want, but somehow I can't find one right now...

Any thoughts (other than "don't do this!", please)?

EDIT:

It seems like everyone does want to discuss the "don't do this", so let me explain the entire problem I'm trying to solve as elegantly as possible and then maybe someone has a cleaner way to do this...

I'm trying to write a flexible eventbus system, where I don't need to declare a billion interface types for each event.

I want to have a class EventBus that looks like this:

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
  }
}

where the interface EventHandler looks like this:

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

That way, I can simply write my event handlers to look like this:

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

where Groups and Events are Enums that describe the possible event types.

Then I register them with

addHandler(myHandler, Groups.EDIT_BOXES, Events.VALUE_CHANGED, Events.CLEARED, ...);

And I can call them with

fireEvent(Groups.EDIT_BOXES, Events.VALUE_CHANGED, myEditField);

In my code, I know that in the group EDIT_BOXES all sources are of type TextBox and I don't want to type-cast my life away in every handler I write. That's why I would like to be able to implement the specific interface in the handler, but call it with an unsafe typecast from the EventBus (which I write once and hide forever) rather than having to write all my handlers like this:

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

And if the cast is wrong, the program will and should crash and burn either way. Even if I put an "instanceof" check in the handler, I will need to somehow throw that as an error. I can do that elegantly, but it doesn't really matter, since any error in this scenario is a bug in the code that needs to be fixed and not some runtime user-error, which I should gracefully inform the user of.

Now, I've seen other libraries implement a completely type-safe event system, but often this involves having to declare interfaces for every possible type of event and every possible type of event handler and sometimes even for functions on the eventbus itself, which, if you ask me, is WAY more pain than it's worth.

If any of you have a clean way to do what I'm trying to achieve, I'd be stoked. But I'm not sure it's doable any other way.

Was it helpful?

Solution 3

Never mind... Just figured it out:

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

OTHER TIPS

You propose that this is a solution:

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

I claim that if this does work that there is something wrong with your API's typing.

One possibility is that all the places where callBar is going to be called, the foo will actually be a Foo<String>. But if that is the case, then the (generic) method signature is incorrect. You should declare it as:

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

Another possibility is that the bar method will actually work just fine when called with an argument that doesn't match the generic type; e.g.

Foo<Integer> f = ...
f.bar("Hello world!");

doesn't cause any runtime breakages due to "Hello world!" having the wrong time. In that case you have probably declared the method signature in the interface incorrectly. It should be:

public void bar(Object object);

A third possibility is that your code works for a particular implementation of the Foo.bar method, and that you are only going to use it with those implementations. But if that is the case, your code should reflect this:

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

and

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

(Note we can't overload bar and realBar; i.e. give them the same name. The methods need to have different erased signatures ...)


But the bottom line is that what you are proposing as the correct solution is liable to result in the foo method being called with an actual argument that doesn't match the base type. That is WRONG, at least from perspective of the type signatures as you have declared them.


UPDATE in response to your comment.

I think that the best solution is to change this:

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

to

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

This means that a specific handler will need to type-cast the source object to the type that it is expecting. But this is pretty much mandated your EventBus API ... which seems to be saying that the handler registration and dispatching is agnostic of the source type of the handler objects. It is probably good from a functional perspective too, because it allows a single handler to handle events from multiple source types.

(Comment: your API design looks like you might be focussing too much on elegant use of generics ... at the expense of supporting the functionality that an event bus needs to provide. My conception of an event bus is that it should avoid static dependencies between the suppliers and consumers of events. This means that it needs to be able to dynamically "deal with" type mismatches between the the producers and consumers. But your use of generics seems to be (re-)introducing static type dependencies ... )

If you have a generic interface, don't make the method call it with a specific type. Or don't use a specific type and stay generic

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

If you do it like

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

The following code will compile fine:

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);
    }
}

But: once you run it you get

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

It requires that foo is a Foo<String>. Use

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

And if you have multiple types of Foo use method overloading.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top