Java 泛型:类型参数、通配符与类型擦除 目录一、泛型要解决的问题二、集合中的泛型三、自定义泛型结构四、自定义泛型方法五、泛型与继承关系六、类型通配符七、有限制的通配符八、泛型的综合应用九、要点回顾示例约定。标注「完整示例」的代码块可保存为.java文件编译运行标注「片段」的代码放入方法或类体。示例以 JDK 8 为基准。文中出现的T、E、K、V均为类型变量的惯用名可以替换为任意合法标识符。一、泛型要解决的问题一.1 一个直观的类比中药店的药柜有许多抽屉每个抽屉外面贴着标签写明里面装的是什么。取药时按标签定位不必逐个拉开确认。Java 的泛型起的是同样的作用给容器贴上类型标签让编译器在写入和取出时都知道里面装的是什么类型而不必等到运行时再判断。一.2 从方法形参到类型参数声明方法时如果完成功能需要一项未知的数据参与而该项数据要到调用时才能确定做法是把它声明为形参方法体中使用形参名代表这个未知值调用者传入实参即可。泛型沿用了同样的思路只是把「值的未知」提升为「类型的未知」。方法参数传递的是值类型参数传递的是类型。JDK 1.5 引入泛型其本质是参数化类型在声明类、接口或方法时用一个标识符代表未知的某种通用类型在真正使用时继承、实现接口、创建对象、调用方法再传入具体类型。一.3 引入泛型的两个动因动因一集合无法预知元素类型。集合类在设计阶段无法确定容器里究竟存放什么类型的对象。JDK 5.0 之前只能把元素类型设计为Object代价是两重问题写入时任何类型都能进入类型约束形同虚设取出时必须强制转换一旦类型判断错误编译期毫无提示运行时才抛ClassCastException。JDK 5.0 引入参数化类型后可以在创建集合时指定元素类型// 片段 ListString list new ArrayList();这条声明表明该List只能保存String。这里有两点值得注意。第一用集合存储数据时元素类型不确定但其他部分是确定的元素如何保存、如何扩容、如何遍历都与元素类型无关。泛型要参数化的正是那个不确定的部分。第二类型检查前移到编译期代价是写法变长收益是错误提前暴露。动因二比较器接口无法约束比较对象。java.lang.Comparable与java.util.Comparator用于比较对象大小。两个接口只约定了语义a 大于 b 返回正整数小于返回负整数相等返回 0。但接口本身并不确定参与比较的是什么类型。JDK 5.0 之前只能用Object表示实现类中不得不写成compareTo(Object o)再自行强转既繁琐又不安全。JDK 5.0 为它们增加了泛型// 片段 public interface ComparableT { int compareTo(T o); } public interface ComparatorT { int compare(T o1, T o2); }其中的T就是类型参数。实现ComparablePerson之后compareTo的形参类型固定为Person传入其他类型直接编译失败。一.4 泛型的定义泛型允许在定义类、接口时用一个标识符表示类中某个属性的类型或某个方法返回值、参数的类型。这个类型参数在使用时确定传入的实际类型称为类型实参。二、集合中的泛型JDK 5.0 引入泛型后核心类库做了大幅改写集合框架中的全部接口与类、Comparable、Comparator、Class等均增加了泛型支持从而可以在声明变量、创建对象时传入类型实参。二.1 使用泛型前后的差别不使用泛型时集合中的元素一律按Object处理// 完整示例 NoGenericDemo.java import java.util.ArrayList; import java.util.Iterator; public class NoGenericDemo { public static void main(String[] args) { ArrayList list new ArrayList(); list.add(56); list.add(76); list.add(Tom); // 编译通过运行时才可能出问题 Iterator iterator list.iterator(); while (iterator.hasNext()) { Object obj iterator.next(); if (obj instanceof Integer) { Integer score (Integer) obj; // 必须强转 System.out.println(score); } } } }同一段逻辑使用泛型后// 完整示例 GenericDemo.java import java.util.ArrayList; import java.util.Iterator; public class GenericDemo { public static void main(String[] args) { ArrayListInteger list new ArrayList(); list.add(56); // 自动装箱 list.add(76); // list.add(Tom); // 编译报错类型不符 IteratorInteger iterator list.iterator(); while (iterator.hasNext()) { Integer score iterator.next(); // 无需强转 System.out.println(score); } } }两次差异可以归结为三点对比项无泛型有泛型类型约束任意类型都可写入写入时即校验取出元素必须强转直接得到声明类型错误暴露时机运行时抛ClassCastException编译期报错由此得出一条判断如果程序在编译期没有产生泛型警告运行时就不会因类型不符抛出ClassCastException。不安全的因素被提前到编译期排除代码同时变得更简洁、更健壮。把一个集合的内容限制为特定数据类型这就是泛型的核心思想。二.2 List 中使用泛型// 完整示例 ListGenericTest.java import java.util.ArrayList; import java.util.Iterator; public class ListGenericTest { public static void main(String[] args) { // 将学生成绩保存在 ArrayList 中 // 标准写法ArrayListInteger list new ArrayListInteger(); // JDK 7 起可用菱形语法由编译器推断类型 ArrayListInteger list new ArrayList(); list.add(56); // 自动装箱 list.add(76); list.add(88); list.add(89); // list.add(Tom); // 编译报错 IteratorInteger iterator list.iterator(); while (iterator.hasNext()) { Integer score iterator.next(); System.out.println(score); } } }二.3 Map 中使用泛型Map有两个类型参数分别对应键与值遍历时三种集合视图的类型也随之确定。// 完整示例 MapGenericTest.java import java.util.*; public class MapGenericTest { public static void main(String[] args) { HashMapString, Integer map new HashMap(); map.put(Tom, 67); map.put(Jim, 56); map.put(Rose, 88); // map.put(67, Jack); // 编译不通过 // 遍历 key 集 SetString keySet map.keySet(); for (String str : keySet) { System.out.println(str); } // 遍历 value 集 CollectionInteger values map.values(); IteratorInteger iterator values.iterator(); while (iterator.hasNext()) { Integer value iterator.next(); System.out.println(value); } // 遍历 entry 集 SetMap.EntryString, Integer entrySet map.entrySet(); IteratorMap.EntryString, Integer iterator1 entrySet.iterator(); while (iterator1.hasNext()) { Map.EntryString, Integer entry iterator1.next(); String key entry.getKey(); Integer value entry.getValue(); System.out.println(key : value); } } }二.4 使用说明创建集合对象时可以指明泛型类型格式为ListString list new ArrayListString();JDK 7.0 起可以简写为ListString list new ArrayList();右侧由编译器推断称为菱形语法泛型参数只能使用引用数据类型。基本类型不能作为类型实参需用对应的包装类替代例如int写作Integer声明集合时可声明泛型参数使用时再具体指明。一旦指明类或接口内部凡是使用该类型参数的位置都替换为具体类型如果没有指明则一律按Object处理。二.5 练习集合与泛型的配合要求创建ArrayList集合对象指定泛型为Integer添加 5 个[0, 100)以内的整数使用 foreach 遍历输出这 5 个整数使用removeIf删除偶数为Predicate接口指定泛型Integer使用Iterator输出剩余元素为Iterator接口指定泛型Integer。// 完整示例 TestList.java import java.util.ArrayList; import java.util.Iterator; import java.util.Random; public class TestList { public static void main(String[] args) { // (1) 创建集合 ArrayListInteger list new ArrayList(); // (2) 添加 5 个 [0, 100) 的随机整数 Random rand new Random(); for (int i 0; i 5; i) { list.add(rand.nextInt(100)); } System.out.println(原始集合 list); // (3) foreach 遍历 System.out.println(--- foreach 遍历 ---); for (Integer num : list) { System.out.println(num); } // (4) 删除偶数Predicate 的泛型由 Lambda 推断为 Integer list.removeIf(num - num % 2 0); System.out.println(删除偶数后 list); // (5) Iterator 遍历 System.out.println(--- Iterator 遍历 ---); IteratorInteger iterator list.iterator(); while (iterator.hasNext()) { Integer num iterator.next(); System.out.println(num); } } }第 4 步中removeIf的形参是Predicate? super EE已由ArrayListInteger确定为Integer因此 Lambda 参数无需显式声明类型。三、自定义泛型结构三.1 语法形式类型这种写法就是泛型。其中尖括号内的标识符称为类型参数习惯上用T表示取自 Type 的首字母也可以使用K、V、E等任意合法标识符K与V分别对应 Key 与 ValueE对应 Element。类型参数代表一个未知的数据类型使用时可以指定为Integer、String、Person等具体类型。类比方法参数声明处的T称为类型形参传入处的Integer称为类型实参。类型变量可以声明在两个位置位置一类名或接口名之后。这样声明的类或接口称为泛型类、泛型接口。// 片段 [修饰符] class 类名类型变量列表 [extends 父类] [implements 接口们] { } [修饰符] interface 接口名类型变量列表 [extends 接口们] { }// 片段JDK 中的例子 public class ArrayListE public interface MapK, V位置二修饰符与返回值类型之间。这样声明的方法称为泛型方法。// 片段 [修饰符] 类型变量列表 返回值类型 方法名([形参列表]) [throws 异常列表] { // ... }// 片段java.util.Arrays 中的例子 public static T ListT asList(T... a)三.2 自定义泛型类与泛型接口当一个类或接口中某个成员的类型不确定且该类型需要在使用时才确定就适合定义为泛型类或泛型接口。三.2.1 使用规则声明完自定义泛型类后可以在类内部使用该泛型属性类型、方法参数与返回值、构造器参数均可使用。创建泛型类对象时可以指明类型实参。一旦指明内部所有使用该类型参数的位置都具体化为该类型。创建对象时若未指明类型实参泛型将被擦除相应位置按Object处理但不等价于Object——这一点在反射与重载解析中会体现。泛型指定必须使用引用数据类型不能使用基本类型此时只能用包装类替换。除创建对象外子类继承泛型类、实现类实现泛型接口时同样可以确定泛型参数。若子类本身也不确定类型可以继续沿用泛型参数也可以在父类已有泛型参数的基础上新增类型参数。关于第 3 条有一条实践经验泛型要么一路都用要么一路都不用。混用时部分位置按具体类型处理、部分位置按Object处理读写规则随之不一致排查成本远高于统一写法。三.2.2 六条限制泛型类可以有多个参数一并写在尖括号内如E1, E2, E3。JDK 7.0 起可用菱形语法简化创建过程如ArrayListString flist new ArrayList();。若泛型结构是接口或抽象类则不能直接创建其对象。不能写new E[]。需要泛型数组时只能绕道E[] elements (E[]) new Object[capacity];。ArrayList源码中声明的正是Object[] elementData而非泛型参数类型数组原因同上——运行时无法得知E的具体类型JVM 无法为其分配确定大小的数组。在类或接口上声明的泛型在本类或本接口中代表某种确定类型但不能在静态方法中使用类上声明的泛型。静态成员随类加载而存在此时类型实参尚未传入。异常类不能带泛型。catch子句需要运行时确定异常类型而泛型在运行时已擦除。三.2.3 举例一泛型类的基本写法// 完整示例 Person.java public class PersonT { private T info; // 用 T 定义属性 public Person() { } public Person(T info) { // 用 T 定义构造器参数 this.info info; } public T getInfo() { // 用 T 定义返回值 return info; } public void setInfo(T info) { // 用 T 定义参数 this.info info; } // 编译错误静态方法中不能使用类上声明的泛型 // public static void show(T t) { } // 编译错误catch 的参数不能是泛型类型 // public void test() { // try { // } catch (MyExceptionT ex) { // } // } }三.2.4 举例二子类如何确定父类泛型父类带有两个类型参数子类有四种处理方式。// 完整示例 FatherSon.java class FatherT1, T2 { } // 子类不保留父类的泛型 // 1) 不写类型擦除等价于 extends FatherObject, Object class Son1 extends Father { } // 2) 写具体类型 class Son2 extends FatherInteger, String { } // 子类保留父类的泛型 // 1) 全部保留 class Son3T1, T2 extends FatherT1, T2 { } // 2) 部分保留 class Son4T2 extends FatherInteger, T2 { }子类也可以在自己新增类型参数的同时继承父类的泛型// 完整示例 FatherSon2.java class FatherT1, T2 { } // 子类新增 A、B 两个类型参数父类泛型被擦除 class SonA, B extends Father { // 等价于 extends FatherObject, Object } // 新增类型参数同时为父类指定具体类型 class Son2A, B extends FatherInteger, String { } // 新增类型参数同时全部保留父类泛型 class Son3T1, T2, A, B extends FatherT1, T2 { } // 新增类型参数同时部分保留父类泛型 class Son4T2, A, B extends FatherInteger, T2 { }四、自定义泛型方法如果定义类或接口时没有使用类型参数而某个方法的形参类型不确定可以只在该方法上定义类型参数。四.1 格式与规则// 片段 [访问权限] 泛型 返回值类型 方法名([泛型标识 参数名称]) [抛出的异常] { }四条规则方法与所在类是否为泛型类无关普通类中同样可以定义泛型方法泛型方法中的类型参数在方法被调用时确定泛型方法可以声明为static此时类型参数来自方法自身与类无关泛型方法的类型参数应写在返回值类型之前否则编译器会将其视为普通返回值类型而报错。四.2 举例一返回类型由调用方决定// 完整示例 DAO.java public class DAO { public E E get(int id, E e) { E result null; return result; } }调用dao.get(1, abc)时E被推断为String调用dao.get(1, 100)时E被推断为Integer。同一个方法对不同调用返回不同类型且无需强转。四.3 举例二数组到集合的搬运// 完整示例 CollectionCopy.java import java.util.*; public class CollectionCopy { public static T void fromArrayToCollection(T[] a, CollectionT c) { for (T o : a) { c.add(o); } } public static void main(String[] args) { Object[] ao new Object[100]; CollectionObject co new ArrayListObject(); fromArrayToCollection(ao, co); // T 推断为 Object String[] sa new String[20]; CollectionString cs new ArrayList(); fromArrayToCollection(sa, cs); // T 推断为 String CollectionDouble cd new ArrayList(); // fromArrayToCollection(sa, cd); // 编译错误T 无法同时为 String 与 Double fromArrayToCollection(sa, co); // T 推断为 ObjectString[] 可赋给 Object[] } }最后一行是关键String[]是Object[]的子类型因此T可以被推断为Object但CollectionString不是CollectionObject的子类型所以fromArrayToCollection(sa, co)只是借用数组的协变关系集合一侧的实参类型仍然是CollectionObject。四.4 举例三泛型方法实现排序// 完整示例 MyArrays.java import java.util.Arrays; 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 (((ComparableT) 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}; // MyArrays.sort(arr); // 错误int[] 不是对象数组T 无法绑定 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)}; MyArrays.sort(circles); // 编译通过运行抛出 ClassCastException } }最后一个调用暴露了T无界的代价编译期只保证元素是Object强转成Comparable的动作放到运行时Circle未实现该接口便直接抛异常。若把签名改为T extends ComparableT这类错误会在编译期被拦下。五、泛型与继承关系如果 B 是 A 的子类型子类或子接口G 是带泛型声明的类或接口那么G 与 G之间不存在父子类型关系。String是Object的子类但ListString不是ListObject的子类。数组则相反String[]是Object[]的子类型。这一差异是理解泛型不变性的起点。// 完整示例 GenericSubClass.java import java.util.List; public class GenericSubClass { public void testGenericAndSubClass() { Person[] persons null; Man[] mans null; persons mans; // 合法Person[] 是 Man[] 的父类型 Person p mans[0]; // 合法 ListPerson personList null; ListMan manList null; // personList manList; // 编译错误ListPerson 与 ListMan 无继承关系 } } class Person { } class Man extends Person { }数组的协变看似方便实则埋了隐患Object[] arr new String[1]; arr[0] 100;能通过编译运行时抛ArrayStoreException。泛型选择不变性正是为了把这类错误提前到编译期。五.1 两道对比题两段代码的形参声明不同接受实参的范围也不同。// 片段一原始类型形参 public void printCollection(Collection c) { Iterator i c.iterator(); for (int k 0; k c.size(); k) { System.out.println(i.next()); } }// 片段二CollectionObject 形参 public void printCollection(CollectionObject c) { for (Object e : c) { System.out.println(e); } }片段一使用的是原始类型任何Collection都能传入但元素按Object处理编译期不产生警告片段二的形参是CollectionObject只能接受CollectionObject及其子类型ListString、ListInteger都无法传入。实际开发中两者都不是好选择前者放弃了类型检查后者适用范围过窄。正确做法是使用通配符Collection?既能接受任意类型集合又不放弃类型安全。六、类型通配符声明变量或形参时如果类型是泛型类或泛型接口但其中的类型实参仍无法确定可以使用类型通配符?。六.1 通配符的含义使用形式为?例如List?、Map?, ?。List?是ListString、ListObject、ListInteger等各种泛型List的父类型。注意这里的措辞List?与它们之间存在父子关系而ListObject与ListString之间没有。六.2 读与写的不对称写操作受限。向Collection?中添加元素不是类型安全的// 片段 Collection? c new ArrayListString(); c.add(new Object()); // 编译时错误原因在于add的形参类型是类型参数E编译器无法得知E到底是什么因此无法确认传入的对象是E的实例。唯一可以插入的是null因为它是所有引用类型的合法值。读操作安全。读取List?中的元素永远是安全的无论实际类型是什么元素都可以用Object接收。操作Collection?CollectionT泛型方法中CollectionObject接收任意类型集合可以适用范围最广可以调用时推断T不可以只能接收CollectionObject写入元素除null外均不可可以必须是T类型可以但通常需要强转读取元素可以只能用Object接收可以用T接收可以用Object接收这张表可以概括为一句通配符适合「只读」泛型方法适合「读写」Object具体类型适合「确定就是 Object」。六.3 举例// 完整示例 TestWildcard.java import java.util.*; public class TestWildcard { public static void m4(Collection? coll) { for (Object o : coll) { System.out.println(o); } } public static void main(String[] args) { List? list null; list new ArrayListString(); list new ArrayListDouble(); // list.add(3); // 编译不通过 list.add(null); // 唯一允许的写入 ListString l1 new ArrayListString(); ListInteger l2 new ArrayListInteger(); l1.add(尚硅谷); l2.add(15); read(l1); read(l2); } public static void read(List? list) { for (Object o : list) { System.out.println(o); } } }六.4 三条使用限制不能用于泛型方法声明。返回值类型前的中不能写?public static ? void test(ArrayList? list)编译错误。类型参数必须是可命名的标识符?表示「未知」无法在方法体中引用。不能用于泛型类的声明。class GenericTypeClass? { }编译错误理由同上。不能用于创建对象。ArrayList? list2 new ArrayList?();编译错误右侧创建对象时必须给出确定的类型实参。七、有限制的通配符七.1 三种形式形式含义范围?允许所有泛型的引用调用全集? extends 类或接口类型必须是该类或其子类或该接口的实现类上限? super 类或接口类型必须是该类或其父类或该接口的父接口下限// 片段 ? extends Number // (无穷小, Number]只允许 Number 及其子类 ? super Number // [Number, 无穷大)只允许 Number 及其父类 ? extends Comparable // 只允许 Comparable 的实现类七.2 举例一类型参数的上限// 完整示例 PersonTest.java 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()); // test(new Creature()); // 编译错误T 的上限是 PersonCreature 不满足 } }T extends Person与? extends Person写法相近语义位置不同前者用于声明类型参数后者用于描述已有泛型的实参范围。七.3 举例二集合形参的上限与下限// 完整示例 WildcardBound.java import java.util.*; public class WildcardBound { public static void main(String[] args) { CollectionInteger list1 new ArrayListInteger(); CollectionString list2 new ArrayListString(); CollectionNumber list3 new ArrayListNumber(); CollectionObject list4 new ArrayListObject(); getElement1(list1); // 合法Integer 是 Number 的子类 // getElement1(list2); // 报错String 不是 Number 的子类 getElement1(list3); // 合法Number 本身 // getElement1(list4); // 报错Object 不是 Number 的子类 // getElement2(list1); // 报错Integer 不是 Number 的父类 // getElement2(list2); // 报错 getElement2(list3); // 合法Number 本身 getElement2(list4); // 合法Object 是 Number 的父类 } // 上限泛型必须是 Number 或 Number 的子类 public static void getElement1(Collection? extends Number coll) { } // 下限泛型必须是 Number 或 Number 的父类 public static void getElement2(Collection? super Number coll) { } }七.4 举例三遍历带界的通配符集合// 片段 public static void printCollection1(Collection? extends Person coll) { // 迭代器的类型参数同样受上限约束用 Iterator? 最稳妥 Iterator? iterator coll.iterator(); while (iterator.hasNext()) { Person per (Person) iterator.next(); System.out.println(per); } } public static void printCollection2(Collection? super Person coll) { Iterator? iterator coll.iterator(); while (iterator.hasNext()) { Object obj iterator.next(); System.out.println(obj); } }两段代码体现了同一个规律上限通配符可以读成具体类型下限通配符只能读成Object。七.5 举例四带界通配符的读写规则// 完整示例 BoundReadWrite.java import java.util.*; public class BoundReadWrite { public void test1() { ListPerson list2 new ArrayListPerson(); List? extends Person list4 null; list2.add(new Person()); list4 list2; // 合法ListPerson 是 List? extends Person 的子类型 Person p1 list4.get(0); // 读取可以返回值按上限 Person 处理 list4.add(null); // 写入只允许 null // list4.add(new Person()); // 编译错误 // list4.add(new Student()); // 编译错误 } public void test2() { ListPerson list2 new ArrayListPerson(); List? super Person list5 null; list2.add(new Person()); list5 list2; // 合法 Object obj list5.get(0); // 读取只能按 Object 接收 list5.add(new Person()); // 写入可以写入 Person list5.add(new Student()); // 可以写入 Person 的子类 } } class Person { } class Student extends Person { }test1不能写入是因为编译器只知道元素类型「至多是Person」实际可能是Student写入Person会破坏集合test2可以写入Person及其子类是因为元素类型「至少是Person」任何Person及其子类都能安全存入。这条规律通常概括为 PECS 原则Producer Extends, Consumer Super。只读取生产数据时用extends只写入消费数据时用super既读又写则不应使用通配符。八、泛型的综合应用八.1 泛型嵌套// 完整示例 NestedGeneric.java import java.util.*; public class NestedGeneric { public static void main(String[] args) { HashMapString, ArrayListCitizen map new HashMapString, ArrayListCitizen(); ArrayListCitizen list new ArrayListCitizen(); list.add(new Citizen(赵又廷)); list.add(new Citizen(高圆圆)); list.add(new Citizen(瑞亚)); map.put(赵又廷, list); SetMap.EntryString, ArrayListCitizen entrySet map.entrySet(); IteratorMap.EntryString, ArrayListCitizen iterator entrySet.iterator(); while (iterator.hasNext()) { Map.EntryString, ArrayListCitizen entry iterator.next(); String key entry.getKey(); ArrayListCitizen value entry.getValue(); System.out.println(户主 key); System.out.println(家庭成员 value); } } } class Citizen { private String name; public Citizen(String name) { this.name name; } Override public String toString() { return name; } }嵌套泛型的类型声明会随层数迅速变长。JDK 7 之后的菱形语法只能简化创建一侧声明一侧仍需写全若出现三层以上嵌套通常应抽出中间类型或改用局部变量分步声明。八.2 个人信息设计用泛型表达关联设计类时常用关联关系表达「一个对象持有另一个对象」。若这个被持有的对象有多个变体联系方式、基本信息等可以把属性的类型声明为泛型再为每种变体提供实现类。// 完整示例 GenericPerson.java 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 PersonT 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) { PersonContact per new PersonContact( new Contact(北京市, 01088888888, 102206)); System.out.println(per); PersonIntroduction per2 new PersonIntroduction( new Introduction(李雷, 男, 24)); System.out.println(per2); } }T extends Info的作用是限定边界Person只能接收Info的实现类从而保证getInfo()的返回值至少具备Info的契约而不会退化为Object。九、要点回顾主题关键结论泛型本质参数化类型把「类型的未知」延迟到使用时确定主要收益类型检查前移到编译期免去强转类型实参只能使用引用类型基本类型需包装类擦除未指定类型实参时按Object处理但不等价于Object泛型类类型参数写在类名后静态方法中不可使用该参数泛型方法类型参数写在返回值类型前与所在类是否为泛型类无关数组不能new E[]需(E[]) new Object[n]继承GA与GB无父子关系数组则有协变通配符?是各种具体泛型的父类型只读安全写入仅允许null有界通配符extends定上限super定下限遵循 PECS异常类不能带泛型参数