1

I am playing with using generics to be able to restrict sub-types in a composite hierarchy, so that I could force different types at different layers of a hierarchy.

For example having a (Business:Division:Department:Group:Person) hierarchy, where the composite nodes at each level would only accept child nodes of the proper type (the next lower level in the hierarchy). So I would construct the levels in the composite using generics at each level type instantiated to accept only nodes from the next lower level.

But I get an error with the generics, and am not sure if it indicates a design error, or just something that java Generics can't do for me. The idea seems valid, I just want to restrict the types accepted by the add() methods to only accept a sub-type from the specific next level in the hierarchy to force the levels of structure. Since I am just lowering the upper-bound on the type at each stage, all messages sent to the list would still be valid.

Top level node in the typical GOF-Composite pattern:

   public abstract class Business { ... }

Composite node hierarchy top level:

  public abstract class Group extends Business {

    protected List<Business> subs;      // composite links
    public Group add(Business comp) {
        subs.add(comp);
        return this;
    }

Subsequent levels in the composite hierarchy:

 public class Area<T extends Business> extends Group {

    protected List<Business> subs;      // composite links

    public Area(String title){
        super(title);
        subs = new ArrayList<Business>();
    }

    @Override
    public Group add(T comp) {    // ** Error Here **
        super.add(comp);
        return this;
    }

The Error is:

 Multiple markers at this line
    - The method add(T) of type Area<T> must override a superclass method

- Name clash: The method add(T) of type Area has the same erasure as add(Business) of type Group but does not override it

I tried a variation where I gave a similar type to the Group::add() method so they would have the same type signatures,

public Group add(T comp) { ... }

But that fails similarly:

 Multiple markers at this line

- The method add(T) of type Area must override a superclass method - overrides labs.composite.company.Group.add - Name clash: The method add(T) of type Area has the same erasure as add(T) of type Group but does not override it

Am I missing something here??? TIA

PS: Actually I think that this use of generics does not do exactly what I want anyway (even if it did work!), since i want to not only change the upper-bound at each level, but require a specific single level in the type hierarchy, not any covariant argument type. I really would want something like, "for any type T which is a sub-type of Composite, accept only that type of object in the add() method", I think that instead the generics says "accept any object as an argument which is a subtype of Composite". I suppose that is impossible since arguments in Java are covariant, and LSP will always allow sub-types to be used.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
guthrie
  • 4,529
  • 4
  • 26
  • 31

2 Answers2

3

If your question is why the error, the reason is simple: T in Area can be an infinitude of Business subclasses, while Business in Group is a very specific class. Both methods are not the same. The compiler works mainly with full types while performing type checking (even if it also considers erasures for certain kind of warnings and special cases). That's the whole point of generics. Otherwise, it's better to revert to pre-generics code style (which is still supported).

If not, then I don't understand the question. But let me add a few more comments:

1) Inheritance describes a "is a kind of" relationship, not a "has" or "is composed of" relationship. In the code, you are saying that a Group is a kind of Business, and that an Area is a kind of Group. To me, it's much more natural to think that a Group belongs to a Business (i.e. a Business has Groups). Same thing with Groups and Areas. None of these two relationships are of inheritance, but of composition.

2) When in class Group<T extends Business> you define method add(T comp) and in class Area<T extends Business> you define method add(T comp), you say Group::add and Area:add have the same signature. That's not correct. The generic parameter T in Group is completely independent of the T in Area. Why? Assume that B1 and B2 are subclasses of Bussiness, but not of each other. Nobody can say that Area<B1>:add() and Group<B2>:add() have the same signature. In fact, the only case when this equivalence holds is when the generic argument is the same (i.e. Area<B1> and Group<B1>). Java can not consider the signatures equivalent when that equivalency holds only in a few particular cases (that are not otherwise described by the code).

3) GOF's Composite design pattern does not apply to this case because it does not represent hierarchical composition, but what we could call "unrestricted" composition. According to this pattern, aComposite can contain differentComponents, but these Components can be any kind of Composites, regardless of how high or low in a class hierarchy. What you want is differentComponents to be of the immediately lower class. No more, and no less.

