Java 泛型

ejnx8053 7年前
   <h2>泛型入门</h2>    <h3>编译时不检查类型的异常</h3>    <pre>  <code class="language-java">public class ListErr  {      public static void main(String[] args)      {          // 创建一个只想保存字符串的List集合          List strList = new ArrayList();          strList.add("布达佩斯");          strList.add("布拉格");          // "不小心"把一个Integer对象"丢进"了集合          strList.add(34);     // ①          strList.forEach(str -> System.out.println(((String)str).length())); // ②      }  }</code></pre>    <p>上述程序创建了一个List集合,且该List集合保存字符串对象——但程序不能进行任何限制,如果程序在①处“不小心”把一个Integer对象"丢进"了List集合,这将导致程序在②处引发ClassCastException异常,因为程序试图把一个Integer对象转换为String类型</p>    <h3>使用泛型</h3>    <p>参数化类型,允许程序在创建集合时指定集合元素的类型。Java的参数化类型被称为泛型(Generic)</p>    <pre>  <code class="language-java">class GenericList  {      public static void main(String[] args)      {          // 创建一个只想保存字符串的List集合          List<String> strList = new ArrayList<>();     // ①          strList.add("布达佩斯");          strList.add("布拉格");          // 下面代码将引起编译错误          strList.add(34);     // ②          strList.forEach(str -> System.out.println(((String)str).length())); // ③      }  }</code></pre>    <p>strList集合只能保存字符串对象,不能保存其他类型的对象。创建特殊集合的方法是:在集合接口、类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象。</p>    <p>①类型声明,在创建这个ArrayList对象时也指定了一个类型参数;②引发编译异常;③不需要进行强制类型转换</p>    <p>泛型使程序更加简洁,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换</p>    <h3>Java7泛型的“菱形”语法</h3>    <p>Java允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息</p>    <pre>  <code class="language-java">public class DiamondTest  {      public static void main(String[] args)      {          // Java自动推断出ArrayList的<>里应该是String          List<String> countries = new ArrayList<>();          countries.add("法兰西第五共和国");          countries.add("西班牙王国");          // 遍历countries集合,集合元素就是String类型          countries.forEach(ele -> System.out.println(ele.length()));          // Java自动推断出HashMap的<>里应该是String , List<String>          Map<String , List<String>> citiesInfo = new HashMap<>();          // Java自动推断出ArrayList的<>里应该是String          List<String> cities = new ArrayList<>();          cities.add("巴黎");          cities.add("巴塞罗那");          citiesInfo.put("Bienvenue" , cities);          // 遍历Map时,Map的key是String类型,value是List<String>类型          citiesInfo.forEach((key , value) -> System.out.println(key + "-->" + value));      }  }</code></pre>    <h2>深入泛型</h2>    <p>所谓泛型:就是允许在定义类、接口、方法时指定类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)</p>    <h3>定义泛型接口、类</h3>    <p>List接口、Iterator接口、Map的代码片段</p>    <pre>  <code class="language-java">// 定义接口时指定了一个类型形参,该形参名为E  public interface List<E>         {      // 在该接口里,E可作为类型使用      // 下面方法可以使用E作为参数类型      void add(E x);      Iterator<E> iterator();    // ①         }    // 定义接口时指定了一个类型形参,该形参为E  public interface Iterator<E>  {      // 在该接口里E完全可以作为类型使用      E next();      boolean hasNext();  }    // 定义该接口时指定了两个类型形参,其形参名为K、V  public interface Map<K, V>  {      // 在该接口里K、V完全可以作为类型使用      Set<K> keySet();           // ②      V put(K key,V value);  }</code></pre>    <p>上面代码说明泛型实质:允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用</p>    <p>包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数个逻辑上的子类,但这种子类在物理上并不存在</p>    <p>可以为任何类、接口增加泛型声明(并不是只有集合类才可以使用泛型声明,虽然集合类是泛型的重要使用场所)</p>    <pre>  <code class="language-java">// 定义Onmyoji类时使用了泛型声明  public class Onmyoji<T>  {      // 使用T类型形参定义实例变量      private T info;      public Onmyoji(){}      // 下面方法使用T类型形参来定义构造器      public Onmyoji(T info)      {          this.info = info;      }      public T getInfo()       {          return info;      }      public void setInfo(T info)       {          this.info = info;      }      public static void main(String[] args)       {          //由于传给T形参的是String,所以构造器参数只能是String          Onmyoji<String> a1 = new Onmyoji<>("安倍晴明");          System.out.println(a1.getInfo());          // 由于传给T形参的是Double,所以构造器参数只能是Double或double          Onmyoji<Double> a2 = new Onmyoji<>(520.1314);          System.out.println(a2.getInfo());      }  }</code></pre>    <p>当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明</p>    <h3>从泛型类派生子类</h3>    <p>当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从父类派生子类。当使用这些接口、父类时不能再包含类型形参</p>    <pre>  <code class="language-java">// 定义类Shikigami继承Onmyoji类,Onmyoji类不能跟类型形参  public class Shikigami extends Onmyoji<T>{ }        // 错误</code></pre>    <p>方法中的形参(或数据形参)代表变量、常量、表达式等数据。定义方法时,可以声明数据形参;调用方法(使用方法)时,必须为这些数据形参传入实际的数据;与此类似的是,定义类、接口、方法时可以声明类型形参,使用类、接口、方法时应为类型形参传入实际的类型</p>    <pre>  <code class="language-java">// 使用Onmyoji类时为T形参传入String类型  public class Shikigami extends Onmyoji<String>{ }        // 正确</code></pre>    <p>调用方法时必须为所有的数据参数传入参数值,而使用类、接口时也可以不为类型形参传入实际的类型参数</p>    <pre>  <code class="language-java">// 使用Onmyoji类时,没有为T形参传入实际的类型参数  public class Shikigami extends Onmyoji{ }        // 正确</code></pre>    <p>子类需要重写父类的Getters和Setters方法</p>    <pre>  <code class="language-java">private String info;  public String getInfo()   {      return "子类"+ super.getInfo();  }  public void setInfo(String info)  {      this.info = info;  }</code></pre>    <h3>并不存在泛型类</h3>    <p>ArrayList<String>类,是一种特殊的ArrayList类。该ArrayList<String>对象只能添加String对象作为集合元素。但实际上,系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<String>当成新类来处理。因为不管泛型的时间类型参数是什么,它们在运行时总有同样的类(class)</p>    <pre>  <code class="language-java">// 分别创建List<String>对象和List<Integer>对象  List<String> l1 = new ArrayList<>();  List<Integer> l2 = new ArrayList<>();  // 调用getClass()方法来比较l1和l2的类是否相等  System.out.println(l1.getClass() == l2.getClass());        // 输出true</code></pre>    <p>不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参</p>    <p>由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。</p>    <pre>  <code class="language-java">if(cs instanceof List<String>)  {      ...  }</code></pre>    <h2>类型通配符</h2>    <p>如果Foo是Bar的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<Foo>并不是G<Bar>的子类型</p>    <p>数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型;但G<Foo>不是G<Bar>的子类型</p>    <p>Java泛型的设计原则是,只要代码在编译时没有出现警告,就不会遇到运行时ClassCastException异常</p>    <h3>使用类型通配符</h3>    <p>为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是未知类型元素的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型</p>    <pre>  <code class="language-java">public void test(List<?> c)  {      for(int i = 0; i < c.size(); i++)      {          System.out.println(c.get(i));      }  }</code></pre>    <p>现在使用任何类型的List来调用它,程序依然可以访问集合c中的元素,其类型是Object,这永远是安全的,因为不管List的真实类型是什么,它包含的都是 Object</p>    <p>这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中</p>    <pre>  <code class="language-java">List<?> c = new ArrayList<String>();      // 下面程序引起编译错误  c.add(new Object());</code></pre>    <p>因为程序无法确认c集合里元素的类型,所以不能向其中添加对象。根据前面的List<E>接口定义的代码可以发现:add ()方法由类型参数E作为集合的元素类型,所以传给add的参数必须是E类的对象或者其子类的对象。但因为在该例中不知道E是什么类型,所以程序无法将任何对象“丢进”该集合。唯一的例外是 null,它是所有引用类型的实例</p>    <p>另一方面,程序可以调用get()方法来返回List<?>集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是,它总是一个Object。因此,把get()的返回值赋值给一个Object类型的变量,或者放在任何希望是Object类型的地方都可以</p>    <h3>设定类型通配符的上限</h3>    <p>List<Circle>并不是List<Shape>的子类型,所以不能把List<Circle>对象当成List<Shape>使用。为了表示List<Circle>的父类,使用List<? extends Shape></p>    <p>List<? extends Shape>是受限通配符的例子,此处的问号(?)代表一个未知的类型,此处的未知类型一定是Shape的子类也可以是Shape,因此可以把shape称为这个通配符的上限(upper bound)</p>    <h3>设定类型形参的上限</h3>    <p>Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是该上限类型,要门是该上限类型的子类</p>    <p>在一种更极端的情况下,程序需要为类型形参设定多个上限(至少有一个父类上限,可以有多个接口上限),表明该类型形参必须是其父类的子类(其父类本事也行),并且实现多个上限接口</p>    <pre>  <code class="language-java">//表明T类型必须是Number类或其子类,并必须实现java.io.Serializablepublic   class Apple<T extends Number & java.io.Serilizable>  {      ...  }</code></pre>    <p>与类同时继承父类、实现接口类似的是:为类型形参指定多个上限,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位</p>    <h2>泛型方法</h2>    <h3>定义泛型方法</h3>    <p>Java不允许把对象放进一个未知类型的集合中</p>    <p>所谓泛型方法,就是在声明方法时定义一个或多个类型形参</p>    <pre>  <code class="language-java">修饰符 <T, S> 返回值类型 方法名(形参列表)  {      // 方法体...  }</code></pre>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000007983793</p>    <p> </p>