继续创造,加速成长!这是我参与「日新方案 10 月更文应战」的第9天,点击查看活动概况

前言

本系列文章主要是汇总了一下大佬们的技能文章,属于Android根底部分,作为一名合格的安卓开发工程师,咱们肯定要熟练掌握java和android,本期就来说说这些~

[非商业用途,如有侵权,请奉告我,我会删去]

DD一下: Android进阶开发各类文档,也可重视大众号<Android苦做舟>获取。

1.Android高级开发工程师必备根底技能
2.Android功用优化中心知识笔记
3.Android+音视频进阶开发面试题冲刺合集
4.Android 音视频开发入门到实战学习手册
5.Android Framework精编内核解析
6.Flutter实战进阶技能手册
7.近百个Android录播视频+音视频视频dome
.......

泛型

1.泛型的根本介绍

1.1泛型的界说和运用

泛型按照运用状况能够分为 3 种。

  • 泛型类。
  • 泛型办法。
  • 泛型接口。
1.2泛型类

咱们能够这样界说一个泛型类。

public class Test<T> {
 T field1;
}

尖括号 <>中的 T 被称作是类型参数,用于指代任何类型。事实上,T 仅仅一种习惯性写法,假如你愿意。你能够这样写。

public class Test<Hello> {
 Hello field1;
}

但出于规范的意图,Java 还是主张咱们用单个大写字母来代表类型参数。常见的如:

  • T 代表一般的任何类。
  • E 代表 Element 的意思,或许 Exception 反常的意思。
  • K 代表 Key 的意思。
  • V 代表 Value 的意思,通常与 K 一同合作运用。
  • S 代表 Subtype 的意思

假如一个类被 的办法界说,那么它就被称为是泛型类。

Test<String> test1 = new Test<>();
Test<Integer> test2 = new Test<>();

只要在对泛型类创立实例的时分,在尖括号中赋值相应的类型便是。T 就会被替换成对应的类型,如 String 或许是 Integer。你能够相像一下,当一个泛型类被创立时,内部主动扩展成下面的代码。

public class Test<String> {
 String field1;
}

当然,泛型类不至承受一个类型参数,它还能够这样承受多个类型参数。

public class MultiType <E,T>{
 E value1;
 T value2;
 
 public E getValue1(){
  return value1;
  }
 
 public T getValue2(){
  return value2;
  }
}
1.3泛型办法
public class Test1 {
 public <T> void testMethod(T t){
  
  }
}

泛型办法与泛型类稍有不同的当地是,类型参数也便是尖括号那一部分是写在返回值前面的。中的 T 被称为类型参数,而办法中的 T 被称为参数化类型,它不是运转时实在的参数。

当然,声明的类型参数,其实也是能够当作返回值的类型的。

public <T> T testMethod1(T t){
  return null;
}
1.4泛型类与泛型办法的共存
public class Test1<T>{
​
 public void testMethod(T t){
  System.out.println(t.getClass().getName());
  }
 public <T> T testMethod1(T t){
  return t;
  }
}

上面代码中,Test1是泛型类,testMethod 是泛型类中的一般办法,而 testMethod1 是一个泛型办法。

而泛型类中的类型参数与泛型办法中的类型参数是没有相应的联络的,泛型办法一直以自己界说的类型参数为准。

所以,针对上面的代码,咱们能够这样编写测验代码。

Test1<String> t = new Test1();
t.testMethod("generic");
Integer i = t.testMethod1(new Integer(1));

泛型类的实践类型参数是 String,而传递给泛型办法的类型参数是 Integer,两者不相干。

可是,为了避免混杂,假如在一个泛型类中存在泛型办法,那么两者的类型参数最好不要同名。比方,Test1代码能够更改为这样

public class Test1<T>{
 public void testMethod(T t){
  System.out.println(t.getClass().getName());
  }
 public <E> E testMethod1(E e){
  return e;
  }
}
1.5泛型接口

泛型接口和泛型类差不多

public interface Iterable<T> {
}
1.6通配符 ?

