Map接口
Map接口存储一组成对儿的键(key))-值(value)对象,提供key到value的映射,通过key来检索。Map接口中的key不要求有序,不允许重复。value同样不要求有序,但允许重复。(映射是指从key找到value的过程)
Map接口中存储的数据都是键-值对
-
使用HashMap类动态存储数据
- 最常用的Map实现类是HashMap,其优点是查询指定元素效率高。
注意:1. 数据添加到HashMap集合后,所有数据的数据类型将转换为Object类型,所以从其中获取数据时需要进行强制类型转换
2. HashMap类不保证映射的顺序,特别是不保证顺序恒久不变
Collections类
- Collections类是Java提供的一个集合操作工具类,它包含了大量的静态方法,用于实现对集合元素的排序、查找和替换的操作。注意:Collections和Collection是不同的,前者是集合的操作类,后者是集合接口
-
对集合元素排序与查找
排序是针对集合的一个常见需求。要排序就要知道两个元素哪个大哪个小。在Java中,如果想实现一个类的对象之间比较大小,那么这个类就要实现Comparable接口。此接口强行对实现它的每个类的对象进行整体排序。这种排序称为类的自然排序,类的Compare To()方法被称为它的自然比较方法。此方法用于比较此对象与指定对象的顺序,如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
实现此接口的对象列表(和数组)可以通过Collections.sort()方法(和Arrays.sort()方法)进行自动排序
Map接口本身是无序的,所以不能对Map接口作排序操作;但是List接口时是有序的,所以可以对List接口进行排序。注意List几口中存放的元素,必须是实现了Comparable接口的元素才可以。
-
替换集合元素
- 若有一个需求,需要把一个List集合中的所有元素都替换为相同的元素,则可以使用Collections类的静态方法fill()方法来实现
泛型
- 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,是代码可以应用于多种类型。Java语言引入泛型的好处是:安全简单,且所有强制转换都是自动和隐式进行的,提高了代码的重用率
-
泛型的定义
- 将对象的类型作为参数,指定到其他类或者方法上,从而保证类型转换的安全性和稳定性
-
泛型在集合中的应用
- 使用集泛型集合在创建集合对象时指定集合中元素的类型,从集合中取出元素时无需进行强制类型转换,并且如果把非指定类型元素放入集合,会出现编译错误
import java.util.List; public class Demo01 { // 不使用泛型,存取数据麻烦 public static void test1(){ List list = new ArrayList(); list.add(100); list.add("zhang"); /* * 从集合中获取的数据是Object类型,Object类型是所有类型的根类,但是在具体使用的时候需要 * 类型检查,类型转化,处理类型转化异常 * 使用麻烦 */ Object o = list.get(1); if (o instanceof String) { String s = (String)o; } System.out.println(o); } // 使用泛型 public static void test2(){ List<String> list = new ArrayList<String>(); //list.add(100); 放数据时安全检查,100不是String类型,不能存放 list.add("存数据安全,取数据省心"); String s = list.get(0); //取出来的数据直接就是泛型规定的类型 System.out.println(s); } public static void main(String[] args) { test1(); test2(); } }</pre>
-
深入泛型
-
在集合中使用泛型只是泛型多种应用的一种,在接口、类、方法的方面也有着泛型的广泛应用。泛型本质时参数化类型,参数化类型的重要性在于允许创建一些类、接口和方法,其所操作的数据类型被定义为参数,可以在真正使用时指定具体类型。
参数化类型:参数化类型包含一个类或者接口,以及实际的类型参数列表。
类型变量:是一种非限定性标识符,用来指定类、接口或者方法的类型。
-
-
定义泛型类、泛型接口和放行方法
-
对于一些常常处理不同类型数据转换的几口或者类,可以使用泛型定义。定义接口或类的过程,与定义一个接口或者类相似
- (1)泛型类简单地说就是具有一个或者多个类型参数的类
* 泛型类 * Java库中 E表示集合的元素类型,K 和 V分别表示表的关键字与值的类型 * T(需要时还可以用临近的字母 U 和 S)表示“任意类型” */ public class Pair<T> { private T name; private T price; public Pair() { } public Pair(T name, T price) { this.name = name; this.price = price; } public T getName() { return name; } public void setName(T name) { this.name = name; } public T getPrice() { return price; } public void setPrice(T price) { this.price = price; } }</pre> * (2)泛型接口:就是拥有一个或多个类型参数的接口。泛型接口的定义方式与定义泛型类类似 ``` <pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n177" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">public interface Generator<T> { public T next(); } public class FruitGenerator implements Generator<String> { @Override public String next() { return "Fruit"; } } public class FruitGenerator<T> implements Generator<T> { private T next; public FruitGenerator(T next) { this.next = next; }//继承接口 @Override public T next() { return next; } public static void main(String[] args){ FruitGenerator<String> fruit = new FruitGenerator<>("Fruit"); System.out.println(fruit.next); } }</pre> * (3)泛型方法:一些方法常常需要对某一类数据进行处理,若处理的数据类型不确定则可以通过泛型的方式来定义,达到简化代码、提高代码重用性的目的。泛型方法实际上就是带有类型参数的方法。需要特别注意的是,定义泛型方法与方法所在的类、或者接口是否是泛型类或者泛型接口没有直接的联系,也就是说无论是泛型类还是非泛型类,如果需要就可以定义泛型方法 ```<pre mdtype="fences" cid="n184" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">public class ArrayAlg { public static <T> T getMiddle(T... a) { return a[a.length / 2]; } public static void main(String[] args){ System.out.println(ArrayAlg.getMiddle(1,2,3,4,5)); } }</pre> ##### 泛型工作原理 java中的泛型都是编译器层面来完成的,在生成的java字节码中是不包含任何泛型中的类型信息的,使用泛型时加上的类型参数,会在编译时被编译器去掉。 这个过程称为类型擦除。泛型是通过类型擦除来实现的,编译器在编译时擦除了所有泛型类型相关的信息,所以在运行时不存在任何泛型类型相关的信息(暂且这么说,实际上并不是完全擦除),譬如 List<Integer> 在运行时仅用一个 List 来表示,这样做的目的是为了和 Java 1.5 之前版本进行兼容。泛型擦除具体来说就是在编译成字节码时首先进行类型检查,接着进行类型擦除(即所有类型参数都用他们的限定类型替换,包括类、变量和方法),下面来看几个关于擦除原理的相关问题,加深一下理解。 * 上文中我们在调用getB方法时不需要手动做类型强转,其实并不是不需要,而是编译器给我们进行了处理,具体来讲,泛型方法的返回类型是被擦除了,并不会进行强转,而是在调用方法的地方插入了强制类型转换,下面看一下a.getB()的字节码。用javap查看下上面代码的字节码。 <pre mdtype="fences" cid="n199" lang="java " class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">//定义处已经被擦出成Object,无法进行强转,不知道强转成什么 ``` public T getB(); Code: 0: aload_0 1: getfield #23 // Field b:Ljava/lang/Object; 4: areturn //调用处利用checkcast进行强转 L5 { aload1 invokevirtual com/ljj/A getB()Ljava.lang.Object); checkcast java/lang/Integer invokevirtual java/lang/Integer intValue(()I); istore2 }</pre>
-
2. 泛型注意事项及带来的问题
泛型类型参数不能是基本类型。例如我们直接使用new ArrayList<int>()是不合法的,因为类型擦除后会替换成Object(如果通过extends设置了上限,则替换成上限类型),int显然无法替换成Object,所以泛型参数必须是引用类型。
泛型擦除会导致任何在运行时需要知道确切类型信息的操作都无法编译通过。例如test1,test2,test3都无法编译通过,这里说明下,instanceof语句是不可以直接用于泛型比较的,上文代码中,a instanceof A<integer>不可以,但是a instanceof A或者 a instanceof A<?>都是没有问题的,只是具体的泛型类型不可以使用instanceof。
private void test1(Object arg) {
if (arg instanceof T) { // 编译不通过
}
}
private void test2() {// 编译不通过
T obj = new T();
}
private void test3() {// 编译不通过
T[] vars = new T[10];
}
}</pre>
* 类型擦除与多态的冲突,我们通过下面的例子来引入。
```<pre mdtype="fences" cid="n221" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">class A<T> {
private T value;
public void setValue(T t) {
this.value = t;
}
public T getValue() {
return value;
}
}
class ASub extends A<Number> {
@Override // 与父类参数不一样,为什么用@Override修饰
public void setValue(Number t) {
super.setValue(t);
}
@Override // 与父类返回值不一样,为什么用@Override修饰
public Number getValue() {
return super.getValue();
}
}
ASub aSub=new ASub();
aSub.setValue(123);//编译成功
aSub.setValue(new Object);//编译不通过</pre>
我们知道@Override修饰的代表重写,**重写要求子类中的方法与父类中的某一方法具有相同的方法名,返回类型和参数列表**。显然子类的getValue方法的会返回值与父类不同。而setValue方法就更奇怪了,A方法的setValue方法在类型擦除后应该是setValue(Object obj),看起来这不是重写,不就是我们认知中的重载(函数名相同,参数不同)吗?而且最后当我们调用aSub.setValue(new Object())时编译不通过,说明确实实现了重写功能,而非重载。我们看一下通过javap编译后的class文件。
``` <pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java " cid="n229" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">Compiled from "ASub.java"
public class com.ljj.ASub extends com.ljj.A<java.lang.Number> {
public com.ljj.ASub();
Code:
0: aload_0
1: invokespecial #8 // Method com/ljj/A."<init>":()V
4: return
public void setValue(java.lang.Number);
Code:
0: aload_0
1: aload_1
2: invokespecial #16 // Method com/ljj/A.setValue:(Ljava/lang/Object;)V
5: return
public java.lang.Number getValue();
Code:
0: aload_0
1: invokespecial #23 // Method com/ljj/A.getValue:()Ljava/lang/Object;
4: checkcast #26 // class java/lang/Number
7: areturn
public void setValue(java.lang.Object);//编译器生成的桥方法,调用重写的setValue方法
Code:
0: aload_0
1: aload_1
2: checkcast #26 // class java/lang/Number
5: invokevirtual #28 // Method setValue:(Ljava/lang/Number;)V
8: return
public java.lang.Object getValue();//编译器生成的桥方法,调用重写的getValue方法
Code:
0: aload_0
1: invokevirtual #30 // Method getValue:()Ljava/lang/Number;
4: areturn
}</pre>
我们可以看到子类真正重写基类方法的是编译器自动合成的桥方法,而桥方法的内部直接去调用了我们复写的方法,可见,加载getValue和setValue上的@Override只是个假象,虚拟机巧妙使用桥方法的方式,解决了类型擦除和多态的冲突。这里同时存在两个getValue()方法,getValue:()Ljava/lang/Number和getValue:()Ljava/lang/Object。如果是我们自己编写的java源代码,是通不过编译器的检查的。这里需要介绍几个概念。**描述符和特征签名**,这里只针对method,不关心field。 描述符是针对java虚拟机层面的概念,是针对class文件字节码定义的,方法描述符是包括返回值的。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n237" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">A method descriptor represents the parameters that the method takes and the value that it returns: </pre>
特征签名的概念就不一样了,java语言规范和java虚拟机规范中存在不同的定义。 java语言层面的方法特征签名可以表述为: **特征签名 = 方法名 + 参数类型 + 参数顺序;** JVM层面的方法特征签名可以表述为: **特征签名 = 方法名 + 参数类型 + 参数顺序 + 返回值类型;** 如果存在类型变量或参数化类型,还包括类型变量或参数化类型编译未擦除类型前的信息(FormalTypeParametersopt)和抛出的异常信息(ThrowsSignature),上面的表述可能不太严谨,不同的jvm版本是有变更的。 这就解释了为什么编译器加入了桥方法后能够正常运行,我们加入却不行的问题。换句话说class文件结构是允许返回值不同的两个方法共存的,是符合class文件规范的。在热修复领域,桥方法的使用有时会给泛型方法修复带来很多麻烦,这里就不多说了,感兴趣的可以阅读美团的这篇文章[Android热更新方案Robust开源,新增自动化补丁工具](https://link.jianshu.com/?t=https%3A%2F%2Ftech.meituan.com%2Fandroid_autopatch.html)。 进一步想一下,泛型类型擦除到底都擦除了哪些信息,是全部擦除吗? 其实java虚拟机规范中为了响应在泛型类中如何获取传入的参数化类型等问题,引入了signature,LocalVariableTypeTable等新的属性来记录泛型信息,所以所谓的泛型类型擦除,仅仅是对方法的code属性中的字节码进行擦除,而原数据中还是保留了泛型信息的,这些信息被保存在class字节码的常量池中,使用了泛型的代码调用处会生成一个signature签名字段,signature指明了这个常量在常量池的地址,这样我们就找到了参数化类型,空口无凭,我们写个非常简单的demo看一下,没法再简单了,我们只写了两个函数,第一个函数入参包含泛型,第二个方法入参只是string。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="cpp" cid="n241" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Test2 {
public static void mytest(List<Integer> s) {
}
public static void mytest(String s) {
}
}</pre>
我们利用javap工具看一下,注意此时要看详细的反汇编信息,要添加-c参数。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="objectivec" cid="n243" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">Constant pool:
#14 = Utf8 mytest
#15 = Utf8 (Ljava/util/List;)V
#16 = Utf8 Signature
#17 = Utf8 (Ljava/util/List<Ljava/lang/Integer;>;)V</pre>

