ArrayList<String> list = new ArrayList<String>();
list = (ArrayList<String>) Files.readAllLines(FilePath);
In the above code, if I remove the explicit casting in second line, the compiler gives an error. Why is this explicit cast required considering the fact that Files.readAllLines(FilePath) returns a List<String>, and ArrayList<String> implements List<String> ?
The method Files.readAllLines() only guarantees to return an object of type List<String>, but not of the more specific type ArrayList<String>. The actual implementation type of the returned list may vary between different JDK implementations (they just need to be sub-classes of List<String>, so while a cast to ArrayList<String> may work in your environment with your JDK implementation it may not work in another.
If you really need your list to be an ArrayList, you can use this code instead:
ArrayList<String> list = new ArrayList<String>();
list.addAll(Files.readAllLines(FilePath));
As you've said, any ArrayList<T> is a List<T>. But the opposite is not true: a List<T> is not always an ArrayList<T>.
This means that if the method readAllLines is returning a List<T>, then you can assign the value to a List<T> variable, not any of its subtypes without an explicit downcast (which should always be avoided).
I don't even see why you would want to downcast it to ArrayList<T>, the less you imply about a type, freer you are.
In your example, you are doing a downcasting, i.e. you want to cast a parent class to a child class. This is very unsafe, since a child may offer more than a parent. In your example, an ArrayList has additional functionalities.
Thus, to ensure safety, the compiler will never allow an implicit downcasting (but upcasting is ok, as a child has necessarily all the functionalities of a parent).
Related
I want to create a Java Object for a class that is defined using generics.
Specifically, i want to create a List of Objects of class that is determined at runtime.
I would want something like
Class clazz = Class.forName("MyClass");
List<clazz> myList = new ArrayList<>(); // This isn't allowed
Defining an array of object would allow me to store a list of MyClass type objects, but that would lead to casting the objects every-time the object is fetched from the list, i would like to avoid such a scenario.
Is there a way to achieve something like the above code using java.
Well, since you know that class, you could (with a warning) cast the List itself; but you would still need to know the class name and some checks for that, like for example:
if(clazz.getName().equals("java.lang.String")) {
// warning here
yourList = (List<String>) yourList;
}
As I understand your question, you want to know if is possible for the compiler to know the runtime type while it is limited to the compile type.
You can't. And your hypothesis is also wrong:
Defining an array of object would allow me to store a list of MyClass
type objects, but that would lead to casting the objects every-time
the object is fetched from the list, i would like to avoid such a
scenario.
In Java, generics does not remove the cast: it is still there in the form of type erasure and (hidden) cast. When you do List<String>, you merely ask the compiler to hide the cast in operation such as T get(int): there will be a cast to String.
If you want to use the compile time information, than that would mean you already have/know the type MyClass available at compile time and you would not use Class::forName but MyClass.class which would return a Class<MyClass>.
What you can do is either:
Use an interface if you have a common ground for theses classes (like JDBC Driver).
Cast the raw list into a known type, for example using Class::isAssignableFrom.
No, you can't. Generics in Java is just a compile-time type checking mechanism. If you don't know the type until runtime, then, obviously, it cannot be used for compile-time type checking. The compiler can't determine at compile time what types to allow you to put into or get out of a List<clazz>, so it's no more meaningful than just a raw type List.
There is a little trick that can be used to work with a specific unknown type: Declare a type parameter that is only used for the unknown type:
public <T> void worksWithSomeUnknownClass() throws ReflectiveOperationException {
#SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) Class.forName("MyClass");
T obj = clazz.getConstructor().newInstance();
List<T> myList = new ArrayList<>();
myList.add(obj);
}
This solution is very limited though. It makes sure that you don't mix it up with other unknown types or Object, but you can not really do anything with T. And you have to declare a type parameter on every method that uses it.
public void run(){
setFont("Courier-24");
//Define list as ArrayList<Integer>
ArrayList<Integer> list = new ArrayList<Integer>();
readList(list);
}
private void readList(ArrayList list){
list.add("Hello");
list.add(2);
println("list = "+list);
println("Type of list = "+list.get(0).getClass());
println("Type of list = "+list.get(1).getClass());
}
Result:
list = [Hello, 2]
Type of list = class java.lang.String
Type of list = class java.lang.Integer
Here is my code and result. My question is, how is it possible that ArrayList of type Integer can store String objects? What's the type of list now? And what mechanism is this?
Java's generics don't actually change the underlying class or object, they just provide (mostly) compile-time semantics around them.
By passing an ArrayList<Integer> into a method expecting an ArrayList (which can hold anything), you're bypassing the compiler's ability to provide you with that type safety.
The Java Generics Tutorial explains this, and why Java implements generics this way. This page, in particular, focusses on it:
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.
What that doesn't say is that this also allows code written with generics (like your run) to interact with code written without generics (like your readList), which is important when adding a feature to a very-well-established language with a huge library base (as they were when adding generics to Java).
When you declare:
private void readList(ArrayList list)
you are not specifying any type to this ArrayList so by default it is of type Object.
Both String and Integer (in fact all classes in java) are sub types of Object. Hence it is possible to add them to list.
For more information about generics without types please read here. In short generics types are for compile time checks only, so that you dont add wrong types (which can later cause exceptions. In this case your operations on String and Integer are compatible so luckily no errors).
In your readList method's parameter you haven't constrained it to only Integer value types, thus list doesn't get the benefits of compile-time checking and must resort to runtime type checking.
Declaration
ArrayList list
in the method readList is equivalent to the
ArrayList<Object> list
It's obvious that String is Object, as well as Integer. When passing to println, both will be toString'ed with their own methods.
I think that the ArrayList type without the generics specification you pass as argument is assument as ArrayList. Both String and Integer inherits Object so they both can be added in the list. However the ArrayList elements are of type Objects.
I have my first list as
List<A> a
I have another list as
List<X.Y.Z> b
How do I add first list to the second one ?
I tried casting -
b.add(List<X.Y.Z>)a) - did not work
Tried adding through iteration of first list - did not work
definitely missed something ?
Unless there is an Inheritance relationship between A and X.Y.Z you cannot have them in the same container because they are not of the same type
You can use the generic superclass Object as the type of the List and this will work.
This is not possible as the reference types for both collections are different. The only way items from one List can be merged with those from another is if they both are of type List<Object> or the types themselves are identical (or at least derived from the same type).
The reason is because of type of List<>
X.Y.Z != A
You can use List<Object>, to which you can add() anything.Even though you added like that
you would have to cast each one back,while getting back.
You need to cast list a as the same type as list b so that they are the same type of object. Check out this article
It should also be noted that if you want to add the elements of List<A> a to List<X.Y.Z> b (which I assume is your intent), rather than the List<A> a itself as an element, you should use the addAll() method, not the add() method. But again, this won't work unless A is a subclass of X.Y.Z, and if A is a super class of X.Y.Z then casting the A variable will only work if it is an instance X.Y.Z.
Either you use List<Object>, to which you can add anything, or you write a method somewhere to convert an object of type A to X.Y.Z.
Notice that, if you use List<Object>, you'll need to cast the object to the desired class when you get it:
List<Object> myList = new List<Object>;
// ...
A myObject = (A) myList.get(0);
X.Y.Z otherObject = (X.Y.Z) myList.get(1);
// ...
Consider the following case
List<Integer> l1=new ArrayList<>();
List<String> l2=new ArrayList<>();
l1.add(2);
l2.addAll((List<String>)l1);
you are trying to do the same thing. here you can't cast integer list to string list.
Same way you can't cast A type list to X.Y.Z. type.
I saw some code using Object instead of wildcard (?) as a parameter of a generic class. That leads to explicit casts in the client code. What are the benefits, resp. trade-offs for such an approach?
Using Object as the type parameter is entirely different from using ?. See these 2 methods:
void frobnicate1(List<?> someList);
void frobnicate2(List<Object> someList);
Both will receive a List and both will receive Object when they get an element from the List but frobnicate1 can be called with a List<String> or even a List<?>, while frobnicate2 can only be called by a List<Object> (or null).
Note, that instantiating a parameterized type can't be done with a wildcard type argument (?).
So the following will not work:
List<?> someList = new ArrayList<?>();
You will have to use Object (or any other non-wildcard type) instead:
List<?> someList = new ArrayList<Object>();
And don't worry about the explicit cast: The JVM can optimize it away (and probably will), since casts of any reference value to Object will always succeed.
I find it helpful to remember it this way:
List<Object> = a list where each element is treated as an instance of Object
List<?> = a list where each element is treated as a specific but unknown subclass of Object
What is the meaning of the Java warning?
Type safety: The cast from Object to List<Integer> is actually checking against the erased type List
I get this warning when I try to cast an Object to a type with generic information, such as in the following code:
Object object = getMyList();
List<Integer> list = (List<Integer>) object;
This warning is there because Java is not actually storing type information at run-time in an object that uses generics. Thus, if object is actually a List<String>, there will be no ClassCastException at run-time except until an item is accessed from the list that doesn't match the generic type defined in the variable.
This can cause further complications if items are added to the list, with this incorrect generic type information. Any code still holding a reference to the list but with the correct generic type information will now have an inconsistent list.
To remove the warning, try:
List<?> list = (List<?>) object;
However, note that you will not be able to use certain methods such as add because the compiler doesn't know if you are trying to add an object of incorrect type. The above will work in a lot of situations, but if you have to use add, or some similarly restricted method, you will just have to suffer the yellow underline in Eclipse (or a SuppressWarning annotation).