除了用 表明泛型外,还有 <?>这种办法。? 被称为通配符。

class Base{}
class Sub extends Base{}
Sub sub = new Sub();
Base base = sub;   

上面代码显示,Base 是 Sub 的父类,它们之间是承继联系,所以 Sub 的实例能够给一个 Base 引证赋值,那么

List<Sub> lsub = new ArrayList<>();
List<Base> lbase = lsub;

最后一行代码建立吗?编译会经过吗?答案是否定的。

 编译器不会让它经过的SubBase 的子类,不代表 List<Sub>和 List<Base>有承继联系

可是,在现实编码中,的确有这样的需求,期望泛型能够处理某一规模内的数据类型,比方某个类和它的子类,对此 Java 引入了通配符这个概念。

所以,通配符的呈现是为了指定泛型中的类型规模,通配符有 3 种办法。

<?>被称作无约束的通配符。
<? extends T>被称作有上限的通配符。
<? super T>被称作有下限的通配符。
1.7无约束通配符 <?>

无约束通配符常常与容器类合作运用,它其间的 ? 其实代表的是不知道类型,所以涉及到 ? 时的操作,必定与详细类型无关。

public void testWildCards(Collection<?> collection){
}

上面的代码中,办法内的参数是被无约束通配符润饰的 Collection 目标,它隐略地表达了一个意图或许能够说是约束,那便是 testWidlCards() 这个办法内部无需重视 Collection 中的实在类型,由于它是不知道的。所以,你只能调用 Collection 中与类型无关的办法。

当 <?>存在时,Collection 目标丧失了 add() 办法的功用,编译器不经过。

List<?> wildlist = new ArrayList<String>();
wildlist.add(123);// 编译不经过

供给了只读的功用,也便是它删减了增加详细类型元素的才能,只保存与详细类型无关的功用。它不管装载在这个容器内的元素是什么类型,它只关怀元素的数量、容器是否为空

1.8<? extends T>
<?>代表着类型不知道,可是咱们的确需求对于类型的描绘再精确一点,咱们期望在一个规模内确认类别,比方类型 A 及 类型 A 的子类都能够。
<? extends T> 代表类型 TT 的子类
public void testSub(Collection<? extends Base> para){ }

上面代码中,para 这个 Collection 承受 Base 及 Base 的子类的类型。 可是,它依然丧失了写操作的才能。也便是说

para.add(new Sub());
para.add(new Base()); 

依然编译不经过。 没有联系,咱们不知道详细类型,可是咱们至少清楚了类型的规模。

<? super T> 这个和 <? extends T>  相对应,代表 TT 的超类。
public void testSuper(Collection<? super Sub> para){ }
 <? super T>. 神奇的当地在于,它具有必定程度的写操作的才能。
public void testSuper(Collection<? super Sub> para){
   para.add(new Sub());//编译经过 
   para.add(new Base());//编译不经过 
} 
The key to understanding why this works is rather simple: if you can only take items from a collection, then using a collection of Strings and reading Objects from it is fine.
Conversely, if you can only put items into the collection, it's okay to take a collection of Objects and put Strings into it: in Java there is List<? super String>, a supertype of List<Object>.
The latter is called contravariance, and you can only call methods that take String as an argument on 
List<? super String> (for example, you can call add(String) or set(int, String)). If you call something that returns T in List<T>, you don't get a String, but rather an Object.
1.9通配符与类型参数的差异

一般来说,通配符精干的事情都能够用类型参数替换。 比方

public void testWildCards(Collection<?> collection){}

能够被

public <T> void test(Collection<T> collection){}

取代

值得注意的是,假如用泛型办法来取代通配符,那么上面代码中 collection 是能够进行写操作的。只不过要进行强制转化。

public <T> void test(Collection<T> collection){
 collection.add((T)new Integer(12));
 collection.add((T)"123");
}

需求特别注意的是,类型参数适用于参数之间的类别依靠联系,举例阐明。

public class Test2 <T,E extends T>{
 T value1;
 E value2;
}
public <D,S extends D> void test(D d,S s){
  }

