I've always wondered why generics mess this up

I figured generics would create less work for me and more extensible architectures.

This is what it comes down to:


public Keys[] getKeys()
      {
            Keys[] keys = new Keys[this.keys.size()];
            System.arraycopy(this.keys.toArray(), 0, keys, 0, this.keys.size());
            return keys;
      }

Everytime I use “collection.toArray()” and cast it to “(Type[])collection.toArray()” I get a ClassCastException.
I managed to do some reading on it and found out that it was the fault of generics.

What I want to know is if are Sun going to fix this issue? I have found absolutely no indication that they are.

However if they are going to fix the issue, are they going to replace type erasure in generics or will they opt for a walkaround?

collection.toArray() returns an Object[].
You can’t cast an Object[] into a Type[] unless it actually IS a Type[] (or subclass thereof). You never could.

You’re probably looking for collection.toArray(new Type[0]), as that will create a new Type[] big enough to hold the collection, and return that (as an Object[]).

Note that if you want to save some copying and allocation, you can do collection.toArray(new Type[collection.size()]).

[quote]new Type[0]
[/quote]
Holy crap! That’s legal and works? Oddly enough I’ve never even thought of trying to declare a zero-length array. :o

Yup, it’s the bog-standard way of utilizing the toArray method (that I’ve been using since 1.1.x to workaround the design flaws in the whole array-casting-scenario outlined here).

It’s not nice as something to have to do, but as soon as you discover that xxx[] cannot be cast to yyy[] and understand why, it’s easy to remember :).

Neat, thanks for that.
I really should stop reading Sun’s forums. :confused:

Another issue with generics.

I have basically hacked together a generic array because generics doesn’t allow me to do so.
Honestly, Sun really needs to scrap the current implementation.

NOTE: This class isn’t complete, just an example.


public class FastestCollection<E>
{
      private E[] items;
      private int idx;
      
      
      public void add(E s) {            
            this.items[idx++] = s;
      }
      
      public void resize(E[] e)
      {
            if(e.length > items.length)
            {
                  System.arraycopy(items, 0, e, 0, e.length);
                  this.items = e;
            }
      }
      
      public void remove(E s) {
            for (int i=0; i < items.length; i++)
            {
                  if(items[i] == s)
                        items[i] = null;
            }
      }
      
      public boolean contains(E s) {
            boolean itemFound = false;
            
            for (int i=0; i < items.length; i++)
            {
                  if(items[i] == s)
                  {
                        itemFound = true;
                        break;
                  }
            }
            
            return itemFound;
      }
      
      
      public FastestCollection(E[] v) {
            items = v;
      }

      /* (non-Javadoc)
       * @see java.util.ArrayList#size()
       */
      public int size()
      {
            return items.length;
      }

      /**
       * @param e An array of the type
       * @return
       */
      public E[] getStates(E[] e)
      {
            return this.items;
      }
}

It’s sick. It’s reall, really sick.
Maybe I should just stick to the collections framework and rework my own custom fast collection? Looks like I have no choice.

I love it when people write things like FastestCollection, FastRand, etc. It implies that they wrote a corresponding SlowCollection, SlowRand as well just for the hell of it. ;D

[quote]I love it when people write things like FastestCollection, FastRand, etc. It implies that they wrote a corresponding SlowCollection, SlowRand as well just for the hell of it. ;D
[/quote]
I did.
Before my “SlowestCollection” class returns a result, it casts 10000 strings to objects and back to strings again.

Actually you can be explicit and do:

Type [] blah = (Type[])list.toArray(new Type[list.size()]);

Then toArray will use the actual array that you pass it and not have to allocate another one.

/me stands corrected :slight_smile:

I suppose “bog standard” doesn’t necessarily mean “optimal” :wink:

Can someone explain why Eclipse doesn’t like this?
ArrayList does this.


Type[] arrayType = (Type[])new Object[5];

[quote][…]
Note that if you want to save some copying and allocation, you can do collection.toArray(new Type[collection.size()]).
[/quote]
Ok, so I don’t know what “copying” I speak about getting saved there. :wink: But it made sense at the time.

[quote]Can someone explain why Eclipse doesn’t like this?
ArrayList does this.


Type[] arrayType = (Type[])new Object[5];

[/quote]
That is not whay ArrayList does. It uses reflection to create an array of the correct type.

No! It does that. I copied it straight from the Java ArrayList source code and changed it.

No, it doesn’t. That cast is not legal java.

[edit:]
Just to prove you wrong, I checked the code for ArrayList:

public Object[] toArray(Object a[]) {
  if (a.length < size)
    a = (Object[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
  System.arraycopy(elementData, 0, a, 0, size);

  if (a.length > size)
    a[size] = null;

  return a;
}

[quote]No, it doesn’t. That cast is not legal java.
[/quote]
Actually, it IS legal java, you only get a compiler warning. And ArrayList indeed uses such a statement, but only for its internal array. Reflection is only used for the type-safe toArray you mentioned (there’s also the old toArray that returns a new Object[]).

Holy moly, is it!

Thanks for correcting me. =)
It lead to some interesting reading about array type safety checks.

[edit:]
Premature correction. Check two posts down.

Care to fill me in?
BTW why is it a compiler warning?

[quote]Holy moly, is it!

Thanks for correcting me. =)
It lead to some interesting reading about array type safety checks.
[/quote]

Compare:

String string = (String)new Object();
String[] strings = (Strings[])new Object[10];

The first one obviously won’t work.
The second one will break the following method, as you can pass an Integer[] that contains, say, a String:

private static void printAll(Integer[] ints)
{
      for (int i = 0; i < ints.length; i++)
      {
            System.out.println(ints[i].intValue());
      }
}

However, I exerimented some, and it seems you really CAN’T do that cast after all:

public class A
{
      public static void main(String[] args)
      {
            Integer[] test = (Integer[])new Object[0];
      }
}
C:\foo>javac A.java

C:\foo>java A
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object;
        at A.main(A.java:5)

C:\foo>java -version
java version "1.5.0_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_02-b09)
Java HotSpot(TM) Client VM (build 1.5.0_02-b09, mixed mode)

So now I’m confused.

[quote]So now I’m confused.
[/quote]
It is valid java code, because you can have something like this:

Object[] unknown = new String[10];
String[] ok = (String[])unknown;

You need to able to perform such a cast, so it’s not an error. It’s a warning though, because the compiler cannot make sure that the cast will succeed; it’s up to the programmer.