Подтвердить что ты не робот

Общий метод в Java без общего аргумента

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

//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/>");
4b9b3361

Ответ 1

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.

Ответ 2

В 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/>");

Ответ 3

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

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

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

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

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

Ответ 4

Я боюсь, что то, что вы пытаетесь сделать, просто не будет работать на 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;
}

Ответ 5

Текущий ответ более безопасен, но технически устарел, потому что в 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