E 类型是 T 类型的子类,显着这种状况类型参数更适合。 有一种状况是,通配符和类型参数一同运用。

public <T> void test(T t,Collection<? extends T> collection){
 
}

假如一个办法的返回类型依靠于参数的类型,那么通配符也无能为力。

public <T> T test1(T t){
 return value1;
}
1.10类型擦除

泛型是 Java 1.5 版别才引入的概念,在这之前是没有泛型的概念的,但显着,泛型代码能够很好地和之前版别的代码很好地兼容。

这是由于,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

浅显地讲,泛型类和一般类在 java 虚拟机内是没有什么特别的当地。回顾开始时的那段代码:

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

打印的成果为 true 是由于 List和 List在 jvm 中的 Class 都是 List.class。

泛型信息被擦除了。或许同学会问,那么类型 String 和 Integer 怎么办?答案是泛型转译。

public class Erasure <T>{
 T object;
 public Erasure(T object) {
  this.object = object;
  }
}

Erasure 是一个泛型类,咱们查看它在运转时的状态信息能够经过反射。

Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());

成果:

erasure class is:FinalizeEscapeGC$Erasure

Class 的类型依然是 Erasure 并不是 Erasure<T>这种办法,那咱们再看看泛型类中 T 的类型在 jvm 中是什么详细类型。
    Erasure<String> erasure = new Erasure<String>("hello");
    Class eclz = erasure.getClass();
    Field[] fs = eclz.getDeclaredFields();
    for ( Field f:fs) {
      System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
     }

成果:Field name object type:java.lang.Object

那咱们可不能够说,泛型类被类型擦除后,相应的类型就被替换成 Object 类型呢?这种说法,不彻底正确,咱们更改一下代码。

public class Erasure <T extends String>{
//  public class Erasure <T>{
 T object;
 public Erasure(T object) {
  this.object = object;
  }
}

成果:Field name object type:java.lang.String

咱们现在能够下结论了,在泛型类被类型擦除的时分,之前泛型类中的类型参数部分假如没有指定上限,

如 则会被转译成一般的 Object 类型,假如指定了上限如 则类型参数就被替换成类型上限。所以,在反射中

public class Erasure <T>{
 T object;
 public Erasure(T object) {
  this.object = object;
  }
 
 public void add(T object){
  
  }
}

add() 这个办法对应的 Method 的签名应该是 Object.class。

Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());
Method[] methods = eclz.getDeclaredMethods();
for ( Method m:methods ){
 System.out.println(" method:"+m.toString());
}

打印成果是:

method:public void FinalizeEscapeGC$Erasure.add(java.lang.Object)

也便是说,假如你要在反射中找到 add 对应的 Method,你应该调用 getDeclaredMethod(“add”,Object.class)不然程序会报错,提示没有这么一个办法,原因便是类型擦除的时分,T 被替换成 Object 类型了。类型擦除带来的局限性

1.11类型擦除带来的局限性

类型擦除,是泛型能够与之前的 java 版别代码兼容共存的原因。但也由于类型擦除,它会抹掉许多承继相关的特性,这是它带来的局限性。

了解类型擦除有利于咱们绕过开发傍边或许遇到的雷区,相同了解类型擦除也能让咱们绕过泛型自身的一些约束。比方

重学Android基础系列篇(二):泛型

正常状况下,由于泛型的约束,编译器不让最后一行代码编译经过,由于类似不匹配,可是,基于对类型擦除的了解,运用反射,咱们能够绕过这个约束

public interface List<E> extends Collection<E>{
 
  boolean add(E e);
}

上面是 List 和其间的 add() 办法的源码界说。

由于 E 代表恣意的类型,所以类型擦除时,add 办法其实等同于

boolean add(Object obj);

那么,运用反射,咱们绕过编译器去调用 add 办法。

