一、简单实例
class Solution<T> { private T obj; Solution(T obj) { this.obj = obj; } public T getObj() { return obj; } public static void main(String[] args) { Solution<Integer> solution = new Solution<>(100); Object obj = solution.getObj(); System.out.println(obj.getClass().getName());//java.lang.Integer System.out.println(obj);//100 } }
二、类型安全
若简单地使用Object来替代泛型并确保类型转换正确,那么即使不适用泛型也能得到相同的功能。但是如果类型转换不正确,程序就会在运行时发生错误,使用泛型可以自动确保类型安全,这个过程中消除了手动输入类型转换以及类型检查的需要,运行时的错误就可以转换为编译时的错误,这是泛型的主要优势。
三、多个类型参数
class Solution<T, V> { private T objA; private V objB; Solution(T objA, V objB) { this.objA = objA; this.objB = objB; } public T getObjA() { return objA; } public V getObjB() { return objB; } public static void main(String[] args) { Solution<Character, Integer> solution = new Solution<>(‘0‘, 0); System.out.println(solution.getObjA()); System.out.println(solution.getObjB()); } }
四、有界类型
通过指定超类限制能够传递给类型参数的类型。
class Solution<T extends Number> { private T[] arr; double getSum() { double sum = 0; for (T i : arr) sum += i.doubleValue(); return sum; } Solution(T... nums) { arr = nums; } public static void main(String[] args) { Solution<Integer> solution = new Solution<>(1, 2, 3, 4, 5); System.out.println(solution.getSum()); } }
除了指定超类作为边界之外,还可以指定接口作为边界。
五、通配符参数
boolean sameSum(Solution<T> ob) { return getSum() == ob.getSum(); }
假设我们需要比较具有不同泛型参数的相同类型对象,上述代码并不能满足要求。只有当比较对象具有相同的泛型参数时,上述代码才可以被调用。正确的解决方法需要用到通配符,通配符同样可指定为有界。
class Solution<T extends Number> { private T[] arr; double getSum() { double sum = 0; for (T i : arr) sum += i.doubleValue(); return sum; } boolean sameSum(Solution<? extends Number> ob) { return getSum() == ob.getSum(); } Solution(T... nums) { arr = nums; } public static void main(String[] args) { Solution<Integer> solutionA = new Solution<>(1, 2, 3, 4, 5); Solution<Double> solutionB = new Solution<>(5.0, 4.0, 3.0, 2.0, 1.0); System.out.println(solutionA.sameSum(solutionB));//true } }
六、泛型方法
class Solution { static <T extends Number, V extends Number> boolean contain(T[] arr, V x) { for (int i = 0; i < arr.length; i++) if (arr[i].doubleValue() == x.doubleValue()) return true; return false; } public static void main(String[] args) { Double[] arr = {1.0, 2.0, 3.0, 4.0, 5.0}; System.out.println(contain(arr, 1)); } }
构造函数同样支持泛型。
class Solution { private double number; <T extends Number> Solution(T t) { number = t.doubleValue(); } }
七、泛型接口
泛型接口与泛型类相似。
interface MinMax<T extends Comparable<T>> { T min(); T max(); } class Solution<T extends Comparable<T>> implements MinMax<T> { private T[] arr; @Override public T min() { T res = arr[0]; for (int i = 1; i < arr.length; i++) if (res.compareTo(arr[i]) > 0) res = arr[i]; return res; } @Override public T max() { T res = arr[0]; for (int i = 1; i < arr.length; i++) if (res.compareTo(arr[i]) < 0) res = arr[i]; return res; } Solution(T... nums) { arr = nums; } public static void main(String[] args) { Solution<Integer> ob = new Solution<>(1, 2, 3, 4, 5); System.out.println(ob.min()); System.out.println(ob.max()); } }
八、历史遗留
早期的Java是不支持泛型的,现在仍然存在大量历史遗留代码。为了使这些遗留代码保留功能同时又和泛型兼容,Java允许使用泛型类而不提供任何类型参数,这会为类创建原始类型。原始类型与不使用泛型的遗留代码是兼容的,但失去了泛型的类型安全性。本质上就是使用Object替换了类型参数所表示的类型。
class Solution<T> { private T ob; Solution(T ob) { this.ob = ob; } T getOb() { return ob; } public static void main(String[] args) { Solution solution = new Solution(new String("Hello World")); System.out.println(solution.getOb()); } }
九、泛型类层次
使用泛型超类。
class A<T> { T a; A(T a) { this.a = a; } T getA() { return a; } } class B<T, V> extends A<T> { V b; B(T a, V b) { super(a); this.b = b; } V getB() { return b; } }
泛型层次比较。
class Solution { public static void main(String[] args) { B<String, String> obB = new B<>("Hello", "World"); System.out.println(obB instanceof A<?>);//true } }
当需要对泛型类进行强制转换时,必须确保其相互兼容并且泛型参数相同。
十、擦除
为了兼容以前的Java版本,对Java语言的语法或虚拟机所做的任何修改必须避免破坏以前的代码,所以Java使用擦除实现泛型。
擦除的工作原理:编译Java代码时,所有泛型信息被擦除。那么必须使用他们的界定类型替换类型参数,如果没有显式指定界定类型,就会使用Object。之后进行适当的类型转换以保持与类型参数所指定类型的兼容。这意味着允许时时没有类型参数的,Java泛型只是一种源代码机制,运行时的类型转换必然会带来开销。
十一、模糊性错误
class Solution<T, V> { private T a; private V b; Solution(T a, V b) { this.a = a; this.b = b; } void set(T a) { this.a = a; } void set(V b) { this.b = b; } }
上述代码出现了模糊性错误,当T和V为相同类型时,无法确定调用的方法。
十二、限制
- 不能实例化类型参数的对象或数组。
- 静态成员不能使用在类中声明的类型参数。
- 泛型类不能扩展Throwable,即不能创建泛型异常类。
若要实例化泛型对象的数组,需要使用通配符。
class Solution<T> { public static void main(String[] args) { Solution<Integer>[] solutionsA = new Solution<Integer>[10];//错误 Solution<?>[] solutionsB = new Solution<?>[10];//正确 } }