## 一、概述
前面我们提到[](http://c.biancheng.net/java/) Java 集合有个缺点,就是把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了 Object 类型(其运行时类型没变)。
Java 集合之所以被设计成这样,是因为集合的设计者不知道我们会用集合来保存什么类型的对象,所以他们把集合设计成能保存任何类型的对象,只要求具有很好的通用性,但这样做带来如下两个问题:
1. 集合对元素类型没有任何限制,这样可能引发一些问题。例如,想创建一个只能保存 Dog 对象的集合,但程序也可以轻易地将 Cat 对象“丢”进去,所以可能引发异常。
2. 由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道它盛装的是 Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制类型转换既增加了编程的复杂度,也可能引发 ClassCastException 异常。
所以为了解决上述问题,从 Java 1.5 开始提供了泛型。泛型可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率。
泛型本质上是提供类型的“类型参数”,也就是参数化类型。我们可以为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全。
## 二、泛型集合
【例题】下面将结合泛型与集合编写一个案例实现图书信息输出。
```
public class Book {
private int id;
private String name;
private int price;
public Book() {}
public Book(int id, String name, int price) {
this.id = id;
this.name = name;
this.price = price;
}
public String toString() {
return this.id + ", " + this.name + ", " + this.price;
}
}
public class BookTest {
public static void main(String[] args) {
// 定义泛型 Map 集合
Map<Integer, Book> bookMap = new HashMap<Integer, Book>();
bookMap.put(1001, new Book(1, "唐诗三百首", 8));
bookMap.put(1002, new Book(2, "成语大全", 12));
bookMap.put(1003, new Book(3, "新华字典", 22));
System.out.println("泛型Map存储的图书信息如下:");
for (int id : books.keySet()) {
System.out.print(id + "---");
System.out.println(bookMap.get(id)); // 不需要类型转换
}
}
}
```
【选择】以下语句正确的是()(选择两项)
```
A List<String> list = new ArrayList<String>();
B List<Integer> list = new ArrayList<>();
C List<Aniaml> list = new ArrayList<Cat>(); // 已知 Cat 是 Animal 的子类
D List<Object> list = new ArrayList<Integer>();
```
## 三、泛型类
除了可以定义泛型集合之外,还可以直接限定泛型类的类型参数。语法格式如下:
```
public class 类名<类型参数1, 类型参数2, …>{}
```
泛型类一般用于类中的属性类型不确定的情况下。在声明属性时,使用下面的语句:
```
private 类型参数1 属性名称1;
private 类型参数2 属性名称2;
```
在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。
【例题】下面的示例代码创建了一个表示学生的泛型类,该类中包括 3 个属性,分别是姓名、年龄和性别。
```
public class Student<N, A> {
private N name; // 姓名
private A age; // 年龄
public Student() {}
public Student(N name, A age) {
this.name = name;
this.age = age;
}
// 省略 getter setter...
}
public class StudentTest {
public static void main(String[] args) {
Student<String, Integer> stu = new Student<>("小明", 20);
String name = stu.getName();
Integer age = stu.getAge();
System.out.println("学生信息如下:");
System.out.println("学生姓名:" + name + ",年龄:" + age);
}
}
```
在该程序的 Student 类中,定义了 2 个类型参数,分别使用 N 和 A 来代替,同时实现了这 2 个属性的 setter/getter 方法。在主类中,调用 Student 类的构造函数创建了 Student 类的对象,同时指定 2 个类型参数,分别为 String 和 Integer。在获取学生姓名、年龄时,不需要类型转换,程序隐式地将 Object 类型的数据转换为相应的数据类型。
## 四、泛型方法
泛型同样可以在类中包含参数化的方法,而方法所在的类可以是泛型类,也可以不是泛型类。
泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。另外,对一个 static 的方法而言,无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。
定义泛型方法的语法格式如下:
```
[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型方法名([形式参数列表])
```
【例题】使用泛型方法执行动物喂食。
```
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal {
public void eat() {
System.out.println("cat eat ...");
}
}
public class Dog extends Animal {
public void eat() {
System.out.println("dog eat ...");
}
}
public class AnimalTest {
public static <T> void feed(T animal) {
if (animal != null && animal instanceof Animal) {
animal.eat();
}
}
public static void main(String[] args) {
feed(new Cat());
feed(new Dog());
}
}
```
## 五、泛型的高级用法
### 5.1 限制泛型可用类型
在 Java 中默认可以使用任何类型来实例化一个泛型类对象。当然也可以对泛型类实例的类型进行限制,语法格式如下:
```
class 类名称<T extends anyClass>
```
其中,anyClass 指某个接口或类。使用泛型限制后,泛型类的类型必须实现或继承 anyClass 这个接口或类。无论 anyClass 是接口还是类,在进行泛型限制时都必须使用 extends 关键字。
【例题】在下面的示例代码中创建了一个 Student 类,并对该类的类型限制为只能是继承 Number 类的类。
```
// 限制 Student 的泛型类型必须继承 Number 类
public class Student<T extends Number> {
private T age;
public Student(T age) {
this.age = age;
}
public static void main(String[] args) {
// 实例化使用 Integer 的泛型类 Student,正确
Student<Integer> s1 = new Student<Integer>(15);
// 实例化使用 Long 的泛型类 Student,正确
Student<Long> s2 = new Student<Long>(15L);
// 实例化使用 String 的泛型类 Student,错误
Student<String> s3 = new Student<String>("hello");
}
}
```
当没有使用 extends 关键字限制泛型类型时,其实是默认使用 Object 类作为泛型类型。如下等价:
```
public class SomeClass<T> {}
public class SomeClass<T extends Object> {}
```
【阅读】已知如下代码,运行结果为()
```
public abstract class Person {
protected String name;
// getter setter ...
public abstract void display();
}
public class Teacher extends Person {
public Teacher(String name) {
this.name = name;
}
public void display() {
System.out.println(this.getName() + "在讲课");
}
}
public class Student extends Person {
public Student(String name) {
this.name = name;
}
public void display() {
System.out.println(this.getName() + "在学习");
}
}
public class Test {
public <T extends Person> void display(T t) {
t.display();
}
public static void main(String[] args) {
Test test = new Test();
test.display(new Teacher("王老师"));
test.display(new Student("张三"));
test.display("李四在学习");
}
}
```
### 5.2 使用类型通配符
Java 中的泛型还支持使用类型通配符,在创建一个泛型类对象时限制这个泛型类的类型必须实现或继承某个接口或类。
使用泛型类型通配符的语法格式如下:
```
泛型类名称<? extends Number> a = null;
```
【例题】下面的示例代码演示了类型通配符的使用。
```
public class Stu<T> {
private T attr;
public Stu() {}
public Stu(T attr) {
this.attr = attr;
}
}
public class StuTest {
public static void main(String[] args) {
Stu<? extends Number> s = null;
s = new Stu<Integer>(); // 正确
s = new Stu<Long>(); // 正确
s = new Stu<String>(); // 错误
}
}
```
### 5.3 继承泛型类和实现泛型接口
定义为泛型的类和接口也可以被继承和实现。
```
public interface MyInter<T1> {}
public class Father<T2> {}
public class Son<T1, T2, T3> extends Father<T2> implements MyInter<T1> {}
```
如果要在 Son 类继承 Father 类时保留父类的泛型类型,需要在继承时指定,否则直接使用 extends Father 语句进行继承操作,此时 T1、T2 和 T3 都会自动变为 Object,所以一般情况下都将父类的泛型类型保留。
- 阶段一 Java 零基础入门
- 步骤1:基础语法
- 第01课 初识
- 第02课 常量与变量
- 第03课 运算符
- 第04课 选择结构
- 第05课 循环结构
- 第06课 一维数组
- 第08课 方法
- 第09课 数组移位与统计
- 第10课 基础语法测试
- 第09课 基础语法测试(含答案)
- 步骤2:面向对象
- 第01课 类和对象
- 第02课 封装
- 第03课 学生信息管理
- 第04课 继承
- 第05课 单例模式
- 第06课 多态
- 第07课 抽象类
- 第08课 接口
- 第09课 内部类
- 第10课 面向对象测试
- 第10课 面向对象测试(含答案)
- 步骤3:常用工具类
- 第01课 异常
- 第02课 包装类
- 第03课 字符串
- 第04课 集合
- 第05课 集合排序
- 第06课 泛型
- 第07课 多线程
- 第08课 输入输出流
- 第09课 案例:播放器
- 第10课 常用工具测试(一)
- 第10课 常用工具测试(一)(答案)
- 第10课 常用工具测试(二)
- 第10课 常用工具测试(二)(答案)
- 阶段二 从网页搭建入门 JavaWeb
- 步骤1:HTML 与 CSS
- 第01课 HTML 入门
- 第01课 HTML 入门(作业)
- 第02课 CSS 入门
- 第02课 CSS 入门(作业)
- 第03课 CSS 布局
- 第03课 CSS 布局(作业)
- 步骤2:JavaScript 与前端案例
- 第01课 JavaScript 入门
- 第01课 JavaScript 入门(作业)
- 第02课 仿计算器
- 第03课 前端油画商城案例
- 第04课 轮播图
- 第05课 网页搭建测试
- 第05课 网页搭建测试(含答案)
- 步骤3:JavaScript 教程
- 入门
- 概述
- 基本语法
- 数据类型
- 概述
- 数值
- 字符串
- undefined, null 和布尔值
- 对象
- 函数
- 数组
- 运算符
- 算术运算符
- 比较运算符
- 布尔运算符
- 位运算符
- 运算顺序
- 语法专题
- 数据类型的转换
- 错误处理机制
- 标准库
- String
- Date
- Math
- DOM
- 概述
- Document 节点
- 事件
- EventTarget 接口
- 事件模型
- 常见事件
- 阶段三 数据库开发与实战
- 步骤1:初始数据库操作
- 第01课 数据类型
- 第02课 表的管理
- 第03课 数据管理
- 第04课 常用函数
- 第05课 JDBC 入门
- 第06课 Java 反射
- 第07课 油画商城
- 第08课 数据库基础测试
- 步骤2:MyBatis 从入门到进阶
- 第01课 IntelliJ IDEA 开发工具入门
- 第02课 Maven 入门
- 第03课 工厂模式
- 第04课 MyBatis 入门
- 第05课 MyBatis 进阶
- 第06课 商品信息管理
- 第07课 MyBatis 基础测试
- 步骤3:Redis 数据库与 Linux 下项目部署
- 第01课 Linux 基础
- 第02课 Linux 下 JDK 环境搭建及项目部署
- 第03课 Redis 入门
- 阶段四 SSM 到 Spring Boot 入门与综合实战
- 步骤1:Spring 从入门到进阶
- 第01课 Spring 入门
- 第02课 Spring Bean 管理
- 第03课 Spring AOP
- 第04课 基于 AspectJ 的 AOP 开发
- 第05课 JDBC Template
- 第06课 Spring 事务管理
- 第07课 人员管理系统开发
- 第08课 Spring 从入门到进阶测试
- 步骤2:Spring MVC 入门与 SSM 整合开发
- 第01课 Spring MVC 入门与数据绑定
- 第02课 Restful 风格的应用
- 第03课 SpringMVC 拦截器
- 第04课 办公系统核心模块
- 步骤3:Spring Boot 实战
- 第01课 Spring Boot 入门
- 第02课 校园商铺项目准备
- 第03课 校园商铺店铺管理
- 第04课 校园商铺商品管理及前台展示
- 第05课 校园商铺框架大换血
- 步骤4:Java 面试
- 第01课 面试准备
- 第02课 基础面试技巧
- 第03课 Web基础与数据处理
- 第04课 主流框架