3

Let's have class A with a method useful for chaining:

class A {
  A foo() {
    // do stuff
    return this;
  }
}

You could now do a.foo().foo().foo() (think: builder pattern). Let class B extend it:

class B extends A {
}

B b;

Now invoking b.foo() returns actual type B, but declared A. Therefore I cannot write:

B other = b.foo();

I would have to write either:

B other = (B) b.foo();

Or override foo() in B:

class B extends A {
  @Override
  B foo() {
    super.foo();
    return this;
  }
}

Is there a nice way to do this?

vektor
  • 3,312
  • 8
  • 41
  • 71
  • 1
    I'd have said that the overwrite is the nicest *possible* way to do this in Java. But obviously, "niceness" is a subjective criterion. – Stephen C Nov 07 '14 at 11:56
  • 1
    I would suggest you can see this answer http://stackoverflow.com/a/1070556/2274724 – Chirag Jain Nov 07 '14 at 11:59
  • possible duplicate of [Method chaining + inheritance don’t play well together? (Java)](http://stackoverflow.com/questions/1069528/method-chaining-inheritance-don-t-play-well-together-java) – Joe Nov 07 '14 at 13:45

4 Answers4

1

Did this by implementing an additional generic method as(type) to the superclass. This helps to have a nicer cast for fluent interfaces.

class A {
    public A foo() {
        return this;
    }
    @SuppressWarnings("unchecked")
    public <T extends A> T as(Class<T> clazz) {
        return (T) this;
    }
}

class B extends A  {}

So you can write a.foo().as(B.class).methodOfB(..). And you do not need to reimplement foo() in all subclasses.

Cfx
  • 2,272
  • 2
  • 15
  • 21
  • This is somewhat dirty... But still, interesting way of doing it. – Squeazer Nov 07 '14 at 11:59
  • You may declare the cast method like `public T $() { return (T) this;}` and then you can do it really dirty `a.foo().$().methodOfB()`. – Cfx Nov 07 '14 at 14:01
1

Sorry, but no you already listed the nicest way:

B other = (B) b.foo();

That would be the solution for any developer, who uses classes A and B.

Overwriting is for you, so that other developers could simply write B other = b.foo();

But there is actually no other way for the compiler to know, that

  • B is sort of an A
  • B is compatible, so that you won't use any information when instantiating an A but putting it in a B

The last one is the reason why you have to cast explicitly. Example:

 int myInt = 2;
 short myShort = (short) myInt; // correct

although this works, the compiler needs you to be explicit, because in many cases (when myInt is big) you will loose precision / information when casting to short.

If this one would work:

short myShort = myInt;   // wrong

Then the compiler would make an assumption on its own. But only the developer can know, if myInt will ever have a value that is bigger than a short can hold.

GameDroids
  • 5,584
  • 6
  • 40
  • 59
1

You can declare class A to have a generic type parameter of its implementing class and foo() could return this dynamic type:

class A<T extends A<T>> {
    @SuppressWarnings("unchecked")
    T foo() {
        // do stuff
        return (T) this;
    }
}

class B extends A<B> {
    B bar() {
        // do other stuff
        return this;
    }
}

After this, the following is valid:

B b = new B();
b.foo().bar();
icza
  • 389,944
  • 63
  • 907
  • 827
  • 1
    This is the "nicest" way. The jdk Enum type does this declaration and thanks to you I finally understood why. – Cfx Nov 07 '14 at 12:15
  • Maybe you should declare B as `class B> extends A`, too. Then you can go down the hierarchy with more subclasses of B. – Cfx Nov 07 '14 at 12:28
  • @Cfx You can't do that. If you pass `T` to `A` like `A`, `foo().bar()` -> `bar()` method is unknown on type `A` because type parameter was not `B` but `T` which extends `A`. Try it. `bar()` is declared in class `B`, so that has to be passed to `A` if we want `bar()` to call on the return value of `foo()`. – icza Nov 07 '14 at 13:02
  • Well, I meant to declare B like `class B> extends A` and C like `class C extends B` (more classes inbetween possible). The drawback is (and in that you are right) you can not get an instance of B by calling `b.foo()`. But you can have a C `new C().foo().bar().methodOfC()` (Because C is a B). So you can only widen the scope to the deepest class, not limit it back to B. – Cfx Nov 07 '14 at 14:28
0

I think the nice way to do this is to keep the reference as type A, since this is the least specific class that has the functionality that you require:

A other = b.foo();

You should only cast to type B if B contains some required functionality:

B another = (B)A;
another.somethingElse();

This is the same logic that makes:

List<String> myList = new ArrayList<>();

more desirable than:

ArrayList<String> myList = new ArrayList<>();
StuPointerException
  • 7,117
  • 5
  • 29
  • 54