I was running some tests earlier and could not find an explanation as to why this code does what it does:
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList(Arrays.asList(Double.valueOf(0.1234)));
System.out.println(list.get(0)); //prints 0.1234
Object d = list.get(0);
System.out.println(d.getClass()); // prints class java.lang.Double
System.out.println(list.get(0).getClass()); // ClassCastException
}
}
That raises a few questions:
why does the List<Integer> accept a Double in the first place (should it compile at all)?
why does the second print work and not the third, although it looks like they are doing the same thing?
EDIT
I understand the following 2 statements:
List aList = new ArrayList(); //I can add any objects in there
List<Integer> aList = new ArrayList<Integer>(); //I can only add something that extends Integer in there
But I don't understand why this one is authorised and why it actually works to some extent at runtime although some operations produce a ClassCastException - I would have expected a ClassCastException at the first line of the code posted above:
List<Integer> aList = new ArrayList(); //I can any objects in there
This:
new ArrayList(Arrays.asList(Double.valueOf(0.1234)))
creates a raw (untyped) ArrayList, in to which you can place anything. This is the correct way to do it:
new ArrayList<Integer>(Arrays.asList(Double.valueOf(0.1234)))
which should now not compile.
If you write
... new ArrayList<Integer>(...
instead it will cause a compiler exception.
On why it works:
System.out.println(list.get(0)); //prints 0.1234
The method Object.toString() is the same in Double and Integer (And because System.out.println() expects an Object this is not cast into an Integer (the compiler optimized the cast away))
Object d = list.get(0);
System.out.println(d.getClass()); // prints class java.lang.Double
Same goes for .getClass(). Here the optimizer again dropped the cast.
System.out.println(list.get(0).getClass()); // ClassCastException
This actually creates an Integer from the list and that fails. It does the cast because the optimizer thought it need to do it, because its not obvious that it doesn't need to.
If you would change that last line to:
System.out.println(((Object)list.get(0)).getClass());
it works :)
The second one does not work because when you use generics the compiler inserts the casts for you (you don't have to). The compiler tries to cast the element to Integer because that is the generic type of the list.
Because you added to the list via an unchecked add, something is now in the list that was not checked when it was going in, thus it fails on coming out.
Related
I am learning the Generics and tried to alter one of the examples. I expected the code will not compile even with the different types passed into the generic method arraysEqual(). Turned out it complies and runs.
Why <T> type is not limiting input parameters to be the same (in this case Integer and String arrays)?
public class GenericMethodDemo {
static <T> boolean arraysEqual (T x[], T y[]){
if (x.length != y.length) return false;
for (int i = 0; i < x.length; i++)
if (!x[i].equals(y[i])) return false;
return true;
}
public static void main(String[] args) {
Integer[] nums = {1,2,3,4,5},
nums2 = {1, 2, 3, 4, 5};
String svals[] = {"1", "2","3","4","5"};
if (arraysEqual(nums, nums2)) System.out.println("nums is equal nums2");
if (!arraysEqual(nums, svals)) System.out.println("nums is not equal svals");
}
}
output is:
nums is equal nums2
nums is not equal svals
Arrays and generics do not work well together. Also, arrays have a broken type system. That broken type system part, that explains why your code compiles here.
To explain what's broken about it, and why that then results in your code working, first, some background info is required.
Variance
In java, the types themselves are covariant. This means Whenever something of type A is required, anything of a subtype of A is also okay.
This works fine:
Object o = "hello";
Because "hello" is a string, and String is a subtype of Object, therefore this is okay.
But generics are invariant: Whenever something of type A is required, only A will do.
Let's try the same construct in generics:
List<Object> o = new ArrayList<String>();
This is a compiler error: thus showing that generics start off invariant.
But, generics are interesting in that you can choose your variance. For example, if you want covariance, you can ask for this:
List<? extends Object> o = new ArrayList<String>();
That does compile: You now have a List of 'covariant Object'.
The reason that generics are invariant by default is because math / life / the universe. It is the correct way to do it. After all, imagine that generics were covariant like the basic types were, then you could write:
List<String> stringsOnly = new Arraylist<String>();
List<Object> objects = stringsOnly;
objects.add(5);
String string = stringsOnly.get(0); // but this is '5', a number!
The above code fortunately does not compile, because the second line won't, because generics are invariant. Opt into covariance and it still does not compile:
List<String> stringsOnly = new Arraylist<String>();
List<?> objects = stringsOnly;
objects.add(5);
String string = stringsOnly.get(0); // but this is '5', a number!
This time, the third line won't compile. You can't add anything to a List of covariant anything (well, except the literal null).
Arrays, unfortunately, breaks all this. Let's do the first snippet in array form:
String[] stringsOnly = new String[1];
Object[] objects = stringsOnly;
objects[0] = 5;
String string = stringsOnly[0];
This actually compiles. However, if you run it, you get an ArrayStoreException on the third line.
The reason your code does work, is:
T is bound to be Object. Which works: String[] and Integer[] are both valid, in that Object[] x = new String[10]; is valid java. This is mathematically unsound, as this last snippet showed, but java thinks it is okay.
As your arraysEqual method only reads, and never writes anything, you don't run into the ArrayStoreException.
I strongly recommend you actually write these snippets and experiment; variance is a hard concept, but fundamental to programming.
Arrays in java
Arrays are low level constructs, especially arrays of non-primitive types. You should not use them unless you have a performance reason (and you rarely should).
They have broken variance.
They will never be fixed (because there are better alternatives, and their problems are sometimes used by code that has performance requirements, so these problems cannot be fixed without breaking old code, and for the past 25 years, java has been highly resistant to making changes that break old code, and there are no indications this is going to change anytime soon).
They have broken hash and equals and toString implementations.
They cannot be coerced into having a read-only view or being immutable.
They cannot grow or shrink.
Use List<T> instead which suffers from none of these problems.
I need to type method signature so it accepts 2 equally typed parameters of different particular concrete subtypes.
Is it possible to code something like this with generics? How would you solve it? (The case is absolutely an example)
public <T extends List<?>> T<String> sum(T<Integer> sublistOfInts, T<Boolean> sublistOfBooleans){
/*fusion both lists*/
return sublistOfStrings;
}
EDIT: In the end, what I am looking for is a way for the compiler to pass:
ArrayList<String> myList = sum(new ArrayList<Integer>(), new ArrayList<Boolean>());
but not:
ArrayList<String> myList = sum(new ArrayList<Double>(), new ArrayList<Boolean>());
nor
ArrayList<String> myList = sum(new LinkedList<Integer>(), new ArrayList<Boolean>());
(...)
EDIT 2: I found a better example. Imagine an interface Tuple, with child classes Duple, Triple>..., it would be perfectly nice to have something like
<T extends Tuple<?>> T<String> reset( T<String> input, T<Boolean> listToNull){
T copy = input.copy();
for (int i=0; i<input.size();i++){
if (listToNull.get(i)){
copy.set(i,null);
}
}
}
What I suggest you do instead
First, get rid of the method argument generics. There's no reason to force a caller to provide ArrayList<Integer> and ArrayList<Boolean> when you want to return an ArrayList<String>. Just accept any List<Integer> and List<Boolean>, and leave it to your method to turn them into the appropriate return List.
Since you know that you want to return some sort of List of String you can write your parameter as <T extends List<String>> and your return type as simply T.
That leaves us with the hard part: getting your method to instantiate an object of unknown type. That's hard. You can't just do new T();. You need to invoke something that will produce a T on your behalf. Luckily, Java 8 provides a Functional Interface for Supplier<T>. You just need to invoke the get() method to get your ArrayList<String> or whatever else you might want. The part that's painful is that your invoker needs to provide their own Supplier. But I think that's as good as it gets in Java 8.
Here's the code:
public <T extends List<String>> T sum(
List<Integer> sublistOfInts,
List<Boolean> sublistOfBooleans,
Supplier<T> listMaker) {
T sublistOfStrings = listMaker.get();
/*fusion of both lists*/
return sublistOfStrings;
}
At least this compiles:
ArrayList<String> myNewList = thing.<ArrayList<String>>sum(intList, boolList, ArrayList::new);
And this does not:
ArrayList<String> myNewList = thing.<ArrayList<String>>sum(intList, boolList, LinkedListList::new);
You can even leave off the type parameter on the invocation. This compiles:
ArrayList<String> myNewList = thing.sum(intList, boolList, ArrayList::new);
And this does not:
ArrayList<String> myNewList = thing.sum(intList, boolList, LinkedListList::new);
Why you can't just do what you're asking
In brief, it's because type arguments can't themselves be parameterized. And that's because we don't know how many type arguments they themselves would take, nor the restrictions that might be placed on them.
Take the relatively obscure class RoleList. It extends ArrayList<Object>, so it fits List<?>. But it doesn't take a type argument at all. So if someone invoked your sum() method with RoleList, that would require in your example:
RoleList<Integer> intList = // something
RoleList<Boolean> boolList = // something
RoleList<String> myNewList = thing.sum(intList, boolList);
That clearly can't work since it requires an unparameterized type to take type arguments. And if you took off the type arguments like so:
RoleList intList = // something
RoleList boolList = // something
RoleList myNewList = thing.sum(intList, boolList);
Then your method needs to be able to accept two List<Object> arguments and return a value of List<Object>. And that violates your basic premise, that you be able to control such things.
In reality, RoleList should not be allowed here at all, because you can't ever guarantee that one instance will contain only Integers, another only Booleans, and a third only Strings. A compiler that allowed RoleList here would necessarily have weaker type checking than we have now.
So the bottom line is that you just can't do what you're asking because Java just isn't built that way.
Why that's ok
You can still get complete type safety inside your sum() method using my suggested method, above. You make sure that the incoming Lists contain only Integer or Boolean values, respectively. You make sure that the caller can rely on the return of a specific subtype of List containing only String values. All of the guarantees that make a difference are there.
There are two things that strike me about the above. How are you instantiating sublistOfStrings, and what advantages do you expect to get above using plain old inheritance?
There are a couple of ways of instantiating T<String>. You could have a factory check the class of your arguments, and instantiate it based on that. Or you could do something like:
(List<String>)sublistOfInts.getClass().newInstance()
But you can't just go new T<String>(). So you're basing the implementation of your return type off of the type of one of your arguments anyway (unless there's a way I haven't thought of).
By specifying both arguments are of type 'T' doesn't mean they're exactly of the same concrete type 'T' either. For instance
sum((int)1, (long)2L); // valid
sum((int)2, (double)2.0D); // valid ... etc
public <T extends Number> T sum(T a, T b) {
return a;
}
So you aren't enforcing that sublistOfInts and sublistOfBooleans are both of type say ArrayList, and therefore you can return an ArrayList. You still need to write code to check what type of List<?> you'll want to return based on the arguments.
I think you're better off not using generics, and using something like this:
public List<String> sum(List<Integer> sublistOfInts, List<Boolean> sublistOfBooleans) {
// Determine what subclass of list you want to instantiate based on `sublistOfInts` and `sublistOfBools`
// Call factory method or newInstance to instantiate it.
// Sum, and return.
}
You can still call it with subtypes of List<?>. I don't beleive there's any advantage you could get from generics even if Java did let you do it (which is doesn't, because it can't parameterize T like that).
I know what you have is just an example but if you only want to return a single list that contains the String value of all the contents in a group of other lists you could just specify a method that takes a varargs of unbounded lists.
public List<String> sum(List<?>... lists) {
List<String> sublistOfStrings = new ArrayList<String>();
for(List<?> list : lists) {
for(Object obj : list) {
sublistOfStrings.add(obj.toString());
}
}
return sublistOfStrings;
}
Why is the println printing "tom" and not showing any runtime exception after casting to List<Integer>, while it is not able to print the value 1 after casting to List<String>?
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String args[]) {
List list = Arrays.asList(1, "tom");
System.out.println(((List<Integer>) list).get(1));
// "tom"
System.out.println(((List<String>) list).get(0));
// ClassCastException: Integer cannot be cast to String
}
}
The first call of println is statically dispatched to PrintStream.println(Object) and the second call is dispatched to PrintStream.println(String). So for the second call the compiler puts an implicit cast to String which then fails with ClassCastException at runtime.
The problem here is that the java compiler picks methods at compile time, not runtime.
And at compile time it will pick the method PrintStream.print(String), not PrintStream.print(int) or PrintStream.print(Object), both of which would succeed.
Integer i = new Integer(101);
String s = new String(i); // undefined and Invalid
StringBuffer sb = new StringBuffer(i); // defined and Valid
String s2 = "tom";
Integer i2 = new Integer(s2); //defined and valid
So when you assign a non generic list to a generic one it is assigned but when you are printing it it checks for type safety or define constructors for casting if there are valid and defined constructors then it is printed else shows class cast exception as the class can not be casted due to lack of undefined constructors for casting.
If I am wrong please help me out with the correct logic...
This type of problem can be avoided by using generics and is the primary motivation for using generics.
This is the actual flow of your code, from your second println() point of view:
your code declares an ArrayList of type Object;
It adds an Integer and a String to the ArrayList.
It cast your list to a String list. Your list is marked as being restricted to String.
Java generics are a compile-time feature only so your list can accepts without any problem String and Integer elements. The object itself knows nothing about what types it's supossed to contain unlike to the compiler.
It attemps to retreive the first element of your casted list which is supposed to be a String and cast it to String implicitly.
Calls println(String x) from PrintStream class.
But this first element is actually not a String and is an Integer.
You cannot cast an Integer to a String.
Read Generics in Java motivation section example.
Could you help me understand the difference between unbounded wildcard type List and raw type List?
List<?> b; // unbounded wildcard type
List a; // raw type
Along with this can anybody help me understand what is a bounded type parameter list?
List<E extends Number> c;
Here's a summary of the three:
List: A list with no type parameter. It is a list whose elements are of any type -- the elements may be of different types.
List<?>: A list with an unbounded type parameter. Its elements are of a specific, but unknown, type; the elements must all be the same type.
List<T extends E>: A list with a type parameter called T. The supplied type for T must be of a type that extends E, or it is not a valid type for the parameter.
You should really look at Effective Java, Item 23: Don't use raw types in new code.
To use the example from that book, consider the following example... what if you have a collection where you do not care what types of elements are in it. For example, you want to see how many elements are in common between two sets. You might come up with the following:
public static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o : s1) {
if (s2.contains(o)) {
++result;
}
}
return result;
}
This example, while it works, is not a good idea to use because of the use of raw types. Raw types just aren't type safe at all... you could end up modifying the set in a way that is not type safe and corrupt your program. Instead, err on the side of caution and use the type safe alternative:
public static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o : s1) {
if (s2.contains(o)) {
++result;
}
}
return result;
}
The difference is that you can only add null to a Set<?>, and you CANNOT assume anything about the element you take out of a Set<?>. If you use a raw Set, you can add anything you want to it. The numElementsInCommon method is a good example where you don't even need to add anything and you don't need to assume anything about what is in the set. That's why it's a good candidate for using the ? wildcard.
Hope this helps. Read that whole Item in Effective Java and it will really become clear.
To answer the second part of your question... remember that I said when you use the ? wildcard, you cannot assume anything about the element you take out of the set? What if you do need to make an assumption about the interface of the object you removed from the set. For example, suppose you want to keep track of a set of Cool things.
public interface Cool {
// Reports why the object is cool
void cool();
}
Then you might have some code like this:
public static void reportCoolness(Set s) {
for (Object item : s) {
Cool coolItem = (Cool) item;
coolItem.cool();
}
}
This is not type safe... you need to make sure you passed in a set with only Cool objects. To fix it, you might say:
public static void reportCoolness(Set<Cool> s) {
for (Cool coolItem : s) {
coolItem.cool();
}
}
This is great! Does exactly what you want and is type safe. But what if later you have this:
public interface ReallyCool extends Cool {
// Reports why the object is beyond cool
void reallyCool();
}
Since all ReallyCool objects are Cool, you ought to be able to do the following:
Set<ReallyCool> s = new HashSet<ReallyCool>();
// populate s
reportCoolness(s);
But you can't do that because generics have the following property: Suppose B is a subclass of A, then Set<B> is NOT a subclass of Set<A>. The technical talk for this is "Generic types are invariant." (As opposed to covariant).
To get the last example to work you would need to create a Set<Cool> by casting (safely) every element in the Set<ReallyCool>. To avoid letting clients of your api go through this nasty, unnecessary code, you can just make the reportCoolness method more flexible like this:
public static void reportCoolness(Set<? extends Cool> s) {
for (Cool coolItem : s) {
coolItem.cool();
}
}
Now your method takes any Set that contains elements that are Cool or any subclass of Cool. All of these types adhere to the Cool api... so we can safely call the cool() method on any element
Make sense? Hope this helps.
On your first question, the difference between List and List<?>:
One significant difference between the two is that when you have an wildcard as the type, the type of the Collection is unknown, so the add method will throw a compile time error.
You can still get values out of the List<?>, but you need an explicit cast.
Both cases let us put into this variable any type of list:
List nothing1 = new ArrayList<String>();
List nothing2 = new ArrayList();
List nothing3 = new ArrayList<>();
List nothing4 = new ArrayList<Integer>();
List<?> wildcard1 = new ArrayList<String>();
List<?> wildcard2 = new ArrayList();
List<?> wildcard3 = new ArrayList<>();
List<?> wildcard4 = new ArrayList<Integer>();
But what elements can we put into this objects?
We can put only String into List<String>:
List<String> strings = new ArrayList<>();
strings.add("A new string");
We can put any object into List:
List nothing = new ArrayList<>();
nothing.add("A new string");
nothing.add(1);
nothing.add(new Object());
And we can't add anything (but for null) into List<?>! Because we use generic. And Java knows that it is typed List but doesn't know what type it is exact. And doesn't let us make a mistake.
Conclusion: List<?>, which is generic List, gives us type safety.
P.S. Never use raw types in your code.
Please explain what are the differences between List - raw type and List<Object>.
The below code gives run time error:
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0); // Compiler-generated cast
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
And this gives compile time error:
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0); // Compiler-generated cast
}
private static void unsafeAdd(List<Object> list, Object o) {
list.add(o);
}
In the second case, you are doing something the compiler can workout is not safe. In the first case, you are using raw types so the compiler doesn't perform the same checks.
Java has not inheritance for parametric types. So List<Integer> is not a subclass of List<Object>, then you can't use List<Integer> or List<String> as parameter for the unsafeAdd method. But you can write:
private static <T> void unsafeAdd(List<T> list, T o) {
list.add(o);
}
and safelly call it:
List<String> strings = ...
unsafeAdd(string, "42");
and get error while:
List<String> strings = ...
unsafeAdd(strings, 42); // error!
You can see more information in the Oracle Generics Tutorial, Generics, Inheritance, and Subtypes
In the first case you pass unparametrized List to unsafeAdd, so compiler has no way to figure out something is wrong.
Passing List<String> to method which expects List is ok. Adding object to List is ok.
In the second case, you are passing List<String> to method which expects List<Object> - and that's not ok. Because this way you are implicitely allowing to add non-String to List<String> - compiler can figure it out in this case and raises an error.
As Peter already said, in the first example you're telling the compiler to use raw types and thus not to perform any checks on the list. Thus it will allow you to add any object to the passed list, even if it is defined to just allow for strings.
In the second example you tell the compiler that it should assume the list to allow any object, thus the add operation would compile. However, passing a List<String> as a List<Object> is not allowed, since the list of strings has more specific restrictions than the contents just being objects and hence the compiler knows that this is unsafe and error-prone.
If you'd define the parameter to be List<? extends Object> list instead, the compiler would accept passing a List<String> since you tell it that the minimum requirement is that the list must accept objects, but it could also impose harder constraints. However, the add operation wouldn't compile now, since the compiler doesn't know if there are harder constraints and if so what these constraints are. Hence it can't assure that the add operation is safe and refuses to compile that line.
However it looks the same, it is not.
List (raw type) does not care what you insert into it.
List<String> is not compatible with List<Object>. You may do this:
String s = "Hello";
Object o = s;
but you must not do this:
List<String> ls = ...;
List<Object> lo = ls; // Compilation error!
Your example is a good illustration why the Java language does not allow it. By allowing such an assignment a malicious method would be able to put anything into a list which a client consider as a list of Strings.
Consider the following code:
public void method changeObject(Object o) {
o = 42;
}
public void method changeList(List<Object> lo) {
lo.add(42);
}
...
String str = "Hello";
changeObject(str);
// Here it is still safe to use str, its value has not been modified
String str2 = str; // OK
List<String> list = new ArrayList<>();
changeList(list); // it is not allowed, but let's assume it is
// here the list variable would violate its contract - everybody has a right
// to expect it is a list of Strings
String str3 = list.get(0); // Ouch! :(