Java中没有泛型参数的通用方法

时间:2023-01-15 09:42:49

In C# I can do actually this:

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

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

但由于某种原因,我不能让它在Java中工作。

The thing I want to do is, to create a static method on a superclass, so the subclasses can be converted to XML.

我想要做的是,在超类上创建一个静态方法,这样子类就可以转换为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 个解决方案

#1


50  

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.

强制转换(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
         );
    }
}

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

我相信JAXB的下一个版本(在6u14中?)在JAXB类中有一些方便的方法。

#2


5  

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:

在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


2  

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

像Java的Collections.emptySet()这样的方法有这样的签名:

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.

Mockito的anyObject()方法是另一个例子。我个人认为这两种语法都不是很棒。将类型作为方法参数传递起作用但总是感觉很笨拙。以emptySet()的方式提供参数看起来更干净但是方法允许指定类型并不总是显而易见的。

#4


1  

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):

我担心你要做的事情根本就不适用于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


1  

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.

目前的答案更安全但技术上已经过时,因为在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


50  

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.

强制转换(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
         );
    }
}

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

我相信JAXB的下一个版本(在6u14中?)在JAXB类中有一些方便的方法。

#2


5  

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:

在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


2  

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

像Java的Collections.emptySet()这样的方法有这样的签名:

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.

Mockito的anyObject()方法是另一个例子。我个人认为这两种语法都不是很棒。将类型作为方法参数传递起作用但总是感觉很笨拙。以emptySet()的方式提供参数看起来更干净但是方法允许指定类型并不总是显而易见的。

#4


1  

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):

我担心你要做的事情根本就不适用于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


1  

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.

目前的答案更安全但技术上已经过时,因为在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