简体   繁体   中英

Generic method in Java without generic argument

In C# I can do actually this:

//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>();

But for some reason I can't get it to work in Java.

The thing I want to do is, to create a static method on a superclass, so the subclasses can be converted to 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/>");
public static <T> T fromXml(Class<T> clazz, String xml) {

Called as:

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

or more explicitly:

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

To be even more confusing you can have constructors that both construct a generic type and have a generic parameter themselves. Can't remember the syntax and have never seen it used in anger (you are probably better off with a static creation method anyway).

The cast (T) is unsafe, and you can't write T.class. So include the T.class as an argument (as JAXBContext.newInstance does) and throw a relevant exception if the type is wrong.

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
         );
    }
}

I believe the next version of JAXB (in 6u14?) has some convenience methods for this sort of thing in the JAXB class.

In Java, generics are compile-time only data, which are lost at run time. So, if you called a method like that, the JVM would have no way of knowing what T.class was. The normal way to get around this is to pass a class instance object as a parameter to the method, like this:

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

Methods like Java's Collections.emptySet() have a signature like this:

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

And are called like this:

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

Mockito's anyObject() method is another example. I personally don't find either syntax to be very awesome. Passing the type in as a method argument works but always felt kludgy. Providing the parameter in the way that emptySet() does seems cleaner but it's not always apparent that a method allows a type to be specified.

The current answer is safer but is technically outdated because in java7 and beyond you don't need the "Class clazz" argument. The type is inferred from the expected return type. You just get a warning about unsafe cast.

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

I am afraid what you are trying to do will simply not work in Java. Not being able to create new instances of generic types is one of those "must have" features that .NET provided while Java is simply missing. This leads to "workarounds" like those suggested earlier. Check out the ArrayList.toArray(T) as a reference how this can be done: essentially you will have to pass a reference to an object that you are trying to create so that you know what class to instantiate at runtime. This is the code from 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM