Reusable method to transform Iterable to T[]?

2020-04-01 08:39发布

I'm trying to write a generic method to return the contents of an Iterable in array form.

Here is what I have:

public class IterableHelp
{
    public <T> T[] toArray(Iterable<T> elements)
    {
        ArrayList<T> arrayElements = new ArrayList<T>();
        for(T element : elements)
        {
            arrayElements.add(element);
        }

        return (T[])arrayElements.toArray();
    }
}

But I'm getting a compiler warning 'Note: ...\IterableHelp.java uses unchecked or unsafe operations.'

Any thoughts on another approach that would avoid such a warning?

标签: java generics
3条回答
家丑人穷心不美
2楼-- · 2020-04-01 09:02

There isn't a way to get rid of the unchecked or unsafe operations warning, or creating a TypeSafe Array without editing your method signature.

See this bug report for the gory details.

One way is to pass in a pre-allocated Array of Type T:

public class IterableHelp
{
    public <T> T[] toArray(final T[] t, final Iterable<T> elements)
    {
        int i = 0;
        for (final T element : elements)
        {
            t[i] = element;
        }
        return t;
    }
}

This gets rid of the unchecked or unsafe warning, but it also puts the onus on the calling class to create the array with the correct bounds to begin with which kind of defeats the purpose of your convenience method.

If you want to dynamically create a TypeSafe array, you really can't do it in a TypeSafe way in Java.

This works and compiles, but it doesn't solve the unchecked or unsafe cast issue, it just moves it to a different place. I had to add the @SuppressWarnings annotation to get it to stop complaining about the cast.

@SuppressWarnings({"unchecked"})
public class IterableHelp
{
    public <T> T[] toArray(Class<T> t, Iterable<T> elements)
    {
        final ArrayList<T> arrayElements = new ArrayList<T>();
        for (T element : elements)
        {
            arrayElements.add(element);
        }
        final T[] ta = (T[]) Array.newInstance(t, arrayElements.size());
        return arrayElements.toArray(ta);
    }
}
查看更多
啃猪蹄的小仙女
3楼-- · 2020-04-01 09:04

There's a method Iterables.toArray in Google Guava.

Looking at the source, it's defined as:

  /**
   * Copies an iterable's elements into an array.
   *
   * @param iterable the iterable to copy
   * @param type the type of the elements
   * @return a newly-allocated array into which all the elements of the iterable
   *     have been copied
   */
  public static <T> T[] toArray(Iterable<? extends T> iterable, Class<T> type) {
    Collection<? extends T> collection = toCollection(iterable);
    T[] array = ObjectArrays.newArray(type, collection.size());
    return collection.toArray(array);
  }

Where ObjectArrays.newArray eventually delegates to a method that looks like:

  /**
   * Returns a new array of the given length with the specified component type.
   *
   * @param type the component type
   * @param length the length of the new array
   */
  @SuppressWarnings("unchecked")
  static <T> T[] newArray(Class<T> type, int length) {
    return (T[]) Array.newInstance(type, length);
  }

So it looks like there's no way to avoid the @SuppressWarnings entirely, but you can and should at least constrain it to the smallest possible scope.

Or, better yet, just use somebody else's implementation!

查看更多
We Are One
4楼-- · 2020-04-01 09:12

You got a bigger problem than the unchecked warning. In fact, it will throw a ClassCastException at runtime if T is anything other than Object.

Try String[] foo = IterableHelp.toArray(new ArrayList<String>());

Simply put, since arrays contain the component type at runtime, to create a proper T[] you must pass in the component class as another argument (either as the class of T or T[] itself, or as an object of T or T[]), and use reflection to create the array. The form of Collection's toArray() method that takes an argument, takes in a T[] object for this reason.

查看更多
登录 后发表回答