如何在Java中编写通用方法

3
如何在Java中编写通用方法。
在C#中,我会这样做:
    public static T Resolve<T>()
    {
        return (T) new object();
    }

在Java中对应的是什么?


5
如果 T 不是 object,你的 C# 代码会抛出一个 InvalidCastException 异常。 - dtb
你的 C# 泛型方法不是一个好的例子,因为对于几乎任何 T 值,那个强制转换都会引发异常。 - Paul Creasey
4个回答

11

首先,你的C#示例是错误的;除非typeof(T) == typeof(object),否则它将抛出InvalidCastException。你可以通过添加constraint来修复它:

public static T Resolve<T>() where T : new() {
    return new T();
}

现在,这将是Java中的等效语法(或者说,至少尽可能接近):
public static <T> T Resolve() {
    return (T) new T();
}

注意声明中对 T 的双重提及:一个是 <T> 中参数化方法的 T,另一个是返回类型的 T
不幸的是,上述方法在Java中不起作用。由于Java泛型的实现方式,关于 T 的运行时类型信息是不可用的,因此上述方法会导致编译时错误。现在,您可以通过以下方式绕过此限制:
public static <T> T Resolve(Class<T> c) {
    return c.newInstance();
}

注意需要传入T.class。这被称为运行时类型标记。这是处理此情况的惯用方式。

那会抛出一些异常!在这些情况下不要使用反射。 - Tom Hawtin - tackline
@tackline:不,这是处理这种情况的惯用Java写法:参见http://java.sun.com/docs/books/tutorial/extra/generics/literals.html - jason
首先,你的C#示例是错误的。我认为在某些情况下(尤其是在关于语法的问题中),方法抛出异常是正确的。但是,我认为方法无法编译是错误的(你的C#示例)。你忘记在“Resolve”后面加上<T>了。 - Matthijs Wessels

2

正如其他评论者指出的那样,您也可以使用Java来完成此操作 - 在运行时可能会创建转换异常的情况下:

@SuppressWarnings("unchecked")
public static <T> T resolve() {
  return (T) new Object();
}

除非您使用@SuppressWarnings注释,否则Java的类型擦除将发挥作用,并且您将收到编译器警告。异常也会在其他地方发生:无论您尝试在哪里使用它:

String s = <String>resolve();

会抛出异常。

另一方面,在C#中,您可能想要使用new T()。但在Java中无法这样做。建议的解决方法是,如果需要在运行时依赖类型信息,则使用Class<T>作为类型参数。对于您的示例,这意味着您需要将其重构为以下版本:

public static <T> T resolve(Class<T> type) {
  try {
    return type.newInstance();
  } catch(Exception e) {
    // deal with the exceptions that can happen if 
    // the type doesn't have a public default constructor
    // (something you could write as where T : new() in C#)
  }
}

顺便提一下,你也可以使用这种方法来消除前面情况中的警告,并将运行时异常放置在更合理的行上:
public static <T> T resolve(Class<T> type) {
  return type.cast(new Object());
}

这段代码的行为与你举例的那个完全相同,包括在 T 为除 Object 以外的任何类型时发生的异常。

1

这在Java中可以正常工作,因为T在编译时被擦除为Object。然而,你并没有从这个实现中获得任何类型安全性。 - Mike Q
@ GreyMatter 9: 对的,如果T不是java.lang.Object类型,Resolve方法将引发java.lang.ClassCastException异常。 - Frank Grimm
CCE实际上是从调用站点抛出的,而不是从这个方法中抛出的。(但这只是纠结细节。) - Tom Hawtin - tackline
+1 这是我认为最好的答案。我不认为这个问题是关于通过泛型参数创建类型实例的...我认为它只是关于泛型方法的语法... - Matthijs Wessels

0

你需要某种工厂:

 public interface MyFactory<T> {
     T newInstance();
 }

然后可以将其传递到需要的位置。在您的代码中:

public static T resolve<T>(MyFactory<T> factory) {
    return factory.newInstance();
}

注意:完全没有理由使用反射来做这件事!!

MyFactory<T> 内部的 newInstance 是如何编写的呢?肯定是使用反射来创建新实例了吧?(抱歉,我还在从 C# 转向 Java)。 - Lisa
算了那个问题。我看到Java类类型(java.lang.Class)有一个newInstance()方法,可以生成一个新的实例,其中有一个无参构造函数。 - Lisa
@Lisa 如果可以的话,你真的不想去涉及反射。MyFactory 的实现可以根据需要构造特定的 T - Tom Hawtin - tackline

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接