-1

I really love using initializers to make constructing and configuring classes more understandable. Basically I prefer this:

new ClassObj().StartingFrom(1).EndingAt(5).IncrementBy(1).Backwards();

Rather than this:

new ClassObj(1,5,1,false);

This is a bit of a pain in Java when mixed with inheritance, as returning types default to as generic as possible. I've found a workable solution using self-referential inheritance (Java: returning subclass in superclass method signature), but I'm having some problems with it.

My issue is that the Parent class implements Iterable, but the generic type parameter gets lost, so the for-each loop wants to return an Object instead of a File.

Here's an SSCCE showing the behavior:

public class SSCCE {
  private static abstract class Sentence<T extends Sentence<T>> implements Iterable<String> {
    protected LinkedList<String> Words = new LinkedList<>();

    abstract T self();

    public T Say(String word) {
      Words.add(word);
      return self();
    }

    @Override
    public Iterator<String> iterator () {
      return Words.iterator();
    }
  }

  static class QuietSentence extends Sentence<QuietSentence> {
    public QuietSentence Whisper(String word) {
      Say(word.toLowerCase());
      return this;
    }

    @Override
    QuietSentence self() {
      return this;
    }
  }

  static class LoudSentence extends Sentence<LoudSentence> {
    public LoudSentence Shout(String word) {
      return Say(word.toUpperCase());
    }

    @Override
    LoudSentence self() {
      return this;
    }
  }

  static void PrintWords(Sentence words) {
    for(Object obj : words) {
        // I'd really like to avoid this cast
        String word = (String)obj;
        System.out.println(new StringBuilder(word).append(": ").append(word.length())
                  .toString());
    }
  }

  public static void main (String[] args) {
    QuietSentence peaceful_words = new QuietSentence().Say("Hello").Whisper("World");
    PrintWords(peaceful_words);

    LoudSentence noisy_words = new LoudSentence().Say("Hello").Shout("World");
    PrintWords(noisy_words);
  }
}

What's going on, and how can I fix it?

Community
  • 1
  • 1
Morgen
  • 1,010
  • 1
  • 11
  • 15
  • you know that `(T) this` is an unsafe cast, right? – newacct Nov 13 '14 at 00:32
  • Normally it would be. In this case `T` is known to be either of type `Sentence` or one of its subclasses, so it's safe make the cast. – Morgen Dec 18 '14 at 22:49
  • Nope. It is not safe. yes, `T` is known to be a subtype of `Sentence`, but `Sentence` is not known to be a subtype of `T`. – newacct Dec 19 '14 at 00:54
  • I'd be interested looking at an alternative solution to the base problem, if you've found something better. For the time being, the limited visibility and idiomatic usage make this a pretty safe 'unsafe' cast. – Morgen Jan 06 '15 at 23:27
  • You can get rid of `self()` and `QuietSentence` can just `return this;` instead of `return self();`. – newacct Jan 07 '15 at 03:21
  • @newacct Sorry about the confusion, I had trimmed the SSCCE a bit too far and it did not showcase the desired behavior the way it should have. Replacing `self()` with `this` causes a break when chaining methods from the parent class with methods from the children. The self-referential type variable is to make sure that the return value is of the correct type. True, this will break if you create a class like `class SomeClass extends Sentennce`, but it's the best solution I've found thus far. – Morgen Jan 07 '15 at 19:31
  • I don't think you should have a bound like `class Sentence>`. Instead, in any generic method that requires the chaining on a `T`, that generic method should have a bound `>`. – newacct Jan 07 '15 at 20:10
  • @newacct Can you modify the SSCCE to show me what you mean? From what I understood from your comment, it wouldn't work because the methods would have nothing differentiating them besides their return values - which aren't part of the method signature. With the bound generic at the class level, the class objects carry that information so the returned value can retain its type sufficiently to allow chaining on the returned value. – Morgen Jan 07 '15 at 23:36
  • In your case, you are directly using `QuietSentence` or `LoudSentence`, classes which are known to extend `Sentence` with themselves as type parameter. Therefore, nothing needs to be done to make chaining work. Only in a case where the thing to be chained has a type parameter type (which would usually happen in a generic method), do you not know this, in which case you would bound that type parameter to add this restriction. – newacct Jan 07 '15 at 23:48
  • @newacct The issue is easiest to see on the `Say` method of `Sentence`. If the class does not have the self-referential bound generic, when `Say` is called Java no longer is able to implicitly determine that the returned value is actually an instance of a derived class and you lose access to the methods of its actual class. As the programmer you can tell it, but the casting gets really ugly, really quickly. You can return `this` from any of the methods of the subclass, it's only in the parent class that these type issues arise. – Morgen Jan 08 '15 at 00:08
  • 1
    Oh I see that you moved the `self()` call to `Sentence.Say()`. If you must have it be an instance method, the only absolutely safe way to do it would be to make `self()` abstract: `abstract T self();`. And every subclass is responsible for overriding it with a method that returns `T`. Your `QuietSentence` and `LoudSentence` classes would just `return this`, and if somebody does `SomeClass extends Sentennce`, then they will be responsible for implementing a method that returns some instance of `OtherClass`. In all cases, it is still type-safe. – newacct Jan 08 '15 at 00:44
  • Alternately, you could make `Say()` a static generic method, with a `>` type parameter, and it would take a `T` as an argument. Of course, this would change the syntax to no longer be chaining. – newacct Jan 08 '15 at 00:44
  • @newacct I like that suggestion. I saw the abstract `self` option discussed while I was initially researching the chaining issue. The discussion mostly covered the pro/con in relation to DRY, but the type safety benefits of delegating this to the subclass were not raised at the time. I'll play with it and update the SSCCE if it works out the way it should. – Morgen Jan 08 '15 at 00:49

1 Answers1

1

So it turns out the problem was that I was not specifying the type correctly. A minimal fix in the PrintWords function solves the problem.

static void PrintWords(Sentence<? extends Sentence> words) {
    for(String word : words) {
        System.out.println(new StringBuilder(word).append(": ").append(word.length())
                .toString());
    }
}
Morgen
  • 1,010
  • 1
  • 11
  • 15
  • I voted u up, I was writing you missed the inheritance – eduyayo Nov 11 '14 at 22:44
  • `Sentence>` would be more proper. Otherwise you've got a raw type in there still. But yes this is basically the solution, because using a raw `Sentence` causes the entire class to be erased (including the type of `Iterable`). – Radiodef Nov 11 '14 at 22:52
  • In this case, I believe that ` extends Sentence>` is more correct because, it retains type safety. The more specific generic restricts references to `Sentence` and its subtypes. – Morgen Dec 18 '14 at 22:47