14 泛型(Generic)
约 7991 字大约 27 分钟
2025-08-22
泛型概述
Java
生活中的例子
- 举例
1
:中药店里每个抽屉外面贴着的标签
- 举例
2
:超市购物架上很多瓶子,每个瓶子装的是什么,有标签
- 举例
3
:家庭厨房中,各类调料的标签
泛型的引入
在Java
中在声明方法时,
受以上启发,JDK1.5
设计了的概念,泛型即:,这个类型参数。
举例1
:
/
,所以在JDK5.0
之前只能把元素类型设计为Object
,JDK5.0
时Java
引入了 (Parameterized Type
)的概念,允许在创建集合时指定集合元素的类型。比如:List<String>
表明该List
只能保存字符串类型的对象。使用集合存储数据时,除了元素的类型不确定,其他部分是确定的(例如:关于这个元素如何保存,如何管理等)。
举例2
:
java.lang.Comparable
java.util.Comparator
,是用于比较对象大小的接口。这两个接口只是限定了当一个对象大于另一个对象时返回正整数
,小于返回负整数
,等于返回0
,但并不确定是什么类型的对象比较大小。JDK5.0
之前只能用Object
类型表示,使用时既麻烦又不安全,因此JDK5.0
给它们增加了泛型。
其中类型参数就是泛型。。
使用泛型举例
JDK5.0
API
。例如:JDK5.0
改写了集合框架中全部接口和类、java.lang.Comparable
接口、java.util.Comparator
接口、Class
类等,为这些接口、类增加了泛型支持,从而可以在声明变量、创建对象时传入类型实参。
使用泛型
集合中没有使用泛型时
集合中使用泛型时
Java
泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException
异常。即:把不安全的因素在编译期间就排除了,而不是运行期;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。 同时,代码更加简洁、健壮。把一个集合中的内容限制为一个特定的数据类型,这就是 generic
背后的核心思想。
// 泛型在List中的使用
@Test
public void test1(){
// 举例:将学生成绩保存在ArrayList中
// 标准写法:
// ArrayList<Integer> list = new ArrayList<Integer>();
// jdk7的新特性:类型推断
ArrayList<Integer> list = new ArrayList<>();
// 自动装箱
list.add(56);
list.add(76);
list.add(88);
list.add(89);
// 当添加非Integer类型数据时,编译不通过
// list.add("Tom"); // 编译报错
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
// 不需要强转,直接可以获取添加时的元素的数据类型
Integer score = iterator.next();
System.out.println(score);
}
}
// 泛型在Map中的使用
@Test
public void test2(){
HashMap<String,Integer> map = new HashMap<>();
map.put("Tom",67);
map.put("Jim",56);
map.put("Rose",88);
// 编译不通过
// map.put(67,"Jack");
// 遍历key集
Set<String> keySet = map.keySet();
for(String str:keySet){
System.out.println(str);
}
// 遍历value集
Collection<Integer> values = map.values();
Iterator<Integer> iterator = values.iterator();
while(iterator.hasNext()){
Integer value = iterator.next();
System.out.println(value);
}
// 遍历entry集
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator1 = entrySet.iterator();
while(iterator1.hasNext()){
Map.Entry<String, Integer> entry = iterator1.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key + ":" + value);
}
}
练习
- 创建一个
ArrayList
集合对象,并指定泛型为<Integer>
- 添加
5
个[0,100)
以内的整数到集合中 - 使用
foreach
遍历输出5
个整数 - 使用集合的
removeIf
方法删除偶数,为Predicate
接口指定泛型 - 再使用
Iterator
迭代器输出剩下的元素,为Iterator
接口指定泛型
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.function.Predicate;
public class TestNumber {
public static void main(String[] args) {
ArrayList<Integer> coll = new ArrayList<Integer>();
Random random = new Random();
for (int i = 1; i <= 5 ; i++) {
coll.add(random.nextInt(100));
}
System.out.println("coll中5个随机数是:");
for (Integer integer : coll) {
System.out.println(integer);
}
// 方式1:使用集合的removeIf方法删除偶数
coll.removeIf(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer % 2 == 0;
}
});
// 方式2:调用Iterator接口的remove()方法
//Iterator<Integer> iterator1 = coll.iterator();
//while(coll.hasNext()){
// Integer i = coll.next();
// if(i % 2 == 0){
// coll.remove();
// }
//}
System.out.println("coll中删除偶数后:");
Iterator<Integer> iterator = coll.iterator();
while(iterator.hasNext()){
Integer number = iterator.next();
System.out.println(number);
}
}
}
比较器中使用泛型
举例
public class Circle{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle [radius=" + radius + "]";
}
}
import java.util.Comparator;
class CircleComparator implements Comparator{
@Override
public int compare(Object o1, Object o2) {
// 强制类型转换
Circle c1 = (Circle) o1;
Circle c2 = (Circle) o2;
return Double.compare(c1.getRadius(), c2.getRadius());
}
}
// 测试
public class TestNoGeneric {
public static void main(String[] args) {
CircleComparator com = new CircleComparator();
System.out.println(com.compare(new Circle(1), new Circle(2)));
// 运行时异常:ClassCastException
// System.out.println(com.compare("圆1", "圆2"));
}
}
:
import java.util.Comparator;
class CircleComparator1 implements Comparator<Circle> {
@Override
public int compare(Circle o1, Circle o2) {
// 不再需要强制类型转换,代码更简洁
return Double.compare(o1.getRadius(), o2.getRadius());
}
}
// 测试
public class TestHasGeneric {
public static void main(String[] args) {
CircleComparator1 com = new CircleComparator1();
System.out.println(com.compare(new Circle(1), new Circle(2)));
// System.out.println(com.compare("圆1", "圆2"));
// 编译错误,因为"圆1", "圆2"不是Circle类型,是String类型,
// 编译器提前报错,而不是冒着风险在运行时再报错。
}
}
相关使用说明
List<Integer> list = new ArrayList<Integer>();
JDK7.0
有新特性,可以简写List<Integer> list = new ArrayList<>(); // 类型推断
- 不能使用基本数据类型,
- 。在使用集合时,可以具体指明泛型的类型。一旦指明,类或接口内部,凡是使用泛型参数的位置,都指定为具体的参数类型。
Object
自定义泛型结构
泛型的基础说明
<类型>
这种语法形式就叫泛型<类型>
的形式称为,"类型"习惯上使用T
表示,是Type
的缩写。即:<T>
<T>
,可以指定为<String>
<Integer>
<Circle>
等- 类比方法的参数,可以把
<T>
称为,将<Circle>
称为 ,有助于理解泛型 - 这里的
T
,可以替换成K
、V
等任意字母
在哪里可以声明类型变量
<T>
- 声明类或接口时,在类名或接口名后面声明泛型类型,这样的类或接口称为:
[修饰符] class 类名<类型变量列表> [extends 父类] [implements 接口们] { } //例如: public class ArrayList<E> [修饰符] interface 接口名<类型变量列表> [implements 接口们] { } //例如: public interface Map<K,V> { ... }
- 声明方法时,在修饰符与返回值类型之间声明类型变量,声明了类型变量的方法称为:
[修饰符] <类型变量列表> 返回值类型 方法名([形参列表])[throws 异常列表]{ //... } //例如:java.util.Arrays类中的 public static <T> List<T> asList(T... a){ .... }
自定义或
说明
- 在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型
- 在创建自定义泛型类的对象时,可以指明泛型参数类型,一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型
Object
Object
- 经验:
如果在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数,还可以在现有的父类的泛型参数的基础上,新增泛型参数。
<E1,E2,E3>
JDK7.0
开始,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>();
- 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象
new E[]
E[] elements = (E[])new Object[capacity];
- 参考:
ArrayList
源码中声明:Object[] elementData
,而非泛型参数类型数组
- 参考:
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但
静态方法中不能使用类的泛型:这句话的意思是,在静态方法中不能直接使用类的泛型类型参数。
例子:
// 在这个例子中,MyClass 类有一个泛型类型参数 T。
// 然而,静态方法 myStaticMethod 试图使用这个泛型类型参数 T,这是不允许的。
// 编译器会报错,因为静态方法在类加载时就已经存在,
// 而泛型类型参数 T 是在创建类的实例时才确定的。
public class MyClass<T> {
// 类的泛型类型参数 T
// 静态方法
public static void myStaticMethod(T value) { // 这里会报错
System.out.println(value);
}
}
解释:
- 类的泛型类型参数(如
T
)是在创建类的实例时才确定的 - 静态方法在类加载时就已经存在,不需要创建类的实例就可以调用
- 因此,静态方法不能依赖于类的泛型类型参数,因为这些参数在静态方法存在时还没有确定
举例
举例1
:
class Person<T> {
// 使用T类型定义变量
private T info;
// 使用T类型定义一般方法
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
// 使用T类型定义构造器
public Person() {}
public Person(T info) {
this.info = info;
}
// static的方法中不能声明泛型
//public static void show(T t) {
//
//}
// 不能在try-catch中使用泛型定义
//public void test() {
//try {
//
//} catch (MyException<T> ex) {
//
//}
//}
}
举例2
:
class Father<T1, T2> { }
// 子类不保留父类的泛型
// 1) 没有类型 擦除
class Son1 extends Father {
// 等价于class Son extends Father<Object,Object>{
}
// 2) 具体类型
class Son2 extends Father<Integer, String> { }
// 子类保留父类的泛型
// 1) 全部保留
class Son3<T1, T2> extends Father<T1, T2> { }
// 2) 部分保留
class Son4<T2> extends Father<Integer, T2> { }
举例3
:
class Father<T1, T2> { }
// 子类不保留父类的泛型
// 1) 没有类型 擦除
class Son<A, B> extends Father{
//等价于class Son extends Father<Object,Object>{
}
// 2) 具体类型
class Son2<A, B> extends Father<Integer, String> { }
// 子类保留父类的泛型
// 1) 全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}
// 2) 部分保留
class Son4<T2, A, B> extends Father<Integer, T2> {
}
练习
练习1
: 声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢?因为,语文老师希望成绩是优秀
、良好
、及格
、不及格
,数学老师希望成绩是89.5
、65.0
,英语老师希望成绩是A
、B
、C
、D
、E
,那么在设计这个学生类时,就可以使用泛型。
class Student<T> {
private String name;
private T score;
public Student() {
super();
}
public Student(String name, T score) {
super();
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getScore() {
return score;
}
public void setScore(T score) {
this.score = score;
}
@Override
public String toString() {
return "姓名:" + name + ", 成绩:" + score;
}
}
public class TestStudent {
public static void main(String[] args) {
// 语文老师使用时
Student<String> stu1 = new Student<String>("张三", "良好");
// 数学老师使用时
// 错误,必须是引用数据类型
// Student<double> stu2 = new Student<double>("张三", 90.5);
Student<Double> stu2 = new Student<Double>("张三", 90.5);
// 英语老师使用时
Student<Character> stu3 = new Student<Character>("张三", 'C');
// 错误的指定
// Student<Object> stu = new Student<String>();//错误的
}
}
练习2
:
定义个泛型类DAO<T>
,在其中定义一个Map
成员变量,Map
的键为String
类型,值为T
类型。
- 分别创建以下方法:
public void save(String id,T entity)
:保存T
类型的对象到Map
成员变量中public T get(String id)
:从map
中获取id
对应的对象public void update(String id,T entity)
:替换map
中key
为id
的内容public List<T> list()
:返回map
中存放的所有T
对象public void delete(String id)
:删除指定id
对象
- 定义一个
User
类:- 包含:
private
成员变量:(int
类型)id
、age
,(String
类型)name
- 包含:
- 定义一个测试类:
- 创建
DAO
对象,调用save
、get
、update
、list
、delete
方法操作User
对象
- 创建
- 使用
Junit
单元测试类进行测试
public class DAO<T> {
private Map<String,T> map ;
{
map = new HashMap<String,T>();
}
// 保存 T 类型的对象到 map 成员变量中
public void save(String id, T entity) {
if(!map.containsKey(id)){
map.put(id,entity);
}
}
// 从 map 中获取 id 对应的对象
public T get(String id){
return map.get(id);
}
// 替换 map 中key为id的内容,改为 entity 对象
public void update(String id,T entity){
if(map.containsKey(id)){
map.put(id,entity);
}
}
// 返回 map 中存放的所有 T 对象
public List<T> list(){
// 错误的:
// Collection<T> values = map.values();
// System.out.println(values.getClass());
// return (List<T>) values;
// 正确的方式1:
// ArrayList<T> list = new ArrayList<>();
// Collection<T> values = map.values();
// list.addAll(values);
// return list;
// 正确的方式2:
Collection<T> values = map.values();
ArrayList<T> list = new ArrayList<>(values);
return list;
}
//删除指定 id 对象
public void delete(String id){
map.remove(id);
}
}
import java.util.Objects;
public class User {
private int id;
private int age;
private String name;
public User() {
}
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id && age == user.age && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
return Objects.hash(id, age, name);
}
}
import java.util.List;
public class DAOTest {
public static void main(String[] args) {
DAO<User> dao = new DAO<>();
dao.save("1001",new User(1,34,"曹操"));
dao.save("1002",new User(2,33,"刘备"));
dao.save("1003",new User(3,24,"孙权"));
dao.update("1002",new User(2,23,"刘禅"));
dao.delete("1003");
List<User> list = dao.list();
for(User u : list){
System.out.println(u);
}
}
}
自定义
<泛型参数>
<泛型参数>
说明
- :
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) [抛出的异常] {
...
}
- 方法也可以被泛型化,与其所在的类是否是泛型类没有关系
- 泛型方法中的泛型参数在方法被调用时确定
static
- 泛型方法可以有自己的类型参数(如:
<E>
- 泛型方法可以有自己的类型参数(如:
举例
举例1
:
// 泛型方法
public class DAO {
public <E> E get(int id, E e) {
E result = null;
return result;
}
}
举例2
:
// 静态泛型方法
public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o);
}
}
// 测试
public static void main(String[] args) {
Object[] ao = new Object[100];
Collection<Object> co = new ArrayList<Object>();
fromArrayToCollection(ao, co);
String[] sa = new String[20];
Collection<String> cs = new ArrayList<>();
fromArrayToCollection(sa, cs);
Collection<Double> cd = new ArrayList<>();
// 下面代码中T是Double类,但sa是String类型,编译错误。
// fromArrayToCollection(sa, cd);
// 下面代码中T是Object类型,sa是String类型,可以赋值成功。
fromArrayToCollection(sa, co);
}
举例3
:
class MyArrays {
public static <T> void sort(T[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length-i; j++) {
if(((Comparable<T>)arr[j]).compareTo(arr[j+1])>0){
T temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
public class MyArraysTest {
public static void main(String[] args) {
int[] arr = {3,2,5,1,4};
// 错误的,因为int[]不是对象数组
// MyArrays.sort(arr);
String[] strings = {"hello","java","song"};
MyArrays.sort(strings);
System.out.println(Arrays.toString(strings));
Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)};
// 编译通过,运行报错,因为Circle没有实现Comparable接口
MyArrays.sort(circles);
}
}
练习
练习1
:编写一个泛型方法,
public static <E> void method1( E[] e,int a,int b)
public class Exer01 {
// 编写一个泛型方法,实现任意引用类型数组指定位置元素交换
public static <E> void method( E[] arr,int a,int b){
E temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
@Test
public void testMethod(){
Integer[] arr = new Integer[]{10,20,30,40};
method(arr,2,3);
for(Integer i : arr){
System.out.println(i);
}
}
}
练习2
:编写一个泛型方法,
public static <E> void method2( E[] e)
public class Exer01 {
// 编写一个泛型方法,接收一个任意引用类型的数组,并反转数组中的所有元素
public static <E> void method1( E[] arr){
for(int min = 0,max = arr.length - 1;min < max; min++,max--){
E temp = arr[min];
arr[min] = arr[max];
arr[max] = temp;
}
}
@Test
public void testMethod1(){
Integer[] arr = new Integer[]{10,20,30,40};
method1(arr);
for(Integer i : arr){
System.out.println(i);
}
}
}
泛型在继承上的体现
B
A
G
G<B>
G<A>
比如:String
是Object
的子类,但是List<String>
并不是List<Object>
的子类。
public void testGenericAndSubClass() {
Person[] persons = null;
Man[] mans = null;
// Person[] 是 Man[] 的父类
persons = mans;
Person p = mans[0];
// 在泛型的集合上
List<Person> personList = null;
List<Man> manList = null;
//personList = manList;(报错)
}
思考:对比如下两段代码有何不同
- 片段
1
public void printCollection(Collection c) {
Iterator i = c.iterator();
for (int k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
- 片段
2
public void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
1
Collection
2
Collection<Object>
泛型擦除和类型安全
- 泛型擦除:
Java
的泛型在编译时会进行类型擦除,这意味着泛型类型信息在运行时会被擦除。- 例如:
List<String>
在运行时会被擦除为List
- 例如:
- 类型安全:
- 泛型的主要目的是提供类型安全。通过使用泛型,编译器可以在编译时检查类型,确保只能向泛型集合中添加正确类型的对象
显示声明泛型参数
当显示声明泛型参数时,编译器会进行严格的类型检查,确保只能向泛型集合中添加正确类型的对象。例如:
List<String> list = new ArrayList<>();
// 编译器会检查类型,确保只能添加 String
list.add("Hello");
在这种情况下,编译器会在编译时检查类型,确保只能向List<String>
中添加String
类型的对象。如果尝试添加其他类型的对象,编译器会报错:
// 编译器会报错,因为 123 不是 String 类型
list.add(123);
不显示声明泛型参数
如果不显示声明泛型参数,而是使用原始类型,例如:List
,编译器不会进行类型检查,这意味着可以向集合中添加任何类型的对象。例如:
List list = new ArrayList();
// 可以添加 String
list.add("Hello");
// 也可以添加 Integer
list.add(123);
在这种情况下,编译器不会进行类型检查,可以向集合中添加任何类型的对象。这可能会导致运行时类型转换异常:
// 运行时会抛出 ClassCastException,因为 list.get(1) 是 Integer 类型
String str = (String) list.get(1);
总结
- :编译器会在编译时进行严格的类型检查,确保只能向泛型集合中添加正确类型的对象。这可以避免运行时类型转换异常
- :编译器不会进行类型检查,可以向集合中添加任何类型的对象。这可能会导致运行时类型转换异常
通配符的使用
Comparator<T>
<T>
?
通配符的理解
使用类型通配符:?
,比如:List<?>
、Map<?,?>
List<?>
List<String>
List<Object>
List
通配符的读与写
写操作
Collection<?> c = new ArrayList();
// 编译时错误
// c.add(new Object());
因为不知道c
的元素类型,不能向其中添加对象。add
方法有类型参数E
作为集合的元素类型。传给add
的任何参数都必须是一个未知类型的子类。因为不知道那是什么类型,所以无法传任何东西进去。唯一可以插入的元素是null
,因为它是所有引用类型的默认值。
读操作
List<?>
list
list
Object
举例1
:
public class TestWildcard {
public static void m4(Collection<?> coll){
for (Object o : coll) {
System.out.println(o);
}
}
}
举例2
:
public static void main(String[] args) {
List<?> list = null;
list = new ArrayList<String>();
list = new ArrayList<Double>();
// list.add(3);//编译不通过
list.add(null);
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
l1.add("尚硅谷");
l2.add(15);
read(l1);
read(l2);
}
public static void read(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
使用注意点
- 注意点
1
:<>
?
()
public static <?> void test(ArrayList<?> list){ }
- 注意点
2
:()
class GenericTypeClass<?>{ }
- 注意点
3
:,右边属于创建集合对象()
ArrayList<?> list2 = new ArrayList<?>();
<?>
- 通配符:
<? extends 类 / 接口>
- 使用时指定的类型必须是 ,即:
<=
- 使用时指定的类型必须是 ,即:
- 通配符:
<? super 类 / 接口>
- 使用时指定的类型必须是 ,即:
>=
- 使用时指定的类型必须是 ,即:
- 说明:
<? extends Number> //(无穷小 , Number]
//只允许泛型为Number及Number子类的引用调用
<? super Number> //[Number , 无穷大)
//只允许泛型为Number及Number父类的引用调用
<? extends Comparable>
//只允许泛型为实现Comparable接口的实现类的引用调用
举例1
class Creature { }
class Person extends Creature { }
class Man extends Person { }
class PersonTest {
public static <T extends Person> void test(T t){
System.out.println(t);
}
public static void main(String[] args) {
test(new Person());
test(new Man());
//The method test(T) in the type PersonTest is not
//applicable for the arguments (Creature)
test(new Creature());
}
}
举例2
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement1(list1);
getElement1(list2);//报错
getElement1(list3);
getElement1(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){ }
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){ }
举例3
public static void printCollection1(Collection<? extends Person> coll) {
// Iterator只能用Iterator<?>或Iterator<? extends Person>
Iterator<?> iterator = coll.iterator();
while (iterator.hasNext()) {
Person per = iterator.next();
System.out.println(per);
}
}
public static void printCollection2(Collection<? super Person> coll) {
// Iterator只能用Iterator<?>或Iterator<? super Person>
Iterator<?> iterator = coll.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println(obj);
}
}
举例4
这两个测试案例展示了Java
泛型中的两个重要概念:? extends T
和? super T
,它们分别表示上界通配符和下界通配符。
? extends T
? extends T
表示类型参数是T
或T
的子类。这种通配符用于读取操作,但不允许写入操作(除了null
)
@Test
public void test1(){
List<Person> list2 = new ArrayList<Person>();
List<? extends Person> list4 = null;
list2.add(new Person());
list4 = list2;
// 读取:可以读
Person p1 = list4.get(0);
// 写入:除了 null 之外,不能写入
list4.add(null);
// list4.add(new Person()); // 编译错误
// list4.add(new Student()); // 编译错误
}
解释:
- 读取操作:
list4
是List<? extends Person>
类型,表示它可以包含Person
或Person
的子类。因此,可以从list4
中读取元素,并将其赋值给Person
类型的变量。 - 写入操作:由于
list4
的类型是? extends Person
,编译器无法确定具体的类型(可能是Person
、Student
或其他Person
的子类)。因此,不允许向list4
中添加任何非null
的元素,以避免类型不匹配。
? super T
? super T
表示类型参数是T
或T
的父类。这种通配符用于写入操作,但读取操作只能返回Object
类型。
@Test
public void test2(){
List<Person> list2 = new ArrayList<Person>();
List<? super Person> list5 = null;
list2.add(new Person());
list5 = list2;
// 读取:可以实现,但只能返回 Object 类型
Object obj = list5.get(0);
// 写入:可以写入 Person 及 Person 子类的对象
list5.add(new Person());
list5.add(new Student());
}
解释:
- 读取操作:
list5
是List<? super Person>
类型,表示它可以包含Person
或Person
的父类。由于无法确定具体的父类类型,因此读取操作只能返回Object
类型。 - 写入操作:由于
list5
的类型是? super Person
,编译器知道它可以包含Person
或Person
的父类。因此,可以向list5
中添加Person
或Person
的子类对象,因为这些对象都可以赋值给Person
或其父类。
泛型应用举例
举例1
:
public static void main(String[] args) {
HashMap<String, ArrayList<Citizen>> map = new HashMap<String, ArrayList<Citizen>>();
ArrayList<Citizen> list = new ArrayList<Citizen>();
list.add(new Citizen("赵又廷"));
list.add(new Citizen("高圆圆"));
list.add(new Citizen("瑞亚"));
map.put("赵又廷", list);
Set<Entry<String, ArrayList<Citizen>>> entrySet = map.entrySet();
Iterator<Entry<String, ArrayList<Citizen>>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Entry<String, ArrayList<Citizen>> entry = iterator.next();
String key = entry.getKey();
ArrayList<Citizen> value = entry.getValue();
System.out.println("户主:" + key);
System.out.println("家庭成员:" + value);
}
}
举例2
:
用户在设计类的时候往往会使用类的关联关系,例如:一个人中可以定义一个信息的属性,但是一个人可能有各种各样的信息(如:联系方式、基本信息等),所以此信息属性的类型就可以通过泛型进行声明,然后只要设计相应的信息类即可。
// 只有此接口的子类才是表示人的信息
interface Info{
}
// 表示联系方式
class Contact implements Info{
// 联系地址
private String address ;
// 联系方式
private String telephone ;
// 邮政编码
private String zipcode ;
public Contact(String address,String telephone,String zipcode){
this.address = address;
this.telephone = telephone;
this.zipcode = zipcode;
}
public void setAddress(String address){
this.address = address ;
}
public void setTelephone(String telephone){
this.telephone = telephone ;
}
public void setZipcode(String zipcode){
this.zipcode = zipcode;
}
public String getAddress(){
return this.address ;
}
public String getTelephone(){
return this.telephone ;
}
public String getZipcode(){
return this.zipcode;
}
@Override
public String toString() {
return "Contact [address=" + address + ", telephone=" + telephone
+ ", zipcode=" + zipcode + "]";
}
}
class Introduction implements Info{
// 姓名
private String name ;
// 性别
private String sex ;
// 年龄
private int age ;
public Introduction(String name,String sex,int age){
this.name = name;
this.sex = sex;
this.age = age;
}
public void setName(String name){
this.name = name ;
}
public void setSex(String sex){
this.sex = sex ;
}
public void setAge(int age){
this.age = age ;
}
public String getName(){
return this.name ;
}
public String getSex(){
return this.sex ;
}
public int getAge(){
return this.age ;
}
@Override
public String toString() {
return "Introduction [name=" + name + ", sex=" + sex + ", age=" + age
+ "]";
}
}
class Person<T extends Info>{
private T info ;
// 通过构造器设置信息属性内容
public Person(T info){
this.info = info;
}
public void setInfo(T info){
this.info = info ;
}
public T getInfo(){
return info ;
}
@Override
public String toString() {
return "Person [info=" + info + "]";
}
}
public class GenericPerson{
public static void main(String args[]){
// 声明Person对象
Person<Contact> per = null ;
per = new Person<Contact>(new Contact("北京市","01088888888","102206")) ;
System.out.println(per);
// 声明Person对象
Person<Introduction> per2 = null ;
per2 = new Person<Introduction>(new Introduction("李雷","男",24));
System.out.println(per2) ;
}
}