Создание экземпляра универсального массива в общем методе

Я пытаюсь создать вспомогательный метод, чтобы превратить два списка строк в преобразование массива в одну строку. Проблема, с которой я столкнулся, заключается в том, что я не уверен, как создать экземпляр T [].

Я пробовал

Array.newInstance(T.class, list.size), но я не могу передать его T.class..

Также попробовал new T[](list.size), но ему не нравятся параметры.

public <T> T[] ConvertToArray(List<T> list)
{
   T[] result = ???

   result = list.toArray(result);

   return result;
}

Любые другие идеи?

Спасибо

Ответ 1

Вы не можете смешивать общие типы и массивы. У генералов есть проверка времени компиляции, массивы имеют проверку времени выполнения, и эти подходы в основном несовместимы. Сначала я предложил следующее:

@SuppressWarnings("unchecked")
public <T> T[] ConvertToArray(List<T> list)
{      
   Object[] result = new Object[list.size()];
   result = list.toArray(result);
   return (T[])result;
}

Это неправильно в незаметном виде, поскольку по крайней мере один другой человек здесь думал, что это сработает! Однако при запуске вы получаете несовместимую ошибку типа, потому что вы не можете передать объект [] в Integer []. Почему мы не можем получить T.class и создать массив подходящего типа? Или new T[]?

Generics использует стирание типа для сохранения обратной совместимости. Они проверяются во время компиляции, но удаляются из среды выполнения, поэтому байт-код совместим с JVM с предварительным генериком. Это означает, что вы не можете иметь знание класса общей переменной во время выполнения!

Итак, пока вы можете гарантировать, что T[] result будет иметь тип Integer [] раньше времени, код list.toArray(result); (или new T[] или Array.newInstance(T.class, list.size());) произойдет только во время выполнения, и он не может знать что T есть!

Здесь версия, которая работает, в качестве награды за чтение этой лекции:

public static <T> T[] convertToArray(List<?> list, Class<T> c) {
    @SuppressWarnings("unchecked")
    T[] result = (T[]) Array.newInstance(c, list.size());
    result = list.toArray(result);
    return (T[]) result;
}

Обратите внимание, что у нас есть второй параметр для обеспечения класса во время выполнения (а также во время компиляции через дженерики). Вы использовали бы это так:

Integer[] arrayOfIntegers = convertToArray(listOfIntegers, Integer.class);

Неужели это стоит хлопот? Нам все еще нужно подавить предупреждение, так что это определенно безопасно?

Мой ответ - да. Предупреждение, созданное там, есть только "я не уверен" в компиляторе. Пройдя через него, мы можем подтвердить, что это приведение всегда будет успешным - даже если вы помещаете неправильный класс в качестве второго параметра, выдается предупреждение о компиляции.

Основным преимуществом этого является то, что мы централизовали предупреждение в одном месте. Нам нужно только доказать правильность этого места, и мы знаем, что код всегда будет успешным. Чтобы процитировать документацию по Java:

язык разработан, чтобы гарантировать, что если все ваше приложение было скомпилировано без предупреждений без использования javac-источника 1.5, оно безопасно [1]

Итак, теперь, вместо того, чтобы иметь эти предупреждения по всему вашему коду, это просто в одном месте, и вы можете использовать это без необходимости беспокоиться - там значительно снижен риск того, что вы допустили ошибку, используя его.

Вы также можете посмотреть этот SO ответ, который объясняет проблему более подробно, и этот ответ который был моим словом для кроватки при написании этого. Как и уже цитируется Java-документация, другая удобная ссылка, которую я использовал, была этот пост в блоге от Neal Gafter, бывшего старшего технического инженера Sun Microsystems и со-дизайнера 1.4 и 5.0 языковых функций.

И, конечно же, благодаря ShaneC, который справедливо указал, что мой первый ответ не удался во время выполнения!

Ответ 2

Если вы не можете пройти в T.class, тогда вы в основном ввернуты. Тип erasure означает, что вы просто не будете знать тип T во время выполнения.

Конечно, существуют и другие способы указания типов, такие как маркеры супер-типа - но я предполагаю, что если вы не можете пройти T.class, вы также не сможете передать токен типа. Если можно, то это здорово:)

Ответ 3

Проблема заключается в том, что из-за стирания типа List не знает его тип компонента во время выполнения, тогда как тип компонента - это то, что вам нужно создать массив.

Итак, у вас есть два варианта, которые вы найдете по всему API:

Единственная возможность, о которой я могу думать, - это огромная проблема:

  • Перейдите по всем элементам списка и найдите "Самый большой общий делитель", наиболее специфический класс или интерфейс, которые все элементы расширяют или реализуют.
  • Создайте массив этого типа

Но это будет намного больше строк, чем ваши два (и это также может привести к тому, что код клиента сделает недопустимые предположения).

Ответ 4

Нужно ли это менять на однострочный? С любым конкретным классом вы уже можете преобразовать List to Array в одну строку:

MyClass[] result = list.toArray(new MyClass[0]);

Конечно, это не будет работать для общих аргументов в классе.

См. Джошуа Блох Эффективное второе издание Java, пункт 25: Предпочтительные списки для массива (стр. 119-123). Что входит в образец главы PDF.

Ответ 5

Вот ближе всего я вижу, как делать то, что вы хотите. Это делает большие предположения, которые вы знаете для класса во время написания кода, и что все объекты в списке являются одним и тем же классом, то есть без подклассов. Я уверен, что это можно было бы сделать более сложным, чтобы облегчить предположение о каких-либо подклассах, но я не вижу, как в Java вы могли бы обойти предположение о знании класса во время кодирования.

package play1;

import java.util.*;

public class Play
{
  public static void main (String args[])
  {
    List<String> list=new ArrayList<String>();
    list.add("Hello");
    list.add("Shalom");
    list.add("Godspidanya");

    ArrayTool<String> arrayTool=new ArrayTool<String>();
    String[] array=arrayTool.arrayify(list);

    for (int x=0;x<array.length;++x)
    {
      System.out.println(array[x]);
    }
  }
}
class ArrayTool<T>
{
  public T[] arrayify(List<T> list)
  {
    Class clazz=list.get(0).getClass();
    T[] a=(T[]) Array.newInstance(clazz, list.size());
    return list.toArray(a);
  }
}