1
2
3
4
5
6
7
8
9
10
11
package org.example;

public class fanxingDemo {
Demo<String> demo=new Demo<>("小白");
}
class Demo<T>{
private T name;
public Demo(T name){
this.name=name;
}
}

T必须是任意非基础类型:任何类类型,任何接口类型,任何数组类型或甚至另一个类型变量。

最常用的类型参数名称是:

  • E - 元素(由 Java 集合框架广泛使用)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.example;

public class fanxingDemo {
public static void main(String[] args) {
Demo<String,Integer> demo=new DemoImpl<>("小白",11);
System.out.println(demo.getKey());
}
}
interface Demo<K,V>{
public K getKey();
public V getValue();
}
class DemoImpl<K,V> implements Demo<K,V>{
private K key;
private V value;
@Override
public K getKey() {return key;}
@Override
public V getValue(){return value;}

public DemoImpl(K key,V value){
this.key=key;
this.value=value;
}
}

原始类型

原始类型是一个通用的类或接口没有任何类型的参数的名称。

1
Demo demo=new DemoImpl("小白",11);

使用原始类型时,会获得预泛型的行为 - 类会为其提供对象。为了向后兼容,允许将参数化类型分配给其原始类型:

1
2
Demo<String,Integer> demo=new DemoImpl<>("小白",11);
Demo demo2=demo;

如果将原始类型赋值给参数化类型或者使用原始类型来调用在相应泛型中定义的泛型方法时,会得到警告。

泛型方法

静态和非静态泛型方法,以及泛型类构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package org.example.demo;

public class FanxingDemo {
public static void main(String[] args) {
Demo<String,Integer> demo=new DemoImpl<>("小白",1);
Demo<String,Integer> demo2=new DemoImpl<>("小白",1);
//完整语法:System.out.println(Util.<String,Integer>compare(demo,demo2));
System.out.println(Util.compare(demo,demo2));
}
}
interface Demo<K,V>{
public K getKey();
public V getValue();
}
class DemoImpl<K,V> implements Demo<K,V>{
private K key;
private V value;
@Override
public K getKey() {return key;}
@Override
public V getValue(){return value;}

public DemoImpl(K key,V value){
this.key=key;
this.value=value;
}
}
class Util{
public static <K,V> boolean compare(Demo<K,V> d1, Demo<K,V> d2){
return d1.getKey().equals(d2.getKey()) && d1.getValue().equals(d2.getValue());
}
}

有界类型

  • 用来限制作为参数化类型的类型参数的类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package org.example.demo;

public class FanxingDemo {
public static void main(String[] args) {
Demo<String,Integer> demo=new DemoImpl<>("小白",1);
demo.getAll();
}
}
interface Demo<K,V>{
public K getKey();
public V getValue();
public <K extends String,V extends Number> void getAll();
}
class DemoImpl<K,V> implements Demo<K,V>{
private K key;
private V value;
@Override
public K getKey() {return key;}
@Override
public V getValue(){return value;}

public DemoImpl(K key,V value){
this.key=key;
this.value=value;
}
public <K extends String,V extends Number> void getAll(){
System.out.println(key+":"+value);
}
}
  • 不仅限于继承了父类的子类, 也可以代指显现了接口的类。所以可以调用父类或者接口的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example.demo;

import java.lang.reflect.Array;

public class FanxingDemo {
public static void main(String[] args) {
Demo<Integer> demo = new Demo<>(111);
System.out.println(demo.getDouble());
}
}
class Demo<T extends Number>{
private T name;
public Demo(T name) {
this.name = name;
}
public double getDouble(){
return name.doubleValue();
}
}

多重界限

具有多个边界的类型变量是边界中列出的所有类型的子类型。如果其中一个边界是一个类,则必须先指定它。

1
class Demo<T extends Number&A&B>{...}

类型推断

编译器会根据目标类型和参数类型来自动推断泛型类型。

1
2
3
4
5
6
7
8
9
10
11
12
package org.example.demo;

public class FanxingDemo {
public static void main(String[] args) {
String s=Util.pick("hello", "world");
}
}
class Util {
public static <T> T pick(T a, T b) {
return a;
}
}

