Something different between array and generics - java

I just read 《Effective Java》and I saw a sentence which said that
As a consequence, arrays provide runtime type safety but not compile-time type safety and vice versa for generics
I don't quite clear about it and I'm confused even after I've read all the examples given.Can anyone explain this to me,thanks a million.

You can't change the type of an array (reference) at runtime. But you can compile code which tries to just fine.
String[] strings = new String[1];
Object[] objects = strings;
objects[0] = new Integer(1); // RUN-TIME FAILURE
When you compile your application, no error will be thrown by the compiler.
On the other hand, if you use generics, this WILL give you an error when you compile (build) your application.
ArrayList<String> a = new ArrayList<String>();
a.add(5); //Adding an integer to a String ArrayList - compile-time failure
In other words, you don't need to actually run your application and execute that section of code to find the problem.
Note, compile time failures are preferable to run time failures, since you find out about the problem before you release it to users (after which it's too late)!

With generic collections, this code, which tries to put an Integer into a String list, gives a compile time error on the second line: Cannot cast from List<String> to List<Object>:
List<String> listOfStrings = new ArrayList<>();
List<Object> listAgain = (List<Object>)listOfStrings;
listAgain.add(123);
The equivalent code with arrays compiles perfectly because it is legal to use a String array as an Object array. (Technically speaking, arrays are covariant.)
String[] arrayOfStrings = new String[10];
Object[] arrayAgain = arrayOfStrings;
arrayAgain[0] = 123;
However, it wouldn't be a valid String array if it actually contained integers, so every operation to store something in it is checked at run time. At run time does it blow up with an ArrayStoreException.

Java Array provides covariant return type while generic doesn't provide covariant return type.
Lets understand with simple example
public class GenericArrayDiff {
public static void main(String[] args) {
List<Vehicle> vehicleList = null;
List<Car> carList = null;
Vehicle[] vehicleArrays;
Car[] carArrays;
// illegal
carList = vehicleList;
// illegal
vehicleList = carList;
// illegal
carArrays = vehicleArrays;
// legal because array provides covariant return type
vehicleArrays = carArrays;
}
}
class Vehicle {
}
class Car extends Vehicle {
}

Related

How To Ensure the Validity of Parameterized Types Prior to Object Instantiation

While studying Item 23 of Effective Java 2nd Edition I decided to attempt instantiating an object with a constructor that has a raw collection with an argument list of mixed objects. I assumed the compiler would pick up that I was attempting to pass a raw type to the constructor. However, the compiler only provides an unchecked exception warning, "at the caller, not the callee."
The class compiles fine, further supporting the statements by Josh Bloch not to use raw types in new code. The fact this compiles is a bit disturbing to me.
Q? How can one ensure the type safety of a class prior to instantiating the object short of having to manually check for object validity in the constructor? The following only provided an unchecked assignment warning from "the caller... in main" How could one defensively program against this.
public class RawType {
private final Collection<Example> example;
public RawType( Collection<Example> example ) {
this.example = example;
}
public Collection<Example> getExample() {
return example;
}
public static void main( String[] args ) {
Collection unsafe = new ArrayList();
unsafe.add( new Example() );
unsafe.add( new Corruption() );
unsafe.add( new Example() );
RawType rawType = new RawType( unsafe ); // Corruption
for(Object type : rawType.getExample()) {
System.out.println(type.getClass().getName()); // Mixed Classes...
}
}
static class Corruption {}
static class Example{}
}
Below lines compile but generate warnings as well.
Collection unsafe = new ArrayList(); // RAW type
unsafe.add(1); // no compile time error (just warning)
Collection<String> example = unsafe; // assign RAW type to parametrized type
example.add(1); // compile time error
Better use Generic collection to avoid such situation then add the values in it. Never mix RAW and parametrized type.
Have a look at below code:
Collection<Integer> unsafe = new ArrayList<Integer>();
unsafe.add(1);
Collection<String> example = unsafe; // compile time error
Read more...
If you can't avoid RAW type then use below code in the constructor:
Collection unsafe = new ArrayList();
unsafe.add(1);
unsafe.add("hello");
// in the constructor, just add String values from the unsafe collection
Collection<String> example = new ArrayList<String>();
for(Object value:unsafe){
if(value instanceof String){
example.add((String)value);
}
}
In order to make java generics compatible with code written before they were introduced, you can use an untyped collection anywhere that a typed collection is expected. So a Collection (with no type parameter) can be passed to a constructor that expects a Collection.
So in answer to your collection, you can only be safe by being careful when you add objects to an untyped colelction. If you want runtime sadfety you can manually validate the type of objects as you retreive them from the collection.

Java Undefined Generics Brackets Usage

I recently finished an online course in AP Computer Science (Java) and on the final exam there was a question that went something like this:
Which of these needs a String cast to use String methods on it:
I. ArrayList a = new ArrayList();
II. ArrayList<Object> b = new ArrayList<Object>();
III. ArrayList<String> c = new ArrayList<String>();
Something about this confused me: can option I ever even be able to be casted? It has no generic definition so, unless the Java compiler defauted to ArrayList<Object>, what class is E then?
This is my test code (the suppress comments are needed because this is an "unchecked" operation):
ArrayList a = new ArrayList();
#SuppressWarnings("unchecked")
a.add(new Object());
#SuppressWarnings("unchecked")
a.add(new String("test"));
#SuppressWarnings("unchecked")
a.add(null);
System.out.println((String)(a.get(0)));
No matter what is in the arguments for the add() method, it always gives the compiler error:
test.java:14: error: <identifier> expected
a.add(new Object());
^
If I try to add an identifier anywhere on the code (e.g.: a<Object>.add(new Object())) it gives the exact same error as before.
The question is what is actually happening when no parameter is passed to the generics parameter and can anything be added to this list in the first place, let alone cast into another object? Thanks in advance!
You have a simple syntax error. #SuppressWarnings can't be used on aribrary statements like a.add(x), only class declarations, method declarations and variable declarations. That's why it asks for an identifier, the #SuppressWarnings is expecting to see one of those three, not a simple statement.
public static void main(String[] args) {
ArrayList a = new ArrayList();
a.add(new Object());
a.add(new String("test" );
a.add(null);
System.out.println((String) (a.get(0)));
}
The code compiles fine like this.
So what happens when you use a raw type? Nothing. No type checking is done. The above code produces a java.lang.ClassCastException error on the println statement. It doesn't catch that you put an Object in when you meant to put a String, and it doesn't know until runtime that your cast on the println will be bad.
When it is not specified, it defaults to the bound of the generic, and if that is not specified it is by default object.
This means that :
List lst = new ArrayList();
Is totally equivalent to :
List<Object> lst = new ArrayList<Object>();
On such a list, everything can be added or retrieved, because everything in Java extends Object, except primitives (but then autoboxing comes into play).
So, you can correctly write :
lst.add("String");
lst.add(new Date());
lst.add(new Integer(1));
lst.add(1); // It is converted to an Integer by auto-boxing
And you can correctly write :
String str = (String)lst.get(0);
But you have to be aware of possible runtime cast exceptions :
String str2 = (String)lst.get(1); // Will give error, cause element 1 is a date.
If you have a specific ArrayList subclass that defines a generic bound, like for example :
public class MyList<E extends MyClass> extends ArrayList<E> {
The you can still use it without generics :
MyList lst = new MyList();
But it will accept only MyClass or subclasses of MyClass :
lst.add(new MyClass());
lst.add(new MySubClass());
MyClass a = lst.get(0);
MyClass b = lst.get(1);
MySubClass c = (MySubClass)lst.get(1);
But nothing else :
lst.add("String"); // Will give compile time error

Why List<String> is not acceptable as List<Object>? [duplicate]

This question already has answers here:
Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic?
(19 answers)
Closed 8 years ago.
Consider below method doSomething(List<Object>) which accepts List<Object> as parameter.
private void doSomething(List<Object> list) {
// do something
}
Now consider below code snippet which tries to call doSomething() where I try to pass List<String> to doSomething()
List<Object> objectList;
List<String> stringList;
doSomething(stringList); // compilation error incompatible types
doSomething(objectList); // works fine
Even below code throws compilation error
objectList = stringList; // compilation error incompatible types
My question is why List<String> can not be passed to a method which accepts List<Object>?
Because while String extends Object, List<String> does not extend List<Object>
Update:
In general, if Foo is a subtype (subclass or subinterface) of Bar, and G is some generic type declaration, it is not the case that G<Foo> is a subtype of G<Bar>.
This is because collections do change. In your case, If List<String> was a subtype of List<Object>, then types other than String can be added to it when the list is referenced using its supertype, as follows:
List<String> stringList = new ArrayList<String>;
List<Object> objectList = stringList;// this does compile only if List<String> where subtypes of List<Object>
objectList.add(new Object());
String s = stringList.get(0);// attempt to assign an Object to a String :O
and the Java compiler has to prevent these cases.
More elaboration on this Java Tutorial page.
You could put an object of a wrong type into the list IF this worked:
private void doSomething(List<Object> list) {
list.add(new Integer(123)); // Should be fine, it's an object
}
List<String> stringList = new ArrayList<String>();
doSomething(stringList); // If this worked....
String s = stringList.get(0); // ... you'd receive a ClassCastException here
This generic question in Java may look confusing to any one who is not very familiar with Generics as in first glance it looks like String is object so List<String> can be used where List<Object> is required but this is not true. It will result in compilation error.
It does make sense if you go one step further because List<Object> can store anything including String, Integer etc but List<String> can only store Strings.
Also have a look at: Why not inherit from List<T>?
The reason for these limitations have to do with variance considerations.
Take the following code:
public void doSomething(List<Object> objects)
{
objects.add(new Object());
}
Expanding your example, you could try to do the following:
List<String> strings = new ArrayList<String>();
string.add("S1");
doSomething(strings);
for (String s : strings)
{
System.out.println(s.length);
}
Hopefully it's obvious why this would break if the compiler allowed this code to be compiled (which it doesn't) - a ClassCastException would occur for the second item in the list when trying to cast the Object to a String.
To be able to pass generalized collection types, you need to do this:
public void doSomething(List<?> objects)
{
for (Object obj : objects)
{
System.out.println(obj.toString);
}
}
Again, the compiler is watching your back and were you to replace the System.out with objects.add(new Object()) the compiler wouldn't allow this because objects could have been created as List<String>.
For more background on Variance see the Wikipedia artical Covariance and contravariance
From Java Tutorials of Generics:
Let's test your understanding of generics. Is the following code snippet legal?
List<String> ls = new ArrayList<String>(); // 1
List<Object> lo = ls; // 2
Line 1 is certainly legal. The trickier part of the question is line 2. This boils down to the question: is a List of String a List of Object. Most people instinctively answer, "Sure!"
Well, take a look at the next few lines:
lo.add(new Object()); // 3
String s = ls.get(0); // 4: Attempts to assign an Object to a String!
Here we've aliased ls and lo. Accessing ls, a list of String, through the alias lo, we can insert arbitrary objects into it. As a result ls does not hold just Strings anymore, and when we try and get something out of it, we get a rude surprise.
The Java compiler will prevent this from happening of course. Line 2 will cause a compile time error.
Source : Generics and Subtyping
It is sometimes expected that a List<Object> would be a supertype of a List<String> , because Object is a supertype of String .
This expectation stems from the fact that such a type relationship exists for arrays:
Object[] is a supertype of String[] , because Object is a supertype of String . (This type relationship is known as covariance .)
The super-subtype-relationship of the component types extends into the corresponding array types.
No such a type relationship exists for instantiations of generic types. (Parameterized types are not covariant.)
Check here for more details
If you are not sure what datatype it will take in you can make use of Generics in Java as follows
public static void doSomething(List<?> data) {
}
public static void main(String [] args) {
List<Object> objectList = new ArrayList<Object>();
List<String> stringList = new ArrayList<String>();
doSomething(objectList);
doSomething(stringList);
}
But while using the data, you will be required to specify proper data type as a Type Cast

Java generics unchecked conversion warning for ArrayList array of arrays

I'm trying to define an array for 3 ArrayLists, each containing a double array. I've used the following syntax:
ArrayList<double[]> testSamples[] = new ArrayList[] {
new ArrayList<double[]>(), new ArrayList<double[]>(), new ArrayList<double[]>()
};
However this generates a "warning: [unchecked] unchecked conversion" warning when I compile the code. Note: the code works fine, I'm just tried to fix my syntax (correctly) to resolve the warning. Since the code works, it appears to be supported by Java, I'm baffled as to why I can't write something like (which generates a compile error):
ArrayList<double[]> testSamples[] = new ArrayList<double[]>[] {
new ArrayList<double[]>(), new ArrayList<double[]>(), new ArrayList<double[]>()
};
What am I doing wrong?
You're implicitly converting from an array of the raw type ArrayList to an array of ArrayList<double[]>s. All in all, you shouldn't do something like this. If you know you will have 3 lists, then you can create a class to hold them instead:
class Container {
private ArrayList<double[]> list1;
private ArrayList<double[]> list2;
private ArrayList<double[]> list3;
... // constructors and whatnot
public ArrayList<double[]> getList(int i) { // analog of testSamples[i]
switch (i) {
case 0:
return list1;
case 1:
return list2;
case 2:
return list3;
default:
throw new IllegalArgumentException();
}
}
}
If the number of lists isn't fixed at 3, then you can use a List<ArrayList<double[]>> instead.
This is the usual problem with creating an array of a parameterized type. Why you cannot use new to create an array of a parameterized type is a long topic that has been covered many times here; it basically has to do with how arrays perform runtime checks on the element types, but runtime checks cannot check type parameters.
Long story short, an unchecked warning of some kind is unavoidable (because it is indeed possible to make it violate the guarantees of the array type). If you don't mind the "unsafeness", the most kosher way to write it is to create the array using a wildcarded type instead of a raw type, and cast it to the proper type of array (yes, this is an unchecked cast):
ArrayList<double[]>[] testSamples =
(ArrayList<double[]>[]) new ArrayList<?>[] { ... }

Java: Type safety - unchecked cast

Here is my code:
Object[] data = GeneComparison.readData(files);
MyGenome genome = (MyGenome) data[0];
LinkedList<Species> breeds = (LinkedList<Species>) data[1];
It gives this warning for the LinkedList:
Type safety: Unchecked cast from Object to LinkedList<Species>
Why does it complain about the linked list and not MyGenome?
Java complains like that when you cast a non-parameterized type (Object) to a parameterized type (LinkedList). It's to tell you that it could be anything. It's really no different to the first cast except the first will generate a ClassCastException if it is not that type but the second won't.
It all comes down to type erasure. A LinkedList at runtime is really just a LinkedList. You can put anything in it and it won't generate a ClassCastException like the first example.
Often to get rid of this warning you have to do something like:
#SuppressWarning("unchecked")
public List<Something> getAll() {
return getSqlMapClient.queryForList("queryname");
}
where queryForList() returns a List (non-parameterized) where you know the contents will be of class Something.
The other aspect to this is that arrays in Java are covariant, meaning they retain runtime type information. For example:
Integer ints[] = new Integer[10];
Object objs[] = ints;
objs[3] = "hello";
will throw a exception. But:
List<Integer> ints = new ArrayList<Integer>(10);
List<Object> objs = (List<Object>)ints;
objs.add("hello");
is perfectly legal.
Because here:
MyGenome genome = (MyGenome) data[0];
You are not using generics
And here
LinkedList<Species> breeds = (LinkedList<Species>) data[1];
You are using them.
That's just a warning, you are mixing types in the data array. If you know what are you doing ( I mean, if the second element do contains a LinkedList ) you can ignore the warning.
But better would be to have an object like this:
class Anything {
private Object [] data;
public Anything( Object [] data ) {
this.data = data;
}
public Gnome getGnome() {
.....
}
public List<Species> getBreeds() {
......
}
}
And have to methods returning proper things, prior to a correct conversion so you end up with:
Anything anything = new Anything( GeneComparison.readData(files) );
MyGenome genome = anything.getGnome(); // similar to data[0]
LinkedList<Species> breeds = anything.getBreeds(); // similar to data[1];
Inside those methods you have to do proper transformations.

Categories