Why is the base type not returned when no generic type specified? [duplicate]
Why is the base type not returned when no generic type specified? [duplicate]
This question already has an answer here:
I have a code like this:
public class A<T extends String> {
T field;
List<T> fields;
public T getField() {
return field;
}
public List<T> getFields() {
return fields;
}
public static void test(){
A a = new A();
String s = a.getField();
List<String> ss = a.getFields();
}
}
Why when creating A
with no generic type getFiled
returns String
but getFileds
returns List<Object>
?
A
getFiled
String
getFileds
List<Object>
I have to define A
as A<String> a = new A<>()
for this to work properly.
A
A<String> a = new A<>()
Thanks,
This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.
test()
getFields()
List getFields()
List<String> getFields()
@M.Prokhorov So why
field
is returned as String
?– MAREK
2 days ago
field
String
Because there is no generics in signature of
T getField()
. Once that type is compiled, there would be a String getField()
and a bridge method generated by the compiler to support using getField()
with arbitrary types. None of those use generics.– M. Prokhorov
2 days ago
T getField()
String getField()
getField()
Please see stackoverflow.com/questions/2770321/…
– Alexey Romanov
2 days ago
getField
will return an Object, why do you keep saying it returns a String? My mistake I see now.– matt
2 days ago
getField
3 Answers
3
The erasure of the type param T
in the declaration T field
is String
, because T
has bound:
T
T field
String
T
<T extends String>
and any subtype
of a String
is also a String
1.
subtype
String
String
But, this doesn't apply to generic List<T>
. It stems from the fact that generics are not covariant:
List<T>
List<String> str = new ArrayList<String>();
List<Object> obj = str; // Error: incompatible types (even though String extends Object)
The erasure of List<T>
in the declaration List<T> fields
is List
, particularly because List<T>
can hold a String
or its subtypes
1. And given that A
is a raw type, compiler can't guess the type of the elements in the list returned by the getFields()
method.
List<T>
List<T> fields
List
List<T>
String
subtypes
A
getFields()
Anyway, this line of code:
List<T> fields;
is compiled (Erasure of Generic Types) into:
List fields;
As a result, when you write:
A a = new A(); instead of A<String> a = new A<>();
you lose the type safety and get an "Unchecked assignment" warning - in this case, compiler can not guarantee that the fields list
is exactly the List<String>
:
fields list
List<String>
List<String> list = a.getFields(); // Unchecked assignment: 'List' to 'List<String>' ...
P.S. Don't use raw types!
1 - As you know, String
class is final and can't be extended. Replace it with any type that can be extended if you wish.
String
+1 … in other words, given the constraint, whatever
getField
returns is assignable to String
, while whatever getFields
returns is not necessarily assignable to List<String>
(if we ignore that String
is final
).– Konrad Rudolph
2 days ago
getField
String
getFields
List<String>
String
final
@Konrad Thank you for comment! That is actually what I wanted to show in my answer.
– Oleksandr
2 days ago
@KonradRudolph yes, but it’s worth noting that a
List<? extends X>
still guarantees that all contained objects are assignable to X
, but you are not allowed to insert new objects of type X
, as they may be incompatible to the list’s actual type. If you can preclude insertions, you can make the assignment, i.e. if you have an A<?> a;
, you can use List<String> list = Collections.unmodifiableList( a.getFields());
, as the wrapper prevents you from inserting incompatible elements.– Holger
2 days ago
List<? extends X>
X
X
A<?> a;
List<String> list = Collections.unmodifiableList( a.getFields());
When you use the raw type, you still have your bounds. So T extends String
you know that whatever is returned will extend String. When you use the raw type it is like having ? extends String
.
T extends String
? extends String
A<?> a = new A<>();
String s = a.getField();
List<? extends String> ss = a.getFields();
String s2 = ss.get(0);
Of course there will be an error NPE, but the idea is there.
One difference for the raw types, all of the type information in get Fields is lost because a raw-typed list is returned.
List ss = a.getFields();
This means, even the bound of String is lost.
Also, String is a final class, so the constraints here don't really add any value, because we know that T
is always a String. You can just as well use:
T
class A {
String field;
List<String> fields;
public String getField() {
return field;
}
public List<String> getFields() {
return fields;
}
public static void test(){
A a = new A();
String s = a.getField();
List<String> ss = a.getFields();
}
}
This is not an answer, but only here I can put the code. So I tried this example (but I've named it TestGenerics
and add main
instead of test()
) and then I decompiled it:
TestGenerics
main
test()
javap -c TestGenerics
Compiled from "TestGenerics.java"
public class TestGenerics<T extends java.lang.String> {
T field;
java.util.List<T> fields;
public TestGenerics();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public T getField();
Code:
0: aload_0
1: getfield #2 // Field field:Ljava/lang/String;
4: areturn
public java.util.List<T> getFields();
Code:
0: aload_0
1: getfield #3 // Field fields:Ljava/util/List;
4: areturn
public static void main(java.lang.String);
Code:
0: new #4 // class TestGenerics
3: dup
4: invokespecial #5 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #6 // Method getField:()Ljava/lang/String;
12: astore_2
13: aload_1
14: invokevirtual #7 // Method getFields:()Ljava/util/List;
17: astore_3
18: return
}
As we can see in code field
and getField
still have the generic T
type, rather than compiled into String
. So why does getField()
work fine?
field
getField
T
compiled into String
getField()
You're within bounds, eg. If I have a
class B extends Runnable
then I can use Runnable b = new B();
In this case you have T extends String
so you can assign a T to String. getFields returns a raw type so just List and the information that T extends String is lost.– matt
2 days ago
class B extends Runnable
Runnable b = new B();
T extends String
By not specifying any generic type, you are declaring that your type use site doesn't use any generics information. This means that in
test()
method effective signature ofgetFields()
isList getFields()
(without generics), notList<String> getFields()
as you thought.– M. Prokhorov
2 days ago