1.泛型的创造者让泛型的使用者能够在使用泛型的时候根据传入泛型类型的不一样而使用对应类型的API。java
2.使用泛型能够解决没必要要的类型转换错误。android
针对第一点:举系统用到泛型的两个例子:程序员
例子一:findViewById()面试
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getWindow().findViewById(id);
}
复制代码
这个方法应该是咱们平常开发中最经常使用的方法了,能够看到根据咱们传入的id最终解析找到返回与之对应的一个View。 层层深刻最终找到源头:数组
protected <T extends View> T findViewTraversal(@IdRes int id) {
if (id == mID) {
return (T) this;
}
return null;
}
复制代码
调用时:bash
TextView textView= findViewById(R.id.title);
textView.setText("123");
ImageView imageView= findViewById(R.id.image);
imageView.setImageResource(R.drawable.ic_smoll_android);
复制代码
能够看到咱们并无显示的获取或者建立对应的TextView或者ImageView实例就直接获取到了对应的类型从而调用到了对应类型的API,好比TextView的setText方法和ImageView的setImageResource方法。app
这里的泛型创造者也就是谷歌写View的这个程序员,泛型的使用者天然就是咱们自身调用findViewById这个方法的人了,因为这是一个泛型方法,咱们最终不用new TextView就能够获取对应的TextView实例,由于泛型的创造者已经帮咱们将新建的逻辑写好了,咱们只须要根据须要用不用的View类型去接收就能够获取到对应类型的实例,这无疑是至关方便的。 这就是根据咱们传入类型不一样而获取调用到对应类型API的一个最好的例子。性能
例子二:系统的Comparable接口ui
public interface Comparable<T> {
public int compareTo(T o);
}
复制代码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
...
public native int compareTo(String anotherString);
}
复制代码
public final class Integer extends Number implements Comparable<Integer> {
...
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
}
复制代码
能够看到String和Integer类实现Comparable接口后经过传入不一样的泛型类型String,Integer从而在复写compareTo方法时即可以调用到传入的类型String,Integer的API。this
List list = new ArrayList();
list.add("123");
int a = (int) list.get(0);//须要作类型强转,在运行时,ClassCastException
复制代码
能够看到若是没有泛型在运行时有可能会出现这样没必要要的类型转换错误,使用泛型之后在编译时就将类型约束好了从而解决了这种问题的产生。
Java中的泛型类型在代码运行时会被擦除掉(通常状况下会被擦成Object类型,若是使用了上限通配符的话会被擦成extends右边的类型,如T extends View则最终会被擦成View类型),也就是说泛型只在编译期起做用。
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2); true
复制代码
能够看到最后结果是true的,由于泛型类型在运行时都会被擦除掉,也就是说其实c1和c2是由相同的class字节码文件加载出来的,他们是相同的Class,new ArrayList()和new ArrayList()最后都会被擦成new ArrayList()。
最主要仍是出于兼容性的考虑,泛型是JDK1.5之后才引入的,为了兼容以前的JDK版本因此在运行时将泛型类型都擦掉以保证和以前JDK版本的java字节码相同。
public class Test<T> {
private T b;
public void setB(T b) {
this.b = b;
}
public T getB() {
return b;
}
}
Test<Integer> a=new Test<Integer>();
a.setB(1);
int b=a.getB();//不须要作类型强转,自动完成
复制代码
//定义处已经被擦除成Object,没法进行强转,不知道强转成什么public T getB();
Code:
0: aload_0
1: getfield #23 // Field b:Ljava/lang/Object;
4: areturn
//调用处利用checkcast进行强转
L5 {
aload1
invokevirtual com/ljj/A getB()Ljava.lang.Object);
checkcast java/lang/Integer
invokevirtual java/lang/Integer intValue(()I);
istore2
}
复制代码
能够看到若是使用了泛型,在运行期间是会自动进行类型强转的而不用咱们主动去调用类型强转。
泛型类是在实例化类的对象时才能肯定的类型,其定义譬如 class Test {},在实例化该类时必须指明泛型 T 的具体类型。
泛型接口与泛型类同样,其定义譬如 interface Generator { E dunc(E e); }。
泛型方法是独立的,它能够不依附与你当前类中的泛型,当前类没有泛型也可使用泛型方法。使用的时候将泛型类型定义到方法返回值的左边,以后就可使用了。
<T> void func(T val) {}
<T> T func(Fruit val) {}
public static <T> void func(T val) {}
复制代码
下边举一个例子
public class Test{
public static <T> T add(T x, T y){
return y;
}
public static void main(String[] args) {
int t0 = Test.add(10, 20.8);
int t1 = Test.add(10, 20);
Number t2 = Test.add(100, 22.2);
Object t3 = Test.add(121, "abc");
int t4 = Test.<Integer>add(10, 20);
int t5 = Test.<Integer>add(100, 22.2);
Number t6 = Test.<Number>add(121, 22.2);
}
}
复制代码
Fruit<String>[] i=new Fruit<String>[10]; //Errot
Fruit<?>[] i=new Fruit<?>[10]; /能够经过,可是没有意义
复制代码
再看一个例子
//Part1
List<Object> list=new ArrayList<String>();//Error
list.add("123");
//Part2
Object[] objects=new Long[10];
objects[0]="123"; //Runtime 异常
复制代码
上面 Part 1 编译出错,Part 2 编译 OK,运行出错。 由于 List 和 ArrayList 没有继承关系,而 Java 的数组是在运行时类型检查的。
泛型不支持传入基本数据类型,只支持引用数据类型,例如咱们直接使用new ArrayList()是不合法的,由于类型擦除后会替换成Object(若是经过extends设置了上限,则替换成上限类型),int显然没法替换成Object,因此泛型参数必须是引用类型。
因此下列方法都是错误的
static <T> void test(T t){ //所有ERROR
T newInstance=new T();
T[] array=new T[0];
Class c=T.class;
List<T> list=new ArrayList<T>();
if(list instanceof List<String>){}
}
复制代码
如何经过反射获取泛型类型?
既然泛型类型在运行时会被擦除那么咱们怎么获取到泛型类型呢?
其实在泛型擦除时并不会将全部的泛型类型都擦除掉,它只会擦除运行时的泛型类型,编译时类中定义的泛型类型是不会被擦除的,对应的泛型类型会被保存在Signature中。 咱们若是想获取对应对象中的泛型类型只需将动态建立的对象改成匿名内部类便可获取,由于内部类实在编译时建立的,泛型类型是会保存下来的。 对应API getGeneric...都是获取泛型类型的。 下边举两个例子:
List<Integer> list = new ArrayList<>();
list.getClass().getGenericSuperclass(); //获取不到泛型信息
List<Integer> list1 = new ArrayList() {};
list1.getClass().getGenericSuperclass(); //能够获取到泛型信息
复制代码
下边以一道题为例:
public class Demo {
public static void main(String[] args) throws Exception {
ParameterizedType type = (ParameterizedType) Bar.class.getGenericSuperclass();
System.out.println(type.getActualTypeArguments()[0]);
ParameterizedType fieldType = (ParameterizedType) Foo.class.getField("children").getGenericType();
System.out.println(fieldType.getActualTypeArguments()[0]);
ParameterizedType paramType = (ParameterizedType) Foo.class.getMethod("foo", List.class).getGenericParameterTypes()[0];
System.out.println(paramType.getActualTypeArguments()[0])
System.out.println(Foo.class.getTypeParameters()[0].getBounds()[0]);
}
class Foo<T extends CharSequence> {
public List<Bar> children = new ArrayList<Bar>();
public List<StringBuilder> foo(List<String> foo) {return null}
public void bar(List<? extends String> param) {}
}
class Bar extends Foo<String> {}
}
复制代码
运行结果以下。
class java.lang.String
class Demo$Bar
class java.lang.String
interface java.lang.CharSequence
经过上面例子会发现泛型类型的每个类型参数都被保留了,并且在运行期能够经过反射机制获取到,由于泛型的擦除机制实际上擦除的是除结构化信息外的全部东西(结构化信息指与类结构相关的信息,而不是与程序执行流程有关的,即与类及其字段和方法的类型参数相关的元数据都会被保留下来经过反射获取到)。
因为泛型不是协变的,它不支持继承,好比在使用 List< Number> 的地方不能传递 List< Integer>,因此引入了通配符去解决对应的问题,能够理解通配符是对泛型功能的扩展和加强。
extends 上限通配符 能够接收extend后的类型及子类
super 下限通配符 能够接收super后的类型及父类
向下边这种写法都是被禁止的
List<Number> list=new ArrayList<Integer>() //Error
List<Object> list;
List<String> strlist=new ArrayList<>();
list=strlist; //Error
复制代码
首先明确两个概念:
形参和实参
public class Shop< T>的那个T
表示我要建立一个 Shop 类,它的内部会用到一个统一的类型,这个类型姑且称他为 T 。
其它地方尖括号里的全是 Type argument,好比 Shop < Apple> appleShop;的 Apple ;
表示「那个统一代号,在这里的类型我决定是这个」
其实用大白话来讲形参就是定义泛型的地方,实参是传入具体泛型类型的地方。
下边以几个例子来看看用法
Vector<? extends Number> x1 = new Vector<Integer>(); //正确
Vector<? extends Number> x2 = new Vector<String>(); //编译错误
Vector<? super Integer> y1 = new Vector<Number>(); //正确
Vector<? super Integer> y2 = new Vector<Byte>(); //编译错误
复制代码
List<? extends Fruit> list = new ArrayList<>();
list.add(new Apple());//Error
list.get(0);//不报错
List<? super Fruit> list2 = new ArrayList<>();
list2.add(new Apple());//不报错
list2.get(0);//Error
复制代码
当咱们使用上限通配符时对应方法参数中使用到泛型的方法将都没法调用,由于咱们不能肯定具体传入的是哪一种类型。
当咱们使用下限通配符时对应方法返回值中使用到泛型的方法将都没法调用,由于咱们不能肯定具体返回的是哪一种类型。
这时咱们就有疑问了,既然使用通配符后对应的对象都处于报废状态,那么这东西有啥用? 其实 ? 以及通配符一般都是用在方法参数中的,好比:
public int getTotalWeight(List<Fruit> list) {
float totalWeight = 0;
for (int i=0;i<list.size();i++) {
Fruit fruit=list.get(i);
totalWeight += fruit.getWeight();
}
return totalWeight;
}
List<Apple> listApple = new ArrayList<>();
listApple.add(new Apple());
List<Banana> listBanana = new ArrayList<>();
listBanana.add(new Banana());
int totalPrice=getTotalWeight(listApple)+getTotalWeight(listBanana);//编译报错
复制代码
这种状况是错误的,由于泛型不支持继承,咱们是没法直接传入的。但咱们只要修改一下便可
public int getTotalWeight(List<? extends Fruit> list) {
float totalWeight = 0;
for (int i=0;i<list.size();i++) {
Fruit fruit=list.get(i);
totalWeight += fruit.getWeight();
}
return totalWeight;
}
复制代码
这种状况就OK了,由于这表明着咱们传入的类型是可知的,上限通配符extends能够接受extends右边的类型及其子类。
定义一个方法去添加自身
public class Apple extends Fruit{
void addMeToList(List<Apple> list){
list.add(this);
}
}
复制代码
List<Fruit> fruits = new ArrayList<>();
Apple apple=new Apple();
apple.addMeToList(fruits);//报错
复制代码
能够看到当调用方法时会出现报错,由于泛型不支持继承。咱们修改一下
public class Apple implements Fruit{
void addMeToList(List<? super Apple> list){
list.add(this);
}
}
复制代码
这样调用时就不会出现任何问题了。由于下限通配符能够接收super类型后的父类,天然Apple的父类Fruit是确定能够接收的。
< T extends E> 和 <? extends E> 有什么区别?
它们用的地方不同,< T extends E>只能用于形参(也就是泛型定义的时候),<? extends E>只能用于实参(也就是传入具体泛型类型的时候)。 好比:
public void addAll(Bean<? extends E> bean;
public <T extends E> void addAll(Bean<T> bean;
复制代码
下面程序合法吗?
class Bean<T super Student> { //TODO }
复制代码
编译时报错,由于super只能用做实参不能用于形参,extends实参形参均可以。
下面两个方法有什么区别?为何?
public static <T> T get1(T t1, T t2) {
if(t1.compareTo(t2) >= 0);
return t1;
}
public static <T extends Comparable> T get2(T t1, T t2){
if(t1.compareTo(t2) >= 0);
return t1;
}
复制代码