Вопрос по generics, java – Создание универсального класса в Java

140

Я знаю, что дженерики Java несколько уступают .Net.

У меня есть общий классFoo<T>и мне действительно нужно создать экземплярT вFoo используя конструктор без параметров. Как можно обойти ограничения Java?

Это всегда беспокоило меня. Не эксперт по Java, а профессор недавно сказал мне: вы просто не можете. colithium
Что является «рефлексивно воплощенным» имею в виду? Selah
Обычным решением этой проблемы в Java является использование класса & lt; T & gt; объект в качестве токена типа, а затем используйте его для рефлексивного создания экземпляра нужного вам объекта. scottb

Ваш Ответ

10   ответов
4

constructor

Простой ответ: «Вы не можете сделать это» Java использует стирание типов, чтобы внедрить непатентованные значения, которые не позволят вам сделать это.

How can one work around Java's limitation?

Один из способов (могут быть и другие) - передать объект, который вы передадите, экземпляру T в конструкторFoo<T>, Или вы могли бы иметь методsetBar(T theInstanceofT); чтобы получить свой T вместо того, чтобы создавать его в своем классе.

7

нта конструктора. Вам необходимо расширить параметризованный абстрактный класс.

public class Test {   
    public static void main(String [] args) throws Exception {
        Generic g = new Generic();
        g.initParameter();
    }
}

import java.lang.reflect.ParameterizedType;
public abstract class GenericAbstract<T extends Foo> {
    protected T parameter;

    @SuppressWarnings("unchecked")
    void initParameter() throws Exception, ClassNotFoundException, 
        InstantiationException {
        // Get the class name of this instance's type.
        ParameterizedType pt
            = (ParameterizedType) getClass().getGenericSuperclass();
        // You may need this split or not, use logging to check
        String parameterClassName
            = pt.getActualTypeArguments()[0].toString().split("\\s")[1];
        // Instantiate the Parameter and initialize it.
        parameter = (T) Class.forName(parameterClassName).newInstance();
    }
}

public class Generic extends GenericAbstract<Foo> {
}

public class Foo {
    public Foo() {
        System.out.println("Foo constructor...");
    }
}
@newacct на самом деле, я думаю, что это может помочь мне создать BuffedArrayList, который, наконец, поможет мне избежать этого противного toArray (new Something [0]), поэтому я могу иметьT toArray() hasslelessly!
@ Гленн, это работает и для ArrayList, но я должен создать его так:new ArrayList<Foo>(){} понятия не имею, почему ...
хорошо, я вижу. Вы даже можете написать его более просто как анонимный класс: & quot; GenericAbstract & lt; Foo & gt; g = new GenericAbstract & lt; Foo & gt; () {}; g.initParameter (); & Quot; и & lt; T расширяет Foo & gt; не является необходимым, просто "T & gt;" Сделаю. но это все еще кажется мне бесполезным, просто запутанный способ передачи Foo.class объекту путем объявления другого класса
0

Для Java 8 ....

Есть хорошее решение наhttps://stackoverflow.com/a/36315051/2648077 сообщение.

Это использует Java 8Supplier функциональный интерфейс

45

Джон Скит предложил:

interface Factory<T> {
    T factory();
}

class Araba {
    //static inner class for Factory<T> implementation
    public static class ArabaFactory implements Factory<Araba> {
        public Araba factory() {
            return new Araba();
        }
    }
    public String toString() { return "Abubeee"; }
}

class Generic<T> {
    private T var;

    Generic(Factory<T> fact) {
        System.out.println("Constructor with Factory<T> parameter");
        var = fact.factory();
    }
    Generic(T var) {
        System.out.println("Constructor with T parameter");
        this.var = var;
    }
    T get() { return var; }
}

public class Main {
    public static void main(String[] string) {
        Generic<Araba> gen = new Generic<Araba>(new Araba.ArabaFactory());
        System.out.print(gen.get());
    }
}

Выход:

Constructor with Factory<T> parameter
Abubeee
@scottb: у вас может не быть простого класса, который можно создать с помощью конструктора без аргументов. В этом случае фабрика была бы более надежным решением, чем реализация класса с отражением.
Это все замечательно и все ... это, безусловно, работает. Но ... если все, что нам нужно, это новый экземпляр Bar (так как<T> является<Bar>) затем задумчиво делает экземпляр бара сClass<Bar> token = Bar.class; а такжеBar newBar = token.newInstance(); кажется гораздо менее многословным для меня.
@ codethulhu: Конечно. Однако одним из условий первоначального вопроса было его намерение использовать «конструктор без параметров».
159

Bar.class (или любой другой тип, который вас интересует - любой способ указатьClass<T> ссылка) и сохраните это значение в виде поля:

public class Test
{   
    public static void main(String [] args)
        throws Exception // Just for simplicity!
    {
        Generic<Bar> x = new Generic<Bar>(Bar.class);
        Bar y = x.buildOne();
    }
}

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

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

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

public class Bar
{
    public Bar()
    {
        System.out.println("Constructing");
    }
}

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

Почему бы не пройтиnew Foo() как аргумент?
@fastcodejava, потому что вам нужно передать тип класса. Foo и Class - это разные типы.
2

Если вы хотите создать объект, но без жесткого соединения конструктора / статического метода, используйте абстрактную фабрику. Вы должны быть в состоянии найти подробную информацию и учебные пособия по шаблону абстрактной фабрики в любой базовой книге шаблонов проектирования, введении в ООП или во всех сетях. Здесь не стоит дублировать код, за исключением упоминания того, что синтаксис закрытия Java - отстой.

