Универсальный метод в Java без универсального аргумента - PullRequest
34 голосов
/ 26 февраля 2009

В C # я могу сделать это на самом деле:

//This is C#
static T SomeMethod<T>() where T:new()
{
  Console.WriteLine("Typeof T: "+typeof(T));
  return new T();
}

//And call the method here
SomeMethod<SomeClassName>();

Но по какой-то причине я не могу заставить его работать на Java.

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

//This is Java, but doesn't work
public static T fromXml<T>(String xml) {
  try {
    JAXBContext context = JAXBContext.newInstance(T.class);
    Unmarshaller um = context.createUnmarshaller();
    return (T)um.unmarshal(new StringReader(xml));
  } catch (JAXBException je) {
    throw new RuntimeException("Error interpreting XML response", je);
  }
}

//Also the call doesn't work...
fromXml<SomeSubObject>("<xml/>");

Ответы [ 5 ]

51 голосов
/ 26 февраля 2009
public static <T> T fromXml(Class<T> clazz, String xml) {

Вызывается как:

Thing thing = fromXml(Thing.class, xml);

или более явно:

Thing thing = MyClass.<Thing>fromXml(Thing.class, xml);

Чтобы быть еще более запутанным, у вас могут быть конструкторы, которые как создают универсальный тип, так и сами имеют универсальный параметр. Не могу вспомнить синтаксис и никогда не видел, чтобы он использовался в гневе (вам все равно лучше использовать статический метод создания).

Приведение (T) небезопасно, и вы не можете написать T.class. Поэтому включите T.class в качестве аргумента (как это делает JAXBContext.newInstance) и сгенерируйте соответствующее исключение, если тип неправильный.

public static <T> T fromXml(Class<T> clazz, String xml) {
    try {
        JAXBContext context = JAXBContext.newInstance(clazz);
        Unmarshaller um = context.createUnmarshaller();
        Object obj = um.unmarshal(new StringReader(xml));
        try {
            return clazz.cast(obj);
        } catch (ClassCastException exc) {
             throw new RelevantException(
                 "Expected class "+clazz+
                  " but was "+obj.getClass()
             );
        }
    } catch (JAXBException exc) {
        throw new RelevantException(
            "Error unmarshalling XML response",
            exc
         );
    }
}

Я считаю, что в следующей версии JAXB (в 6u14?) Есть несколько удобных методов для такого рода вещей в классе JAXB.

5 голосов
/ 26 февраля 2009

В Java дженерики - это данные только во время компиляции, которые теряются во время выполнения. Итак, если бы вы вызвали такой метод, у JVM не было бы возможности узнать, что было T.class. Обычный способ обойти это - передать объект экземпляра класса в качестве параметра методу, например так:

public static <T> T fromXml(Class<T> clazz, String xml) {
  try {
    JAXBContext context = JAXBContext.newInstance(clazz);
    Unmarshaller um = context.createUnmarshaller();
    return (T)um.unmarshal(new StringReader(xml));
  } catch (JAXBException je) {
    throw new RuntimeException("Error interpreting XML response", je);
  }
}

fromXml(SomeSubObject.class, "<xml/>");
2 голосов
/ 05 ноября 2014

Методы, подобные Java Collections.emptySet(), имеют такую ​​подпись:

public static final <T> Set<T> emptySet()

И называются так:

Set<Foo> foos = Collections.<Foo>emptySet();

Метод Mockito anyObject() является еще одним примером. Лично я не нахожу ни один синтаксис, чтобы быть очень удивительным. Передача типа в качестве аргумента метода работает, но всегда выглядела глупо. Предоставление параметра таким образом, что emptySet() кажется более чистым, но не всегда очевидно, что метод позволяет указывать тип.

1 голос
/ 09 марта 2017

Текущий ответ более безопасен, но технически устарел, потому что в java7 и более поздних версиях вам не нужен аргумент «Class clazz». Тип выводится из ожидаемого типа возвращаемого значения. Вы просто получаете предупреждение о небезопасном приведении.

public class Main {

    private static class Dog {
        public String toString() { return "dog"; }
    }

    private static class Cat {
        public String toString() { return "cat"; }
    }

    public static void main(String[] args) {
        Cat cat = parse("cat");
        Dog dog = parse("dog");
        System.out.println("the cat object is a " + cat);
        System.out.println("the dog object is a " + dog);
    }

    private static Object untypedParse(String stringToParse) {
        if(stringToParse.equals("dog")) {
            return new Dog();
        } else if(stringToParse.equals("cat")) {
            return new Cat();
        } else {
            throw new RuntimeException("not expected");
        }
    }

    public static <T> T parse(String stringToParse) {
        return (T)untypedParse(stringToParse);
    }

}


~/test/generics$ javac Main.java
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
~/test/generics$ java Main
the cat object is a cat
the dog object is a dog
1 голос
/ 26 февраля 2009

Боюсь, то, что вы пытаетесь сделать, просто не будет работать в Java. Неспособность создавать новые экземпляры универсальных типов - это одна из тех «обязательных» функций, которые предоставляет .NET, а Java просто отсутствует. Это приводит к «обходным путям», подобным тем, которые были предложены ранее. Посмотрите ArrayList.toArray(T) в качестве ссылки, как это можно сделать: по сути, вам нужно будет передать ссылку на объект, который вы пытаетесь создать, чтобы вы знали, какой класс создавать во время выполнения. Это код из ArrayList.toArray(T):


public <T> T[] toArray(T[] a)
{
   if (a.length < size)
   {
      a = (T[]) java.lang.reflect.Array.
         newInstance(a.getClass().getComponentType(), size);
   }
   System.arraycopy(elementData, 0, a, 0, size);
   if (a.length > size)
   {
      a[size] = null;
   }
   return a;
}

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