Инициализация типа дженериков в Java - PullRequest
56 голосов
/ 12 марта 2010

Дубликат: обобщение Java, почему это не сработает

Дубликат: создание универсального класса в Java

Я хотел бы создать объект типа Generics в Java. Подскажите, пожалуйста, как мне добиться того же.

Примечание. Это может показаться тривиальной проблемой общего характера. Но держу пари, что это не так. :)

предположим, у меня есть объявление класса:

public class Abc<T> {
    public T getInstanceOfT() {
       // I want to create an instance of T and return the same.
    }
}

Ответы [ 12 ]

65 голосов
/ 12 марта 2010
public class Abc<T> {
    public T getInstanceOfT(Class<T> aClass) {
       return aClass.newInstance();
    }
}

Вам нужно будет добавить обработку исключений.

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

26 голосов
/ 12 марта 2010

В размещенном вами коде невозможно создать экземпляр T, поскольку вы не знаете, что это за тип:

public class Abc<T>
{
       public T getInstanceOfT()
       {
           // There is no way to create an instance of T here
           // since we don't know its type
       }
} 

Конечно, это возможно, если у вас есть ссылка на Class<T>, а T имеет конструктор по умолчанию, просто вызовите newInstance() для объекта Class.

Если вы создаете подкласс Abc<T>, вы даже можете обойти проблему стирания типов, и вам не придется передавать какие-либо ссылки Class<T> вокруг:

import java.lang.reflect.ParameterizedType;

public class Abc<T>
{
    T getInstanceOfT()
    {
        ParameterizedType superClass = (ParameterizedType) getClass().getGenericSuperclass();
        Class<T> type = (Class<T>) superClass.getActualTypeArguments()[0];
        try
        {
            return type.newInstance();
        }
        catch (Exception e)
        {
            // Oops, no default constructor
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args)
    {
        String instance = new SubClass().getInstanceOfT();
        System.out.println(instance.getClass());
    }
}

class SubClass
    extends Abc<String>
{
}
13 голосов
/ 12 марта 2010

То, что вы написали, не имеет никакого смысла, универсальные в Java предназначены для добавления функциональности параметрического полиморфизма к объектам.

Что это значит? Это означает, что вы хотите, чтобы некоторые переменные типа ваших классов не определились, чтобы иметь возможность использовать ваши классы со многими различными типами.

Но ваша переменная типа T является атрибутом, который разрешается во время выполнения, компилятор Java скомпилирует ваш класс, проверяя безопасность типов, не пытаясь узнать, какой тип объекта T, поэтому для него невозможно позволить использовать переменную типа в статическом методе. Тип связан с экземпляром объекта во время выполнения, тогда как public void static main(..) связан с определением класса, и в этой области T ничего не значит.

Если вы хотите использовать переменную типа внутри статического метода, вы должны объявить метод как универсальный (это потому, что, как объяснено переменные типа класса шаблона связаны с его экземпляр времени выполнения ), а не класс:

class SandBox
{
  public static <T> void myMethod()
  {
     T foobar;
  }
}

это работает, но, конечно, не с main методом, поскольку нет способа вызвать его универсальным способом.

EDIT : Проблема в том, что из-за стирания типа 1036 * только один универсальный класс компилируется и передается в JVM. Проверка типов просто проверяет, является ли код безопасным, тогда, поскольку он доказал, что всякая общая информация отбрасывается.

Чтобы создать экземпляр T, вам нужно знать тип T, но это может быть много типов одновременно, поэтому одно решение с минимальным количеством отражений - это использование Class<T> для создания нового экземпляра. объекты:

public class SandBox<T>
{
    Class<T> reference;

    SandBox(Class<T> classRef)
    {
        reference = classRef;
    }

    public T getNewInstance()
    {
        try
        {
            return reference.newInstance();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args)
    {
        SandBox<String> t = new SandBox<String>(String.class);

        System.out.println(t.getNewInstance().getClass().getName());
    }
}

Конечно, это означает, что тип, который вы хотите создать:

  • не является примитивным типом
  • имеет конструктор по умолчанию

Чтобы работать с разными видами конструкторов, вам нужно глубже погрузиться в отражение.

5 голосов
/ 12 марта 2010

Вам необходимо получить информацию о типе статически. Попробуйте это:

public class Abc<T> {
    private Class<T> clazz;

    public Abc(Class<T> clazz) {
        this.clazz = clazz;
    }

    public T getInstanceOfT()
            throws InstantiationException, IllegalAccessException {
        return clazz.newInstance();
    }
}

Используйте это так:

