9

I've been searching for an answer for this but to no avail. My question is why is it not possible to downcast with generics. I have a class called Job and extends a class called Model

Job extends Model 

Now I get a collection of Jobs from a reusable code that generates a list of Models

// error: Cannot cast from List<Model> to List<Job> List<Job> jobs = (List<Job>) jobMapper.fetchAll(); 

where jobMapper.fetchAll() returns a List where each model inside it is a Job object.

I assumed this would work because I can do:

EditText mUsername = (EditText) findViewById(R.id.editUserName); 

which is a simple downcasting.

4
  • Job is a subclass of Model, Job[] is a subclass of Model[], but List<Job> is not a subclass of List<Model>. Commented Mar 15, 2013 at 8:53
  • @JermaineXu: "subtype" is more correct than "subclass" here. As List<Job> is not really a class, but a type. (And you could argue that Job[] isn't a class either). Commented Mar 15, 2013 at 9:13
  • Exactly, thanks for your correction. @JoachimSauer Commented Mar 15, 2013 at 13:36
  • This is not down-casting. This is up-casting, and in virtually any situation where you need to do something like that you are doing something terribly wrong. You should rewrite whatever is necessary so that you do not need to do such a thing. Commented May 11, 2022 at 7:06

4 Answers 4

12

You cant do this, because Java does not allows this. Read this. You should do the trick:

 List<Job> jobs = (List<Job>) ((List<?>)jobMapper.fetchAll()); 
Sign up to request clarification or add additional context in comments.

5 Comments

Great! That did the trick. While I get an unchecked cast warning, I can live with that because I'm pretty sure of the values being cast. Thanks for your help. Appreciate it a lot!
By the way: that's a pretty bad idea. It only hides the warning and effectively removes all generic type information (and all guarantees you get from generics) from this list.
@Joachim is there a better way to do this? can you please post an answer. thanks
@JonasTandinco: a better way is to return a List<Model> when you need a List<Model>. The type system is designed to disallow casting List<Job> to List<Model>, because it can't check if the cast is valid at runtime (i.e. it is an "unchecked cast"!). It is allowed to make legacy (pre-generics) code compile with generics code.
+1 to @JoachimSauer. If Java tells you that it doesn't want to do something, means that you doing something wrong and should redesign your code if it's possible.
4

You can do the following:

List<Job> jobs = (List) (jobMapper.fetchAll()); 

(And suppress the warning if you're convinced it's safe in your case)

The compiler doesn't allow the cast you tried because once you have a List<Job> and a List<Model> pointing to the same list, you can add Model instances to the latter, and make the List<Job> have a Model item in it, which breaks the type safety.

Therefore, be careful when allowing this kind of tricks - it can come back to you later in a form of ClassCastException where you don't expect it to occur.

Regarding your last question: Note that while Job is a Model and Job[] is a Model[], it's not true for collections: List<Job> is not a List<Model>. This is a bit surprising, but it follows from my explanation above. It would ruin type safety to allow this cast without warning/error.

Comments

0
List<Job> jobs = jobMapper.fetchAll(); 

Is wrong, it is never explicit a list of Jobs.

Use

List<? super Job> jobs = jobMapper.fetchAll(); 

instead.

5 Comments

This only allows iterating over jobs as Objects (And casting them to Job).
Because it is never explicit a list ob Jobs. :D
I'm sorry but this does not work for me. While the line only issues a warning. I can no longer use the jobs variable as I'm using it in an array adapter ArrayAdapter<Job> and could no longer add this collection to the adapter as it no longer recognizes it as List<Job>
Because you can not be sure that fetchAll always returns a list of Jobs. Using this kind of logic, you can cast java.lang.System to Cloneable and clone it to get a brand new PC.
Ok, so you give me a -1 because my solution might not work depending on the actual type / class hierarchy of jobMapper, and instead give a solution that you already know will never work for the OP's problem? Hmmm....
0

It is not allowed because it would/could lead to runtime errors. What if the list already contained objects that are not of class Job?

You should either:

  1. Change jobMapper.fetchAll() to return List<Job>.

  2. Cast the object instead of the list, ie. Job job = (Job) jobs.get(0).

6 Comments

In the similar fashion the cast of View to EditText in the findViewById() example could lead to RuntimeExceptions.. but it is allowed nevertheless.
I cannot make jobMapper.fetchAll() to return List<Job> because fetchAll is a method of a base class that I am reusing. I am also doing userMapper.fetchAll() where it returns a User object (User extends Model).
@baske: The cast to EditText casts an object. You have to expect a ClassCastException when you do a cast like that. But when retrieving an object from a generic container, you do not cast the element returned and so you won't ever get a ClassCastException (unless you cast your collection and ignore all warnings - see Joachim Sauer's comment to the solution by Leonidas).
@PeterRader: Yes, but that just means OP didn't provide enough information. The big difference is, that when using (Job) jobs.get(0) you will get the ClassCastException at the site of your cast. When casting your list, you will not get a ClassCastException (because generic type information is discarded by the compiler) before trying to actually retreieve an item from the list (where the compiler implicitly adds a cast to Job). This might be quite far away from the code where you cast the list and might prove to be hard to debug.
@Axel: The cast to EditText casts a View, not an Object.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.