public class ToolTest {
 public static void main(String[] args) {
  List<Integer> ls = new ArrayList<>();
  ls.add(23);
//   ls.add("text");
  try {
   Method method = ls.getClass().getDeclaredMethod("add",Object.class);
   
   
   method.invoke(ls,"test");
   method.invoke(ls,42.9f);
   } catch (NoSuchMethodException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   } catch (SecurityException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   } catch (IllegalAccessException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   } catch (IllegalArgumentException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   } catch (InvocationTargetException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   }
  
  for ( Object o: ls){
   System.out.println(o);
   }
  }
}

成果:

23
test
42.9

能够看到,运用类型擦除的原理,用反射的手段就绕过了正常开发中编译器不允许的操作约束。

1.12泛型中值得注意的当地

泛型类或许泛型办法中,不承受 8 种根本数据类型,需求运用它们对应的包装类。

List<Integer> li = new ArrayList<>();
List<Boolean> li1 = new ArrayList<>();

2.战略形式中心思维剖析

2.1根本介绍
  1. 战略形式(Strategy Pattern)中,界说算法族(战略组) ,别离封装起来,让他们之间能够互相替换,此形式让算法的改动独立于运用算法的客户
  2. 这算法体现了几个规划准则,榜首、 把改动的代码从不变的代码中分离出来;第二、针对接口编程而不是详细类(界说了战略接口);第三、多用组合/聚合,少用承继(客户经过组合办法运用战略)。
  3. 类图
    重学Android基础系列篇(二):泛型

阐明:从上图能够看到,客户context有成员变量strategy或许其他的战略接口 ,至于需求运用到哪个战略,咱们能够在构造器中指定。

2.2战略形式在JDK-Arrays应用的源码剖析
  1. JDK的Arrays的Comparator就运用了战略形式

  2. 代码剖析+Debug源码+形式人物剖析

    重学Android基础系列篇(二):泛型

    //办法1
    //数组
    Integer[] data = {9, 1, 2, 8, 4, 3};
    //完成升序排序,返回-1放左面,1放右边,0保持不变
    //阐明
    // 1.完成了Comparator接口(战略接口),匿名类目标new Comparator<Integer>(){..}
    // 2.目标new Comparator<Integer>(){..} 便是完成了战略接口的目标
    // 3.public int compare(Integer o1, Integer o2){}指定详细的处理办法
    Comparator<Integer> comparator = new Comparator<Integer>() {
       public int compare(Integer o1, Integer o2) {
         if (o1 > o2) {
           return 1;
         } else {
           return -1;
         }
       }
    };
    /*
    阐明
    public static <T> void sort(T[] a, Comparator<? super T> c) {
       if (c == null) {
         sort(a); //默认办法
       } else {
         if (LegacyMergeSort.userRequested)
           legacyMergeSort(a, c); //运用战略目标c
         else
           //运用战略目标c
           TimSort.sort(a, 0, a.length, c, null, 0, 0);
       }
    }
    */
    Arrays.sort(data, comparator);
    System.out.println(Arrays.toString(data)); //升序摆放
    //办法2 - lambda表达式完成 战略形式
    Integer[] data2 = {9, 1, 2, 8, 4, 3};
    Arrays.sort(data2, (var1, var2) -> {
       if (var1.compareTo(var2) > 0) {
         return 1;
       } else {
         return -1;
       }
    });
    System.out.println("data2=" + Arrays.toString(data2));
    
2.3 战略形式的注意事项和细节
  1. 战略形式的关键是:剖析项目中改动部分与不变部分。
  2. 战略形式的中心思维是:多用组合/聚合少用承继;用行为类组合,而不是行为的承继。更有弹性。
  3. 体现了 “对修改关闭,对扩展敞开” 准则,客户端增加行为不用修改原有代码,只要增加一种战略(或许行为)即可,避免了运用多重搬运句子(if..else if..else)。
  4. 供给了能够替换承继联系的办法:战略形式将算法封装在独立的Strategy类中使得你能够独立于其Context改动它,使它易于切换、易于了解、易于扩展。
  5. 需求注意的是:每增加一个战略就要增加一个类,当战略过多是会导致类数目巨大。

3.泛型上下鸿沟

泛型的泛参(type argument)能够运用实践类型或许通配符(wildcard)。其间通配符能够经过鸿沟(bound)来约束其承受的实践参数的类型。依据其种类,能够分为无界(unbounded)、上界(upper bound)和下界(lower bound)。其泛型鸿沟决定了输入(input)和输出(output)别离能承受什么类型。

输入为其函数的参数、属功能够赋值的值的类型,输出为函数的返回值、获取到的特点的值的类型。

3.1 实践类型

泛型的泛参能够运用实践类型。也便是类似于List,直接指定泛型的类型。这时分泛型的体现最简单了解,输入和输出都为实践类型。需求注意的一点是,泛型不支撑协变(Covariant),协变需运用通配符。为什么泛型不支撑协变呢。咱们先从支撑协变的数组开始考虑。考虑以下代码:

Object[] array = new String[1];
array[0] = 12.450F;

这段代码是能够经过编译的,但是会让静态类型的Java语言在没有任何强制类型转化的状况下呈现类型反常。咱们尝试往一个String类型的数组索引为0的位置赋值一个Float类型的值,这当然是行不通和彻底过错的。Java数组能够协变是一个规划上的根本过错,它能导致你的代码在你彻底不知情的状况下溃散和反常,但现在改现已为时已晚。幸好咱们有和常常运用调集API,不然最常见的状况或许如下:

public Number evil;
public void setAll(Number[] array) {
  for (int i = 0;i < array.length;i++) {
  array[i] = evil;
  }
}
public void looksGood() {
  atSomewhereWeDontKnown(); //We summoned evil to our kawaii and poor code
  Float[] ourKawaiiArray = getOurKawaiiArray(); //Oops
}
public void atSomewhereWeDontKnown() {
  evil = 12450;
}
public Float[] getOurKawaiiArray() {
  Float[] weWantFloatFilled = new Float[0xFF];
  setAll(weWantFloatFilled); //Buts... we got (1)E(2)V(4)I(5)L(0)...
  return weWantFloatFilled;
}

注:我试了一下,以上代码执行looksGood()时会出错,第4行报java.lang.ArrayStoreException。所以,不知道作者所说的we got (1)E(2)V(4)I(5)L(0)…是什么意思。或许是java某个老版别运转的成果。

咱们可不想让(1)E(2)V(4)I(5)L(0)充满咱们的代码。所以,泛型吸取了这个教训,自身便是为了提高类型安全性而规划的泛型不能犯这样的初级过错。所以你不能写以下代码:

List<Object> array = new ArrayList<String>;
array.set(0, 12.450F); 

这段代码在榜首行就无法经过编译,由于你尝试协变一个泛型。其解决办法和其他的阐明将在后续讨论。

3.2 通配符
3.2.1 无界通配符

无界通配符为”?”,能够承受任何的实践类型作为泛参。其能承受的输入和输出类型十分有限。

①可用输入类型

严格意义上不能承受任何的类型作为输入,考虑以下代码:

<spanhljs-attribute">font-family:Source Sans Pro, Helvetica, sans-serif;color:#141412;"><spanhljs-attribute">line-height: 24px;">List<?> list = new ArrayList<String>();
list.add("123");//报反常,</span></span><spanhljs-attribute">font-family:Source Sans Pro, Helvetica, sans-serif;color:#141412;"><spanhljs-attribute">line-height: 24px;">list.add(null)正确</span></span> 

你或许觉得这段代码看起来没有问题。通常会这样考虑,咱们能够简单的把无界通配符”?”当作Object,往一个Object类型的列表加一个String有什么问题?何况其实践便是String类型。其实并不能经过编译,这并不是编译器呈现了过错。这儿有个逻辑缝隙,咱们细心考虑无界通配符的意义。无界通配符代表其承受任何的实践类型,但这并不意味着任何的实践类型都能够作为其输入和输出。其语义上有微妙的但巨大的差异。其意义是不确认究竟是哪个实践类型。或许是String,或许是UUID,或许是任何或许的类型。假如这是个UUID列表,那么往里面加String等就会出事。

假如是String列表,往里面加UUID等也会出事。或许咱们不管其是什么类型的列表,往里面加Object,但是Object里有你的实践类型的特点和办法么。即便实践是Object列表,咱们也无法确认。那么,无界通配符就不能承受任何输入了么,看起来是这样。其实有个例外,null作为一个十分特别的值,表明不引证任何目标。咱们能够说String类型的值能够为null、UUID类型的值能够为null,甚至Object类型的值能够为null。不管是什么类型,都能够承受null作为其值,表明不引证任何目标。所以无界通配符的输入仅有可承受的是可为一切类型的null。

②可用输出类型

无界通配符的输出类型一直为Object,由于其意义为承受任何的实践类型作为泛参,而任何的实践类型都能够被协变为Object类型,所以其输出类型天然就为Object了。没有什么需求注意的当地。

3.2.2 上界通配符

上界通配符为”extends”,能够承受其指定类型或其子类作为泛参。其还有一种特别的办法,能够指定其不仅要是指定类型的子类,并且还要完成某些接口。这种用法十分少用,我在许多开源项目中根本没看到这种用法。由于这和本章内容无关,不影响输入和输出的类型,所以暂不描绘。

①可用输入类型

严格意义上相同不能承受任何的类型作为输入,出于严谨意图,咱们再从头剖析一遍,这次以Minecraft的源代码为例,考虑以下代码:

 List<? extends EntityLiving> list = new ArrayList<EntityPlayer>();
list.add(player);

你或许觉得这段代码又没问题了,EntityPlayer的确承继了EntityLiving。往一个EntityLiving的列表里加EntityPlayer有什么问题?猖狂!12450!好不闹/w\。这儿的问题在于假如实践上是EntityPig的列表呢。这么想你就应该懂了,和无界通配符差不多,其仅仅约束了列表必须是EntityLiving的子类而已,咱们并不知道实践是什么。所以在这儿咱们只能增加EntityLiving类型的目标。是不是觉得有什么不对?对了,我便是超威蓝猫!好不闹/w\,咱们能在EntityLiving上调用EntityPlayer的getGameProfile么,显着不能,何况咱们究竟能不能实例化EntityLiving也是个问题。这儿真的很简单混杂概念,必定要牢记,只能运用null作为上界通配符的输入值。

②可用输出类型

好了,这次总算能玩了,上界通配符的输出类型为其指定的类型,实践上假如通配符位于泛型类的声明中例如:

 public class Foo<T extends EntityLiving> {
  public T entity;
}

这个类中entity字段的实践类型不是一切类型的父类Object了,而是EntityLiving,这能够用查看字节码的办法证明。当然其类型是Object也不会有太大的差别,能够想到的问题是当咱们以某种办法往其内部传入了Object类型或其他不是EntityLiving类型或其子类的目标时,或许会呈现类型转化反常或许更严峻的留下随时代码会溃散的危险。而直接运用EntityLiving类型作为其实践类型就会在尝试这么做的一起抛出类型转化反常,然后避免这种问题。

3.2.3 下界通配符

下界通配符为”super”,能够承受其指定类型或其父类作为泛参。或许许多人都没有用过下界通配符,由于其真的很少用。其主要用途之一是在运用Java或第三方的API的泛型类时,对泛参类型不同,但泛参具有承继联系,且主要重视其输入的泛型目标进行概括。以Minecraft的源码为例,考虑以下代码:

private EntityMob ourKawaiiMob;
private EntityMob otherKawaiiMob;
public int compareMobEntity(Comparator<? super EntityMob> comparator) {
  return comparator.compare(ourKawaiiMob, otherKawaiiMob);
}

