这里不讲泛型的概念和基础知识,就单纯的就我的理解说一下泛型中的<T>
一. <T>
下面的一段码就可以简单使用了<T>参数,注释就是对<T>的解释。
package xayd.hjj; import java.util.ArrayList; import java.util.List; class A{} class B{} class C{} class D extends C{} public class Test<T> extends ArrayList<T>{ public static <T> void main(String[] args) { Test<C> testC = new Test<C>(); // testC = new Test<B>(); //编译错误 //testC = new Test<D>(); //编译错误 //一旦指定了参数为C这个类就不能把这个对象在赋值为其他类型,即便是其父类 testC.add(new C()); //testC.add(new B()); //编译错误,不能在C类的容器中存放无关C的类B的对象 testC.add(new D()); //编译正确,因为父类容器可以放置父类对象,同时也可以放置子类对象 C testC1 = testC.get(0); C testC2 = testC.get(1); //从父类容器中拿出子类对象,用最初传递的参数类型接收 // D testC3 = testC.get(1); //编译错误 D testC4 = (D) testC.get(1); //从父类容器中拿出子类对象,用最初传递的参数类型的子类对象接收,要进行强制类型转换 System.out.println(testC1); //[email protected] System.out.println(testC2); //[email protected] System.out.println(testC4); //[email protected] } }
其实继承ArrayList的目的是为了使用add和get方法,以便测试,其实你也可以自己实现类似于add和get的方法。
ArrayList关于add和get方法的实现如下:
private transient Object[] elementData; private int size; public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } public E get(int index) { RangeCheck(index); return (E) elementData[index]; }
下面还有一段和上面有点类似的代码。如下:
package xayd.hjj1; import java.util.ArrayList; class A{} class B{} class C{} class D extends C{} public class Test<T> extends ArrayList{ //注意这里不是ArrayList<T>,其实应该相当于ArrayList<Object> public static void main(String[] args) { Test<C> testC = new Test<C>(); // testC = new Test<D>();//同样编译有错误 testC.add(new C()); testC.add(new A()); //奇怪的是可以通过编译 testC.add(new B()); //这样也可以通过编译 testC.add(new D()); //C objC = testC.get(0); //编译不能通过 Object objC1 = testC.get(0); //编译通过 C objC2 = (C) testC.get(0); //编译通过 //C objC3 = (C) testC.get(1); //这样的结果编译可以通过,但是运行毫无疑问会抛出ClassCastException A objC4 = (A) testC.get(1); //编译通过,不过这样程序员必须知道容器中第二个对象是A类型. Object objC5 = (A) testC.get(1); //编译通过 Object objC6 = (Object) testC.get(1); //编译通过 } }
这段代码和第一段代码的区别就是继承ArrayList时的不同。第一段代码是Test<T> extends ArrayList<T>,这段代码是Test<T> extends ArrayList。从代码运行的结果可以看到这段代码是不安全的,这段代码用testC调用add方法时参数传递没有限制,并不像第一段代码会出现编译错误。而且用get方法拿出时必须进行类型转化,即使你执行了这样的代码 C objC3 = (C) testC.get(1),编译期并不能检查到错误,而是在运行时抛出ClassCastException。
在这里我对于ArrayList<T>和ArrayList这两种不同写法的解释如下(可能也不太准确,如有错误还请指正):
Test<T> extends ArrayList<T>: Test<C> testC = new Test<C>(),这样Test和ArrayList的参数类型都为C,因此add方法参数只能传递C和C的子类。从add方法的源码中可以看到是这样添加元素的:elementData[size++] = e,在这里elementData其实是一个Object类型的数组,但是因为你限制了T的参数为类C的类型,所以编译器会限制这Object必须为C类型(或者是它的子类),所以在你加入了其他与类C无关的对象的时候编译是不能通过的。
Test<T> extends ArrayList: Test<C> testC = new Test<C>(), 这样Test参数类型为C,而ArrayList的参数没有传递,因此默认为Object(public class ArrayList<E>,ArrayList类在源码中是这样的实现形式)。所以只要是Object类型都可以添加,所以才会导致编译期编译器可以无限制的添加任类型却没有任何错误,而运行期拿出时一旦转型错误便抛出异常。
二.关于上面的代码导致的两种不同结果,是因为ArrayList本身的实现导致的。所以自己实现了一个简单的统一的类重新解释T.
package xayd.hjj2; import java.util.ArrayList; class A{} class B{} class C{} class D extends C{} public class Test<T>{ private T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } public static <T> void main(String[] args) { Test<C> testC = new Test<C>(); testC.setObj(new C()); C objC = testC.getObj(); System.out.println(objC); //[email protected] // testC.setObj(new B()); //编译错误 testC.setObj(new D()); //编译成功 C objC1 = testC.getObj(); System.out.println(objC1); //[email protected] } }
这段代码就比较简单清晰了,Test类和类的成员变量参数类型一致,因此不会有不同的结果。