开发一个能够存储各种类型对象(比如:String 对象、Integer 对象等)的容器(容器就是存储数据的,可以是对象,可以是数组等等)。
在 JDK 1.5 之前是没有泛型的,最好的办法是开发一个能够存储和检索 Object
类型本身的容器,然后再将该对象用于各种类型时进行类型转换。
public class ObjectContainer { public Object obj; }
虽然这个容器会达到预期效果,但就我们的目的而言,它并不是最合适的解决方案。它不是类型安全的(Java 的编译器对于类型转换的错误是检测不到的,在运行时执行到 checkcast
这个字节码指令时,如果类型转换错误才会抛出 ClassCastException
),并且要求在检索封装对象时使用显式类型转换(向下转型),因此有可能引发运行时异常。
测试方法
public static void main(String[] args) { ObjectContainer myObj = new ObjectContainer(); // 这里发生向上转型 myObj.obj = "Test"; // 检索封装对象,发生向下转型 String myStr = (String) myObj.obj; System.out.println("myStr: " + myStr); }
注意:一个面试点,发生向下转型的必要条件是先发生向上转型。
从 JDK 1.5 开始出现了泛型,使用泛型可以很好的解决我们的场景需求。在实例化时为所使用的容器分配一个类型,也称泛型类型,这样就可以创建一个对象来存储所分配类型的对象。泛型类型可以看成一种弱类型(类似于 js 中的 var,定义的时候你可以随便定义,使用的时候就需要给出具体类型),这意味着可以通过执行泛型类型调用分配一个类型,将用分配的具体类型替换泛型类型。然后,所分配的类型将用于限制容器内使用的值,这样就无需进行类型转换,还可以在编译时提供更强的类型检查。
// 根据这个代码来看,泛型类型就是 T。泛型类型也可以称为泛型形参。 public class ObjectContainer<T>{ public Object obj; }
测试方法
代码里的注释很重要!很重要!很重要!建议多看几遍。
public static void main(String[] args) { // 执行泛型类型调用分配 String 这个具体类型,String 也可以称为泛型实参。 ObjectContainer<String> myObj = new ObjectContainer<String>(); myObj.obj = "ypf"; // 这里不需要类型转型,因为通过执行泛型类型调用分配了 String 这个类型,编译器会帮我们去生成一个 checkcast 字节码指令来做类型转换。也就是说我们以前需要手动去做的事(类型转型),现在编译器帮我们做了。其实泛型也可以看成是 Java 的一种语法糖。 String myStr = myObj.obj; System.out.println("myStr: " + myStr); }
从上面的类中我们已经知道了泛型类型就是 T。在这个测试方法中执行泛型类型调用对应的代码就是第 2 行,可以看到我们泛型类型 T 在执行时分配了 String 这个类型。其实执行泛型类型调用就是在写类的时候把 ClassName<T> 写成 ClassName<具体的类型>,也就是说把泛型类型替换成具体的类型。
ClassCastException
,可以节省时间。public class GenericClass<T>{ // key 这个成员变量的类型为 T,T 的类型由外部使用时指定。 private T key; public Generic(T key) { this.key = key; } public T getKey(){ return key; } }
定义一个泛型接口
public interface Generator<T> { public T next(); }
定义实现泛型接口的类
// 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中。 public class FruitGenerator<T> implements Generator<T>{ // 使用泛型类型 T。 @Override public T next() { return null; } }
// 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型。 public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; // 泛型类型 T 被替换成分配的类型 String @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; } }
当我们只想在某个方法中使用泛型而并不需要给整个类加个泛型时,可以使用泛型方法来指定一个泛型类型。泛型方法的泛型类型完全独立于类,也就是说可以与泛型类中声明的 T 不是同一种类型。通过下面的代码来验证这个结论。
public class GenericTest<T> { public T t; public static <T> T genericMethod(Class<T> clazz)throws InstantiationException ,IllegalAccessException{ T instance = clazz.newInstance(); return instance; } public static void main(String[] args) throws IllegalAccessException, InstantiationException { GenericTest g = new GenericTest(); g.t = "666"; System.out.println(g.t); GenericTest g2 = genericMethod(GenericTest.class); System.out.println(g2); } }
输出结果
从输出结果可以看出 GenericTest 类的泛型类型 T 为 String,genericMethod 方法的泛型类型 T 为 GenericTest 。
通过泛型方法和可变参数,我们可以 new 出任何类型的数组。这样我就很方便创建一个数组,其实在底层实现上是编译器帮我们去 new 数组这个操作了。
public class GenericTest<T> { // 巧妙利用语言的特性。多去了解一下语言的特性,利用起来。 public static <T> T[] of(T... array){ return array; } public static void main(String[] args) { Integer[] numArr = of(1,2,3,4); String[] strArr = of("y", "p", "f"); System.out.println(Arrays.toString(numArr)); System.out.println(Arrays.toString(strArr)); } }
泛型类型是没有继承关系的。
public class GenericTest<T> { public T t; public GenericTest(T t) { this.t = t; } public static void showKeyValue(GenericTest<Number> obj){ System.out.println(obj.toString()); } public static void main(String[] args) { GenericTest<Integer> gInteger = new GenericTest<Integer>(123); GenericTest<Number> gNumber = new GenericTest<Number>(456); showKeyValue(gNumber); // 下面这个会报错,编译不通过。因为泛型没有继承这个说法。 // GenericTest<Integer> 和 GenericTest<Number> 经过泛型擦除后都变为 GenericTest。 // showKeyValue(gInteger); } }
当操作类型时,不需要使用类型的具体功能时,只使用 Object 类中的功能。那么可以用 ?
通配符来表未知类型。?
和 Number、String、Integer 一样都是一种被分配的具体类型,可以把?
看成所有类型的父类来理解(也可以把这个看成 Java 语言的一种规范)。
public class GenericTest<T> { public T t; public GenericTest(T t) { this.t = t; } // 使用泛型通配符,可以接收任意类型的泛型类。 public static void showKeyValue(GenericTest<?> obj){ System.out.println(obj.toString()); } public static void main(String[] args) { GenericTest<Integer> gInteger = new GenericTest<Integer>(123); GenericTest<Number> gNumber = new GenericTest<Number>(456); showKeyValue(gNumber); showKeyValue(gInteger); } }
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。
泛型上下边界这块具体怎么使用在下次分析时介绍。
泛型类型不可以是基本类型,只能是类。
泛型类型没有继承关系。
不能对确切的泛型类型使用 instanceof 操作。
不可以创建一个确切的泛型类型的数组,但是可以声明泛型数组。
下次分析时从字节码角度来讲解。
参考链接:https://blog.csdn.net/s10461/article/details/53941091
本文分享自微信公众号 - Java知其所以然(gh_37a1335e2608)
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2019-01-22
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句