一. 什么是泛型

“泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束。

二. 为什么要使用泛型

以ArrayList为例,如果我们不用泛型。那么存储Integer我们需要使用IntegerArrayList,存储String需要使用StringArrayList。这样子很麻烦,而且很多部分都重复操作了。但如果我们使用泛型,我们只需要传入指定存储的类就好,例如ArrayList,ArrayList

三. 泛型类

1. 模板

1
2
3
public class ClassName<泛型标识> {
private 泛型标识 property;
}

2. 泛型标识

  • 对于泛型标识符,可以是字母、中文字符等,不过一般用大写英文字符。一般有一些约定俗称的写法。
  • 如果是表示集合的元素类型,一般用字符E,如public class ArrayList<E>
  • 如果是表示关键字和值的类型,一般用字符K和V,如public class HashMap<K,V>
  • 如果是表示一个类型,一般用字符T,如public class Person<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
//车库
public class Garage<E> {
//向车库中添加车
public void add(E car){
//...
}
}

//映射关系
//K,V: 蔬菜,是否有机; 水果,产地; 服装,类型; 汽车,品牌
public class Mapping<K, V> {

private K key;

private V value;
}

public class Person<T> {
private T t;
public Person(T t){
this.t = t;
}
public void run(){
System.out.println(t);
}
}

3. 如何使用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person<T> {
//成员变量
private T t;

//构造方法
public Person(T t){
this.t = t;
}

//成员方法
public void run(){
System.out.println(t);
}
}
1
2
3
4
5
6
7
8
9
//正常用法
Person<String> s1 = new Person<String>("张三");

//jdk7.0后可以省略后面的参数类型
Person<String> s2 = new Person<>("张三");
s2.run();

//泛型的类型不能是八大基本类型,下面会编译出错
Person<int> s = new Person<>(1);

四. 泛型接口

1. 模板

1
2
3
4
5
6
7
public interface Person<T> {
//方法
T parent();

//方法
String eat();
}

当我们定义了一个类要实现该接口时,那么该类的泛型类型必须和接口类的泛型类型一致,未传递实参的情况下,继续使用泛型类型T,传递了实参的情况下,泛型类型必须使用实参类型。

1
2
3
4
5
6
7
8
9
10
11
public class Teacher<T> implements Person<T> {
@Override
public T parent() {
return null;
}

@Override
public String eat() {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//Teacher不必再定义类型了,因为泛型类型在Person处已经定义好了
public class Teacher implements Person<Integer> {
//这里的返回类型必须为Integer,否则必须出错
@Override
public Integer parent() {
return null;
}

@Override
public String eat() {
return null;
}
}

五. 泛型方法

1.介绍

泛型方法可以定义在普通类和泛型类中,比泛型类更为常用,一般能用泛型方法解决的问题优先使用泛型方法而不使用泛型类,类型变量放在修饰符的后面,如public static ,public final等的后面。

1
2
3
4
5
6
7
8
9
10
11
public class Teacher {
public static <T> T println(T t){

System.out.println(t);

return t;
}
}

//使用
String s = Teancher.println("str");

这个时候大家可能会有疑问,返回值返回T不就行了吗,为什么要写成 T。其实这是一个标志,返回值就是T,但是有这样的才算是泛型方法;仅仅使用了泛型变量并不算是泛型方法。

2. 区别

定义在泛型类中的泛型方法的泛型变量之间是没有关系的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Teacher<T> {
//变量
T teacher;

//构造方法
public Teacher(T t){
this.teacher = t;
}

//泛型方法
public <T> T println(T t){
System.out.println(t);
return t;
}
}

Teacher<String> teacher = new Teacher<>("张三");
Integer in = teacher.println(123456);

类泛型类型为String,方法的泛型类型为Integer,虽然都是用T来表示的。

六. 限定类型变量

不论是泛型类还是泛型方法,目前来说其实都是没有做类型限定,无论我们传递什么样类型的变量进去都可以。假如我们想传递的参数类型仅仅是某个大类(父类)下面的一些小类(子类),那么怎么做呢?

1
2
//限制传入的类需要implements Comparable并且implements Serializable
public static <T extends Comparable & Serializable> T getMax(T[] array)

此处用的是extends关键字,extends在这里是表示的是绑定了Comparable接口及其子类型,是“绑定、限定”的意思,非“继承”的意思,后面也可以是接口或者类,如果有多个限制,可以使用&分隔。

七. 泛型通配符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//定义了一个书籍类
public class Book {}
//定义了一个小说书籍类继承书籍类
public class Novel extends Book {}

//定义了一个书柜类用来装书
public class Bookcase<T> {
T b;
public Bookcase(T t){
this.b = t;
}
public void set(T t) {
b=t;
}
public T get() {
System.out.println(b.getClass());
return b;
}
}

1. 上界通配符

1
Bookcase<? extends Novel> bc = new Bookcase<Novel>(new Novel());

使用 extends 关键字。

含义是该书柜只能放置小说类书籍(如什么都市小说、爱情小说、玄幻小说都可以),但不能放置父类书籍、其他类如史书、职场类书籍、财经类书籍等。

特点,对上有限制,根据java多态向上造型的原则,不适合频繁插入数据,适合频繁读取数据的场景。

2. 下界通配符

1
Bookcase<? super Novel> bc = new Bookcase<Novel>(new Novel());

使用 super 关键字。

含义就是书柜放置设置了下限,我们只能放置Book书籍以及Novel书籍,却无法再将细分的都市小说、爱情小说类书籍放进去。

特点,和上界通配符正好相反,不适合频繁读取数据,适合频繁插入数据的场景。

3. 无限定通配符

1
2
3
4
5
6
7
List<?> list = new ArrayList<>();
//无法编译通过
list.add(new Object());

//下面这样的却可以添加任何类型
List<Object> list = new ArrayList<>();
list.add(new Object());

无限定通配符意味着可以使用任何对象,因此使用它类似于使用原生类型。但它是有作用的,原生类型可以持有任何类型,而无限定通配符修饰的容器持有的是某种具体的类型。

参考文章

Java泛型使用的简单介绍