Java泛型学习

Java泛型是非常重要、有用的用法。因为Java是静态语言,和php,python等语言编程不同,需要在开发时声明变量类型,这样使得在开发时不可传入多种类型的变量,而泛型就是为了解决这个问题。它使得类型也可以参数化,这是JDK1.5开始引入的一个特性。

public class Fanxing<T,E> {

    private  T value1;

    private E value2;

    public Fanxing(T value1,E value2){
        this.value1 = value1;
        this.value2 = value2;
    }


    public T getValue1(T t){
        System.out.println(t);
        return  t;
    }

    public E getValue2(E e){
        System.out.println(e);
        return  e;
    }

}

上面T,E都是参数化的类型,具体传入哪个类型不再受限制。

再看下类泛型的应用  Class<T>

比如要泛型json序列化和反序列化:


    public  <T> T convert (Object obj,Class<T> tClass){
        return GoServerHelper.gson.fromJson(GoServerHelper.gson.toJson(obj),tClass);
    }

上面的泛型用法在集合中有些不方便,比如集合中的元素我们是用的泛型,但包含元素的集合却不是.

   public class Base{}

    public class Son extends Base{}

    List<Base> bases = new ArrayList();
    List<Son> sons = new ArrayList<>();

   sons = bases; //wrong

因此,针对这个问题,还有一种用法是通配符:?

用法:

  1. 无限定  ?
  2. 上限 ? extends SomeClass    指定了父类,但不知道具体是哪个子类,因此只能读,不能写
  3. 下限  ? super SomeClass      指定了是谁的父类,但不知道具体的根类,所以只能写,不能读。
  public class Base{}

    public class Son extends Base{}


    public void test(List<? super Son> pa){
     //只能插入
        pa.add(new Son());
    }

    public void test2(List<? extends Base> pa){
      //只能读
        System.out.println(pa.get(0));
    }

它和泛型的区别是T一旦传入,就 是一个确定的类型,通常用于泛型类和泛型方法的定义,?是一个不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

此外,除了一个方法需要有返回值,且也是泛型,那么不能用通配符了。

上面说了一大堆,那么泛型通过什么实现的呢?它是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

看一下下面的例子:

        List<String> list1 = new ArrayList<String>();
        List<Integer> integerList = new ArrayList<Integer>();
        System.out.println(list1.getClass() == integerList.getClass());

上面代码是返回true,在JVM中,上面两个都是List.其中String,Integer等都会被转换为Object类型,如果指定了上限如 <T extends String> 则类型参数就被替换成类型上限。

其实List本身就是利用泛型定以的:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

    transient Object[] elementData; // non-private to simplify nested class access


  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }


}

我们在开发中,使用add方法,其实也是利用了泛型操作,E都会被转换为Object。因此,利用这一点,我们可以绕过编译阶段实现不同类型数据的添加。

        List<String> list1 = new ArrayList<String>();
        try {
            Method method = list1.getClass().getDeclaredMethod("add",Object.class);
            method.invoke(list1,true);
            method.invoke(list1,1333);
            System.out.println(list1);
        }catch (Exception e){
            log.info(e);
        }

上面,虽然我定义了String元素类型的list,但通过反射,依然可以添加其他类型的value。

再看一个泛型对我们代码的影响:

  public static String test(List<String> s){
        return "";
    }

    public static String test(List<Integer> s){
        return "s";
    }

上述代码编译就会失败的,因为泛型擦除后都变成了Object。

有了泛型就要注意,当我们进行方法重载时,注意泛型擦除的问题。

 public static void method(List<String> list) {   
        System.out.println("invoke method(List<String> list)");   
    }   
 
    public static void method(List<Integer> list) {   
        System.out.println("invoke method(List<Integer> list)");   
    }   

如果我们在某一个类中定义了上面两个方法,编译会报错的,因为编译期间的泛型擦除会导致两个方法的形参是一致的。

参考资料:

揭开Java 泛型类型擦除神秘面纱

聊一聊-JAVA 泛型中的通配符 T,E,K,V,?

Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同?

面试重灾区-泛型攻克

--------EOF---------
微信分享/微信扫码阅读