一、泛型的概念
Java5引入参数化类型(Parameterized Type)的概念,也称为泛型(Generic)。泛型:就是允许在定义类、接口、方法时使用类型形参。这个类型形参将在声明变量、创建对象、调用方法时动态指定,即传入实际的类型参数(也叫传入类型实参)。传入的类型实参的类型必须是引用类型。
二、泛型类
2.1、定义泛型类
public class A<T> { // 泛型类:定义类的时候指定类型形参T,在类里面T就可以当成类型使用 private T a; public T getA() { return a; } public void setA(T a) { this.a = a; } }
2.2、继承泛型类的几种方式
class B1 extends A<String> {} class B2<E> extends A<String> {} class B3<E> extends A<E> {} class B4<E1, E2> extends A<E1> {}
2.3、使用泛型类
public static void main(String[] args) { B1 b1 = new B1(); b1.setA("b1"); System.out.println(b1.getA()); A<String> a1 = new B1(); a1.setA("a1"); System.out.println(a1.getA()); //B2<?> b2 = new B2<String>(); //B2<String> b2:声明变量时已经指定了B2的类型形参E为String, //new B2<>():创建对象时可以使用菱形语法(泛型推断) B2<String> b2 = new B2<>();//菱形语法 b2.setA("b2"); System.out.println(b2.getA()); // 无法通过A<String>推断出B2的类型形参E的类型,不可以使用菱形语法 A<String> a2 = new B2<Object>(); a2.setA("a2"); System.out.println(a2.getA()); B3<String> b3 = new B3<>();//菱形语法 b3.setA("b3"); System.out.println(b3.getA()); A<String> a3 = new B3<>();//菱形语法 a3.setA("a3"); System.out.println(a3.getA()); }
2.4、JDK7新特性:菱形语法(泛型推断)
菱形语法(泛型推断):从JDK 7 开始,Java允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号<>即可,Java可以推断出尖括号里面应该是什么类型。
比如:List<String> list = new ArrayList<>();
解释:ArrayList和list的关系是:
public interface List<E>
public class ArrayList<E> implements List<E>
左边List<String> list定义变量时已经指定了ArrayList类型形参E为String,所以通过泛型推断知道new ArrayList<>()的尖括号里面类型是String。
三、泛型方法
3.1、泛型方法与方法重载
下面程序 fun(2) 调用的是 public static void fun(int a) 方法。如果没有定义public static void fun(int a) 方法,就会去调用泛型方法public static <T> void fun(T a)。
package com.oy.type; public class Demo01 { public static void main(String[] args) { fun(2); // int: 2 fun(Integer.valueOf(2)); // Integer: 2 fun(20.0); // 泛型方法: 20.0 fun("hello"); // 泛型方法: hello } public static <T> void fun(T a) { // 泛型方法,<T>定义在返回值前面 System.out.println("泛型方法: " + a); } public static void fun(Integer a) { System.out.println("Integer: " + a); } public static void fun(int a) { System.out.println("int: " + a); } }
3.2、不能通过new T()来创建对象
public class Demo02 { public static void main(String[] args) { String str = null; try { str = fun2(String.class); } catch (Exception e) { e.printStackTrace(); } System.out.println("str = " + str); // str = Integer i = null; try { // Integer类没有空参构造,调用Class类的newInstance()方法时抛InstantiationException异常 i = fun2(Integer.class); } catch (Exception e) { e.printStackTrace(); } System.out.println("i = " + i); // i = null } public static <T> T fun2(Class<T> clazz) throws Exception { try { return clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); throw new Exception("创建对象出错!"); } } }
对于泛型方法定义时的类型形参T,不能通过new T()来创建对象,所以要创建类型T对应的对象,需要传递Class<T> clazz参数。
四、泛型构造器
//定义泛型类,给类型形参T传值时必须是Number类或Number类的子类 public class A<T extends Number> { private T a; public A() {} public A(T a) { this.a = a; } // 泛型构造器 public <E> A(E e) { System.out.println("调用泛型构造器:" + e); } public T getA() { return a; } public void setA(T a) { this.a = a; } public static void main(String[] args) { A<Integer> a1 = new A<Integer>(); a1.setA(1); System.out.println(a1.getA()); // 构造器传入int类型的参数,则调用构造器public A(T a) A<Integer> a2 = new A<Integer>(2); System.out.println(a2.getA()); // 构造器传入字符串类型的参数,则调用泛型构造器 A<Integer> a3 = new A<Integer>("字符串1"); System.out.println(a3.getA()); // 指定了泛型构造器类型形参E为String类型 A<Integer> a4 = new <String> A<Integer>("字符串1"); System.out.println(a3.getA()); // 使用菱形语法:左边指定了类型形参T为Integer,可以通过泛型推断,得知右边<>里面应该是什么类型 A<Integer> a5 = new A<>("字符串2"); // 下面错误。如果显示指定了泛型构造器的类型形参的实际类型,则不可以使用菱形语法 //A<Integer> a6 = new <String> A<>("字符串2");// 使用菱形语法 } }
五、类型形参的限制(指定类型形参的上限)
//定义泛型类,给类型形参T传值时必须是Number类或Number类的子类 public class A<T extends Number> { private T a; public T getA() { return a; } public void setA(T a) { this.a = a; } }
// 定义泛型类,T必须是Number类或Number类的子类,并且必须实现Serializable接口 // 可以指定多个接口上限,但如果指定类上限,类要位于接口前面 public class A<T extends Number & Serializable> { private T a; public T getA() { return a; } public void setA(T a) { this.a = a; } }
public class Demo02 { public static void main(String[] args) { fun(2); fun(20.0); //fun("hello");
} public static <T extends Number> void fun(T a) { // 泛型方法,T必须是Number类或其子类
System.out.println(a); } }
// 错误 public class A<T super Number> {}
六、并不存在所谓的泛型类
实际上,深入到类的class文件时发现,并不存在所谓的泛型类。下面代码中list1的类型是java.util.ArrayList,而不是java.util.ArrayList<String>。因此,不管泛型的实际类型是什么,运行时总有相同的类(或说字节码文件*.class)。
ArrayList<String> list1 = new ArrayList<>(); ArrayList<Integer> list2 = new ArrayList<>(); System.out.println(list1.getClass().getName()); // java.util.ArrayList System.out.println(list2.getClass().getName()); // java.util.ArrayList System.out.println(list1.getClass() == list2.getClass()); // true
不管为泛型的类型形参传入什么类型实参,对于java来说,它们依然被当成同一个类来处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化代码块或者静态变量的声明和初始化中不允许使用类型形参。下面程序演示了这种错误:
public class A<T> { static T info; // 错误,不能在静态变量声明中使用类型形参 T age; public void foo(T msg) {} // 下面代码错误,不能在静态方法中使用类型形参 public static void bar(T msg) {} }
由于并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。下面程序演示了这种错误:
List<String> list = new ArrayList<>(); if (list instanceof List) { // list instanceof List: true System.out.println("list instanceof List: " + (list instanceof List)); } // 下面代码错误,instanceof后面不能使用泛型 if (list instanceof List<String>) {}