当前位置: 首页 > 面试题库 >

泛型类中的Java泛型方法

蒋嘉颖
2023-03-14
问题内容

如果在Java中创建泛型类(该类具有泛型类型参数),则可以使用泛型方法(该方法带有泛型类型参数)吗?

考虑以下示例:

public class MyClass {
  public <K> K doSomething(K k){
    return k;
  }
}

public class MyGenericClass<T> {
  public <K> K doSomething(K k){
    return k;
  }

  public <K> List<K> makeSingletonList(K k){
    return Collections.singletonList(k);
  }
}

正如您对通用方法所期望的那样,我可以使用任何对象调用doSomething(K)的实例MyClass

MyClass clazz = new MyClass();
String string = clazz.doSomething("String");
Integer integer = clazz.doSomething(1);

但是,如果我尝试使用MyGenericClass
指定泛型类型的实例,则无论传入什么,我都会调用doSomething(K)返回ObjectK

MyGenericClass untyped = new MyGenericClass();
// this doesn't compile - "Incompatible types. Required: String, Found: Object"
String string = untyped.doSomething("String");

奇怪的是,如果返回类型是通用类,它将编译List<K>(例如(实际上,这可以解释-参见下面的答案)):

MyGenericClass untyped = new MyGenericClass();
List<String> list = untyped.makeSingletonList("String"); // this compiles

此外,如果输入通用类,即使仅使用通配符,它​​也会编译

MyGenericClass<?> wildcard = new MyGenericClass();
String string = wildcard.doSomething("String"); // this compiles
  • 有充分的理由为什么在无类型的泛型类中调用泛型方法不起作用?

  • 是否存在一些与通用类和通用方法相关的巧妙技巧?

编辑:

为了明确起见,我希望未类型化或未类型化的泛型类不接受泛型类的类型参数(因为尚未提供)。但是,我不清楚为什么未类型化或原始类型的泛型类将意味着不遵循泛型方法。

可以看出,此问题已在SO上提出,请参见此问题。对此的答案说明,当一个类未类型化/以其原始格式时, 所有 泛型都将从该类中删除-包括泛型方法的键入。

但是,关于这种情况的原因,并没有真正的解释。因此,请允许我澄清我的问题:

  • Java为什么要删除无类型或原始类型的通用类上的通用方法类型?是否有充分的理由,还是仅仅是疏忽?

编辑-JLS的讨论:

已经建议(作为对先前的SO问题和对此问题的回答)在JLS
4.8
中对此进行了处理,其中指出:

未从其超类或超接口继承的原始类型C的构造函数(第8.8节),实例方法(第8.4节,第9.4节)或非静态字段(第8.3节)M的类型为对应的原始类型在与C对应的通用声明中删除其类型。

对我来说,这很明显与无类型的类有什么关系-类的泛型被替换为擦除类型。如果类的泛型是绑定的,则擦除类型对应于那些边界。如果它们未绑定,则擦除类型为对象-例如

// unbound class types
public class MyGenericClass<T> {
  public T doSomething(T t) { return t; }
}
MyGenericClass untyped = new MyGenericClass();
Object t = untyped.doSomething("String");

// bound class types
public class MyBoundedGenericClass<T extends Number> {
  public T doSomething(T t) { return t; }
}
MyBoundedGenericClass bounded = new MyBoundedGenericClass();
Object t1 = bounded.doSomething("String"); // does not compile
Number t2 = bounded.doSomething(1); // does compile

尽管通用方法是实例方法,但我不清楚JLS
4.8是否适用于通用方法。通用方法的类型(<K>在前面的示例中)不是未类型化的,因​​为它的类型是由方法参数确定的-只有类是未类型化/原始类型的。


问题答案:

“为了向后兼容”似乎是擦除类泛型类型的充分理由-
需要这样做,例如,允许您返回无类型的List并将其传递给某些旧代码。将此扩展到泛型方法似乎是一个棘手的子案例。

4.8(您引用)中的JLS片段涵盖了构造函数,实例方法和成员字段-泛型方法通常只是实例方法的一种特殊情况。因此,似乎您的案例已被此代码段覆盖。

使JLS 4.8适应此特定情况:

泛型方法的类型是原始类型,它对应于在对应于C的泛型声明中擦除其类型。