test2.png
一目了然,可以看出来调用到了泛型的地方会添加signature和LocalVariableTypeTable,现在就明白了泛型擦除不是擦除全部,不然理解的就太狭隘了。其实,jdk提供了方法来读取泛型信息的,利用class类的get GenericSuperClass()方法我们可以在泛型类中去获取具体传入参数的类型,本质上就是通过signature和LocalVariableTypeTable来获取的。我们可以利用这些虚拟机给我们保留的泛型信息做哪些事呢?
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n249" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public abstract class AbstractHandler<T> {
T obj;
public abstract void onSuccess(Class<T> clazz);
public void handle() {
onSuccess(getType());
}
private Class<T> getType() {
Class<T> entityClass = null;
Type t = getClass().getGenericSuperclass();
if (t instanceof ParameterizedType) {
Type[] p = ((ParameterizedType) t).getActualTypeArguments();
entityClass = (Class<T>) p[0];
}
return entityClass;
}
}
-------------------------------------------------
public class Test1 {
public static void main(String[] args) {
new AbstractHandler<Person>() {
@Override
public void onSuccess(Class<Person> clazz) {
System.out.println(clazz);
}
}.handle();
}
static class Person {
String name;
}
}
------------------------------
输出结果:class com.ljj.Test1$Person</pre>
我们来简单的分析下这段代码,定义一个抽象类AbstractHandler,提供一个回调方法onSuccess方法。然后通过一个匿名子类传入一个Person进行调用,结果在抽象类中动态的获取到了Person类型。jdk提供的api的使用基本上像getType方法所示。我们想想其实序列化的工具就是将json数据序列化为clazz对象,前提就是要传入Type的类型,这时候Type的类型获取就很重要了,我们完全可以在泛型抽象类里面来完成所有的类型获取、json序列化等工作,有些网络请求框架就是这么处理的,这也是在实际工作场景的应用。 好了,泛型引入带来的问题介绍的差不多了,最后说一下泛型的通配符。
#### 3\. 泛型的通配符
泛型中的通配符一般分为非限定通配符和限定通配符两种,限定通配符有两种: <? extends T>和 <? super T>。<? extends T> 保证泛型类型必须是 T 的子类来设定泛型类型的上边界,<? super T> 来保证泛型类型必须是 T 的父类来设定类型的下边界,泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。非限定通配符指的是<?>这种形式,可以用任意泛型类型来代替,因为泛型是不支持继承关系的,所以<?>很大程度上弥补了这一不足。说一个简单的例子来体验下?的作用。 比如说现在有两个List,一个是List<Integer>,一个是List<String>,我想用一个方法打印下list里面的值,因为泛型是无法继承的,List<Integer>和List<Object>是没有关系的,我们此时可以借助于通配符解决。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="cpp" cid="n253" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class Test1 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(12);
handle(list);
List<Float> list1 = new ArrayList<Float>();
list1.add(123.0f);
handle(list1);
}
private static void handle(List<?> list) {
System.out.println(list.get(0));
}
}</pre>
ok,成功运行了,那如果我想把第一个元素在添加一遍呢,好说直接加一条语句就行了。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="cpp" cid="n255" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">private static void handle(List<?> list){
System.out.println(list.get(0));
list.add(list.get(0));
}</pre>
此时,你会发现编译不过去了。
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="tsx" cid="n257" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">The method add(capture#2-of ?) in the type List<capture#2-of ?> is not applicable for the arguments (capture#3-of ?)。</pre>
“capture#2 of ?” 表示什么?当编译器遇到一个在其类型中带有通配符的变量,它认识到必然有一些 T ,它不知道 T 代表什么类型,但它可以为该类型创建一个占位符来指代 T 的类型。占位符被称为这个特殊通配符的捕获(capture)。这种情况下,编译器将名称 “capture#2of ?” 以 List类型分配给通配符,每个变量声明中每出现一个通配符都将获得一个不同的捕获,错误消息告诉我们不能调用add方法,因为形参类型是未知的,编译器无法检测出来了。所以我们在使用?通配符时一定要注意写入问题。
简单总结一句话:**一旦形参中使用了?通配符,那么除了写入null以外,不可以調用任何和泛型参数有关的方法,当然和泛型参数无关的方法是可以调用的。**