简体   繁体   中英

Reference to an object of any class that implements two or more given interfaces

Given any interface I , it is possible to declare a variable that holds a reference to any object of a class C that implements I :

I i = new C();

I want to do something similar. Given two interfaces, I want to declare a variable that holds a reference to any object of a class that implements both interfaces:

interface Foo { void foo(); }
interface Bar { void bar(); }

class Humpty implements Foo, Bar {
  public void foo() { System.out.println("Humpty.foo()"); }
  public void bar() { System.out.println("Humpty.bar()"); }
}

class Dumpty implements Foo, Bar {
  public void foo() { System.out.println("Dumpty.foo()"); }
  public void bar() { System.out.println("Dumpty.bar()"); }
}

public class Program {
  public static void main(String[] args) {
    // I actually have no idea what the syntax should be.
    Random random = new Random();
    // Fix: I previously used <? extends Foo, Bar>, thanks Jon Skeet and vijucat
    <? extends Foo & Bar> foobar;
    if (random.nextBoolean())
      foobar = new Humpty();
    else
      foobar = new Dumpty();
    foobar.foo();
    foobar.bar();
  }
}

I have tried the above snippet, but <? extends Foo, Bar> <? extends Foo, Bar> causes a compilation error. What should the correct syntax be? I would like to know if this is possible in other statically typed JVM languages, too: Scala, Kotlin, Ceylon, etc.

If you don't mind suppressing the infamous "Unchecked cast" warning, this is a "solution":

@SuppressWarnings("unchecked")
public class Program {
  public static<FooBar extends Foo & Bar> void main(String[] args) {
    // Note the explicit cast needed
    FooBar foobar = (FooBar) new Humpty();
    foobar.foo();
    foobar.bar();

    // Note the explicit cast needed
    foobar = (FooBar) new Dumpty();
    foobar.foo();
    foobar.bar();
  }
}

Also see Jon Skeet's answer, which goes into the circumstances where this is possible elegantly.

Honestly, this should be possible without so many corner cases. The implementation of Java Generics is so messed up that not understanding them probably indicates you know how to allocate your time wisely more than that you are not an expert in a technical area! :-)

Given two interfaces, I want to declare a variable that holds a reference to any object of a class that implements both interfaces

Unfortunately you can't do that. You can do so for a parameter in a generic method, like this:

public static <T extends Foo & Bar> void someMethod(T value) {
    Foo x = value;
    Bar y = value;
}

... or you could do likewise for an instance variable in a generic class:

class Test<T extends Foo & Bar> {

    private T value;

    public Test(T value) {
        this.value = value;
    }
}

... but you can't declare a variable which needs to just satisfy both of those constraints.

(Note the syntax here for two constraints - it's & rather than a comma.)

You could just cast the object. You could do something like this:

Humpty foobar = new Humpty();
((Foo)foobar).foo();
((Bar)foobar).bar();

You can create an interface hierarchy, but that really only applies to related interfaces. This will not work if Bar and Foo are completely unrelated.

interface Foo { void foo(); }
interface Bar extends Foo { void bar(); } //implementations of Bar will need to implement both foo() and bar()

Humpty and Dumpty only need to implement Bar, then you can create an object :

<? extends Foo> foobar = new Humpty();

This is not pretty, but it works, it is safe (modulo null references), and it does not require suppressing any warnings. Jon Skeet was actually headed towards the right direction, but stopped at the middle:

/**
 * Existentially quantify over things that implement Foo and Bar.
 * 
 *   FooBar = \exists (T extends Foo & Bar). T
 */
interface FooBar extends Foo, Bar {}

/**
 * Universally quantify over things that implement Foo and Bar.
 * 
 *   \forall (T extends Foo & Bar). T --> WrappedFooBar<T>
 *   \forall (T extends Foo & Bar). WrappedFooBar<T> --> FooBar
 */
class WrappedFooBar<T extends Foo & Bar> implements FooBar {
  private T wrapped;
  public WrappedFooBar(T wrapped) { this.wrapped = wrapped; }
  public void foo() { this.wrapped.foo(); }
  public void bar() { this.wrapped.bar(); }
}

public class Program {
  public static void main(String[] args) {
    Random random = new Random();
    FooBar foobar;
    if (random.nextBoolean())
      foobar = new WrappedFooBar(new Humpty());
    else
      foobar = new WrappedFooBar(new Dumpty());
    foobar.foo();
    foobar.bar();
  }
}

The main caveat is that this solution obviously does not scale: one could potentially have to write a huge amount of wrappers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM