public interface Foo<E> {} public interface Bar<T> {} public interface Zar<?> {}
上面的代码有什么区别?
1、为何引入泛型?
Java 泛型也是一种语法糖,使用泛型可以在代码编译阶段完成类型的转换,避免代码在运行时强制转换而出现ClassCastException的异常。
网络搜索出来一大堆的名称解释,我们先看英文 Generic type ,从英文大概也能明白,Generic 这里可以理解为普通的,一般的,或者我们可以说通用的。
其实可以理解为Java中的一种类型,通用类型。
Java从1.5的版本就开始支持泛型,不过很多小伙伴对泛型还是模凌两可,今天大概讲讲泛型,基础好的小伙伴,就当复习复习。
在1.5版本以前
public static void main(String[] args){ List list = new ArrayList(); list.add("兔子托尼啊"); list.add(1234); //正常运行 System.out.println((String)list.get(0)); //:x:运行时报错 System.out.println((String)list.get(1)); }
从上面的代码可以看出了,第一句打印不报错,第二句打印会报错的。
List默认是Object的类型的,向List里面存数据都是没有问题的,但是取数据的时候,必须要要进行类型的转换。
List集合get数据的时候并不清楚里面存放的什么数据类型,默认取出来的都是Object的类型,如果取数据的时候转换的类型和原始存放存的类型不一样,会报ClassCastException的异常。
2、引入了泛型
看代码
List<String> list = new ArrayList<String>(); list.add("兔子托尼啊"); //:x:编译时错误 list.add(1234); //不需要再进行转换了 String str = list.get(0);
3、泛型带来好处
这在编码的时候就给我们解决了,类型转换的问题,可以放心写代码。
取数据的时候再也不要考虑我前面存的什么类型,我应该转换为什么类型,不怕类型转换报错。
上面讲了泛型,泛型虽然带来了好处,但是泛型也带了一个问题叫做类型擦除。
Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。
Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
class GenericU { public void foo() { System.out.println("GenericU.foo()"); } } public class Operater<T> { private T obj; public Operater(T obj) { this.obj = obj; } public void doIt() { //:x:报错,提示找不到foo方法 obj.foo(); } public static void main(String[] args) { GenericU genericU = new GenericU(); Operater<GenericU> operater = new Operater<>(genericU); operater.doIt(); } }
上面的代码就是因为泛型擦除,带来编译就报错了,代码中的obj不知道是什么类型?
正确的代码应该是什么,只要指定T的类型就好
class Operater2<T extends GenericU> { private T obj; public Operater2(T obj) { this.obj = obj; } public void doIt() { //正确:ballot_box_with_check: obj.foo(); } }
区分在 Operater2<T extends GenericU>
和 Operater<T>
必须指定泛型的类型。
上面的例子是运用在类上面的,方法中是什么效果呢?
class Foo{ //定义泛型方法.. public <T> void show(T t) { System.out.println(t); } }
调用方法
public static void main(String[] args) { //创建Foo对象 Foo foo = new Foo(); //不同的类型参数 foo.show("兔子托尼啊"); foo.show(1234); foo.show(12.34); }
我们大家在java的源码中肯定看到这样的例子。一个下限,一个上限
? extends T
VS ? super T
上限通配符可以代表未知的T类型,或者通过关键字 extends 所继承的T类的任何一个子类。
同样,下限通配符可以代表未知的T类型,或者通过关键字super出来的的T类的任何一个父类。
//通配符 public void foo1(List<?> list) { } //使用泛型方法 public <T> void foo2(List<T> t) { }
问: 上面两种代码都是可以的,但是什么场合用那种呢?
问:关于 ? extends T
和 ? super T
什么场景下用呢?
我从网上搜索了下
当你需要从一个数据结构中获取数据时(get),那么就使用 ? extends T;如果你需要存储数据(put)到一个数据结构时,那么就使用 ? super T; 如果你又想存储数据,又想获取数据,那么就不要使用通配符 ? ,即直接使用具体泛型T。
泛型大概就讲了上面的内容,你看明白了吗?希望你又学到了,每天学一点,进步一点。升职加薪就是你了。
码字不易,关注后送福利,求关注。