4

I followed this pattern to implement method chaining with subclasses in Java. The goal is that I have a method on a superclass, but can assign the subclass, like:

interface Screen {
    <T extends Screen> T setBrightness(int value);
    <T extends Screen> T setContrast(int value);
}

class CrtScreen implements Screen {

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Screen> T setBrightness(int value) {
        // ...
        return (T) this;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Screen> T setContrast(int value) {
        // ...
        return (T) this;
    }
}

class ColorCrt extends CrtScreen { /* ... */ }

public static void main(String[] args) {
    ColorCrt cc = new ColorCrt().setBrightness(50).setContrast(50);
}

Now I also have container objects that I want to add my objects to, like:

class Stuff {
    Stuff add(Screen s) {
        // ...
        return this;
    }

    Stuff add(String s) {
        // ...
        return this;
    }

    Stuff add(int i) {
        // ...
        return this;
    }
}

public static void main(String[] args) {
    new Stuff().add(new ColorCrt().setBrightness(50).setContrast(50)).add(25).add("...");
}

This now does no longer work in Java 8, giving me The method add(Screen) is ambiguous for the type Stuff. I understand the reason as explained here. I only see two options at the moment:

  • I don’t use <T extends, but just Screen setBrightness(value). It takes me the option to assign my implementing class to an according variable, and I have to cast it when I want to execute an implementation-specific method.

  • I have to add casts or type parameters in my method chaining. This is very ugly to read and hard to fix for large constructs (many levels of boxing) that have to be ported from Java 7.

Is there a way to implement method chaining in Java 8 so that I still have both features? If not, can one of the both approaches be considered more intended?

Eran
  • 387,369
  • 54
  • 702
  • 768
Matthias Ronge
  • 9,403
  • 7
  • 47
  • 63
  • 1
    What about `class Screen {`? – Joop Eggen Oct 10 '17 at 10:12
  • I don't quite understand why `add(Screen)` should be ambiguous. – lexicore Oct 10 '17 at 11:04
  • @lexicore … because a subclass of `String` (assume `String` would not be `final`) might implement the `interface Screen`. At compile time, the compile does not know if the object at runtime might be a subclass that does so. (The compiler does not care about that `String` actually cannot be subclassed because it is `final`.) See here: https://stackoverflow.com/q/28466925/1503237 – Matthias Ronge Oct 11 '17 at 06:49
  • 1
    Given that Java supports covariant return types, I see no reason to use generics at all. `Screen` can declare `Screen setBrightness(int)`, and `CrtScreen` can implement/override that method as `CrtScreen setBrightness(int)`. Your original implementation is not and was never type safe. – Mike Strobel Oct 11 '17 at 07:26

2 Answers2

0

In your code:

class CrtScreen implements Screen {

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Screen> T setBrightness(int value) {
        // ...
        return (T) this;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Screen> T setContrast(int value) {
        // ...
        return (T) this;
    }
}

Your member functions setContrast and setBrightness don't need to be generic, and if it is generic, you need to specify what the type is (at the call site). I've used this approach that rather makes the interface generic, which means the methods need'nt be:

interface Screen<T extends Screen> {
    T setBrightness(int value);
    T setContrast(int value);
}

class CrtScreen implements Screen<CrtScreen> {
    @SuppressWarnings("unchecked")
    @Override
    public CrtScreen setBrightness(int value) {
        // ...
        return this;
    }

    @SuppressWarnings("unchecked")
    @Override
    public CrtScreen setContrast(int value) {
        // ...
        return this;
    }
}

If you want to proceed doing it your way, you would need to specify which type applies to the generic (see below):

public static void main(String[] args) {
    new Stuff()
     .add(new ColorCrt().<ColorCrt>setBrightness(50))
     .add(25).add("...");
}
Werner Erasmus
  • 3,988
  • 17
  • 31
  • This is exactly what I described. Your first recommendation does not work with assignments like `ColorCrt cc = new ColorCrt().setBrightness(50).setContrast(50);`—error is *Type mismatch: cannot convert from CrtScreen to ColorCrt*. The second approach works, but is ugly to write. What I am looking for is a solution which solves both, allowing assignments of subclasses *and* method chaining *without* the need to specify the type everywhere. Maybe this requires a totally different approach. – Matthias Ronge Oct 11 '17 at 06:59
  • CrtScreen is a base of ColorCrt, and therefore Color... is convertible (polymorphically compatible with) to CrtScreen, but not visa versa - this is purposefully so. You can do the assignment when converting to base, but not derived. In both cases they are polymorphically compatible with Screen (base of all), which is what you've wanted. I'm not sure what you want to do. Perhaps you should post more code, as in my mind the cost I've posted does enough – Werner Erasmus Oct 11 '17 at 07:14
  • @Paramaeleon, apart from that, I think you don't need generics at all in this case. Your base should give you all the functionality required via its interface, otherwise you misunderstand polymorphism. – Werner Erasmus Oct 11 '17 at 07:17
  • You are using a raw type `Screen` in the bound of `T` in the declaration of `Screen`. – newacct Oct 14 '17 at 16:15
0

Finally I found a solution that works for me by making the interface an abstract class. This seems to be specific enough to satisfy the Java 8 compiler.

Proof of concept:

static abstract class Screen {
    abstract <T extends Screen> T setBrightness(int value);
    abstract <T extends Screen> T setContrast(int value);
}

static class CrtScreen extends Screen {

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Screen> T setBrightness(int value) {
        // ...
        return (T) this;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends Screen> T setContrast(int value) {
        // ...
        return (T) this;
    }
}

static class ColorCrt extends CrtScreen { /* ... */ }

static class Stuff {
    Stuff add(Screen s) {
        return this;
    }

    Stuff add(String s) {
        return this;
    }

    Stuff add(int i) {
        return this;
    }
}

public static void main(String[] args) {
    ColorCrt cc = new ColorCrt().setBrightness(50).setContrast(50);
    new Stuff().add(new ColorCrt().setBrightness(50).setContrast(50)).add(25).add("...");
}
Matthias Ronge
  • 9,403
  • 7
  • 47
  • 63
  • 2
    This is a broken generic construct. Just consider `ColorCrt cc = new CrtScreen().setBrightness(0);`, which now gets compiled without any errors, but obviously will fail at runtime. That’s why you get these *unchecked* warnings you are suppressing. – Holger Oct 11 '17 at 10:15
  • Why not rely on covariant return types? Look at how `StringBuilder` implements `Appendable`, for example. – Mike Strobel Oct 11 '17 at 11:32