(此处方法的“类型”将包括所有参数和返回类型)。如果您将“擦除”解释为“擦除所有泛型”,那么这似乎与观察到的行为相符,尽管它不是很直观,甚至没有用。删除所有泛型,而不仅仅是泛型类参数,似乎几乎是过于狂热的一致性(尽管我是第二位猜测设计师的人)。

类通用参数与方法通用参数交互时可能会出现问题-
在代码中它们是完全独立的,但是您可以想象其他情况下将它们分配/混合在一起的情况。我认为值得指出的是,根据JLS,不建议使用原始类型:

仅允许使用原始类型作为对遗留代码兼容性的让步。强烈建议不要在将通用性引入Java编程语言后在代码中使用原始类型。Java编程语言的未来版本可能会禁止使用原始类型

Java开发人员的一些想法在这里显而易见:

http://bugs.sun.com/view_bug.do?bug_id=6400189

(bug +修复表明,为了消除此类型,将方法的返回类型视为方法的类型的一部分)

还有一个请求,其中有人似乎在请求您描述的行为-
仅删除类的泛型参数,而不是其他泛型-但由于以下原因而被拒绝:

要求修改类型擦除,以便在类型声明中Foo<T>,擦除仅从T参数化类型中删除。然后,碰巧在Map<K,V>的声明中,将Set<Map.Entry<K,V>>擦除为Set<Map.Entry>

但是如果Map<K,V>有一个方法可以键入Map<String,V>,它的擦除就可以了Map<String>。对于类型擦除而言,更改类型参数的数量非常可怕,尤其是对于编译时方法解析而言。我们绝对不会接受此请求。

期望能够使用原始类型(Map)同时仍能获得一些泛型(Set<Map.Entry>)的类型安全性,这实在太多了。



 类似资料:
  • 问题内容: 如果在Java中创建泛型类(该类具有泛型类型参数),则可以使用泛型方法(该方法带有泛型类型参数)吗? 考虑以下示例: 正如您对通用方法所期望的那样,我可以使用任何对象调用的实例: 但是,如果我尝试使用 不 指定泛型类型的实例,则无论传入什么,我都会调用返回, 奇怪的是,如果返回类型是通用类,它将编译(例如(实际上,这可以解释-参见下面的答案)): 此外,如果输入通用类,即使仅使用通配符

  • 如何获取这个类的类型?对于上下文,我使用ModelMapper,我需要类类型T从S转换为T。 背景: 我已经尝试了N种方法,其中我放置了“//一些方法来获取类型”,但没有任何效果。例如: 或

  • 编译时,此代码将产生以下+错误: 问题是访问中的类型T与列表中的T不相同。如何修复此编译问题?

  • 问题内容: 背景 我曾经写过这种方法: 应该这样称呼它: 这很好用(尽管我在研究当前容易出错的问题时在这里的答案中已经看到)。 目前的情况 无论如何,现在我正在编写以下代码(在扩展javax.servlet.jsp.tagext.TagSupport的类中): 目的是可以这样称呼: 我的评估方法中的代码显然不起作用。的第二个参数应该是Class对象。这导致我: 我的问题 如何获得通用(返回)类型的

  • 问题内容: 我有一个代表文本片段的泛型类。该文本片段可能具有多种不同模式(突出显示的不同类型)中的任何一种。这些模式用枚举表示。每个项目的Enum可能不同,但是它必须实现一个接口,该接口提供了一种将其中两个结合的方法(可以突出显示并加粗显示)。所以我有一个界面: 然后我的TextFragment是文本字符串和模式的容器。但是当我尝试声明该类时: 我收到以下错误: 令牌“扩展”的语法错误,预期 根据

  • 问题内容: 在C#中,我实际上可以这样做: 但是由于某种原因,我无法使其在Java中工作。 我要做的是在超类上创建一个静态方法,以便可以将子类转换为XML。 问题答案: 称为: 或更明确地: 更令人困惑的是,您可以拥有既构造泛型类型又具有泛型参数的构造函数。不记得该语法,也从未在愤怒中使用过它(无论如何,最好还是使用静态创建方法)。 强制转换是不安全的,并且您不能编写T.class。因此,将T.c