Why do we lose type safety when using List and not while using List<Object>? Aren't they basically the same thing?
EDIT: I found that the following gives a compilation error
public class TestClass
{
static void func(List<Object> o, Object s){
o.add(s);
}
public static void main(String[] args){
func(new ArrayList<String>(), new Integer(1));
}
}
whereas this doesn't
public class TestClass
{
static void func(List o, Object s){
o.add(s);
}
public static void main(String[] args){
func(new ArrayList<String>(), new Integer(1));
}
}
Why?
List is a list of some type you don't know. It could be a List<String>, List<Integer>, etc.
It's effectively equivalent to List<?>, or List<? extends Object>, except that it doesn't document that fact. It's only supported for backwards compatibility.
List<Object> is a list of Objects. Any object of any type can be put inside it, contrary to a List<String>, for example, which only accepts strings.
So no, they're not the same thing.
Why do we lose type safety when using List and not while using List<Object>? Aren't they basically the same thing?
No they are not the same thing.
If you are providing an API,
class API {
static List<Object> getList() { ... }
static void modifyList(List<Object> l) { ... }
}
and a client uses it improperly
List<Integer> list = API.getList();
API.modifyList(list);
for (Integer i : list) { ... } // Invalid
then when your API specifies List<Object> they get a compile-time error, but they don't when API.getList() returns a List and API.modifyList(list) takes a List without generic type parameters.
EDIT:
In comments you mentioned changing
void func(List<Object> s, Object c) { s.add(c); }
to
void func(List s, Object c) { s.add(c); }
so that
func(new List<String>(), "");
would work.
That is violating type safety. The type-safe way to do this is
<T> void func(List<? super T> s, T c) { s.add(c); }
which is basically saying that func is a parameterized function that takes a List whose type can be any super class of T, and a value of type T, and adds the value to the list.
A List<Object> isn't really any more typesafe than a List. However, the Object in the code does imply intent. When someone else looks at it later, they can see that you purposefully chose Object as the type, rather than wondering if you just forgot to put a type or are storing something else and typecasting it elsewhere.
Since code gets read more than it gets written, hints at the intent of the code can be very valuable later on.
The reason you have a compiler warning when you use List instead of List<Object> is that when you have a List the compiler doesn't know what type of List it is, so while you could treat it as a List<Object>, the compiler can't ensure that at some other point in the code it wasn't set to reference a List<String> and the type safety of the Generics cannot be checked. That is really the compiler warning here - it is saying it can't help ensure the type safety of the generics, and it won't happen at runtime either (until at some later point there is an actual cast in the code).
For purposes here, you could say they're the same thing. But presumably you almost never actually fill a List with pure instance of Object. They're Strings or something. In this example, List<Object> is technically using generics but not really taking any advantage of it. So, it loses the compile-time type checking of generics.
Type Erasure is one answer and the backward compatibility to pre Java 1.5 and tighter type check in case of first one.
Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:
Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
Insert type casts if necessary to preserve type safety.
Generate bridge methods to preserve polymorphism in extended generic types.
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.
Related
I have a Problem with a generic method after upgrading to Java 1.8, which was fine with Java 1.6 and 1.7
Consider the following code:
public class ExtraSortList<E> extends ArrayList<E> {
ExtraSortList(E... elements) {
super(Arrays.asList(elements));
}
public List<E> sortedCopy(Comparator<? super E> c) {
List<E> sorted = new ArrayList<E>(this);
Collections.sort(sorted, c);
return sorted;
}
public static void main(String[] args) {
ExtraSortList<String> stringList = new ExtraSortList<>("foo", "bar");
Comparator<? super String> compGen = null;
String firstGen = stringList.sortedCopy(compGen).get(0); // works fine
Comparator compRaw = null;
String firstRaw = stringList.sortedCopy(compRaw).get(0); // compiler ERROR: Type mismatch: cannot convert from Object to String
}
}
I tried this with the Oracle javac (1.8.0_92) and with Eclipse JDT (4.6.1) compiler. It is the same result for both. (the error message is a bit different, but essentially the same)
Beside the fact, that it is possible to prevent the error by avoiding raw types, it puzzles me, because i don't understand the reason.
Why does the raw method parameter of the sortedCopy-Method have any effect on the generic type of the return value? The generic type is already defined at class level. The method does not define a seperate generic type. The reference list is typed to <String>, so should the returned List.
Why does Java 8 discard the generic type from the class on the return value?
EDIT: If the method signature of sortedCopy is changed (as pointed out by biziclop) to
public List<E> sortedCopy(Comparator c) {
then the compiler does consider the generic type E from the type ExtraSortList<E> and no error appears. But now the parameter c is a raw type and thus the compiler cannot validate the generic type of the provided Comparator.
EDIT: I did some review of the Java Language Specification and now i think about, whether i have a lack of understanding or this is a flaw in the compiler. Because:
Scope of a Declaration of the generic type E is the class ExtraSortList, this includes the method sortedCopy.
The method sortedCopy itself does not declare a generic type variable, it just refers to the type variable E from the class scope. see Generic Methods in the JLS
The JLS also states in the same section
Type arguments may not need to be provided explicitly when a generic method is invoked, as they can often be inferred (ยง18 (Type Inference)).
The reference stringList is defined with String, thus the compiler does not need to infer a type forE on the invocation of sortedCopy because it is already defined.
Because stringList already has a reified type for E, the parameter c should be Comparator<? super String> for the given invocation.
The return type should also use the already reified type E, thus it should be List<String>.
This is my current understanding of how i think the Java compiler should evaluate the invocation. If i am wrong, an explanation why my assumptions are wrong would be nice.
To bring an final answer to why this happens:
Like #Jesper mentioned already, you're using raw types when you shouldn't (Especially when using the Generic as type in multiple cases).
Since you pass an Comparator without an Generic-Type, there will actually be none. You could think of the E-Generic as null to make it easier. Therefore your code becomes to this:
public List sortedCopy(Comparator c) {
List sorted = new ArrayList(this);
Collections.sort(sorted, c);
return sorted;
}
Now you're attemptig/assuming you get an String from an List without Generics and therefore an Object (hence it's the super-class of everything ).
To the question why the raw-type parameter has no effect on the return type, since you don't specify an certain Level of abstraction. You'd have to define an Type that the Generic has to extend/implement at least to make that happen (compilation errors), for example.
public class ExtraSortList<E extends String> extends ArrayList<E> {
will now only allow Strings or Classes which extend it (not possible here since string is final). With that, your fallback Type would be String.
Compiling the below code is failing:
public static void swap(List<?> list, int i, int j) {
list.set(i, list.set(j, list.get(i)));
}
like:
Swap.java:5: set(int,capture#282 of ?) in List<capture#282 of ?> cannot be applied to (int,Object)
list.set(i, list.set(j, list.get(i)));
But if I do this:
public static void swap(List<?> list, int i, int j) {
swapHelper(list, i, j);
}
private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i)));
}
Its working perfectly.
But I have a basic doubt here. Generics are said to be invariant, so List<String> is not subtype of List<Object>, right?
If that is the case, then how come in the above method, we are able to pass List<?> to List<E>? How does this works?
the answer is wildcards.
List<?> is not the same as List<Object>
while java assumes both are collections of Object, the former will match the type of any other List<T>, while the latter will not match the type of any List<T> besides List<Object>.
example:
public void doSomething(List<Object> list);
This function will accept only List<Object> as it's parameter. However this:
public void doSomething(List<?> list);
will accept any List<T> as it's parameter.
This can be extremely useful when used with generic constraints. For instance if you'd like to write a function that manipulates numbers (Integers, Floats, etc.) you could:
public void doSomethingToNumbers(List<? extends Number> numbers) { ... }
Because you are using wildcard capturing.
In some cases, the compiler infers the type of a wildcard. For
example, a list may be defined as List but, when evaluating an
expression, the compiler infers a particular type from the code. This
scenario is known as wildcard capture.
Thanks to the helper method, the compiler uses inference to determine T, the capture variable, in the invocation.
List<?> means "list of an unknown type". You can pass a List<String> for it, not because it is a subclass (it isn't), but because "unknown type" is supposed to accept whatever you throw at it.
Because type is unknown, you cannot perform operations with it, that require knowing the type of the elements (like set or add etc).
List<E> is a generic. It is not the same as List<?> even though it kinda looks similar.
One difference is, as you noted, you can do things like set or add to it, because the type of the element is now known.
Whildcards as function parameters are not very useful (<E> void foo(List<E> l) is not much different from void foo(List<?> l)). They also bypass all the compile-time type check, thus defeating the purpose of generics, and should really be avoided.
A more common (and less harmful) use for wildcards is in the return types: List<? extends DataIterface> getData() means that getData returns a list of some objects that implement a DataInterface, but won't tell you their concrete type. This is often done in the API design to isolate implementation details from the interface. The convention usually is, that you can pass a list of objects, returned by the API to other API methods, and they will accept and handle them. This concept is called existential types.
Also, note that getData from the above example can return lists of different types, depending on some condition, that is outside of the caller's domain, unlike if it was declared to return List<E>, in which case, the caller would have to specify the expected type in some way.
I have this code from the book Thinking in Java,where Bruce indicates the warning on calling the method set(). The code is as follows:
package testPackage;
class GenericBase<T> {
private T element;
public void set(T arg) { arg = element; }
public T get() { return element; }
}
class Derived1<T> extends GenericBase<T> {}
class Derived2 extends GenericBase {} // No warning
// class Derived3 extends GenericBase<?> {}
// Strange error:
// unexpected type found : ?
// required: class or interface without bounds
public class ErasureAndInheritance {
#SuppressWarnings("unchecked")
public static void main(String[] args) {
Derived2 d2 = new Derived2();
Object obj = d2.get();
d2.set(obj); // Warning here!
}
}
If I remove the annotation, I get the following warning:
Type safety: The method set(Object) belongs to the raw type GenericBase. References to generic type GenericBase should be parameterized
My question is, Why is the warning displayed on the set() method? And can somebody please explain what this warning means?
Btw, I am completely new to Java Generics and although I read other questions on Generics, I am still somewhat confused on Erasure.
From the Java doc here
Generics were introduced to the Java language to provide tighter type
checks at compile time and to support generic programming. To
implement generics, the Java compiler applies type erasure to:
Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode,
therefore, contains only ordinary classes, interfaces, and methods.
Insert type casts if necessary to preserve type safety.
Generate bridge methods to preserve polymorphism in extended generic types.
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.
Simple: Every generic is a Object after the compile process, it just adds the casting for you, and if you did something wrong it will generate a compiler error.
About the unchecked annotation, it's used when the compiler can't be sure that what are you doing is correct (in fact, you use the raw type which is the same thing as GenericBase<Object>)
You have the warning since the set methods except the type T but since you are using a raw type, it don't know what T is, and generate this warning to let you know that you are using a raw type (bad thing).
You can use the annotations to say: "I know it's a raw type, but i know it's legal and i know what i'm doing".
Mixing raw types and generic types is possible due to the way that generics are implemented, as i said above, after the compiler process the T becomes Object and it means that a raw object like GenericBase is the same thing like GenericBase<T>.
The reason that it doesn't asks for any casting with generics, is because it will add the casts for you (and you can be sure that the code will always work in runtime without worry about a possible ClassCastException).
I saw a java function that looked something like this-
public static<T> foo() {...}
I know what generics are but can someone explain the in this context? Who decides what T is equal to? Whats going on here?
EDIT: Can someone please show me an example of a function like this.
You've missed the return type out, but apart from that it's a generic method. As with generic types, T stands in for any reference type (within bounds if given).
For methods, generic parameters are typically inferred by the compiler. In certain situations you might want to specify the generic arguments yourself, using a slightly peculiar syntax:
List<String> strings = Collections.<String>emptyList();
In this case, the compiler could have inferred the type, but it's not always obvious whether the compiler can or can't. Note, the <> is after the dot. For syntactical reasons the type name or target object must always be specified.
It's possible to have generic constructors, but I've never seen one in the wild and the syntax gets worse.
I believe C++ and C# syntaxes place the generic types after the method/function name.
The context is a generic method as opposed to a class. The variable <T> applies only to the call of the method.. The Collections class has a number of these; the class itself is not generic, but many of the methods are.
The compiler decides what T is equal to -- it equals whatever gets the types to work. Sometimes this is easier then others.
For example, the method static <T> Set<T> Collections.singleton(T o) the type is defined in the parameter:
Collections.singleton(String T)
will return a Set<String>.
Sometimes the type is hard to define. For example sometimes there is not easily enough information to type Collection.emptyList(). In that case you can specify the type directly: Collection.<String>emptyList().
T it's the formal type parameter wich will be replaced by the actual type
argument used at the instantiation of the object.
For example, here is the List and Iterator definitios in package java.util:
public interface List<E>{
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E>{
E next();
boolean hasNext();
}
Then you can instantiate a List this way:
List<String> ls = new ArrayList<String>()
Where you might imagine that List stands for a version of List where E has
been uniformly replaced by String:
public interface StringList{
void add(String x)
Iterator<String> iterator();
}
I have a class that maps incoming messages to matching readers based on the message's class. All message types implement the interface message. A reader registers at the mapper class, stating which message types it will be able to handle. This information needs to be stored in the message reader in some way and my approach was to set a private final array from the constructor.
Now, it seems I have some misunderstanding about generics and / or arrays, that I can't seem to figure out, see the code below. What is it?
public class HttpGetMessageReader implements IMessageReader {
// gives a warning because the type parameter is missing
// also, I actually want to be more restrictive than that
//
// private final Class[] _rgAccepted;
// works here, but see below
private final Class<? extends IMessage>[] _rgAccepted;
public HttpGetMessageReader()
{
// works here, but see above
// this._rgAccepted = new Class[1];
// gives the error "Can't create a generic array of Class<? extends IMessage>"
this._rgAccepted = new Class<? extends IMessage>[1];
this._rgAccepted[0] = HttpGetMessage.class;
}
}
ETA:
As cletus correctly pointed out, the most basic googling shows that Java does not permit generic arrays. I definitely understand this for the examples given (like E[] arr = new E[8], where E is a type parameter of the surrounding class). But why is new Class[n] allowed? And what then is the "proper" (or at least, common) way to do this?
Java does not permit generic arrays. More information in the Java Generics FAQ.
To answer your question, just use a List (probably ArrayList) instead of an array.
Some more explanation can be found in Java theory and practice: Generics gotchas:
Generics are not covariant
While you might find it helpful to
think of collections as being an
abstraction of arrays, they have some
special properties that collections do
not. Arrays in the Java language are
covariant -- which means that if
Integer extends Number (which it
does), then not only is an Integer
also a Number, but an Integer[] is
also a Number[], and you are free to
pass or assign an Integer[] where a
Number[] is called for. (More
formally, if Number is a supertype
of Integer, then Number[] is a
supertype of Integer[].) You might
think the same is true of generic
types as well -- that List<Number>
is a supertype of List<Integer>, and
that you can pass a List<Integer>
where a List<Number> is expected.
Unfortunately, it doesn't work that
way.
It turns out there's a good reason it
doesn't work that way: It would break
the type safety generics were supposed
to provide. Imagine you could assign a
List<Integer> to a List<Number>.
Then the following code would allow
you to put something that wasn't an
Integer into a List<Integer>:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
Because ln is a List<Number>, adding
a Float to it seems perfectly legal.
But if ln were aliased with li, then
it would break the type-safety promise
implicit in the definition of li --
that it is a list of integers, which
is why generic types cannot be
covariant.
It is right what cletus said. There is a general mismatch between the usually enforced invariance of generic type parameters vs. covariance of Java arrays.
(Some background: Variance specifies how types relate to each other concerning subtyping. I.e. because of generic type parameters being invariant
Collection <: Collection does not hold. So, concerning the Java type system, a String collection is no CharSequence collection. Arrays being covariant means that for any types T and U with T<:U, T[] <: U[]. So, you can save a variable of type T[] into a variable of type U[]. Since there is a natural need for other forms of variance, Java at least allows wildcards for these purposes.)
The solution (the hack, actually) I often use, is declaring a helper method which generates the array:
public static <T> T[] array(T...els){
return els;
}
void test(){
// warning here: Type safety : A generic array of Class is created for a varargs parameter
Class<? extends CharSequence>[] classes = array(String.class,CharSequence.class);
}
Because of erasure the generated arrays will always be of type Object[].
And what then is the "proper" (or at least, common) way to do this?
#SuppressWarnings(value="unchecked")
public <T> T[] of(Class<T> componentType, int size) {
return (T[]) Array.newInstance(componentType, size);
}
public demo() {
Integer[] a = of(Integer.class, 10);
System.out.println(Arrays.toString(a));
}
Arrays are always of a specific type, unlike how Collections used to be before Generics.
Instead of
Class<? extends IMessage>[] _rgAccepted;
You should simply write
IMessage[] _rgAccepted;
Generics don't enter into it.
IMHO,
this._rgAccepted = (Class<? extends IMessage>[])new Class[1];
is the appropriate way to handle this. Array component types have to be reified types, and Class is the closest reified type to Class<whatever>. It'll work just as you would expect a Class<whatever>[] to work.
Yes, technically it is unchecked and might cause issues later if you cast it to another more general array type and put wrong stuff in it, but since this is a private field in your class, I presume you can make sure it is used appropriately.