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
因此,针对这个问题,还有一种用法是通配符:?
用法:
- 无限定 ?
- 上限 ? extends SomeClass 指定了父类,但不知道具体是哪个子类,因此只能读,不能写
- 下限 ? 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 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同?
微信分享/微信扫码阅读