Создать экземпляр универсального типа в Java? - PullRequest
535 голосов
/ 16 сентября 2008

Можно ли создать экземпляр универсального типа в Java? Я думаю, основываясь на том, что я видел, что ответ no ( из-за стирания типа ), но мне было бы интересно, если кто-то может увидеть то, что мне не хватает:

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

РЕДАКТИРОВАТЬ: Оказывается, Super Type Tokens можно использовать для решения моей проблемы, но для этого требуется много основанного на отражении кода, как указано в некоторых ответах ниже.

Я оставлю это открытым на некоторое время, чтобы посмотреть, придет ли кто-нибудь что-нибудь кардинально отличное от Артима Яна Робертсона .

Ответы [ 25 ]

310 голосов
/ 16 сентября 2008

Вы правы. Вы не можете сделать new E(). Но вы можете изменить его на

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

Это боль. Но это работает. Заворачивание в фабричный шаблон делает его более терпимым

122 голосов
/ 16 сентября 2008

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

public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

Итак, когда вы создаете подкласс Foo, вы получаете экземпляр Bar, например,

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

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

95 голосов
/ 30 марта 2016

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

class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

Вы бы построили этот класс так:

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

Синтаксис String::new в этой строке является ссылкой на конструктор .

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

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));
87 голосов
/ 16 сентября 2008

Вам понадобится какая-то абстрактная фабрика того или иного вида, чтобы передать доллар:

interface Factory<E> {
    E create();
}

class SomeContainer<E> {
    private final Factory<E> factory;
    SomeContainer(Factory<E> factory) {
        this.factory = factory;
    }
    E createContents() {
        return factory.create();
    }
}
23 голосов
/ 03 сентября 2010
package org.foo.com;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Basically the same answer as noah's.
 */
public class Home<E>
{

    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass()
    {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>
    {
    }

    private static class StringBuilderHome extends Home<StringBuilder>
    {
    }

    private static class StringBufferHome extends Home<StringBuffer>
    {
    }   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException
    {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }

}
20 голосов
/ 07 января 2013

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

public final class Foo<T> {

    private Class<T> typeArgumentClass;

    public Foo(Class<T> typeArgumentClass) {

        this.typeArgumentClass = typeArgumentClass;
    }

    public void doSomethingThatRequiresNewT() throws Exception {

        T myNewT = typeArgumentClass.newInstance();
        ...
    }
}

Использование:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

Плюсы:

  • Намного проще (и менее проблематично), чем подход Super Type Token (STT) Робертсона.
  • Гораздо эффективнее, чем подход STT (который съест ваш мобильный телефон на завтрак).

Минусы:

  • Невозможно передать Class в конструктор по умолчанию (именно поэтому Foo является финальным). Если вам действительно нужен конструктор по умолчанию, вы всегда можете добавить метод установки, но тогда вы должны помнить, чтобы позвонить ей позже.
  • Возражение Робертсона ... Больше баров, чем черной овцы (хотя указание класса аргумента типа еще раз точно не убьет вас) И вопреки утверждениям Робертсона, это все равно не нарушает принцип DRY, потому что компилятор обеспечит корректность типа.
  • Не совсем Foo<L> доказательство. Для начала ... newInstance() сгенерирует воблер, если класс аргумента типа не имеет конструктора по умолчанию. Это в любом случае относится ко всем известным решениям.
  • Отсутствует полная инкапсуляция подхода STT. Хотя это не страшно (учитывая огромные потери производительности STT).
18 голосов
/ 08 августа 2014

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

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

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

Вот JavaDoc для TypeToken .

12 голосов
/ 16 апреля 2015

Подумайте о более функциональном подходе: вместо создания некоторого E из ничего (что явно является запахом кода), передайте функцию, которая знает, как ее создать, т.е.

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
8 голосов
/ 13 сентября 2012

С Учебное пособие по Java - ограничения по универсальным :

Невозможно создать экземпляры параметров типа

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

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

В качестве обходного пути вы можете создать объект параметра типа с помощью отражения:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

Вы можете вызвать метод добавления следующим образом:

List<String> ls = new ArrayList<>();
append(ls, String.class);
6 голосов
/ 16 сентября 2008

Вот вариант, который я придумал, он может помочь:

public static class Container<E> {
    private Class<E> clazz;

    public Container(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E createContents() throws Exception {
        return clazz.newInstance();
    }
}

РЕДАКТИРОВАТЬ: В качестве альтернативы вы можете использовать этот конструктор (но для этого требуется экземпляр E):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...