泛型的使用形式有两种:
1、泛型类/泛型接口
2、泛型方法
一、泛型类/接口
下面先来看下 JDK1.5改写后的 ArrayList 类、Iterator 接口、Map的代码片段。
从上面的代码中,可以看出在定义接口、类时指定类型形参,如上面的E、K、V。
当使用这些集合时,就可以为E、K、V指定具体的类型实参。
Demo:
1 import java.util.HashMap;
2 import java.util.Iterator;
3 import java.util.Set;
4
5 public class TestHashMap {
6 public static void main(String[] args) {
7 HashMap<Integer,String> map = new HashMap<Integer,String>();
8 map.put(1, "Hello");
9 map.put(2, "World");
10 Set<Integer> keySet = map.keySet();
11 Iterator<Integer> iter = keySet.iterator();
12 while(iter.hasNext()){
13 Integer key = iter.next();
14 System.out.println(key + "->" + map.get(key));
15 }
16 }
17 }
1、如何定义泛型类、接口?
我们可以为任何类和接口增加泛型声明,并不是只有集合类才可以使用泛型声明。
泛型形参的命名一般使用单个的大写字母,如果有多个类型形参,那么使用逗号分隔。
语法格式:
1 2 | 【修饰符】 class 类名<泛型形参列表>{} 【修饰符】 interface 接口名<泛型形参列表>{} |
Demo:定义学生类,其中的学生成绩可以是如下各种类型:整数、小数、字符串(优秀,良好,合格,不合格)
1 public class TestStudent {
2 public static void main(String[] args) {
3 Student<Integer> s1 = new Student<Integer>("张三",95);
4 Student<String> s2 = new Student<String>("张三","优秀");
5 Student<Double> s3 = new Student<Double>("张三",80.5);
6 }
7 }
8 class Student<T>{
9 private String name;
10 private T score;
11
12 public Student() {
13 super();
14 }
15 public Student(String name, T score) {
16 super();
17 this.name = name;
18 this.score = score;
19 }
20 public String getName() {
21 return name;
22 }
23 public void setName(String name) {
24 this.name = name;
25 }
26 public T getScore() {
27 return score;
28 }
29 public void setScore(T score) {
30 this.score = score;
31 }
32 @Override
33 public String toString() {
34 return "姓名:" + name + ", 成绩:" + score;
35 }
36 }
从上面的代码中,可以看出可以在定义接口、类时指定类型形参,类型形参在整个接口或类中可以当成类型使用,几乎所有可以使用其他普通类型的地方都可以使用这种类型形参,如:属性类型、方法的形参类型、方法返回值类型等。
Tips:
当创建带泛型声明的类时,为该类定义构造器时,构造器名还是原来的类名,不需要增加泛型声明。例如:Student<T>类定义的构造器依然是Student(),而不是Student<T>,但调用构造器时缺可以使用Student<T>的形式,而且应该为T类型形参传入实际的类型实参。
2、泛型类或泛型接口上的<泛型形参>这个类型可以用在哪些地方?
(1)可以用于属性、方法的数据形参、局部变量等的类型
(2)不能用于声明静态变量,不能用于静态成员上
原因:因为静态成员的初始化是随着类的初始化而初始化的,此时泛型实参无法指定,那么泛型形参的类型就不确定。所以请不要在静态成员上使用类或接口上的泛型形参。
3、如何为泛型类、泛型接口指定泛型实参?
(1)泛型实参的要求
泛型实参必须是引用数据类型,不能是基本数据类型。
(2)什么时候指定泛型实参
a、在用泛型类、接口声明变量时
1 class EmployeeManager{
2 private ArrayList<Employee> list;
3 }
4 //接口中的方法
5 public static void test(ArrayList<String> list){
6 //....省略代码
7 }
b、在继承泛型类或实现泛型接口时,如果子类不延续使用该泛型,那么必须明确指定实际类型,此时子类不再是泛型类了。
c、在创建泛型类对象时
1 2 | ArrayList<String> list = new ArrayList<String>(); ArrayList<String> list = new ArrayList<>(); (JDK1. 7 之后支持这种简化写法) |
4、如何延续使用父类、父接口的泛型形参?
如果继承泛型类、实现泛型接口时,想要继续保留父类、父接口的泛型,必须在父类、父接口和子类、子接口中都要保留泛型。
Demo:
集合中大量是这种情况:
5、设定泛型形参的上限?
语法格式:
1 2 | <T extends 上限> T的类型实参只能上限本身或上限的子类 <T extends 上限 1 & 上限 2 &....> 如果多个上限,都要满足 |
Demo : 需求,定义学生类,其中的学生的成绩可以是数字类型,如Integer、Float、Double等
1 class Student<T extends Number> {
2 private String name;
3 private T score;
4
5 public Student() {
6 super();
7 }
8
9 public Student(String name, T score) {
10 super();
11 this.name = name;
12 this.score = score;
13 }
14
15 public String getName() {
16 return name;
17 }
18
19 public void setName(String name) {
20 this.name = name;
21 }
22
23 public T getScore() {
24 return score;
25 }
26
27 public void setScore(T score) {
28 this.score = score;
29 }
30
31 @Override
32 public String toString() {
33 return "姓名:" + name + ", 成绩:" + score;
34 }
35 }
如果泛型形参没有设定上限,那么泛型实参可以是任意引用数据类型。如果形参设定了上限(如:T extends 父类上限),那么只能指定为该父类本身或其各子类类型。
如:
1 public class TestStudentUpperBound {
2 public static void main(String[] args) {
3 Student<Integer> s1 = new Student<Integer>("张三", 95);
4 Student<Double> s3 = new Student<Double>("张三", 80.5);
5 //以下代码编译报错,因为String不是Number的子类
6 Student<String> s2 = new Student<String>("张三", "优秀");
7 }
8 }
在一种更极端的情况下,程序需要为形参设定多个上限(至多有一个父类上限,可以多个接口上限)表名该类型形参必须是其父类的子类(包括是父类本身也行),并且实现多个上限接口。
如:
1 class Student<T extends Number & Comparable & java.io.Serializable> {
2 //...省略其他代码
3 }
与类同时继承父类、实现接口类似的是:为类型形参指定多个上限时,所有的接口上限必须位于类上限之后,也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位。
6、泛型的形参一般代表什么的类型?
如:
ArrayList<E>: 这个E为element元素的缩写,代表集合的元素的类型
Map<K,V>:这个K代表Key的类型,V代表value的类型。
Comparable<T>:这个T为Type,表示要与当前对象比较的另一个对象的类型
Student<T>:这个T代表成绩的类型。
在声明泛型类或泛型接口,泛型形参最好见名知意。
二、泛型方法
1、什么情况下需要声明泛型方法?
上面介绍了在定义类、接口时可以使用类型形参,在该类的方法和属性定义、接口的方法定义中,这些类型形参可被当成普通类型来用。还有另外一些情况:
(1)如果定义类、接口是没有使用类型形参,但是某个方法定义时,想要自己定义类型形参;
(2)类和接口上的类型形参是不能用于静态方法中,那么当某个静态方法想要定义类型形参;
JDK1.5之后,提供了泛型方法的支持。
2、如何声明泛型方法?
语法格式:
1 2 3 | [修饰符] <泛型形参列表> 返回类型 方法名([形参列表]) 抛出的异常列表{ //方法体... } |
其中泛型形参列表,可以是一个或多个,如果多个,使用逗号分隔,和定义泛型类、接口时一样,而且<泛型形参列表>必须在修饰符和返回值类型之间。
与接口、类声明中定义的泛型形参不同,方法声明中定义的泛型形参只能在当前方法中使用,和其他方法无关。
与接口、类声明中定义的泛型形参不同,方法声明中定义的泛型形参无需显式传入实际类型参数,编译器可以根据实参类型直接推断形参的实际类型。
Demo:编写一个方法负责将一个数组的所有元素添加到一个Collection集合中
1 public static void fromArrayToCollection(Object[] a,Collection<Object> c){
2 for (Object object : a) {
3 c.add(object);
4 }
5 }
上面的这个方法没有任何问题,关键在于上面方法中的 c 形参,它的数据类型是 Collection<Object>。正如上面所说,Collection<Object> 不是 Collection<String> 类的父类——所以这个方法的功能非常有限,形参c只支持Collection<Object>类型的实参,不接收其他类型的实参。
1 String[] array = {"hello","world","java"};
2 Collection<String> coll = new ArrayList<String>();
3 //编译报错,因为Collection<Object>形参不接收Collection<String>实参,因为它俩不是父子类关系
4 fromArrayToCollection(array,coll);
为了解决上面的问题可以使用泛型方法。
1 public static <T> void fromArrayToCollection(T[] a,Collection<T> c){
2 for (T object : a) {
3 c.add(object);
4 }
5 }
6 public static void main(String[] args) {
7 String[] array = {"hello","world","java"};
8 Collection<String> coll = new ArrayList<String>();
9 fromArrayToCollection(array,coll);
10 }
3、什么时候给泛型方法指定类型实参?
调用这个方法时,编译器会根据方法的实参的类型,来确定泛型的类型实参的具体类型
4、如何设定泛型形参的上限?
泛型方法的<泛型形参列表>中的类型也可以指定上限
1 2 | <T extends 上限> T的类型实参只能上限本身或上限的子类 <T extends 上限 1 & 上限 2 & 。。。。> 如果多个上限,都要满足 |
Demo:
1 public abstract class Graphic{ //图形类
2 public abstract double getArea();
3 }
4
5 //圆类
6 public class Circle extends Graphic{
7 private double radius;
8
9 public Circle(double radius) {
10 super();
11 this.radius = radius;
12 }
13
14 @Override
15 public double getArea() {
16 return Math.PI * radius * radius;
17 }
18 }
19
20 //矩形类
21 public class Rectangle extends Graphic{
22 private double length;
23 private double width;
24 public Rectangle(double length, double width) {
25 super();
26 this.length = length;
27 this.width = width;
28 }
29 @Override
30 public double getArea() {
31 return length * width;
32 }
33 }
34
35 import java.util.ArrayList;
36 import java.util.List;
37
38 public class TestGraphic {
39 //定义一个方法,求多个图形的面积
40 public static <T extends Graphic> void printArea(List<T> graphics){
41 for (T t : graphics) {
42 System.out.println(t.getArea());
43 }
44 }
45 public static void main(String[] args) {
46 ArrayList<Circle> cList = new ArrayList<Circle>();
47 cList.add(new Circle(1.2));
48 cList.add(new Circle(2.3));
49 printArea(cList);
50
51 ArrayList<Rectangle> rList = new ArrayList<Rectangle>();
52 rList.add(new Rectangle(1,2));
53 rList.add(new Rectangle(2,3));
54 printArea(rList);
55 }
56 }
Tips:其实没有设定泛型形参上限的,可以看成它的上限默认是Object。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器