- 泛型程序设计(Generic Programming)是一种编程范式,它允许在类、接口或方法的定义中使用类型参数,使得类、接口或方法可以在多种数据类型上进行操作 (即同一模板,可填入不同的类型),提高了代码的重用性和类型安全性。
- 泛型程序设计的目标是编写通用的代码,可以在不同的类型上进行操作 (即可赋予不同的类型),而无需为每种数据类型编写特定的代码】 (一份代码,可填入不同的类型)。通过泛型,可以将代码从具体的数据类型中解耦,使得代码更加通用、灵活和可重用。
- Java中泛型通过使用类型参数(Type Parameters)来实现,它允许在类、接口或方法的声明中使用一个或多个类型参数。类型参数可以用于指定泛型类、接口或方法中的具体类型,使得类、接口和方法可以以一种通用的方式处理不同的数据类型。
- Java中泛型是允许在类、接口和方法的定义类型参数( Type Parameters ),以实现在多种数据类型上进行通用操作的能力 (一份代码,可以填入不同的数据类型,得到不同的效果) 。
- 通过使用泛型,可以编写一次代码,在多个数据类型上使用 ( 可填入不同的数据类型),而不需要为每个类型编写特定的代码。
- 泛型的原理:在类、接口、方法的声明中使用类型参数。类型参数可用于指定泛型类、接口或方法中使用的具体类型。
类型参数在泛型中可看做占位符,类型参数一般用在泛型类、接口或方法中。
类型参数语法形式为:
或 T 类型参数使用①一个尖括号 <> 和 单个大写字母 或 ②单个大写字母来声明,并放置在类名、接口名、方法名的后面。如class Student
、 class Student 、T age 、T name等。 允许在泛型类、接口或方法的定义中使用占位符来表示一个未知且可自定义的类型。
类型参数可让我们在使用泛型代码时自定义泛型类、接口或方法中的具体的类型,实例化对象时指定要操作的是具体类型。如实例化对象后,类型参数就确定了,属性的类型、方法的返回值等内容的类型也确定了。
例子如:
看泛型类、接口或泛型方法的例子,泛型参数用在其中。
泛型类是一种可以在实例化时指定 类型/数据类型 的类。通过添加尖括号 <> 和类型参数 T 来创建泛型类。
泛型类能增加代码的复用性和灵活性,让我们在创建类时不需要指定具体的类型,而是实例化时根据需要来指定类型。这样不用为每种类型都编写一个新的类,从而创建一个具体类型为T的对象。
例子如:
public class Student<T,U> { //T U为类型参数 private T name; //使用类型参数T赋予name变量的数据类型 private U age; //使用类型参数U赋予age变量的数据类型 public T getName() { //使用类型参数T决定方法的返回值类型 return name; } public void setName(T name) { //决定方法参数的数据类型 this.name = name; } public U getAge() {//使用类型参数U决定方法的返回值类型 return age; } public void setAge(U age) { this.age = age; } public static void main(String[] args) { // T:String类型 U:Integer类型 Student<String,Integer> stu1 = new Student<String,Integer>(); stu1.setName("张三"); stu1.setAge(20); //张三:String类型 20:Integer类型 System.out.println(stu1.getName()+" "+stu1.getAge()); // T:String类型 U:String类型 Student<String,String> stu2 = new Student<String,String>(); stu2.setName("李四"); stu2.setAge("21"); //张三:String类型 20:String类型 System.out.println(stu2.getName()+" "+stu2.getAge()); } }
泛型接口是一种可以在实现接口时指定类型的接口。通过添加尖括号 <> 和类型参数 T 来创建泛型接口。
泛型类能增加代码的复用性和灵活性,让我们在创建接口时不需要指定具体的类型,而是在实现接口时根据需要来指定类型。这样不需要为每一个类型编写一个接口。
例子如:
定义一个泛型接口:
public interface Animal_Interface<T,U> { //两个类型参数 T run(); //类型参数决定该方法的返回值类型 U eat();//类型参数决定该方法的返回值类型 }
实现泛型接口:
//实现泛型接口 public class Cat implements Animal_Interface<String, String> { //自定义类型参数的类型 @Override public String run() { //确定了返回值类型 return ""; } @Override public String eat() { return ""; } }
泛型方法是一种在方法声明题中使用类型参数的方法。通过添加尖括号 <> 和类型参数 T 来创建泛型方法。
在方法体中,可以使用这些类型参数作为方法的参数列表、返回值类型或局部变量类型等。
例子如:
class Eample { public static <T> void printList(T[] elements) { // T:类型参数 for (T element : elements) { //打印数组信息 System.out.println(element); } } public static void main(String[] args) { Integer[] intArray = { 1, 2, 3, 4, 5 }; //Integer类型 String[] strArray = { "Hello", "World" }; //String类型 // 使用泛型方法打印 “整数数组” Eample.<Integer>printList(intArray); // 使用泛型方法打印 “字符串数组“ Eample.<String>printList(strArray); } }
- 泛型通配符是一种特殊的类型参数,是对Java泛型的一种拓展。
(但一般不泛型通配符与类型参数混为一谈)- 当类型参数被固定时,泛型通配符任能表示任意类型。
- 泛型通配符包括 ①“未限定通配符” 、 ②“上界通配符”、 ③“下界通配符”
- 常用未限定通配符,并搭配 “上界通配符” 和 “下界通配符”使用。
? 表示未限定通配符,语法形式为:>
未限定通配符 ? 可表示任意类型。
例子如:
//在方法参数中用"未限定通配符" public void print(List<?> list) { }
//实例化集合(对象)中用"未限定通配符" List<?> list = new ArrayList<>();
//“类型参数”搭配“未限定通配符”使用 public class MyObject<T> { //泛型类 T att; // > : 未限定通配符,可表示任意类型 public void print(MyObject<?> object) { //泛型方法,其中用了未限定通配符 System.out.println(object.att); } } class Test { public static void main(String[] args) { //实例化泛型类,其中的泛型参数的类型固定 //创建一个具体类型为:Integer 的MyObject对象 MyObject<Integer> obj1 = new MyObject<Integer>(); //创建一个具体类型为:String 的MyObject对象 MyObject<String> obj2 = new MyObject<String>(); obj2.att = "123"; obj1.print(obj2); } }
未限定通配符? 不能用来当作类型参数的名称。
当类型参数被固定时,未限定通配符 ? 任能表示任意类型。可通过为泛型通配符设置上界和下界来对其进行限制,未限定通配符的边界不能与类型参数设置的边界自相矛盾。
当泛型类被实例化后,此时类型参数也被固定了,导致方法中类型也被固定,如果传入方法中的参数不符合类型要求,会出现 “类型不匹配”报错。 可通过指定未限定通配符 ? 来解决此类问题。
可对未限定通配符设置“上界”和“下界”,来限制泛型通配符的范围。
上界通配符的关键字是 extends ,语法形式为:
或 extends 类型> 上界通配符用于指定类型参数 (T) 或 未限定通配符(?) 必须是其 “指定类型” 或是其 “子类”。
(往上已经到顶了,不能再往上了,为上界,自然只能往下走,所以是其指定类型或其子类)例子如:
//泛型类 public class Teacher<T extends Number> { //为类型参数指定"上界通配符" List<? extends Number> list = new ArrayList<>(); //为 “未定义通配符” 指定 “上界通配符” public void print(Teacher<? extends Integer> teacher) { //创建集合中为 “未定义通配符”指定 “上界通配符” List<? extends Number> list = new ArrayList<>(); } public static void main(String[] args) { List<? extends Number> list = new ArrayList<>(); } }
上界通配符的关键字是 super,语法形式为: super 类型>
ps : 通常情况下不为类型参数 (T) 设置下界。
下界通配符用于指定 未限定通配符(?) 必须是其 “指定类型” 或是其 “父类”。
(往下已经到底了,不能再往下了,为下界,自然只能往上走,所以是其指定类型或其父类)例子如:
//泛型类 public class Teacher<T> { //一般不为类型参数设置下界 List<? super Number> list = new ArrayList<>(); //为 “未定义通配符” 指定 “下界通配符” public void print(Teacher<? super Integer> teacher) { //创建集合中为 “未定义通配符”指定 “下界通配符” List<? super Number> list = new ArrayList<>(); } public static void main(String[] args) { List<? super Number> list = new ArrayList<>(); } }
当泛型类被实例化后,类型参数被固定,导致该类中的所有类型都被固定。如:方法中的参数也被固定了类型,如果想传入其他类型的参数就会报“类型不匹配”。可通过设置 未限定通配符? 来解决“类型不匹配”。
例如:(“类型不匹配”报错)
public class MyObject<T> { T att; //泛型方法,参数为一个具体类型为T的MyObject对象 public void print(MyObject<T> object) { //需要的参数为: 具体类型为T的MyObject对象 System.out.println(object.att); } } class Test { public static void main(String[] args) { //实例化泛型类,其中的泛型参数的类型固定 MyObject<Integer> obj1 = new MyObject<Integer>(); MyObject<String> obj2 = new MyObject<String>(); obj2.att = "123"; /* 调用obj1对象的print()方法,参数是obj2对象引用,该句代码会有 “类型不匹配”报错,可用泛型通 配符 ? 来解决这个问题。 原因: 当实例化obj1对象时完成时,此时类型参数已固定为Integer,obj1对象中的print()方法变为: public void print(MyObject
object) { //该方法需要传入的参数为一个具体类 型为 Integer 的MyObject对象 但实际传入的是 : 一个具体类型为 String 的MyObject对象。 自然会有类型冲突的报错。可用泛型通配符 ?来解决这个问题。 */ obj1.print(obj2); //会有“类型不匹配”报错 } }
通过设置 未限定通配符 ?,在类型参数被固定后,未限定通配符 ?任能表示任意类型来解决“类型不匹配”
例子如:
//通过“泛型通配符 ? ”解决 “类型不匹配”的报错 public class MyObject<T> { T att; /* 此处如果用类型参数,当实例化对象后,类型参数被固定,会导致该方法只能接收特定类型的对象作为参 数,不太符合开发需要 而用了泛型通配符之后,类型参数被固定后,println()方法可以以任何具体类型的对象作为参数 ?表示任意类型 当类型参数被固定时,print()方法任然能接收任意类型对象作为参数 */ public void print(MyObject<?> object) { System.out.println(object.att); } } class Test { public static void main(String[] args) { //实例化泛型类,其中的泛型参数的类型固定 MyObject<Integer> obj1 = new MyObject<Integer>(); MyObject<String> obj2 = new MyObject<String>(); obj2.att = "123"; /* * MyObject的print()方法参数设置了泛型通配符,这时它可不受类型参数的固定而依然能接收任意类 * 型对象作为参数 * ? 表示任意类型 */ obj1.print(obj2); //这里不再会报错。 } }
泛型的类型参数只存在于编译时:泛型在编译后会进行类型擦除,即泛型的类型参数会被擦除到其上界类型 (或object类型) ,因此在运行时无法获取类型参数的具体信息。
泛型数组的创建是非法的 : 不能直接创建带有类型参数的数组。例如:
List
[] listArray = new List [10]; // 非法
无法使用基本数据类型作为泛型类型参数:泛型类型参数只能是引用类型,不能使用基本数据类型。如果需要使用基本数据类型,可以使用其包装类。
泛型类型参数不能使用类型运算符:不能使用 instanceof 运算符来判断泛型类型参数的具体类型,例如
if (obj instanceof List) { ... }
是非法的。可以通过其他方式来判断,如使用辅助方法或通过类型转换。类型参数不能是静态变量或静态方法的类型:在泛型类中,类型参数不能是静态变量的类型,也不能作为静态方法的参数或返回值类型。
泛型类型参数不能使用原始类型:在泛型类或泛型方法中,不能使用泛型类型参数的原始类型,例如不能使用
T
作为instanceof
运算符的操作数。类型擦除可能导致运行时的类型不安全:由于类型擦除的存在,泛型在某些情况下可能导致运行时的类型不安全。在使用泛型时需要注意类型转换和类型检查,以避免可能的类型错误。
不能使用一个本地类型(如int、float)来替换泛型。
运行时类型检查,不同类型的泛型类是等价的。
不能继承Exception类,不能作为异常被抛出。
不能使用泛型构造对象。
在static方法中不能使用泛型,泛型变量也不可用static关键字来修饰