开发一个能够存储各种类型对象(比如: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