6

Imagine we have the following generic class:

public class GenericClass<U> {
    private U value;

    public GenericClass(U value) {
        this.value = value;
    }
}

and the following generic method in some other class MyClass:

public <T extends BT, BT> void genericMethod(T arg) {
    Object genericClass = new GenericClass<BT>(arg);
}

Which value will BT type parameter obtain if we call

genericMethod("text");

?

Some notes:

The above code compiles without errors or warnings, which seems strange for me. Decompiling (by means of IntelliJ IDEA 2016) shows the following code:

public <T extends BT, BT> void genericMethod(T arg) {
    new MyClass.GenericClass(arg);
}

Notice that new GenericClass<BT>(arg) is not the same as new GenericClass(arg) because the latter is equivalent of new GenericClass<T>(arg) (type deduction), and although T extends BT, these are different types, and GenericClass may have internal logic where the exact type name plays an important role (e.g. is used as a string key in some map etc). So for me it is strange why the compiler silently uses type deduction instead of producing some warning (or maybe even error) stating that BT type parameter is not specified. Maybe I'm missing smth. important about generics in Java, however...

2 Answers2

3

The first part of this answer will address the type inference part of your question

The only place where a type is automatically inferred in your example is when calling the genericMethod.

The definition of genericMethod declares two generic type parameters: an unbound BT and T which should be a subclass of BT.

Type T will be inferred according to the rules specified in Java language specification section 18.2.4:

A constraint formula of the form ‹S = T›, where S and T are types, is reduced as follows:

...

Otherwise, if T is an inference variable, α, and S is not a primitive type, the constraint reduces to the bound S = α.

In your example you provide an object of type String as an argument of type T. According to the rule above, T will be inferred to be equal to String.

Now that T is fixed, the inference will proceed with BT. This type is inferred by following section 18.3.1 which says that an expression of type:

T extends BT, T == String

implies

String extends BT

This relationship allows the compiler to bind BT to String, thus you end up with the following implied call:

genericMethod<String,String>("text")

Hope this clears things up a bit.

To answer the second part of your question:

and although T extends BT, these are different types, and GenericClass may have internal logic where the exact type name plays an important role (e.g. is used as a string key in some map etc).

Without adding more constraints, the compiler will treat T as equivalent to BT and will allow you to call only the methods defined as part of the BT type.

Unless you're doing something very weird using reflection, the code should not depend on the runtime value of type argument. The current GenericClass as it stands isn't very useful because U doesn't have any constraints so the compiler will allow you to do only things that are valid for all objects. You can consider U in that context the same as Object.

Denis Rosca
  • 3,199
  • 16
  • 37
  • Thank you for the answer. What is "inference variable"? Can you point to such in my code sample? In the genericMethod() declaration I see only two *type variables* (T and BT) and one usual variable (arg). I've read in Chapter 18.1.1 the following: _"Inference variables are meta-variables for types - that is, they are special names that allow abstract reasoning about types. To distinguish them from type variables, inference variables are represented with Greek letters, principally α"_. However, it didn't clear up this term for me. – Alexey Yumashin Sep 15 '16 at 05:05
  • **You write:** _String extends BT This relationship allows the compiler to bind BT to String, thus you end up with the following implied call: genericMethod("text")_ - I can't understand why it allows to do this: "String extends BT" is not the same as "BT == String"! **You also write:** _Without adding more constraints, the compiler will treat T as equivalent to BT and will allow you to call only the methods defined as part of the BT type._ - the same here! The compiler would better produce an error that BT is not used anywhere. – Alexey Yumashin Sep 15 '16 at 05:08
  • **You write:** _The current GenericClass as it stands isn't very useful [...]. You can consider U in that context the same as Object._ - I agree: this sample is a too distilled one. In my real project, _JAXBElement_ stands here (in place of the GenericClass). – Alexey Yumashin Sep 15 '16 at 05:11
1

Based on this part of your question

GenericClass may have internal logic where the exact type name plays an important role (e.g. is used as a string key in some map etc).

I think there may be some confusion, about how these types are inferred. It is important to realize, that the type that GenericClass will contain is the runtime type of whatever you pass in. Anything dependent on the exact type, that you might be doing inside (although generally you really shouldn't be) will work just fine.

Overall the type checks done at compile time may seem bit loose to you (especially if you come from C++ for example). There is interesting discussion about some of it in the answers to this question. Java generics - type erasure - when and what happens

Community
  • 1
  • 1
bajermi2
  • 358
  • 1
  • 4
  • 11