此办法能够承受一个比较器,用于比较两EntityMob。这儿的意义是,咱们期望承受一个EntityMob或其父类的比较器。例如Comparator只会把EntityMob当作一个Entity进行比较,这样咱们就能够对EntityMob的某一部分进行比较。咱们不能将一个彻底不是EntityMob的父类的比较器,例如Comparator作为参数传入。也不能将一个EntityMob的子类的比较器,例如Comparator作为参数传入。由于实践咱们比较的是EntityMob或其子类的目标,即便咱们传入的是其子类的比较器,咱们也不能保证不会发生用Comparator比较一个EntityEnderman的状况。又或许即便咱们运用Java的类型擦除这么做了,java的动态类型查看会强制抛出ClassCastException。所以在这种状况下应该运用下界通配符。

①可用输入类型

下界通配符的输入类型为其指定的类型或子类。由于其意义为承受其指定类型或其父类作为泛参。那么不管咱们供给的目标是什么类型,只要是其指定的类型或子类的目标,那么毫无例外必定是其指定的类型的目标。咱们不能供给其指定的类型的父类作为目标,考虑以下代码:

private EntityLiving our;
private EntityLiving other;
Comparator<? super EntityMob> comparator = new EntityMobComparator();
comparator.compare(our, other);

这段代码不能经过编译,咱们尝试用一个EntityMob的比较器来比较EntityLiving。不细心考虑或许认为这并没有什么问题,EntityMob的比较器彻底有才能来比较EntityLiving啊?可是实践状况是假如这段代码成功编译,并且没有动态类型查看的话EntityMob的比较器就或许会尝试其获取EntityLiving并没有的,属于EntityMob的特点,然后就会获取到非法的数据,或导致Java运转时溃散,这当然是不可的。好在咱们即便这么做了,Java也会强制抛出ClassCastException。

②可用输出类型

下界通配符的输出类型一直为Object,由于其意义为承受其指定类型或其父类作为泛参,咱们并不知道详细是哪一个父类。而任何的实践类型都能够被协变为Object类型,所以其输出类型天然就为Object了。

3.3 回顾泛型鸿沟和输入输出类型的差异

泛型鸿沟并不直接代表着能承受的输入输出的类型,其意义为能承受什么样的实践类型。而输入输出类型能是什么则是依据泛型鸿沟的意义得出的,其间的约束是由于咱们只能经过泛型鸿沟对实践类型进行猜测而发生的,期望大家能细心了解其间的意义。

3.4 编译前后比较

泛型体系是作为Java 5的一套增强类型安全及削减显式类型转化的体系呈现的。泛型也叫参数化类型,望文生义,经过给类型赋予必定的泛型参数,来达到提高代码复费用和削减复杂性的意图。

在Java中,泛型是作为语法糖呈现的。在虚拟机层面,并不存在泛型这种类型,也不会对泛型进行膨胀,生成出类似于List、List之类的类型。在虚拟机看来,List这个泛型类型仅仅一般的类型List而已,这种行为叫泛型擦除(Type Erasure)。

那么在Java中泛型是怎么怎么完成其意图的呢?Java的泛型充分运用了多态性。将无界(unbounded)的通配符(wildcard)了解为Object类型,由于Object类型是一切除标量(Scalar)以外,包含一般的数组和标量数组的类型的父类。将一切有上界(upper bound)的通配符了解为其上界类型例如将被了解为CharSequence类型。并在相应的当地主动生成checkcast字节码进行类型查看和转化,这样就既能够完成泛型,又不需求在字节码层面的进行改动来支撑泛型。这样的泛型叫做伪泛型。

编译前 :

publicclass Foo<T extendsCharSequence> {
  privateT value;
  publicvoid set(T value) {
  this.value = value;
  }
  publicT get() {
  returnthis.value;
  }
  publicstatic void main(String[] args) {
   Foo<String> foo = newFoo<String>();
   foo.set("foo");
  String value = foo.get();
  }
}

编译后:

publicclass Foo {
  privateCharSequence value;
  publicvoid set(CharSequence value) {
   this.value = value;
  }
  publicCharSequence get() {
 returnthis.value;
  }
  publicstatic void main(String[] args) {
   Foo foo = newFoo();
   foo.set("foo");
  String value = (String) foo.get();
  }
}