IIRC, C # имеет особый случай для указания универсального типа, имеет конструктор без аргументов. Эта нерегулярность, по определению, предполагает, что клиентский код хочет использовать эту конкретную форму конструкции, и поощряет изменчивость.

Использование отражения для этого просто неправильно. Обобщения в Java - это функция статической типизации во время компиляции. Попытки использовать их во время выполнения - явный признак того, что что-то идет не так. Отражение вызывает подробный код, сбои во время выполнения, непроверенные зависимости и уязвимости безопасности. (Class.forName это особенно зло.)

Обобщения в Java определенно уступают обобщениям в C #.
@specializt Вы намеревались поставить этот комментарий на вопрос?
это утверждение определенно является самоуверенным и совершенно бессмысленным. Моя вещь лучше чем твоя вещь - по причинам.
@WChargin: Да, поэтому универсальная гарантия безопасности типов в Java требует, чтобы ваш код компилировался без ошибок или каких-либо предупреждений. Если ваш код не генерирует предупреждений, вы гарантированно гарантируете безопасность кода без каких-либо накладных расходов на проверку типов во время выполнения. Являются ли дженерики Java «лучше» или нет, зависит от того, являетесь ли вы парнем наполовину полным или наполовину пустым.
"Обобщения в Java, как правило, более мощные, чем в C #" ВТ. Они даже не овеществлены.
1

Я хотел протестировать фасад Hibernate, поэтому я искал общий способ сделать это. Обратите внимание, что фасад также реализует общий интерфейс. Здесь T - класс базы данных, а U - первичный ключ. Ifacade<T,U> является фасадом для доступа к объекту базы данных T с первичным ключом U.

public abstract class GenericJPAController<T, U, C extends IFacade<T,U>>

{
    protected static EntityManagerFactory emf;

    /* The properties definition is straightforward*/
    protected T testObject;
    protected C facadeManager;

    @BeforeClass
    public static void setUpClass() {


        try {
            emf = Persistence.createEntityManagerFactory("my entity manager factory");

        } catch (Throwable ex) {
            System.err.println("Failed to create sessionFactory object." + ex);
            throw new ExceptionInInitializerError(ex);
        }

    }

    @AfterClass
    public static void tearDownClass() {
    }

    @Before
    public void setUp() {
    /* Get the class name*/
        String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[2].getTypeName();

        /* Create the instance */
        try {
            facadeManager = (C) Class.forName(className).newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
            Logger.getLogger(GenericJPAController.class.getName()).log(Level.SEVERE, null, ex);
        }
        createTestObject();
    }

    @After
    public void tearDown() {
    }

    /**
     * Test of testFindTEntities_0args method, of class
     * GenericJPAController<T, U, C extends IFacade<T,U>>.
     * @throws java.lang.ClassNotFoundException
     * @throws java.lang.NoSuchMethodException
     * @throws java.lang.InstantiationException
     * @throws java.lang.IllegalAccessException
     */
    @Test
    public void  testFindTEntities_0args() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException {

        /* Example of instance usage. Even intellisense (NetBeans) works here!*/
        try {
            List<T> lista = (List<T>) facadeManager.findAllEntities();
            lista.stream().forEach((ct) -> {
                System.out.println("Find all: " + stringReport());
            });
        } catch (Throwable ex) {
            System.err.println("Failed to access object." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }


    /**
     *
     * @return
     */
    public abstract String stringReport();

    protected abstract T createTestObject();
    protected abstract T editTestObject();
    protected abstract U getTextObjectIndex();
}
0

Я вижу, что уже есть ответ на этот вопрос, и это, возможно, даже не лучший способ сделать это. Кроме того, для моего решения вам понадобитсяGson.

Тем не менее, я столкнулся с ситуацией, когда мне нужно было создать экземпляр универсального класса типаjava.lang.reflect.Type.

Следующий код создаст экземпляр класса, который вы хотите с переменными экземпляра null.

T object = new Gson().fromJson("{}", myKnownType);

кудаmyKnownType известен заранее и получен черезTypeToken.getType().

Теперь вы можете установить соответствующие свойства для этого объекта. Опять же, это может быть не лучшим способом сделать это, но это работает как быстрое решение, если это то, что вам нужно.

1

Отhttps://stackoverflow.com/a/2434094/848072, Вам нужен конструктор по умолчанию для класса T.


import java.lang.reflect.ParameterizedType;

class Foo {

  public bar() {
    ParameterizedType superClass = (ParameterizedType) getClass().getGenericSuperclass();
    Class type = (Class) superClass.getActualTypeArguments()[0];
    try {
      T t = type.newInstance();
      //Do whatever with t
    } catch (Exception e) {
      // Oops, no default constructor
      throw new RuntimeException(e);
    } 
  }
}

0

Constructor.newInstance метод.Class.newInstance Начиная с Java 9, этот метод устарел, чтобы улучшить распознавание компилятором исключений экземпляров.

public class Foo<T> {   
    public Foo()
    {
        Class<T> newT = null; 
        instantiateNew(newT);
    }

    T instantiateNew(Class<?> clsT)
    {
        T newT;
        try {
            newT = (T) clsT.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
            | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
        return newT;
    }
}

Похожие вопросы