gpt4 book ai didi

java - Java类型删除如何对待'?'

转载 作者:行者123 更新时间:2023-11-30 12:02:29 25 4
gpt4 key购买 nike

希望在理解了通用边界之后,我试图理解通配符的上下限。我的参考是:https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
我发现有一个句子我可以理解:“通配符可以在多种情况下使用:作为参数,字段或局部变量的类型;”
字段和局部变量?无法想象。为什么这么重要的资料没有通过简单的例子来强调呢?

我试图理解使用哪个参考Java编译器替换(同时擦除)“?”。也许我有一个很大的误解,并且发生了任何擦除操作(因此,以下所有示例均不相关)。
在以下示例中:
1。

public static void funcA (List<? extends Number>l)


2。

public static void funcB(List<? super Integer>l)


第二个示例与以下代码之间有区别:
3。

public static <T extends Integer> funcC(List<? extends T>l)


如果示例2与以下示例之间存在任何差异:
4。

public static <T extends Integer> void funcC(List<T>l)

最佳答案

前言:您已经询问了有关类型擦除的许多问题,包括在聊天室中。尽管此答案将解决此特定问题,但它可能还会回答您的其他一些问题(甚至可能尚未回答)。

警告:这个答案很长,本文中专门提出的问题仅在最后直接解决。



什么是类型擦除?

其他问答中对此进行了很好的介绍,因此我将简单地链接到它们:


Java generics type erasure: when and what happens?
What is the concept of erasure in generics in Java?
What is a raw type and why shouldn't we use it?




类型擦除的规则是什么?

