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.

user
  • 86,916
  • 18
  • 197
  • 190
Strategist
  • 633
  • 1
  • 9
  • 11
  • Job is a subclass of Model, Job[] is a subclass of Model[], but List is not a subclass of List. – StarPinkER Mar 15 '13 at 08:53
  • @JermaineXu: "subtype" is more correct than "subclass" here. As `List` is not really a class, but a type. (And you *could* argue that `Job[]` isn't a class either). – Joachim Sauer Mar 15 '13 at 09:13
  • Exactly, thanks for your correction. @JoachimSauer – StarPinkER Mar 15 '13 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. – Mike Nakis May 11 '22 at 07:06

4 Answers4

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());
Community
  • 1
  • 1
Leonidos
  • 10,482
  • 2
  • 28
  • 37
  • 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! – Strategist Mar 15 '13 at 08:30
  • 2
    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 Sauer Mar 15 '13 at 08:31
  • @Joachim is there a better way to do this? can you please post an answer. thanks – Strategist Mar 15 '13 at 08:34
  • 1
    @JonasTandinco: a better way is to return a `List` when you need a `List`. The type system is *designed* to disallow casting `List` to `List`, 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. – Joachim Sauer Mar 15 '13 at 08:39
  • 1
    +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. – Leonidos Mar 15 '13 at 09:01
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.

Eyal Schneider
  • 22,166
  • 5
  • 47
  • 78
0
List<Job> jobs = jobMapper.fetchAll();

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

Use

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

instead.

Grim
  • 1,938
  • 10
  • 56
  • 123
  • This only allows iterating over jobs as Objects (And casting them to Job). – Eyal Schneider Mar 15 '13 at 08:47
  • Because it is never explicit a list ob Jobs. :D – Grim Mar 15 '13 at 08:50
  • 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 and could no longer add this collection to the adapter as it no longer recognizes it as List – Strategist Mar 15 '13 at 08:56
  • 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. – Grim Mar 15 '13 at 09:03
  • 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.... – Axel Mar 15 '13 at 11:08
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).

Grim
  • 1,938
  • 10
  • 56
  • 123
Axel
  • 13,939
  • 5
  • 50
  • 79
  • 1
    In the similar fashion the cast of View to EditText in the findViewById() example could lead to RuntimeExceptions.. but it is allowed nevertheless. – baske Mar 15 '13 at 08:43
  • I cannot make jobMapper.fetchAll() to return List 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). – Strategist Mar 15 '13 at 08:46
  • @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). – Axel Mar 15 '13 at 10:55
  • @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 Mar 15 '13 at 11:04
  • @Axel: The cast to EditText casts a View, not an Object. – baske Mar 15 '13 at 11:26
  • @baske: I meant you cast the object you retrieve from the collection as opposed to casting the collection. I am quite sure that a View is an object (at least in Java). I didn't say "cast to Object" or "cast from Object". – Axel Mar 15 '13 at 12:19