I don't remember to have seen this case as a design pattern. Probably because it's too simple. See the last variant in next point 4).

4) Your code should probably take the following form:

public class Grouping<C,T> {
    C curr;
    List<T> subs;
    public C add(T comp) {
        this.subs.add(comp);
        return this.curr ;
    }
}

public class Business extends Grouping<Business,Group> {
    // Grouping::add does not need to be overriden
}
public class Group extends Grouping<Group,Area> {
    // Grouping::add does not need to be overriden
}

And so on. If you allow method add to return void instead of C, you can eliminate a generic parameter in all classes:

public class Grouping<T> {
    List<T> subs;
    public void add(T comp) {
        this.subs.add(comp);
    }
}

public class Business extends Grouping<Group> {
    // Grouping::add does not need to be overriden
}
public class Group extends Grouping<Area> {
    // Grouping::add does not need to be overriden
}

And if you want to make it really simple (but perhaps not as powerful):

public class Business {
    List<Group> subs;
    public Business add(Group comp) {
        this.subs.add(comp);
        return this ;
    }
}
public class Group {
    List<Area> subs;
    public Group add(Area comp) {
        this.subs.add(comp);
        return this ;
    }
}

This creates code duplication (method add in every class) that under more realistic scenarios could be much higher (you could also want methods count, list, retrieve, etc).

Mario Rossi
  • 7,651
  • 27
  • 37
  • Good answer, both explaining the compile error and pointing out that composition should be used instead of inheritance. Note that `return this` doesn't work in your first example, since `this` isn't a `C`. And looks like you forgot to remove it from your second example. – Paul Bellora Aug 16 '13 at 04:39
  • @Paul Bellora Thanks. Corrected both, even if the solution I could quickly give to the first point is not of my entire liking. How to solve it without traits? – Mario Rossi Aug 16 '13 at 05:08
  • There's no real "self-type" in Java. It can be emulated but usually shouldn't be - see my post [here](http://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable/7355094#7355094) for more info. For simplicity's sake the OP should opt to return `void` like you're saying (+1). – Paul Bellora Aug 16 '13 at 05:17
  • @Paul Bellora Checked your post. What's different from `return (C)this ;`? Just how obvious the unchecked conversion is? IMHO, in both cases there **is** one. – Mario Rossi Aug 16 '13 at 07:04
  • It does avoid an unchecked cast, but at the cost of verbosity. We know there's no difference under the hood, but to me it's having a semblance of a contract. But the pattern can still be abused anyway, so it really comes down to preference. – Paul Bellora Aug 16 '13 at 14:41
  • @Mario Thanks for the comments, I realize that I had bungled the original design and you are right about a better relationship. These comments helped me rethink it. – guthrie Aug 16 '13 at 16:26
  • @Paul Bellora I think I'll stick to well-documented and very obvious dirty unchecked conversions. But I'm sure you had a lot of fun (an insight into Java's type system) ellaborating that solution. Thanks,Paul. – Mario Rossi Aug 17 '13 at 05:11
1

Overriding is when you have the same signature, your signature is different so it is not a valid override. Your method signature should be.

public Group add(Business comp)

  1. Since the T has to extend business anyway I can't see why this is a problem, if it is just get rid of the override annotation.

  2. It's possible you may be confusing overriding with overloading, which is when the signatures differ but the name is the same.

  3. technically the @Override annotation has no functionality, it is only there to catch programmer error, so if it doesn't compile just get rid of it

aaronman
  • 18,343
  • 7
  • 63
  • 78
  • Thanks, but I understand the difference in overriding, and I want static type checking, so that one could only add a Person to a Group, a Group to Department, etc. Changing the signature as you suggest would allow any sub-composite to be added to any other. I want overloading where the signatures are not identical, but are type compatible. – guthrie Aug 16 '13 at 03:57
  • @guthrie then just get rid of the `@Override` annotation it has no actual functionality anyway, just to help out programmers when you are ***actually*** overriding the method – aaronman Aug 16 '13 at 03:59