2

I have 3 classes:

class Class1 {}
class Class2 extends Class1 {}
class Class3 extends Class1 {}

I am trying to change the type of array containing Class1 so:

Class1[] arr = ...  // [Class2, Class2, Class3, Class3]
arr = Arrays.copyOf(arr,2)
Class2[] arr2 = (Class2[]) arr

which seems to be impossible yet I can do

Class2[] arr2 = new Class2[arr.length]
for ...
    arr2[i] = (Class2) arr[i]

Is there a way to do this without iterating?

We can assume all the elements in arr (after copyOf) can be typed to Class2.

The error I get is:

CassCastException: [LStuff.Class1; cannot be cast to [LStuff.Class2;

Zerg Overmind
  • 955
  • 2
  • 14
  • 28
  • 1
    By `Class2[] arr2 = (Class1[]) arr` you probably meant `Class2[] arr2 = (Class<<2>>[]) arr` - `<<...>>` was used for readability, not for syntax. – Pshemo Oct 28 '17 at 18:21
  • No actually that was exactly what i used in my code. So that way i can change the type? – Zerg Overmind Oct 28 '17 at 18:22
  • I think System.arraycopy will do this faster than a for loop. – cpp beginner Oct 28 '17 at 18:23
  • Notice you are casting `Class1[] arr` to `Class1[]` so you are not changing any type. `Class2[] arr2 = (Class1[]) arr;` is like writing `Class2[] arr2 = arr;` – Pshemo Oct 28 '17 at 18:23
  • My bad, that was a type, i did cast it to the other class. but it doesnt work. Also i want to make it work it with Array,copyOf that is why im trying to type it. I want to get all the Class2 at the front, then make the copy of the front part with just that part and change the type – Zerg Overmind Oct 28 '17 at 18:24
  • Define ["doesn't work"](http://importblogkit.com/2015/07/does-not-work/). – Pshemo Oct 28 '17 at 18:25
  • 1
    Not every `Class1` can be a `Class2`, right? – Naman Oct 28 '17 at 18:25
  • I get an error saying i cant change the type – Zerg Overmind Oct 28 '17 at 18:28
  • Both your edits are contradictory!! – Naman Oct 28 '17 at 18:38
  • 1
    Compiler allows us to use `Class2[] arr2 = (Class2[]) arr` because it is possible that `Class1[] arr = ...` will be initialized like `Class1[] arr = new Class2[size];`. If you initialize `arr` with array of `Class2[]` type then exception will not be thrown. Otherwise it `arr` holds array of `Class1[]` type it can accept instances of `Class1`, which will cause problems if we will try to use them via `Class2[] arr2` like `arr2[i].methodAddedInClass2();`. Java will not take that risk. – Pshemo Oct 28 '17 at 18:40
  • @Pshemo So if i originaly had arr1, which had Class2 and Class3 (extends Class1), initiliazed as Class1, but i use copyOf to cut the front part to create array that only contains Class2, then java will not allow me to type it to Class2 because it wont risk it, ignoringless of actuall contents? – Zerg Overmind Oct 28 '17 at 18:45
  • 2
    If you have an array that was created by writing new Class1[...], it is simply impossible to change the type to a Class2[]. The ClassCastException is caused by the type of the array, not the elements of the array. It makes no difference that they are all Class2 instances. – cpp beginner Oct 28 '17 at 18:46
  • 1
    @ZergOvermind What `Class2[] arr2 = (Class2[]) arr;` cares about is type of array held by `arr`, not type of elements which are placed inside `arr` because even if at time of casting there ware no elements other than `Class2` at the future via `arr` we can place there any elements of type Class1 and its subclass like `Class3`. Letting `Class2[] arr2` handle `Class1[]` type array will not be type-safe (which is one of Java's foundations). – Pshemo Oct 28 '17 at 19:01
  • @Pshemo Could you post an answer with this explanation? – Zerg Overmind Oct 28 '17 at 19:17

5 Answers5

5

Considering you want to change the type of all the objects and put it to another array where possible; you can use filter along with map. i.e:

Class2[] result = Arrays.stream(arr).filter(e -> e instanceof Class2)
                        .map(e -> (Class2)e).toArray(Class2[]::new);
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • `stream(Class1)` needs to be corrected and how about others that are filtered? Would that require clarity of question? – Naman Oct 28 '17 at 18:30
  • You might want to relook at the question and maybe explain why shouldn't one cast other instances as the OP is trying to. – Naman Oct 28 '17 at 18:34
  • @nullpointer as of now, I am basing my answer on the example given, I cannot assume anything more. however, if there are other classes also extending `Class1` and OP really wants to use _java 8_ then they have to filter by multiple criteria which can get messy and then once they get to the map they will need to use a lambda statement rather than a lambda expression and check for the appropriate types prior to casting and so forth... is this what you're asking? – Ousmane D. Oct 28 '17 at 18:39
  • Yeah this answer is usefull. ANd Aomine, as i stated i already checked that there are no other classess extending Class1 in there. So what you are saying is that no matter what is in there java will ignore actuall type and say i can't do it? – Zerg Overmind Oct 28 '17 at 18:42
  • @ZergOvermind what my code currently does is internally iterate over all the objects in `arr` and then call `filter` which basically says "given a element `e`, is this actually an instance of type `Class2`, if it's then keep hold of it else filter it out of the intermediate stream". by the time we get to `map` we would have all objects that are instances of `Class2` which we can then safely cast and reduce it into an array of type `Class2`. – Ousmane D. Oct 28 '17 at 18:45
  • @Aomine Could you also add explanation as to why what i tried doesnt work to your answer up there? Just to make the answer more complete in case someone looks for the same thing and ends up here (since you also provided the way how to fix it). – Zerg Overmind Oct 28 '17 at 18:51
  • 1
    @Aominè my first thought when I saw this, was can you do a method reference on an operator? `Class2::intanceOf`. I know this will not work now :) – Eugene Oct 28 '17 at 20:43
  • @Eugene would have been interesting. ;) – Ousmane D. Oct 28 '17 at 20:48
  • @Eugene: you can use method references. See [my answer](https://stackoverflow.com/a/46999449/2711488)… – Holger Oct 29 '17 at 11:12
  • @Holger up vote for simplicity as usual, but that's not what I had in mind, I was more thinking for `Class::instanceOf` - but that would require a lot more work from the compiler I assume – Eugene Oct 29 '17 at 20:33
  • 1
    @Eugene: the compiler could simply compile a construct like `Class2::instanceOf` the same way as `Class2.class::isInstance`, but I doubt that saving six characters would justify the introduction of another language construct. – Holger Nov 01 '17 at 10:49
3

If you know for sure that the first two elements of you array have the desire type, you can request the appropriate array type right when creating the copy using Arrays.copyOf, i.e.

Class1[] arr = ...  // [Class2, Class2, Class3, Class3]
Class2[] arr2 = Arrays.copyOf(arr, 2, Class2[].class);

This is not a Java-8 specific solution, but there is no reason to try to use Java-8 features at all costs. The code above will throw a ClassCastException if your assumption does not hold, but depending on the application logic, this might be better than filtering elements based on their type and silently continuing with different content than expected in the case of an error.

But if you want to filter the elements at runtime, the Stream API does indeed offer the most concise solution:

Class1[] arr = ...  // [Class2, Class2, Class3, Class3]
Class2[] arr2 = Arrays.stream(arr)
   .filter(Class2.class::isInstance).map(Class2.class::cast)
   .toArray(Class2[]::new);

or

Class1[] arr = ...  // [Class2, Class2, Class3, Class3]
Class2[] arr2 = Arrays.stream(arr)
   .flatMap(o -> o instanceof Class2? Stream.of((Class2)o): null)
   .toArray(Class2[]::new);

But note that theses solutions contain more formalism than actually necessary (which is still good for documentation/readability). The toArray method does not require the result array type to be a supertype of the stream’s element type (simply because Java Generics do not support expressing this requirement), therefore, you don’t need the map step:

Class2[] arr2 = Arrays.stream(arr).filter(Class2.class::isInstance).toArray(Class2[]::new);

which is the most concise solution with filtering.

Holger
  • 285,553
  • 42
  • 434
  • 765
1

First here : Class1[] arr = ...

Elements of the arr are declared with the Class1 type.
It means that these elements are not necessarily Class2 instances.
So you may have runtime error during the cast of the array if arr doesn't refer to a Class2[] object.
For example, this may create this kind of issue :

Class1[] arr = new Class1[5];
arr[0] = new Class1();
Class2[] arr2 = (Class2[]) arr; // exception at runtime  [LClass1; cannot be cast to [LClass2;

You should use an instanceof check rather than casting directly such as :

Class2[] arr2 = (Class2[]) arr;

With Java 8, you could use filter() to make the instanceof check and combined it to toArray() to create a array of Class2[] :

   Class1[] arr = new Class1[]{new Class1(), new Class2()};
   Class2[] arr2 = Arrays.stream(arr)
            .filter(e -> e instanceof Class2)
            .toArray(Class2[]::new);

As result you will have only the Class2 instance in arr2.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • They might not neccesary be but they ARE that type because i made sure to clear all of the other types away, i just dont wanna go through loops and copy into new array. I have an array in which i organize fitting types, then cut them off with copyOf and i want to change the type into Class2 so i can return it. But for some reason i get an error, eventhe debugger confirms i have no other classess but Class2 in there yet it runs into the error. – Zerg Overmind Oct 28 '17 at 18:37
1

You can not cast base type to its child, otherwise you will get exception. Doing this:

Class2[] arr2 = new Class2[arr.length]
for ...
    arr2[i] = (Class2) arr[i] // arr might contain different type of derrived class

is the same as casting class1 (base) type to class2 (child). So, I think that the context of the problem might be wrong.

Ibrokhim Kholmatov
  • 1,079
  • 2
  • 13
  • 16
0

Yeah if you use java8 you can use lambdas to iterate and cast in a one-liner. For older versions you can also use Arrays.copyOf(...).

Here is the result of a similar question: casting Object array to Integer array error

DataWorm
  • 73
  • 5