tags:

views:

3709

answers:

4

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/>");
+6  A: 
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.

Tom Hawtin - tackline
won't even compile... You cannot write T.class
pgras
I didn't mean write T.class in the method. I said as an argumnet. And the calling function may no (or may have it passed as an argumnet).
Tom Hawtin - tackline
Tom Hawtin - tackline
Please rewrite JAXBContext.newInstance(T.class) to JAXBContext.newInstance(clazz) so I can accept it.
doekman
Gah. I got myself into a pickle editing this...
Tom Hawtin - tackline
+2  A: 

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/>");
Avi
Still not the way to write a generic method.
Tom Hawtin - tackline
Almost. The <T> needs to be right after the "static", not "fromXml" I think.
doekman
Not ideally, no. But if he is going to need access to the type information at run time (to call JAXBContext.newInstance()), he will need an object of that class, or the class object.
Avi
@doekman: Thanks, I fixed it
Avi
+2  A: 

I did something similar some time ago. You need some clever introspection code. Look at that excellent article.

(to remove any confusion: that article is not mine- but I found it when working on the subject)

cadrian
+1  A: 

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

Bogdan