6

I am trying to understand why Java does not allow using implicit types in generic classes as it does in generic methods.

I have scoured the webs for answers on this, but have yet to come across a reason as to why something like the following is not supported in Java:

// This will not compile:
public <T> interface ElementPicker<L extends List<T>> { ... }

// This on the other hand will
public interface ElementPicker<T, L extends List<T>> { ... }

And so we must explicitly mention the type T in the class generic arguments. This of course means that we must now always write:

ElementPicker<Integer, List<Integer>>
// instead of just:
ElementPicker<List<Integer>>

This leads to a constant head-ache in my code where I am attempting to balance using generics wisely, while making my types readable and short.

Unfortunately, in my current project I am dealing with a bunch of nested generic types, and their type arguments are bubbling to the top so that I have very long top-level classes that must include all generic type data.

To see how this can become a problem consider:

interface ScalarValue<T extends Number> {
  T toNumber();
}

interface ScalarAdder<T, U extends ScalarValue<T>> {
  T add(U v1, U v2);
}

class ScalarAccumulator<T, U extends ScalarValue<T>, 
                           A extends ScalarAdder<T, U>> {
  ...
}

// Assuming we have these implementations:
class RationalValue implements ScalarValue<Float> { .. }
class RationalAdder implements ScalarAdder<Float, RationalValue> { .. }

// A typical type could look like this monster:
ScalarAccumulator<Float, RationalValue, RationalAdder>

// Whereas an implicit declaration like this:
public <T extends Number, 
        U extends ScalarValue<T>,
        A extends ScalarAdder<T, U>
class ScalarAccumulator<A> { ... }

// ... would allow us to simply write
ScalarAccumulator<RationalAdder>
// ...as all other types can be inferred.

Again this is an example, but I encounter these kinds of things quite often in my work. And I have yet to find a reason for why this is not possible. Methods can this just fine (infer types from a single value and its class).

So why can Java support this in methods but not in classes? I cannot think of an example where it would be a problem for a class to not explicitly include the type. But I am probably missing something.

If anybody has any good tips on work-arounds to deal with such situations in general, I would also appreciate it.

shmosel
  • 49,289
  • 6
  • 73
  • 138
Marius Renn
  • 391
  • 2
  • 5
  • Do you actually need `L`? The example doesn't convey it very well. – shmosel Jan 11 '19 at 03:40
  • Possible duplicate of [When do Java generics require extends T> instead of and is there any downside of switching?](https://stackoverflow.com/questions/897935/when-do-java-generics-require-extends-t-instead-of-t-and-is-there-any-down) – Gauravsa Jan 11 '19 at 03:43
  • Added a dummy method just to use L, but please don't read too much into this example. I am sure there could be more realistic ones, but it really is just about the interface declaration. – Marius Renn Jan 11 '19 at 04:01
  • 1
    @MariusRenn And you can't write `void ingest(List list);` because? – Elliott Frisch Jan 11 '19 at 04:02
  • I am beginning to regret I wrote a body to that interface at all. I will edit the question and remove it. I really just want to know why such an interface declaration is not allowed. I'll try to come up with a more motivating example, but I also do not want to detract from the question. – Marius Renn Jan 11 '19 at 04:05
  • Related: https://stackoverflow.com/questions/25416498/java-implicit-generic-parameters-from-superclass – shmosel Jan 11 '19 at 04:10
  • Thanks shmosel, I did see that question, but also felt that folks there were focusing too much on that example rather than answering the overall question. Sadly, I think I also failed at this, so I can dupe this if you feel it's too similar. – Marius Renn Jan 11 '19 at 04:27
  • Removed the body of the first example, and provided a (hopefully?) more motivating example. – Marius Renn Jan 11 '19 at 04:29
  • Does `ScalarAccumulator` accept any constructor arguments? – shmosel Jan 11 '19 at 04:59
  • 1
    I don't have a good answer to this, but I have been dealing with exactly this problem lately. I have a generic interface that returns an opaque token; the client gets, stores, and sends it back, but otherwise it doesn't care what type it is. There's no clean way to make the generic type "private"; it infects the client's clients. – chrylis -cautiouslyoptimistic- Jan 11 '19 at 05:01
  • @chrylis It's more about redundant types than private types. What you're describing sounds more like the middle parameter in the `Collector` interface that's necessary for the implementation but irrelevant to clients. – shmosel Jan 11 '19 at 05:09
  • @shmosel Hmm, okay, I think I see your distinction. Still driving me nuts. ;-) – chrylis -cautiouslyoptimistic- Jan 11 '19 at 05:12
  • 1
    *"So why can Java support this in methods but not in classes?"* ... asking this seems similar in spirit to asking the question "why do we have to specify parameters in method signatures?" – scottb Jan 11 '19 at 05:16
  • 1
    @chrylis You might find this thread interesting: https://mail.openjdk.java.net/pipermail/lambda-libs-spec-experts/2013-June/001924.html – shmosel Jan 11 '19 at 05:22

1 Answers1

2

In Java 10 you can use var to skip type declarations. Here's a (somewhat hacky) way to combine that with type inference so you can create an instance without declaring all the nested types:

static <T, U extends ScalarValue<T>, A extends ScalarAdder<T, U>> ScalarAccumulator<T, U, A> create(Class<A> adderClass) {
    return new ScalarAccumulator<>();
}

static void test() {
    var a = create(RationalAdder.class);
}
shmosel
  • 49,289
  • 6
  • 73
  • 138