        Abc<String> abc = new Abc<String>(String.class);
        abc.getInstanceOfT();

В зависимости от ваших потребностей, вы можете использовать Class<? extends T>.

4 голосов
/ 13 марта 2010

Единственный способ заставить его работать - это использовать Reified Generics . И это не поддерживается в Java (пока - это было запланировано для Java 7, но было отложено). Например, в C # поддерживается, предполагая, что T имеет конструктор по умолчанию . Вы даже можете получить тип времени выполнения по typeof(T) и получить конструкторы по Type.GetConstructor(). Я не делаю C #, поэтому синтаксис может быть неверным, но он выглядит примерно так:

public class Foo<T> where T:new() {
    public void foo() {
        T t = new T();
    }
}

Лучший «обходной путь» для этого в Java - передать Class<T> в качестве аргумента метода вместо этого, как уже отмечалось в нескольких ответах.

3 голосов
/ 12 марта 2010

Прежде всего, вы не можете получить доступ к параметру типа T в методе static main, только на нестатических членах класса (в данном случае).

Во-вторых, вы не можете создать экземпляр T, потому что Java реализует обобщения с Типом Erasure . Почти вся общая информация стирается во время компиляции.

В принципе, вы не можете сделать это:

T member = new T();

Вот хороший урок по generics .

2 голосов
/ 12 марта 2010

Кажется, вы не понимаете, как работают дженерики. Вы можете посмотреть на http://java.sun.com/j2se/1.5.0/docs/guide/language/generics.html В основном то, что вы могли бы сделать, это что-то вроде

public class Abc<T>
{
  T someGenericThing;
  public Abc(){}
  public T getSomeGenericThing()
  {
     return someGenericThing;
  }

  public static void main(String[] args)
  {
     // create an instance of "Abc of String"
        Abc<String> stringAbc = new Abc<String>();
        String test = stringAbc.getSomeGenericThing();
  }

} 
1 голос
/ 15 мая 2010

Тип решения Erasure

Вдохновленный ответом @ martin , я написал вспомогательный класс, который позволяет мне обойти проблему стирания типа. Используя этот класс (и небольшой уродливый трюк), я могу создать новый экземпляр из шаблона:

public abstract class C_TestClass<T > {
    T createTemplateInstance() {
        return C_GenericsHelper.createTemplateInstance( this, 0 );
    }

    public static void main( String[] args ) {
        ArrayList<String > list = 
            new C_TestClass<ArrayList<String > >(){}.createTemplateInstance();
    }
}

Гадкий трюк в том, чтобы сделать класс abstract, чтобы пользователь этого класса был вынужден подтипить его. Здесь я делю его на подклассы, добавляя {} после вызова конструктора. Это определяет новый анонимный класс и создает его экземпляр.

После того, как универсальный класс подтипирован с конкретными типами шаблонов, я могу получить типы шаблонов.


public class C_GenericsHelper {
    /**
     * @param object instance of a class that is a subclass of a generic class
     * @param index index of the generic type that should be instantiated
     * @return new instance of T (created by calling the default constructor)
     * @throws RuntimeException if T has no accessible default constructor
     */
    @SuppressWarnings( "unchecked" )
    public static <T> T createTemplateInstance( Object object, int index ) {
        ParameterizedType superClass = 
            (ParameterizedType )object.getClass().getGenericSuperclass();
        Type type = superClass.getActualTypeArguments()[ index ];
        Class<T > instanceType;
        if( type instanceof ParameterizedType ) {
            instanceType = (Class<T > )( (ParameterizedType )type ).getRawType();
        }
        else {
            instanceType = (Class<T > )type;
        }
        try {
            return instanceType.newInstance();
        }
        catch( Exception e ) {
            throw new RuntimeException( e );
        }
    }
}
1 голос
/ 12 марта 2010

Я реализовывал то же самое, используя следующий подход.

public class Abc<T>
{
   T myvar;

    public T getInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException
    {
        return clazz.newInstance();
    }
}

Я пытался найти лучший способ добиться того же.

Разве это не возможно?

0 голосов
/ 18 августа 2017

Есть обходные пути, когда вам действительно нужно это сделать.

Вот пример метода преобразования, который я считаю очень полезным; и предоставляет один способ определить конкретный класс универсального.

Этот метод принимает коллекцию объектов в качестве входных данных и возвращает массив, в котором каждый элемент является результатом вызова метода получения поля для каждого объекта во входной коллекции. Например, скажем, у вас есть List<People> и вы хотите String[], содержащий фамилию каждого.

Тип значения поля, возвращаемого получателем, определяется универсальным E, и мне нужно создать экземпляр массива типа E[] для хранения возвращаемого значения.

Сам метод немного уродлив, но код, который вы пишете, использует его, может быть намного чище.

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

public <E> E[] array (Collection c) {
    if (c == null) return null;
    if (c.isEmpty()) return (E[]) EMPTY_OBJECT_ARRAY;
    final List<E> collect = (List<E>) CollectionUtils.collect(c, this);
    final Class<E> elementType = (Class<E>) ReflectionUtil.getterType(c.iterator().next(), field);
    return collect.toArray((E[]) Array.newInstance(elementType, collect.size()));
}

Полный код здесь: https://github.com/cobbzilla/cobbzilla-utils/blob/master/src/main/java/org/cobbzilla/util/collection/FieldTransformer.java#L28

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...