通配符

上界通配符

通配符可用于多种情况:作为参数的类型、字段或局部变量、作为返回类型。

通配符不能用作泛型方法调用、泛型类实例创建或超类型的类型参数。

1
2
3
4
5
6
7
8
public static double sumOfList(List<? extends Number> list) {
//?表示可以接收任意Number类型的子类或接口的实现类
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}

List<? extends Number>:一个装Number或其子类的列表,但具体是哪个子类不知道

List<T extends Number>:一个装T的列表,且T被绑定为Number的子类型

无界通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example.demo;

import java.util.Arrays;
import java.util.List;

public class FanxingDemo {
public static void main(String[] args) {
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
}
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
}

**<?>****<T>**的区别

``用于使用泛型类型时,表示不关心(或不明确)具体是什么类型。它永远不作为定义出现。

``用于定义泛型类、泛型接口或泛型方法。它相当于一个占位符,在整个作用域内代表某一个具体但可变的类型,该类型在使用时确定。

下界通配符

下限的通配符将未知类型限制为该类型的特定类型或超类型。

1
2
3
4
5
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}

上下界的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.example;

import java.util.List;
import java.util.ArrayList;

public class fanxingDemo {
public static void main(String[] args) {
//上界通配符表示Number的子类型或者Number本身
List<? extends Number> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
//可以读取数据,是因为不管是Integer或者Double都是Number;但是写入数据(如:list.add(11,50);)则不行,因为不知道写入的是Integer还是Double会导致类型不匹配
System.out.println(list.get(0));

//下界通配符表示Number的超类或者Number本身
List<? super Number> list2 = new ArrayList<>();
//可以写入数据,因为不管是List<Number>还是List<Object>都能装Number
list2.add(0,50);
System.out.println(list2);
//只能当Object读取
Object objects = list2.get(0);
System.out.println(objects);
}
}

类型擦除

编译器在编译阶段完成类型检查,并在生成字节码时进行类型擦除 。无界泛型会被擦除成Object类型,上界泛型会被擦除成其超类。

设计目的:兼容老代码

反射依然可以调用到该泛型,因为编译阶段泛型会被擦除成上界类型并同时在class文件里记录泛型签名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;

import java.lang.reflect.Field;
import java.util.List;

public class fanxingDemo {
public static void main(String[] args) {
Field field=Demo.class.getDeclaredFields()[0];
System.out.println(field.getGenericType());
}
}
class Demo{
List<String> list;
}

生成桥方法

Java 编译器在类型擦除后,为了保持泛型类的多态性而自动生成的一种合成方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.example;

import java.lang.reflect.Field;
import java.util.List;

public class fanxingDemo {
public static void main(String[] args) {
Node<Integer> demo=new MyNode();
demo.setData(1);
//父类的setData方法参数被擦除后变为Objec类型,而子类的setData方法参数则是Integer类型,无法正确重写;所以编译器会在子类中插入生成桥方法。
}
}
class Node<T> {
private T data;
public void setData(T data) {
this.data = data;
}
}

class MyNode extends Node<Integer> {
@Override
public void setData(Integer data) {
super.setData(data);
}
}


泛型的限制

  • 不能实例化具有原始类型的泛型类型
1
List<int> list = new ArrayList<>(); //错误
  • 无法创建类型参数的实例
1
2
3
class Test<T> {
T obj = new T(); //编译后T会被擦除成Object,不知道new了什么
}
  • 不能声明类型是类型参数的静态字段
1
2
3
4
5
6
class Test<T> {
static T value;
}
Test<String> str=new Test<>();
Test<Integer> str=new Test<>();
//因为静态字段被类共享,不能同时是多个类型
  • 不能使用带有参数化类型的转换或instanceof
1
2
3
4
5
6
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) {
// ...
}
}
//运行时被<Integer>擦除了
  • 不能创建参数化类型的数组
  • 无法创建,捕捉或抛出参数化类型的对象
  • 不能重载每个过载的形式参数类型擦除到相同的原始类型的方法