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.





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 of getFields() is List getFields() (without generics), not List<String> getFields() as you thought.
– M. Prokhorov
2 days ago


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 String1.


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 subtypes1. 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

Comments

Popular posts from this blog

paramiko-expect timeout is happening after executing the command

Export result set on Dbeaver to CSV

Opening a url is failing in Swift