1

I have an inheritance issue where it is easier to explain the problem in code rather than text so given the following inheritance design for the Parents:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Parent<T extends Offspring> {

    @OneToMany(mappedBy = "parent", targetEntity = Offspring.class)
    private Set<T> offsprings;

    // Getters/Setters relevant for all sub types
}

@Entity
public class Cat extends Parent<Kitten> {

}

@Entity
public class Dog extends Parent<Puppy> {

}

And the Offspring:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Offspring<T extends Parent> {
    @ManyToOne(targetEntity = Parent.class)
    @JoinColumn(name = "parent_id", referencedColumnName = "id")
    private T parent;

    // Getters/Setters relevant for all sub types
}

@Entity
public class Kitten extends Offspring<Cat> {

}

@Entity
public class Puppy extends Offspring<Dog> {

}

Say I want to get a list of parents: List<Parent> parents = parentService.getAll();

The parent list, because it is now a raw type, has no understanding that the parents..get(0).getOffsprings() should return a type Set<Offspring>. It instead, because of the raw typing, thinks it returns Set.

I might want to do this because a Person might have a list of all their Parent's like:

@Entity
public class Person {
    @OneToMany(mappedBy = 'person')
    private Set<Parent> parents;
}

And I might want to get all of the offspring of all the parents owned by the person: person.getParents().get(0).getOffsprings() but this then returns a type Set because it is a raw type.

Clearly I am going down the wrong route with this so:

The goals are:

  • To be able to have an object of Cat and only be able to put Kitten offsprings into its set. Or an object of Puppy and only be able to set Dog as its parent.
  • To be able to query for all Parents and for it to keep its typing so that only Offspring can be put into the object.

I have gone through many different permutations of how this should be done. One option was for instance the Cat object to hold its own container of Kitten offspring:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Parent {
    @OneToMany(mappedBy = "parent")
    private Set<Offspring> offsprings;
}

@Entity
public class Cat extends Parent {

    private Set<Cat> offsprings;
}

But this doesn't work because the Cat class is unable to override the getter/setter for the parent class Parent. As well as causing Hibernate issues because it now things that the database table Kitten has a link to Cat.

Disclaimer

I might be missing something obvious or going about this the whole wrong way so I am open to suggestions on the best way to handle this. Also my inheritance strategy might be causing the issues so all advice is welcome and I am happy to provide more if needed.

Zoe
  • 27,060
  • 21
  • 118
  • 148
matthew.kempson
  • 1,014
  • 1
  • 12
  • 23
  • 4
    My alarm bells are firing: why declare `public class Offspring` then declare `public class Animal ` ? – Lai Aug 22 '18 at 09:23
  • The reason I did that is because both classes contain a link back to each other. I.e. `Offspring` has a field for their parent `Animal` but also `Animal` has a field for their children `Offspring`. Again this might be wrong to do this. See point one of the goals @Lai – matthew.kempson Aug 22 '18 at 09:26
  • 2
    Another problem with your design that you've missed is that it's perfectly legal to have `class Cat extends Animal` even though a puppy is an `Offspring` – Michael Aug 22 '18 at 09:29
  • @Michael Okay that makes sense. Do you have any advice on how to better define these classes to avoid that? – matthew.kempson Aug 22 '18 at 10:04
  • 1
    Is the JPA part really relevant? It seems like a purely design question. – user1803551 Aug 22 '18 at 10:38
  • 1
    @user1803551 Perhaps it is not relevant, I left it in purely because it is designed around a database structure which _might_ be wrong so I am open to opinions on that also – matthew.kempson Aug 22 '18 at 10:47
  • 1
    Alright, that's fine. Let's flesh out the details. An "animal" can be either of type `Animal` *or* of type `Offspring`? An `Animal` contains a list of `Offspring`s, but it doesn't have a `parent` itself, and an `Offspring` only has a `parent` but no `Offspring`s of its own? If so we're dealing, with a strict parent-children design. Do they share any traits? – user1803551 Aug 22 '18 at 10:51
  • 1
    @user1803551 Your first statement is not correct but I can see why you might think that because of the table names i've used in the example. An `Animal` is *only* an `Animal` and same for `Offspring`. Your second point is correct so it does sound like it is a parent-children design. `Animal` and `Offspring` do not share any traits. If it makes it easier to think, call `Animal`: `List` and `Offspring`: `ListItem`. – matthew.kempson Aug 22 '18 at 10:57
  • 1
    In the first statement I meant that what we would call an animal - cat, dog, kitten... can only be represented by one of the types `Animal` or `Offspring`. I'm asking because a kitten seems to me like both (it's also an animal). So you might want to call them `Parent` and `Offspring`. it's just a naming thing, as you said. – user1803551 Aug 22 '18 at 11:01
  • 1
    @user1803551 I'll update it to `Parent` and `Offspring` and I understand what you mean now so yep you are correct. – matthew.kempson Aug 22 '18 at 11:16

2 Answers2

2

Looks like what you're looking for are abstract classes (and methods). These allow you to specify a behavior and leave the implementation to its subclass. I would make Parent and Offspring abstract classes and then inherit from them while specifying the exact return types:

public abstract class Parent {

    public abstract Set<? extends Offspring> getOffsprings();
}

public abstract class Offspring {

    public abstract Parent getParent();
}

public class Cat extends Parent {

    private Set<Kitten> offsprings;

    @Override
    public Set<Kitten> getOffsprings() {
        return offsprings;
    }
}

public class Kitten extends Offspring {

    private Cat parent;

    @Override
    public Cat getParent() {
        return parent;
    }
}

public class Dog extends Parent {

    private Set<Puppy> offsprings;

    @Override
    public Set<Puppy> getOffsprings() {
        return offsprings;
    }
}

public class Puppy extends Offspring {

    private Dog parent;

    @Override
    public Dog getParent() {
        return parent;
    }
}

While the Offspring class dictates a method that returns a Parent, its subclasses can return a subtype of it like Cat or Dog. I believe it's called Liskov substitution principle.

When it comes to Animal, it dictates a method that returns Set<? extends Offspring>, which means "a specific single type that extends Offspring" (in contrast to Set<Offspring>, which means "any Offspring" and will give you a compilation error). This allows subtypes to return a Set of their specific Offspring, like Set<Kitten> or Set<Puppy>.

Now, when you have a List<Parent> parents = parentService.getAll(); each Parent will return a Set<? extends Offspring> via parent.getOffsprings().

user1803551
  • 12,965
  • 5
  • 47
  • 74
  • Good spot with the typo. Please give me some time to try this solution out but it does look like what I want. Thank you – matthew.kempson Aug 22 '18 at 11:26
  • are you able to provide setters and possibly JPA annotations in your example for completeness? – matthew.kempson Aug 22 '18 at 11:51
  • @matthew.kempson Well, you code just says "// Getters/Setters relevant for all sub types" so I don't know which you need here. – user1803551 Aug 22 '18 at 12:15
  • @matthew.kempson Please note an important [edit](https://stackoverflow.com/posts/51965727/revisions) to the answer: while I wrote *"... the `Offspring` class dictates a method that returns a `Parent`, its subclasses can return a subtype of it like `Cat` or `Dog`."* I didn't do this in the code by mistake and returned `Parent` instead (so no LSP). It is fixed now. – user1803551 Aug 22 '18 at 12:26
  • Apologies I have worked out the setters myself. For the JPA would having `@ManyToOne(targetEntity = Parent.class) @JoinColumn(name = "parent_id", referencedColumnName = "id")` over the `getParent` in `Offspring` provide the same effect as it did previously – matthew.kempson Aug 22 '18 at 13:09
  • 1
    @matthew.kempson It depends what the JPA keys are. Your classes are incomplete. You have some hidden "id". You should also decide which of the entities is the owner of the relationship (because they contain references to each other one has to own the other). – user1803551 Aug 22 '18 at 15:53
  • I have realised that now we have this inheritance structure I can put the links back onto the `Kitten` and `Puppy` tables rather than have it on the `Offspring` table now. Thank you for your help it has been really really useful, I've spent far to many days going backwards and forwards trying to figure this mess out. I'm going to mark this as the solution. – matthew.kempson Aug 22 '18 at 16:39
  • @matthew.kempson You're welcome. "*I've spent far to many days going backwards and forwards trying to figure this mess out.*" Yeah, that's how you learn. I did that too. Also, you can have a look at the answers [here](https://stackoverflow.com/questions/11938253/whats-the-difference-between-joincolumn-and-mappedby-when-using-a-jpa-onetoma) to compare notes. – user1803551 Aug 22 '18 at 16:44
0

There is a technique to pass the class itself as a generic parameter:

class MyDerived extends MyBase<MyDerived> { ... }

class MyBase<T> {
    T ...
}

In Java SE enum classes are implemented as such, as extends Enum<TheEnumClass>.

Here not all parameter types are parametrized themselves; so do:

public class Parent<P, T extends Offspring<P>> {
    public Set<T> getOffsprings();

public class Offspring<O, T extends Parent<O>> {

public class Cat extends Parent<Cat, Kitten> {

public class Kitten extends Offspring<Kitten, Cat> {

Cat cat = ...;
cat.getOffsprings.add(new Kitten()); // Okay.
cat.getOffsprings.add(new Puppy()); // ERROR.

Kitten kitty = ...
cat = kitty.getParent();

However I am wondering whether such fine grained inheritance is needed.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • I think it would help if you'd show some implementations. – user1803551 Aug 22 '18 at 11:43
  • @user1803551 Thanks, Those classes are for database entities with as O/R mapping: JPA annotations. So much more than the linking getters/setters there is not. But the usage now is type safe on the subclass level. – Joop Eggen Aug 22 '18 at 12:02
  • 1
    I have marked @user1803551 answer as the solution because it was exactly what I was looking for. Thank you for your input though – matthew.kempson Aug 22 '18 at 16:43
  • Feedback very welcome. Not using too over-engineered type parametrisation is good. – Joop Eggen Aug 23 '18 at 08:42