学习目标

1 泛型的概念

1.1 泛型的概述

  • 泛型的介绍

    ​ 泛型是JDK5中引入的特性,它提供了编译时类型安全检测机制

  • 泛型的好处

    1. 把运行时期的问题提前到了编译期间
    2. 避免了强制类型转换
  • 泛型的定义格式

    • <类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如:
    • <类型1,类型2…>: 指定多种类型的格式,多种类型之间用逗号隔开.例如: <E,T> <K,V>
举例:

java.lang.Comparable接口和java.util.Comparator接口,是用于对象比较大小的规范接口,这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0。但是并不确定是什么类型的对象比较大小,之前的时候只能用Object类型表示,使用时既麻烦又不安全,因此JDK1.5就给它们增加了泛型。

1
2
3
public interface Comparable<T>{
int compareTo(T o) ;
}
1
2
3
public interface Comparator<T>{
int compare(T o1, T o2) ;
}

其中就是类型参数,即泛型。

1.2 泛型的好处

引入泛型的好处
既能保证安全,又能简化代码。
因为把不安全的因素在编译期间就排除了;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换

示例代码:
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
33
34
35
36
package com.buxianxian;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Demo{

/*
引入泛型的好处
既能保证安全,又能简化代码。
因为把不安全的因素在编译期间就排除了;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换
*/
public static void main(String[] args) {

Collection c = new ArrayList();
c.add(new Student("张三",23)); //Object obj = new Student("张三",23);
c.add(new Student("李四",24));
c.add(new Student("王五",25));
c.add("aab");
c.add(33);
c.add(true);
//遍历集合
Iterator it = c.iterator();// 由于在定义Collection中没有定义泛型,所以存入的内容默认为Object
while (it.hasNext()) {
Object obj = it.next();
// System.out.println(obj.getName()); //报错 object 不能访问子类特有的成员

Student s = (Student)obj; // 需要 向下强转

// 下面代码报错 ClassCastException String cannot be cast to com.buxianxian.Student
// System.out.println(s.getName() + "..." + s.getAge());
}

}
}
Student类
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
33
34
35
36
37
38
39
40
package com.buxianxian;

public class Student {

private String name;
private int age;

public Student() {
}

public Student(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

2 泛型的应用

1662471750600

1 泛型类

声明类或接口时,在类名或接口名后面声明类型变量,我们把这样的类或接口称为泛型类或泛型接口

1
2
修饰符 class 类名<类型> {
}

例如:

1
public class ArrayList<E>{}

泛型类的类型在创建对象时被确认

1、使用核心类库中的泛型类

自从JDK1.5引入泛型的概念之后,对之前核心类库中的API做了很大的修改,例如:集合框架集中的相关接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等等。

下面以ArrayList集合为例演示泛型的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.buxianxian.stringdemo;

import java.util.ArrayList;

public class Demo {

public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");

System.out.println(list);
}

}

1662472062951

2、自定义泛型类

格式: 修饰符 class 类名<类型>{}
<类型>: 指定一种类型的格式,里面可以任意书写,按变量定义规则即可,一般只写一个字母

1
2
示例:public class Box<T>{
}

此处的T可以随意写任意的大写标识,常见的有T、E、K、v等标识泛型

代码演示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.buxianxian.stringdemo;

public class Box<E> {
private E element;

public E getElement() {
return element;
}

public void setElement(E element) {
this.element = element;
}
}

测试泛型类的应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.buxianxian.stringdemo;

public class Demo {

public static void main(String[] args) {

Box<String> box1 = new Box<>();
box1.setElement("革命尚未成功,同志还需努力");// 如果不写泛型,此时参数类型为Object
System.out.println(box1.getElement());

Box<Integer> box2 = new Box<>();
box2.setElement(20);

System.out.println(box2.getElement());
}

}

泛型对应的类型均按照Object处理,但不等价于Object

2 泛型接口

1、核心类中的泛型接口

java.util.List
实现类 java.util.ArrayList

泛型接口的使用方式
1> 实现类没有确定具体的数据类型(ArrayList属于此类)
2> 实现类确定了具体的数据类型

1662474662978

2、自定义泛型接口

在创建对象时,确认泛型的数据类型

具体的实现类,不需要写泛型

1
2
格式: 修饰符 interface 接口名<类型> {  }
示例: public interface Book<T>{}
代码演示
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
33
34
35
36
37
38
39
40
41
package com.buxianxian.stringdemo;

import java.util.ArrayList;

public class Demo {

public static void main(String[] args) {

// 在创建对象时,确认泛型的数据类型

//实现类是没确定具体泛型的实现类,要带泛型
new BookImpl1<String>().method("书中自有黄金屋");
new BookImpl1<Boolean>().method(true);

// 实现类是确定了具体泛型的实现类,不需要写泛型
new BookImpl2().method(333);

}
}

interface Book<E>{
void method(E e);
}

// 1>实现类没有确定具体的数据类型
class BookImpl1<E> implements Book<E>{

@Override
public void method(E e) {
System.out.println(e);
}
}

// 2>实现类确定了具体的数据类型
class BookImpl2 implements Book<Integer>{

@Override
public void method(Integer integer) {
System.out.println(integer);
}
}

3 泛型方法

1、使用核心类中的泛型方法

在java.util.ArrayList中有个泛型方法

T[] toArray(T[] a) 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。

Object[] toArray() 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组。

代码演示
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
package com.buxianxian.stringdemo;

import java.util.ArrayList;
import java.util.Arrays;

public class Demo {

public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
System.out.println(list);

Object[] objects = list.toArray();
System.out.println(Arrays.toString(objects));

String[] strings = list.toArray(new String[list.size()]);
System.out.println(Arrays.toString(strings));
}

}

