6

I have the following Java generics question

I have the following generic class thay may be sketched as:

public class MyClass<T> {
    AnotherClass<T> another;
    OtherClass<T> other;
    ...
}

where ... represents code that is not relevant to the case.

For the class MyClass<T> is not as important which exact type T is (as of now) but for both:

  • AnotherClass<T>

  • OtherClass<T>

is absolutely crucial what the generic type is and decisions will be made at runtime in base of that.

Based on that, the type T is not completely arbitrary, it may be either an instance of a hierarchy of classes T_1 or a hierarchy of classes T_2.

As is defined the class, the type T is equivalent to Object but I know that is equivalent to either T_1 or T_2

There is not businnes relation between entities T_1 and T_2 therefore I'm not doing:

public interface BaseT { ... }
public class T_1 implements BaseT { ... }
public class T_2 implements BaseT { ... }

public class MyClass<T extends BaseT>

Clarification about why using a generic if they are unrelated:

I'm defining (trying to) a generic class for both because even they are unrelated explictly, there is a implicit relation because both T_1 and T_2 can and will appear associated to the entity represented in MyClass

T will be the same for MyClass, AnotherClass and OtherClass so that in a instance there will only be either T_1 or T_2 but never both at the same time.

My question is, which alternatives do I have here other than design an interface for MyClass and implement it for both T_1 and T_2?.

Can I achieve something like MyClass<T extends T_1 or T_2>?

Kind regards

Jorge Lavín
  • 937
  • 8
  • 22
  • 5
    why would you use a generic T for both classes if the actual types are not related? – Tim Sep 06 '18 at 07:48
  • 2
    What's wrong with defining it as `MyClass` or even `MyClass`? This will allow you to set the bounds of every type separately. – Robby Cornelissen Sep 06 '18 at 07:50
  • I didn't investigate further but you could check out this: https://stackoverflow.com/questions/36538133/can-i-use-java-annotations-to-define-compile-time-checks – Robert Kock Sep 06 '18 at 07:51
  • @TimCastelijns fair question, updated OP – Jorge Lavín Sep 06 '18 at 07:53
  • @RobbyCornelissen The entity represented in `MyClass` is a container for one and only one of them, not both at the same time, therefore `MyClass` is not a proper model as far as I can tell – Jorge Lavín Sep 06 '18 at 07:54
  • 2
    Oh, OK. So basically you're after something like `MyClass`? – Robby Cornelissen Sep 06 '18 at 07:55
  • 1
    @RobbyCornelissen exactly, this is the pseudocode that may represent it. Thanks for that, added to the OP – Jorge Lavín Sep 06 '18 at 07:58
  • 2
    That's called "union types", and I don't think Java has them (except in `catch` blocks for exceptions). You'd need to emulate it with some wrapper. – Thilo Sep 06 '18 at 08:00
  • @Thilo thanks for pointing it out, and giving the "union types" reference. I found the following article (https://blog.jooq.org/2016/02/16/an-ingenious-workaround-to-emulate-sum-types-in-java/) that exactly states that some work-around is needed in Java. Would you please post an answer so future visitors may read that? Thanks – Jorge Lavín Sep 06 '18 at 08:09
  • 1
    According me there is a design flaw here and you should not make runtime decisions based on the generic type specialization. Generics, templates and all type-inference languages mechanisms should not replace polymorphism – Carmine Ingaldi Sep 06 '18 at 08:18
  • 3
    If T_1 and T_2 are that disjunct that a common interface has no value, it might be that one should make wrapper classes around T_1 and T_2 that expose common functionality stored at the moment in MyClass, AnotherClass, OtherClass. Then have a common interface. I am afraid that currently there would be instanceofs in the classes. – Joop Eggen Sep 06 '18 at 08:52
  • @JoopEggen The current implementation has a design that is far from simple (that I'm using as a proxy of design goodness) and it has a lot of `instanceof` and `getClass().equals(expectedClass)` clauses. Can you please sketch such a wrapper in an answer? Thanks – Jorge Lavín Sep 06 '18 at 09:30
  • @JorgeLavín unfortunately I have no time for such an open speculative thing. At the end it might be best to duplicate the MyClass (without generics), as that might be better than adding hoops for the classes to jump through. And _than_ look whether something can be refactored. Advantage: you are starting from a working situation, with all design decisions resolved optimally. – Joop Eggen Sep 06 '18 at 09:41
  • you can always answer your own question btw with the resolution that you found... – Eugene Sep 06 '18 at 09:49
  • Too theorical for me, I need a concrete example of why we would need that, or a complete pattern description – pdem Sep 07 '18 at 09:00

2 Answers2

2

Probably, this is not exactly what your're looking for, but you might give it a try:

Create an abstract generic class that implements everything:

public abstract class MyClass<T>
{
  AnotherClass<T> another;
  OtherClass<T> other;

  // Add any code needed

}

Then create 2 generic classes for both base classes.
These classes may be empty if all code can be implemented in the abstract one:

public class MyT1Class<T extends T_1> extends MyClass<T>
{
}

public class MyT2Class<T extends T_2> extends MyClass<T>
{
}
Robert Kock
  • 5,795
  • 1
  • 12
  • 20
0

I know it's not a very good answer but I couldn't leave it as a comment to the question.
You can check the type at runtime by trying the following:

public class MyClass<T>
{
  // This factory-method creates an instance of the class if the correct type is passed
  // It throws a RuntimeException if not.
  public static <T> MyClass<T> getInstance(Class<T> type)
  {
    if (T_1.class.isAssignableFrom(type) || T_2.class.isAssignableFrom(type))
      return (new MyClass<T>());
    else
      throw new RuntimeException("Cannot create instance of MyClass<" + type.getName() + ">");
  }

  ...

}

Then

class T_3 extends T_2
{
}

....

MyClass<T_3> test_1;
test_1 = MyClass.getInstance(T_3.class); // This will succeed

MyClass<String> test_2;
test_2 = MyClass.getInstance(String.class); // Fails
Robert Kock
  • 5,795
  • 1
  • 12
  • 20