导读:
本篇是JAVA基础系列的第14篇,今天我们梳理java中的泛型机制。Java泛型这个特性是从JDK 1.5才开始加入的,引入泛型的意义在于:适用于多种数据类型执行相同的代码(代码复用)。
1.泛型概述
Java从JDK 1.5 开始提供了泛型。泛型可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率。
泛型本质上是提供类型的“类型参数”,也就是参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。我们可以为类、接口或方法指定一个类型参数,通过这个参数限制操作的数据类型,从而保证类型转换的绝对安全。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
示例代码:
private static int jia(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static float jia(float a, float b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static double jia(double a, double b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; }
两个数字相加,如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法。上边3个加法只声明了参数类型一样的情况,还有参数类型不同的情况存在。
而通过泛型,我们可以直接定义为一个方法:
private static <T extends Number> double jia(T a, T b) { System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue())); return a.doubleValue() + b.doubleValue(); }
泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
2.泛型类
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。
泛型除了可以定义泛型集合之外,还可以直接限定泛型类的类型参数。语法格式如下:
public class class_name<data_type1,data_type2,…>{}
其中,class_name 表示类的名称,data_ type1 等表示类型参数。Java 泛型支持声明一个以上的类型参数,只需要将类型用逗号隔开即可。
泛型类一般用于类中的属性类型不确定的情况下。在声明属性时,使用下面的语句:
private data_type1 property_name1; private data_type2 property_name2;
该语句中的 data_type1 与类声明中的 data_type1 表示的是同一种数据类型。
在实例化泛型类时,需要指明泛型类中的类型参数,并赋予泛型类属性相应类型的值。例如,下面的示例代码创建了一个表示学生的泛型类,该类中包括 3 个属性,分别是姓名、年龄和性别。
public class Stu<N, A, S> { private N name; // 姓名 private A age; // 年龄 private S sex; // 性别 // 创建类的构造函数 public Stu(N name, A age, S sex) { this.name = name; this.age = age; this.sex = sex; } // 下面是上面3个属性的setter/getter方法 public N getName() { return name; } public void setName(N name) { this.name = name; } public A getAge() { return age; } public void setAge(A age) { this.age = age; } public S getSex() { return sex; } public void setSex(S sex) { this.sex = sex; } }
接着创建测试类。在测试类中调用 Stu 类的构造方法实例化 Stu 对象,并给该类中的 3 个属性赋予初始值,最终需要输出学生信息。测试类的代码实现如下:
public class Demo { public static void main(String[] args) { Stu<String, Integer, Character> stu = new Stu<String, Integer, Character>("张三", 18, '男'); String name = stu.getName(); Integer age = stu.getAge(); Character sex = stu.getSex(); System.out.println("学生信息如下:"); System.out.println("学生姓名:" + name + ",年龄:" + age + ",性别:" + sex); } }
在该程序的 Stu 类中,定义了 3 个类型参数,分别使用 N、A 和 S 来代替,同时实现了这 3 个属性的 setter/getter 方法。在主类中,调用 Stu 类的构造函数创建了 Stu 类的对象,同时指定 3 个类型参数,分别为 String、Integer 和 Character。在获取学生姓名、年龄和性别时,不需要类型转换,程序隐式地将 Object 类型的数据转换为相应的数据类型。
3.泛型方法
泛型同样可以在类中包含参数化的方法,而方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是不是泛型没有关系。
泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。另外,对一个 static 的方法而言,无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。
定义泛型方法的语法格式如下:
[访问权限修饰符] [static] [final] <类型参数列表> 返回值类型 方法名([形式参数列表])
例如:
public static <T> List find(Class<T> cs,int userId){}
一般来说编写 Java 泛型方法,其返回值类型至少有一个参数类型应该是泛型,而且类型应该是一致的,如果只有返回值类型或参数类型之一使用了泛型,那么这个泛型方法的使用就被限制了。
示例代码:
public class Demo { public static <T> void List(T book) { // 定义泛型方法 if (book != null) { System.out.println(book); } } public static void main(String[] args) { Book book = new Book(1, "Java面向对象"); List(book); // 调用泛型方法 } }
说明一下,定义泛型方法时,必须在返回值前边加一个<T>
,来声明这是一个泛型方法,持有一个泛型T
,然后才可以用泛型T作为方法的返回值。
4.泛型接口
泛型除了可以被定义在方法和类上,接口同样适用
定义语法:
interface interface1<T>{} interface SubClass<T> implements Interface1<T>{}
Java接口的泛型,常用一共就两种!
-
第一种在实现接口的子类依旧使用泛型,在实例化的时候在动态添加type
-
第二种形式,子类实现接口的时候具体化type,在实例化的时候不用再动态添加type
interface Message<T>{ public void demo(T t); } //第一种在实现接口的子类依旧使用泛型,在实例化的时候在动态添加type class MessageOne<T> implements Message<T>{ public void demo(T t){ System.out.println(t.toString()); } } //第二种形式,子类实现接口的时候具体化type,在实例化的时候不用再动态添加type class MessageTwo implements Message<String>{ public void demo(String t){ System.out.println(t.toString()); } }
5.泛型的上下限
在 Java 中默认可以使用任何类型来实例化一个泛型类对象。当然也可以对泛型类实例的类型进行限制,在使用泛型的时候,我们可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
语法格式如下:
class 类名称<T extends anyClass> // 上限 class 类名称<T super anyClass> // 下限
上限
public class Num<T extends Number> { // 定义泛型变量 private T var; public void setVar(T var) { this.var = var; } public T getVar() { return this.var; } // 直接打印 public String toString() { return this.var.toString(); } public static void main(String args[]) { // 声明Integer的泛型对象 Num<Integer> num = new Num<Integer>(); num.setVar(10); System.out.println(num); } }
下限
public class Info<T> { private T var; // 定义泛型变量 public void setVar(T var) { this.var = var; } public T getVar() { return this.var; } public String toString() { // 直接打印 return this.var.toString(); } } class Demo { public static void main(String args[]) { // 声明String的泛型对象 Info<String> info1 = new Info<String>(); // 声明Object的泛型对象 Info<Object> info2 = new Info<Object>(); info1.setVar("hello"); info2.setVar(new Object()); fun(info1); fun(info2); } // 只能接收String或Object类型的泛型,String类的父类只有Object类 public static void fun(Info<? super String> temp) { System.out.print(temp + ", "); } }
泛型总结
<?> 无限制通配符
<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类