Java语言规范(JLS)的§4.6 Type Erasure中指定了类型擦除的规则:


  类型擦除是从类型(可能包括参数化类型和类型变量)到类型(从不参数化类型或类型变量)之间的映射。我们写|T|来擦除类型T。擦除映射定义如下:
  
  
  参数化类型(§4.5G<T1,...,Tn>的擦除为|G|
  嵌套类型T.C的擦除为|T|.C
  数组类型T[]的擦除为|T|[]
  类型变量(§4.4)的擦除是其最左边界的擦除。
  其他所有类型的擦除都是该类型本身。
  
  
  类型擦除还将映射构造函数或方法的签名(§8.4.2)到没有参数化类型或类型变量的签名。删除构造函数或方法签名s是由与s相同的名称以及s中给出的所有形式参数类型的擦除组成的签名。
  
  如果擦除方法或构造函数的签名,则方法的返回类型(§8.4.5)和通用方法或构造函数的类型参数(§8.4.4§8.8.4)也将被擦除。
  
  通用方法签名的擦除没有类型参数。


对于这个答案,我们应该关注第一和第四点:


  参数化类型(§4.5G<T1,...,Tn>的擦除为|G|


和:


  类型变量(§4.4)的擦除是其最左边界的擦除。


分别。特别要注意的是,第四个要点仅是说明类型变量的擦除。为什么这么重要?我会回到那。

术语

了解术语对于理解规则的应用方式很重要:


通用类


在JLS的§8.1.2 Generic Classes and Type Parameters中指定。
泛型类是声明一个或多个类型变量的类。

通用接口


在JLS的§9.1.2 Generic Interfaces and Type Parameters中指定。
通用接口是声明一个或多个类型变量的接口。

通用方法


在JLS的§8.4.4 Generic Methods中指定。
泛型方法是声明一个或多个类型变量的方法。

通用构造函数


在JLS的§8.8.4 Generic Constructors中指定。
泛型构造函数是声明一个或多个类型变量的构造函数。

类型变量


在JLS的§4.4 Type Variables中指定。
类型变量由在泛型成员上声明的类型参数引入。
类型变量的syntax为:{Annotation} TypeIdentifier

类型参数


在JLS的许多部分中指定。我链接到上述术语的每个部分都提到类型参数。
类型参数是泛型成员上类型变量及其边界的声明。
类型参数的syntax为:{TypeParameterModifier} TypeIdentifier [TypeBound]其中TypeParameterModifier扩展为Annotation

参数化类型


在JLS的§4.5 Parameterized Types中指定。
参数化类型是带有类型实参的泛型类或泛型接口的实际使用。

类型参数


在JLS的§4.5.1 Type Arguments of Parameterized Types中指定。
类型实参是代替类型实参使用的实际类型或通配符。



请注意,类型形参和类型形参之间的区别类似于Java如何区分方法形参和形参。当将方法声明为void bar(Object obj)时,Object obj是参数。但是,当您调用bar(someObjInstance)之类的方法时,someObjInstance的值是参数。

术语代码示例

查看代码中的一些示例可以帮助您理解每个术语适用于代码的哪些部分。

具有类型参数的泛型类

public class Foo<T extends CharSequence, U> {
// class body...
}


有两个类型参数:


T extends Charsequence


类型变量是 T
类型界限是 extends CharSequence

U


类型变量是 U
类型界限是 extends Object(隐式定义)



通用接口的代码看起来相似。

具有类型参数的通用方法

public void <V extends Number> bar(V obj) {
// method body...
}


此方法具有一个类型参数:


V extends Number


类型变量是 V
类型界限是 extends Number



对于通用构造函数,该代码看起来类似。

参数化类型(无通配符)

public <E extends Number> void bar(List<E> list) {
// method body...
}


有一种参数化类型:


List<E>


类型参数是 E



参数化类型(带通配符)

public void bar(List<? extends Number> list) {
// method body...
}


有一种参数化类型:


List<? extends Number>


类型参数是 ? extends Number



返回规则

正如我所提到的,重要的是要注意的是,这些规则仅提到了擦除类型变量。之所以如此重要,是因为在可以定义类型变量的地方(即,在类型参数中)不允许使用通配符。通配符只能在类型参数中使用,该参数是参数化类型的一部分。

参数化类型的擦除只是原始类型。



类型擦除何时生效?

在通用代码类型的日常开发中,擦除实际上是不相关的。在原始数据类型中,您唯一需要详细了解类型擦除的工作方式之一。在理想和公正的世界中,当您处理无法更改的旧代码(从Java 5之前的日子开始)时,将只使用原始类型。换句话说,除非您被迫使用原始类型,否则应始终适当地使用泛型。

但是,也许您被迫使用原始类型,或者您只是好奇。在这种情况下,您需要知道类型擦除的工作原理,因为擦除确定了要使用的类型。这是一个通用类的示例:

public class Foo<T extend CharSequence, U> {

private List<T> listField;
private U objField;

public void bar(List<? extends T> listParam) {
// method body...
}

public U baz(T objParam) {
// method body...
}

public <V extends Number> V qux(V objParam) {
// method body...
}

}


遵循前面提到的类型擦除规则,下面是上述类的样子:

// the raw type of Foo
public class Foo {

private List listField;
private Object objField;

public void bar(List listParam) {
// method body...
}

public Object baz(CharSequence objParam) {
// method body...
}

public Number qux(Number objParam) {
// method body...
}

}


但是同样,当您使用原始类型时,您只需要了解后一个版本。



将此知识应用于您的问题

到目前为止,我们已经了解到一些通配符只能在类型参数中使用,因此仅适用于参数化类型。参数化类型的擦除只是原始类型。如果我们将此知识应用于您的示例,您将获得以下信息:


例子1


原版的


public static void funcA(List<? extends Number> l)


已清除

public static void funcA(List l)


范例#2


原版的


public static void funcB(List<? super Integer> l)


已清除

public static void funcB(List l) 


例子#3


原始(假定为 void,则忘记指定有问题的返回类型)


public static <T extends Integer> void funcC(List<? extends T> l)


已清除

public static void funcC(List l)


例子#4


原版的


public static <T extends Integer> void funcC(List<T> l)


已清除

public static void funcC(List l)




强化观点

为了真正指出类型变量的擦除与参数化类型的擦除之间的区别,让我们看另一个示例。

public class Foo<T extends Number> {

public void bar(T obj) {
// method body...
}

public void baz(List<? extends T> list) {
// method body...
}

}


方法 bar具有类型为 T的单个参数。该参数直接使用类型变量。类型变量的擦除是其最左边界的擦除,在这种情况下为 Number。这意味着擦除后,该方法的参数为 Number

方法 baz具有类型为 List<? extends T>的单个参数。在这里,类型变量 T用作参数化类型的类型参数的上限。换句话说,尽管使用了类型变量,但此处实际使用的擦除是参数化类型的擦除。这意味着擦除后,该方法的参数仅为 List。即使type参数是低界通配符(例如 List<? super T>),无界通配符(例如 List<?>)或什至是非通配符(例如 List<T>),也会发生这种情况。

如何通过类型擦除来处理通配符

要直接回答有关类型擦除如何处理通配符的问题,答案是有效的:不是,不是直接。通配符只会消失(当删除参数化类型时),并且在生成的原始类型中没有任何意义。

通配符为使用通用API的用户提供了灵活性。以下是解决该概念的一些问答:


What is PECS (Producer Extends Consumer Super)?
Difference between <? super T> and <? extends T> in Java [duplicate]
What is the use and point of unbound wildcards generics in Java?


希望这些问答可以帮助您回答辅助问题,即帖子中第二个和第四个示例之间的区别是什么。

关于java - Java类型删除如何对待'?',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58575372/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com