Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics 2-way reference

Tags:

java

generics

I'm attempting to create a generics relationship in java between two classes which contain each other. The objects essentially form an alernating layer tree. So far, the closest SO issue i found was this: Java generics of generics of, which was close and somewhat helpful for my issue, but still different enough that I'd like additional guidance. Here's the situation or rather, what I'd like it to be:

abstract class Group<I extends Item<Group<I>>>{
     private List<I> items;
     public List<I> getItems(){...}
     public void setItems(List<I> items){...}
}

abstract class Item<G extends Group<Item<G>>>{
     private List<G> subGroups;
     public List<G> getSubGroups(){...}
     public void setSubGroups(List<G> subGroups){...}
}

Beyond the getters and setters, there are aspects of the class that make them notably different from each other, but the inclusion should follow like that. The reason behind this is that I want to enforce that if I have implementing classes, they have to behave like this:

class AGroup extends Group<AItem>{...} //works
class AItem extends Item<AGroup>{...} //works
class BGroup extends Group<BItem>{...} //works
class BItem extends Item<BGroup>{...} //works
class MixedGroup extends Group<AItem>{...} //fails since AItem does
                                           //not extend Item<MixedGroup>

So far, the compiler is fine with

abstract class Group<I extends Item<Group<I>>>{
     private List<I> items;
     public List<I> getItems(){...}
     public void setItems(List<I> items){...}
}

abstract class Item<G extends Group>{ //raw types warning
     private List<G> subGroups;
     public List<G> getSubGroups(){...}
     public void setSubGroups(List<G> subGroups){...}
}

This mostly covers what I'm looking for since I can know that I can get a group's items' child groups and get the same kind of group back. But the compiler don't know that if I get an item's groups' items I'll get the same kind of item back (for instance, the item could be an orphan). Also, the raw types warning always makes me feel like I'm doing something wrong. Also also, if there's a better way of enforcing this kind of class binding, I'd be interested to hear it.

like image 863
Jocken Avatar asked Jan 10 '14 15:01

Jocken


2 Answers

You could try the following:

abstract class Group<I extends Item<I, G>, G extends Group<I, G>> {
     private List<I> items;
     public List<I> getItems() { return null; }
     public void setItems(List<I> items) { }
}

abstract class Item<I extends Item<I, G>, G extends Group<I, G>> {
     private List<G> subGroups;
     public List<G> getSubGroups() { return null; }
     public void setSubGroups(List<G> subGroups) { }
}

class AGroup extends Group<AItem, AGroup> { }         // works
class AItem extends Item<AItem, AGroup> { }           // works
class BGroup extends Group<BItem, BGroup> { }         // works
class BItem extends Item<BItem, BGroup> { }           // works
class MixedGroup extends Group<AItem, MixedGroup> { } // fails

(ideone)

The reason for using two type parameters is that since each type is parameterized with a type that does the opposite, each needs to keep track of both the other's type and its own "self type".

This can be generalized to an arbitrary number of "participating" types:

// one type
interface SelfParameterized<T extends SelfParameterized<T>> { }

// two types
interface SelfParameterizedPairA<
        A extends SelfParameterizedPairA<A, B>,
        B extends SelfParameterizedPairB<A, B>
> { }
interface SelfParameterizedPairB<
        A extends SelfParameterizedPairA<A, B>,
        B extends SelfParameterizedPairB<A, B>
> { }

// three types
interface SelfParameterizedTrioA<
        A extends SelfParameterizedTrioA<A, B, C>,
        B extends SelfParameterizedTrioB<A, B, C>,
        C extends SelfParameterizedTrioC<A, B, C>
> { }
interface SelfParameterizedTrioB<
        A extends SelfParameterizedTrioA<A, B, C>,
        B extends SelfParameterizedTrioB<A, B, C>,
        C extends SelfParameterizedTrioC<A, B, C>
> { }
interface SelfParameterizedTrioC<
        A extends SelfParameterizedTrioA<A, B, C>,
        B extends SelfParameterizedTrioB<A, B, C>,
        C extends SelfParameterizedTrioC<A, B, C>
> { }

However the use and implementation of these kinds of recursive generics tend to be overly complicated and seldom very beneficial (I describe one use case on this post: Is there a way to refer to the current type with a type variable?). It might be better to step back and reevaluate your design to see whether this two-way generic relationship is really necessary. More often than not I've found that recursive generics arise out of a type that's trying to do too much and whose responsibilities should be decoupled into multiple simpler types.

like image 63
Paul Bellora Avatar answered Sep 28 '22 08:09

Paul Bellora


If you'd define the groups and items like this, it should get you a step farther:

abstract class Group<I extends Item<? extends Group<I>>>

abstract class Item<G extends Group<? extends Item<G>>>

This should result in the following:

class AGroup extends Group<AItem>{}
class AItem extends Item<AGroup>{}

//doesn't work since the item could only be added to MixedGroup instances
//but MixedGroup only accepts AItem instances
class MixedItem extends Item<MixedGroup>{} 

//works since the item might be added to any AGroup
class MixedItem2 extends Item<AGroup>{} 

//works, since AItem can be added to any AGroup (and MixedGroup is a Subclass)
class MixedGroup extends Group<AItem> {} 
like image 26
Thomas Avatar answered Sep 28 '22 09:09

Thomas