2、自定义泛型方法

方法在调用时才能确认泛型类型,声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明了类型变量的方法称为泛型方法

1
2
3
格式:修饰符<类型> 返回值类型 方法名(类型 变量名){}

示例:public <T> void show(T t){}
代码演示

定义一个泛型方法,传递一个集合和四个元素,将元素添加到集合并返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.buxianxian.stringdemo;

import java.util.ArrayList;

public class Demo {

public static void main(String[] args) {

ArrayList<String> list1 = addElement(new ArrayList<String>(), "aa", "bb", "cc", "dd");
System.out.println(list1);

}

public static <T> ArrayList<T> addElement(ArrayList<T> list,T t1, T t2,T t3, T t4) {

list.add(t1);
list.add(t2);
list.add(t3);
list.add(t4);

return list;
}
}

3、知识点扩展

1 泛型方法的补充

一个类中可以出现多个泛型

  1. 如果一个方法 ,声明上没有泛型,但是使用到了泛型,默认认为方法的泛型和类的泛型是一致的(推荐方法的泛型与类的泛型一致)

  2. 如果 一个实例方法的泛型和当前类的泛型是一致的,那么 <泛型类型> 是可以省略的( 修饰符 <泛型类型> 返回值类型)

  3. 如果一个方法,使用泛型时,类型和类的泛型不一致 ,那么必须要在方法声明上标出

  4. 如果一个静态方法,使用泛型,必须要单独定义,而且要和类泛型不一致(即使是相同的字母,代表的也是不同的类型)

1
2
3
4
5
6
7
class GenericClass<T> {

// 静态方法必须单独定义泛型参数,即使是相同的字母
static <U> void print(U element) {
System.out.println(element);
}
}

4.静态方法的加载时机是随着类的加载而加载,优先于对象,所以必须单独定义(在静态方法中不能使用类的泛型。)

| | 数据类型确定时间 |
| ———- | —————- | —- |
| 类的泛型 | 创建对象时 |
| 方法的泛型 | 调用方法时 |

2 泛型数组

不能使用new E[]。但是可以:E[] elements = (E[]) new Object[capacity];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Order<T> {
String orderName;
int orderId;

//类的内部结构就可以使用类的泛型
T orderT;

public Order(){
//编译不通过,会被当成类
T[] arr = new T[10];
//编译通过
T[] arr = (T[]) new Object[10];
}

参考: ArrayList源码中声明: Object elementData,而非泛型参数类型数组。

3 子类继承父类时泛型的不同情况

edceec69140ff432a76b25d25f6107d

92fc862b020c502dc757ae43659034f

在这个例子中,Parent类是一个泛型类,Child1Child2Child3是它的子类。

  • Child1类是按需实现泛型,它继承自Parent<Integer>,在实例化时指定了具体的类型为Integer
  • Child2类完全保留了父类的泛型,它继承自Parent<T>,在实例化时指定了具体的类型为String
  • Child3类部分保留了父类的泛型,它继承自Parent<T>,并添加了一个额外的泛型类型U,在实例化时指定了TIntegerUString

4 类型通配符

1.类型通配符

  • 类型通配符: <?>

    • ArrayList<?>: 表示元素类型未知的ArrayList,它的元素可以匹配任何的类型
    • 但是并不能把元素添加到ArrayList中了,获取出来的也是父类类型
  • 类型通配符上限: <? extends 类型>(规定了上边界)

    • ArrayListList <? extends Number>: 它表示的类型是Number或者其子类型
  • 类型通配符下限: <? super 类型>(规定了下边界)

    • ArrayListList <? super Number>: 它表示的类型是Number或者其父类型
代码演示
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
33
34
35
36
37
38
39
package com.buxianxian.stringdemo;

import java.util.ArrayList;

public class Demo {

public static void main(String[] args) {

ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();
ArrayList<Number> list3 = new ArrayList<>();
ArrayList<Object> list4 = new ArrayList<>();

method1(list1);
method1(list2);
method1(list3);
method1(list4);

method2(list1);
method2(list2);//报错
method2(list3);
method2(list4);//报错

method3(list1);//报错
method3(list2);//报错
method3(list3);
method3(list4);
}

// 泛型通配符: 此时的泛型?,可以是任意类型
public static void method1(ArrayList<?> list){}

// 泛型的上限: 此时的泛型?,必须是Number类型或者Number类型的子类
public static void method2(ArrayList<? extends Number> list){}

// 泛型的下限: 此时的泛型?,必须是Number类型或者Number类型的父类
public static void method3(ArrayList<? super Number> list){}

}

2. 通配符小结

? extends A:
G<? extends A> 可以作为G和G的子类,其中B是A的子类 ? super A:

G<? super A> 可以作为G和G的父类,其中B是A的父类