ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 一、概述 前面我们提到[](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,所以一般情况下都将父类的泛型类型保留。