• 第九章.泛型


    泛型入门:

      Java集合有个缺点:把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object类型(其运行时类型没变)。

      Java集合之所以被设计成这样,是因为集合的设计者不知道我们会用集合来保存什么类型的对象,所以把集合设计成可以保存任何类型的对象,只要求有很好的通用性。但这样

       做带来了两个问题:

        1.集合对元素类型没有任何限制,这样引发一些问题。如:想创建一个只能保存Dog对象的集合,但程序也可以轻易的将Cat对象“丢”进去,所以可能引发异常

        2.由于把对象“丢进”集合时,集合丢失了对象的状态信息,集合只知道盛装它的是Object,因此取出集合元素后通常还需要进行强制类型转换。这种强制转换既增加了编

         程的复杂度,也可能引发ClassCastException异常。

      编译时不检查类型的异常:

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class ListErr{
     5     public static void main(String[] args){
     6         //创建一个只想保存字符串的List集合
     7         List strList = new ArrayList();
     8         strList.add("疯狂Java讲义");
     9         strList.add("疯狂Android讲义");
    10         //"不小心"把一个Integer对象"丢进"了集合
    11         strList.add(5);
    12         strList.forEach(str -> System.out.println(((String) str).length()));
    13     }
    14 }
    View Code

      使用泛型

        从Java5后,Java引入了”参数化类型“(parameterized type)”概念,允许程序在创建集合时指定集合元素的类型。Java的参数化类型被称为泛型(Generic)

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class GenericList{
     5     public static void main(String[] args){
     6         //创建一个只想保存字符串的List集合
     7         List<String> strList = new ArrayList<String>();
     8         strList.add("疯狂Java讲义");
     9         strList.add("疯狂Android讲义");
    10         //"不小心"把一个Integer对象"丢进"了集合
    11         strList.add(5);
    12         strList.forEach(str -> System.out.println(((String) str).length()));
    13     }
    14 }
    View Code

      Java7泛型的“菱形”语法:

        在Java7以前,若使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器后面也必须带泛型,这就显得有些多余:

        List<String> strList = new ArrayList<String>();

        Map<String, Integer> scores = new HashMap<String, Integer>();

      Java8之后,可以这样写:

        List<String> strList = new ArrayList<>();

        Map<String, Integer> scores = new HashMap<>();

        把两个尖括号并排放在一起非常像一个菱形,这种语法也被称为“菱形”语法。

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 import java.util.Map;
     4 import java.util.HashMap;
     5 
     6 
     7 public class DiamondTest{
     8     public static void main(String[] args){
     9         //Java 自动推断出ArrayList的<>里应该是String
    10         List<String> books = new ArrayList<>();
    11         books.add("疯狂Java讲义");
    12         books.add("疯狂Android讲义");
    13         //遍历books集合,集合元素就是String类型
    14         books.forEach(ele -> System.out.println(ele.length()));
    15         //Java自动推断出HashMap的<>里应该是String,List<String>
    16         Map<String, List<String>> schoolsInfo = new HashMap<>();
    17         //Java自动推断出ArrayList的<>里应该是String
    18         List<String> schools = new ArrayList<>();
    19         schools.add("斜月三星洞");
    20         schools.add("西天取经路");
    21         schoolsInfo.put("孙悟空", schools);
    22         //遍历Map时,Map的key是String类型,value是List<String>类型
    23         schoolsInfo.forEach((key, value) -> System.out.println(key + "-->" + value));
    24     }
    25 }
    View Code

    深入泛型:

      定义泛型接口、类:

        泛型就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。

        可以为任何类、接口增加泛型声明(并不是只有集合类才可以使用泛型声明,虽然集合类是泛型的重要使用场所)。

     1 //定义Apple类时使用了泛型声明
     2 public class Apple<T>{
     3     //使用T类型形参定义实例变量
     4     private T info;
     5     public Apple(){}
     6     //下面方法中使用T类型形参来定义构造器
     7     public Apple(T info){
     8         this.info = info;
     9     }
    10     public void setInfo(T info){
    11         this.info = info;
    12     }
    13     public T getInfo(){
    14         return this.info;
    15     }
    16     public static void main(String[] args){
    17         //由于传给T形参的是String,所以构造器参数只能是String
    18         Apple<String> a1 = new Apple<>("苹果");
    19         System.out.println(a1.getInfo());
    20         //由于传给T形参的是Double,所以构造器参数只能是Double或double
    21         Apple<Double> a2 = new Apple<>(5.67);
    22         System.out.println(a2.getInfo());
    23     }
    24 }
    View Code

         当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。如:为Apple<T>类定义构造器,其构造器名依然是Apple,而不

         是Apple<T>!调用该构造器是却可以使用Apple<T>的形式,应该为T形参传入实际的类型参数。

      从泛型类派生子类:

        创建带泛型声明的接口、父类后,可以为该接口创建实现类或派生子类。需要注意的是:当使用这些接口、父类是不能再包含类型形参<T>:

          //定义类A继承Apple类,Apple类不能跟类型形参

          public class A extends Apple<T>{ }//这是错误的

        定义方法时,方法中的形参数据,被称为形参或数据形参。在使用方法时,必须为这些形参传入实际的数据。

        类型形参也一样,在定义类、接口、方法时可以声明类型形参如:<T>,但使用这些类、接口、方法时应该为该类型形参传入实际的类型,如:<String>。

          //使用Apple类时,为T形参传入String类型

          public class A extends Apple<String>{ }

        也可以不为类型形参传入实际的类型:

          //使用Apple类时,没有为T形参传入实际的类型参数

          public class A extends Apple{ }

        若从Apple<String>类派生子类,则在Apple类中所有使用T类型形参的地方都将被替换成String类型,那么它的子类将会继承到String getInfo()和void setInfo(String info)两

         个方法,若子类需要重写父类的方法,就必须注意这一点:

     1 public class A1 extends Apple<String>{
     2     //正确重写了父类的方法,返回值
     3     //与父类Apple<String>的返回值完全相同
     4     public String getInfo(){
     5         return "子类" + super.getInfo();
     6     }
     7     /*
     8     //下面方法是错误的,重写父类方法时返回值类型不一致
     9     public Object getInfo(){
    10         return "子类";
    11     }
    12     */
    13 }
    View Code

        若使用Apple类没有传入实际的类型参数,java编译器可能发出警告:使用了未经检查或不安全的操作——这就是泛型检查的警告。若希望看到该警告的更详细信息,可

         以为javac命令增加-Xlint:unchecked选项来实现。此时系统会把Apple<T>类里的T类型参数当成Object类型处理:

    1 public class A2 extends Apple{
    2     //重写父类的方法
    3     public String getInfo(){
    4         //super.getInfo()方法返回值是Object类型
    5         //所以加toString()才返回String类型
    6         return super.getInfo().toString();
    7     }
    8 }
    View Code

      并不存在泛型类:

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class GeneicCompare{
     5     public static void main(String[] args){
     6         //分别创建List<String>对象和List<Integer>对象
     7         List<Integer> list1 = new ArrayList<>();
     8         List<String> list2 = new ArrayList<>();
     9         //调用getClass()方法来比较list1和list2的类是否相等
    10         System.out.println(list1.getClass() == list2.getClass());
    11     }
    12 }
    View Code

      List<String> 和 List<Integer>的类是相同的,不管泛型的实际类型参数是什么,它们在运行时总有同样的类。

      不管为泛型类型参数传入哪一种类型实参,对Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间,因此不能在静态方法、静态初始化块或者静态变量

       的声明和初始化中使用类型参数:

    1 public class R<T>{
    2     //下面代码是错误的,不能在静态变量中使用类型形参
    3     static T info;
    4     T age;
    5     public void foo(T msg){    }
    6     //下面代码是错误的,不能在静态方法声明中使用类型形参
    7     public static void bar(T msg){ }
    8 }
    View Code

      由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类:

     1 import java.util.Collection;
     2 import java.util.ArrayList;
     3 
     4 public class GeneicErrorInstanceof{
     5     public static void main(String[] args){
     6         Collection<String> cs = new ArrayList<>();
     7         if(cs instanceof ArrayList){ }
     8         if(cs instanceof ArrayList<String>){ }
     9     }
    10 }
    View Code

    类型通配符:

      当使用一个泛型类时(包括声明变量和创建对象两种情况),都应该为这个泛型类传入一个类型实参。若没有传入类型实际参数,编译器就会提出泛型警告。假设需要定义一

       个方法,该方法里有一个集合形参,集合形参是不确定的 ,怎样定义?

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class GeneicTest{
     5     public void test(List c){
     6         for(int i = 0; i < c.size(); i++){
     7             System.out.println(c.get(i));
     8         }
     9     }
    10     
    11     public static void main(String[] args){
    12         List<String> list = new ArrayList<>();
    13         list.add("疯狂Java讲义");
    14         list.add("疯狂Android讲义");
    15         GeneicTest gt = new GeneicTest();
    16         gt.test(list);
    17     }
    18 }
    View Code

      更改上面代码,让List带上类型参数<Object>:

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class GeneicTest{
     5     public void test(List<Object> c){
     6         for(int i = 0; i < c.size(); i++){
     7             System.out.println(c.get(i));
     8         }
     9     }
    10     
    11     public static void main(String[] args){
    12         List<String> list = new ArrayList<>();
    13         list.add("疯狂Java讲义");
    14         list.add("疯狂Android讲义");
    15         GeneicTest gt = new GeneicTest();
    16         gt.test(list);
    17     }
    18 }
    View Code

      上面的编译错误,表明List<String>对象不能被当成List<Object>对象使用。

      若Foo是Bar的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<Foo>并不是G<Bar>的子类型!这一点非常值得注意,因为它与大部分人的习惯认为是不

       同的。

     1 public class ArrayErr{
     2     public static void main(String[] args){
     3         //定义一个Integer数组
     4         Integer[] ia = new Integer[5];
     5         //可以把一个Integer[]数组赋给Number[]变量
     6         Number[] na = ia;
     7         //下面代码编译正常,但运行时会引发ArrayStoreException异常
     8         //因为0.5并不是Integer
     9         na[0] = 1;
    10         na[1] = 0.5;
    11     }
    12 }
    View Code

      数组允许将子类引用变量赋值给父类引用变量,但是父类引用变量存储的对象必须是子类对象,否则报错。

      但是,Java在泛型设计时进行了改进,它不在允许吧List<Integer>引用变量赋值给List<Number>变量,这在根源上杜绝了类型不匹配的错误。

      数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[] 依然是Bar[] 的子类型;但G<Foo>不是G<Bar>的子类型。

      使用类型通配符:

        为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List)

         这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。可以将上面的方法改为如下:

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class GeneicTest{
     5     public void test(List<?> c){
     6         for(int i = 0; i < c.size(); i++){
     7             System.out.println(c.get(i));
     8         }
     9     }
    10     
    11     public static void main(String[] args){
    12         List<String> list = new ArrayList<>();
    13         list.add("疯狂Java讲义");
    14         list.add("疯狂Android讲义");
    15         GeneicTest gt = new GeneicTest();
    16         gt.test(list);
    17     }
    18 }
    View Code

      但是这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中。如下面代码会引起错误:

    1 import java.util.List;
    2 import java.util.ArrayList;
    3 
    4 public class GeneicTest1{
    5     public static void main(String[] args){
    6         List<?> list = new ArrayList<String>();
    7         list.add("疯狂Java讲义");
    8     }
    9 }
    View Code

      设定类型通配符的上限:

        当直接使用List<?>这种形式,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,程序不希望这个List<?>是任何泛型List父类,只希望它代表某一类

         泛型List的父类:

    1 //定义一个抽象类Shape
    2 public abstract class Shape{
    3     public abstract void draw(Canvas c);
    4 }
    View Code
    1 //定义Shape的子类Circle
    2 public class Circle extends Shape{
    3     //实现画图方法,以打印字符串来模拟画图方法实现
    4     public void draw(Canvas c){
    5         System.out.println("在画布" + c + "上画个圆");
    6     }
    7 }
    View Code
    1 //定义Shape的子类Rectangle
    2 public class Rectangle extends Shape{
    3     public void draw(Canvas c){
    4         //实现画图方法,以打印字符串来模拟画图方法实现
    5         System.out.println("把一个矩形画在画布" + c + "上");
    6     }
    7 }
    View Code
    View Code

      上面的错误关键在于List<Circle>并不是List<Shape>的子类,所以不能把List<Circle>对象当成List<Shape>使用。为了表示List<Circle>的父类,可以考虑使用List<?>:

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class Canvas{
     5     //同时在画布上绘制多个形状
     6     public void drawAll(List<?> shapes){
     7         for(Object obj : shapes){
     8             Shape s = (Shape) obj;
     9             s.draw(this);
    10         }
    11     }
    12     
    13     public static void main(String[] args){
    14         //下面的代码将会引起错误
    15         //drawAll方法的形参类型是List<Shape>而不是List<Circle>
    16         List<Circle> circleList = new ArrayList<>();
    17         Canvas c = new Canvas();
    18         //不能把List<Circle>当成List<Shape>使用
    19         c.drawAll(circleList);
    20     }
    21 }
    View Code

        上面程序使用了通配符来表示所有的类型。问题是上面的方法实现体显得非常臃肿且繁琐:使用了泛型还需要强制类型转换。

      实际上需要一种泛型表示方法,它可以表示所有Shape泛型List的父类,即List<?>可以匹配所有Shape和Shape的子类的List集合,而不用使用强制类型转换。

      Java为泛型提供了被限制的泛型通配符。被限制的通配符表示如下:

        //它表示所有Shape泛型List的父类

        List<? extends Shape>

        有了这种被限制的泛型通配符,把上面的Canvas程序改成如下:

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class Canvas{
     5     //同时在画布上绘制多个形状
     6     public void drawAll(List<? extends Shape> shapes){
     7         for(Shape s : shapes){
     8             s.draw(this);
     9         }
    10     }
    11     
    12     public static void main(String[] args){
    13         //下面的代码将会引起错误
    14         //drawAll方法的形参类型是List<Shape>而不是List<Circle>
    15         List<Circle> circleList = new ArrayList<>();
    16         Canvas c = new Canvas();
    17         //不能把List<Circle>当成List<Shape>使用
    18         c.drawAll(circleList);
    19     }
    20 }
    View Code

        List<? extends Shape>:意思是List<>尖括号中只要是Shape或Shape的子类都可以。

      但是不能对List<? extends Shape> shapes直接进行操作:

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 
     4 public class Canvas{
     5     //同时在画布上绘制多个形状
     6     public void drawAll(List<? extends Shape> shapes){
     7         for(Shape s : shapes){
     8             s.draw(this);
     9         }
    10     }
    11     
    12     public void addRectangle(List<? extends Shape> shapes){
    13         //下面代码引起编译错误
    14         shapes.add(0, new Rectangle());
    15     }
    16     
    17     public static void main(String[] args){
    18         //下面的代码将会引起错误
    19         //drawAll方法的形参类型是List<Shape>而不是List<Circle>
    20         List<Circle> circleList = new ArrayList<>();
    21         Canvas c = new Canvas();
    22         //不能把List<Circle>当成List<Shape>使用
    23         c.drawAll(circleList);
    24     }
    25 }
    View Code

      设定类型形参的上限:

        Java泛型不仅允许在使用通配符形参时设定上限,而且还可以在定义类型形参时设定上限,用于表示给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类

     1 //定义Apple类时使用了泛型声明
     2 public class Apple<T extends Number>{
     3     //使用T类型形参定义实例变量
     4     private T info;
     5     public static void main(String[] args){
     6         Apple<Integer> ai = new Apple<>();
     7         Apple<Double> ad = new Apple<>();
     8         //下面代码将引发编译异常,下面代码试图把String类型传给T形参
     9         //但String不是Number的子类型,所以引起编译错误
    10         Apple<String> as = new Apple<>();
    11     }
    12 }
    View Code

        在一种极端情况下,程序需要为类型形参设定多个上限(至多有一个父类上限,可以有多个接口上限),表明该类型形参必须是其父类的子类(父类本身也可以),并且

         实现了多个上限接口:

    1 //表明T类型必须是Number类或其子类,并必须实现java.io.Serializable接口
    2 public class Apple<T extends Number & java.io.Serializable>{
    3     ...
    4 }
    View Code

        与类同时继承父类、实现接口类似,为类型形参指定多个上限时,所有接口上限必须位于类上限之后。即若需要为类型形参指定类上限,类上限必须位于第一位。

    泛型方法:

      Java5提供了对泛型方法的支持。

      定义泛型方法格式:

    1 //泛型方法,在声明方法时定义一个或多个类型形参,格式如下
    2 修饰符 <T, S> 返回值类型 方法名(形参列表){
    3     //方法体...
    4 }
    View Code

      泛型方法举例:

    1 static <T> void fromArrayToCollection(T[] a, Collection<T> c){
    2     for(T o : a){
    3         c.add(o);
    4     }
    5 }
    View Code

      泛型方法完整程序用法:

     1 import java.util.Collection;
     2 import java.util.ArrayList;
     3 
     4 public class GenericMethodTest{
     5     //声明一个泛型方法,该泛型方法中带一个T类型形参
     6     static <T> void fromArrayToCollection(T[] a, Collection<T> c){
     7         for (T o : a){
     8             c.add(o);
     9         }
    10     }
    11     
    12     public static void main(String[] args){
    13         Object[] oa = new Object[100];
    14         Collection<Object> co = new ArrayList<>();
    15         //下面代码中T代表Object类型
    16         fromArrayToCollection(oa, co);
    17         String[] sa = new String[100];
    18         Collection<String> cs = new ArrayList<>();
    19         //下面代码中T代表String类型
    20         fromArrayToCollection(sa, cs);
    21         //下面代码中T代表Object类型
    22         fromArrayToCollection(sa, co);
    23         Integer[] ia = new Integer[100];
    24         Float[] fa = new Float[100];
    25         Number[] na = new Number[100];
    26         Collection<Number> cn = new ArrayList<>();
    27         //下面代码中T代表Number类型
    28         fromArrayToCollection(ia, cn);
    29         //下面代码中T代表Number类型
    30         fromArrayToCollection(fa, cn);
    31         //下面代码中T代表Number类型
    32         fromArrayToCollection(na, cn);
    33         //下面代码中T代表Object类型
    34         fromArrayToCollection(na, co);
    35         //下面代码中T代表String类型,但na是一个Number数组
    36         //因为Number既不是String类型
    37         //也不是它的子类,所以出现编译错误
    38         fromArrayToCollection(na, cs);
    39     }
    40 }
    View Code

      数组T[]可以是其子类数组,Collection<T>只能是同类T。

      为了让编译器可以准确地判断出泛型方法中类型形参的类型,不要制造迷惑,一旦系统迷惑,就是你错了!如下:

     1 import java.util.Collection;
     2 import java.util.List;
     3 import java.util.ArrayList;
     4 
     5 public class ErrorTest{
     6     //声明一个泛型方法,该泛型方法中带一个T类型形参
     7     static <T> void test(Collection<T> from, Collection<T> to){
     8         for(T ele : from){
     9             to.add(ele);
    10         }
    11     }
    12     
    13     public static void main(String[] args){
    14         List<Object> ao = new ArrayList<>();
    15         List<String> as = new ArrayList<>();
    16         //下面代码将产生编译错误
    17         test(as, ao);
    18     }
    19 }
    View Code

      上面程序调用test()方法传入两个实际参数,as是List<String>,ao是List<Object>,与test(Collection<T> a, Collection<T> c)相比,编译器无法正确识别T所代表的实际类型。

      虽然String是Object的子类,但是集合中没有使用类型形参的上限,所以T只能代表String或Object类中的一个,不能代表两个。

      改写为如下程序:

     1 import java.util.Collection;
     2 import java.util.List;
     3 import java.util.ArrayList;
     4 
     5 public class ErrorTest{
     6     //声明一个泛型方法,该泛型方法中带一个T类型形参
     7     static <T> void test(Collection<? extends T> from, Collection<T> to){
     8         for(T ele : from){
     9             to.add(ele);
    10         }
    11     }
    12     
    13     public static void main(String[] args){
    14         List<Object> ao = new ArrayList<>();
    15         List<String> as = new ArrayList<>();
    16         //下面代码将产生编译错误
    17         test(as, ao);
    18     }
    19 }
    View Code

        程序中test(Collection<? extends T> from, Collection<T> to):只要前一个Collection集合里的元素类型是后一个Collection集合里元素类型的子类即可。

      那么什么时候使用泛型方法?什么时候使用类型通配符?

      泛型方法和类型通配符的区别:

        大多数时候都可以使用泛型方法来代替类型通配符。如,Java的Collection接口中的两个方法:

    1 public interface Collection<E>{
    2     boolean containsAll(Collection<?> c);
    3     boolean addAll(Collection<? extends E> c);
    4 }
    View Code

        上面集合中两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式:

    1 public interface Collection<E>{
    2     <T> boolean containsAll(Collection<T> c);
    3     <T extends E>boolean addAll(Collection<T> c);
    4 }
    View Code

        若方法中一个形参(a)的类型或返回值类型依赖于另一个形参(b)的类型,则形参(b)的类型声明不应该是通配符,如:

          public static <T> void copy(List<T> dest, List<? extends T> src) { ... }//这里dest形参的类型不能是通配符?,否则两个形参类型模糊。

        上面的代码可以改为如下:

          public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }//正是因为这里的S类型形参只使用了一次,所以没有存在的必要,直接用类型通配符代替即可

      Java7的“菱形”语法与泛型构造器:

        泛型构造器:

     1 class Foo{
     2     public <T> Foo(T t){
     3         System.out.println(t);
     4     }
     5 }
     6 
     7 public class GenericConstructor{
     8     public static void main(String[] args){
     9         //泛型构造器中的T参数为String
    10         new Foo("疯狂Java讲义");
    11         //泛型构造器中的T参数为Integer
    12         new Foo(200);
    13         //显示指定泛型构造器中的T参数为String
    14         //传给Foo构造器的实参也是String对象,完全正确
    15         new <String> Foo("疯狂Android讲义");
    16         //显示指定泛型构造器的T参数为String
    17         //但传给Foo构造器的实参是Double独享,下面代码是错误的
    18         new <String> Foo(12.3);
    19     }
    20 }
    View Code

        Java7新增的“菱形”语法,允许调用构造器时在构造器后使用<>来代表泛型信息:

     1 class MyClass<E>{
     2     public <T> MyClass(T t){
     3         System.out.println("t 参数的值为:" + t);
     4     }
     5 }
     6 
     7 public class GenericDiamondTest{
     8     public static void main(String[] args){
     9         //MyClass类声明中的E形参是String类型
    10         //泛型构造器中声明的T参数是Integer类型
    11         MyClass<String> mc1 = new MyClass<>(5);
    12         //显示指定泛型构造器中声明的T形参是Integer类型
    13         MyClass<String> mc2 = new <Integer> MyClass<String>(5);
    14         //MyClass类声明中的E形参是String类型
    15         //若显示指定泛型构造器中声明的T形参是Integer类型
    16         //此时就不能使用“菱形”语法,下面代码是错误的
    17         MyClass<String> mc3 = new <Integer> MyClass<>(5);
    18     }
    19 }
    View Code

       

     i. 和使用普通泛型方法一样没区别,一种是显式指定泛型参数,另一种是隐式推断,如果是显式指定则以显式指定的类型参数为准,如果传入的参数的类型和指定的类型实参不符,将会编译报错;

    !典型示例:A a = new <String>A("lala"); // 菱形实参还是写在方法名之前

              ii. 这里唯一需要特殊注明的就是,如果构造器是泛型构造器,同时该类也是一个泛型类的情况下应该如何使用泛型构造器:

                  a. 因为泛型构造器可以显式指定自己的类型参数(需要用到菱形,放在构造器之前),而泛型类自己的类型实参也需要指定(菱形放在构造器之后),这就同时出现了两个菱形了,这就会有一些小问题,具体用法再这里总结一下;

                  b. 这里使用的类是这样定义的:public A<T> { public <E> A(E e) { ... } }

                  b. 全指定,例如:A<String> a = new <Integer>A<String>(15); // 所有类型实参全部显式指定,Integer代表E,String代表T

                  c. 全隐藏,例如:A<String> a = new A<>(15);   // 可以隐式推断出Integer是E,String是T

                  d. 半隐藏,例如:A<String> a = new A<String>(15);  // 还是可以推断出E是Integer,而String是T

                  e. 上面的叫做半隐藏T,但是不能半隐藏E,例如:A<String> a = new <Integer>A<>(15);  // 虽然也可以正确推断,但是这种语法不允许!!会直接编译报错!

    !!因此这里麻烦的是需要稍微记忆一下,不能半隐藏E

    !!平时使用的时候就使用其中一种,推荐是全隐藏,越简洁越好,就是用一种就不会增加记忆负担;

    上面一段泛型构造器篇幅,参考网址:http://blog.csdn.net/lirx_tech/article/details/51603531

      设定通配符下限:

        现在要实现一个工具方法:将src集合里的元素复制到dest集合里的功能,分析:因为dest集合可以保存src集合里的所有元素,所以dest集合元素的类型是src集合元素类

         型的父类,代码如下:

    1 public static <T> void copy(Collection<T> dest, Collection<? extends T> src){
    2     for(T ele : src){
    3         dest.add(ele);
    4     }
    5 }
    View Code

        现在又要让该方法具有返回值,返回最后一个被复制的元素,代码如下:

     1 public static <T> T copy(Collection<T> dest, Collection<? extends T> src){
     2     T last = null;
     3     
     4     for(T ele : src){
     5         last = ele;
     6         dest.add(ele);
     7     }
     8     
     9     return last;
    10 }
    View Code

        表面上看起来方法实现了返回值的功能,但是若用下面的程序验证,则会报错:

    1 List<Number> ln = new ArrayList<>();
    2 List<Integer> li = new ArrayList<>();
    3 //下面代码引起编译错误
    4 Integer last = copy(ln, li);
    View Code

        ln类型是List<Number>,li类型是List<Integer>,与copy()方法比较后,确认T类型形参是Number,返回值类型也是Number类型,所以Integer last不能赋值为Number类型

        解决方法:可以这样理解两个集合参数之间的依赖关系:不管src集合元素的类型是什么,只要dest集合元素的类型与前者相同或是前者的父类即可。为了表达这种约束关系

         Java允许设定通配符的下限:<? super Type>,这个通配符表示它必须是Type本身,或是Type的父类,下面程序采用设定通配符下限方式改写了前面程序:

     1 import java.util.List;
     2 import java.util.ArrayList;
     3 import java.util.Collection;
     4 
     5 public class MyUtils{
     6     //下面dest集合元素的类型必须与src集合元素的类型相同,或是其父类
     7     public static <T> T copy(Collection<? super T> dest, Collection<T> src){
     8         T last = null;
     9         for(T ele : src){
    10             last = ele;
    11             dest.add(ele);
    12         }
    13         return last;
    14     }
    15     
    16     public static void main(String[] args){
    17         List<Number> ln = new ArrayList<>();
    18         List<Integer> li = new ArrayList<>();
    19         li.add(5);
    20         //此处可准确地知道最后一个被复制的元素时Integer类型
    21         //与src集合元素的类型相同
    22         Integer last = copy(ln, li);
    23         System.out.println(ln);
    24     }
    25 }
    View Code

        这样可以保证程序可以推断出最后一个被复制的元素类型是Integer,而不是Number类型。

      泛型方法与方法重载:

        因为泛型既允许设定通配符的上限,也允许设定通配符的下限,所以允许在一个类里包含如下两个方法定义:

    public class MyUtils{
        public static <T> void copy(Collection<T> dest, Collection<? extends T> src){ }
        public static <T> T copy(Collection<? super T> dest, Collection<T> src) { }
    }

        上面MyUtils类中包含两个copy()方法,但调用这个方法就会引起编译错误:

    List<Number> ln = new ArrayList<>();
    List<Integer> li = new ArrayList<>();
    copy(ln, li);

        编译器无法确定这行代码想要调用哪个copy()方法。

    泛型与数组:

      参考网址:http://blog.csdn.net/orzlzro/article/details/7017435

  • 相关阅读:
    GRE协议基础配置
    OSPFv3基础配置
    初级作业2
    缺省静态路由发布进OSPF
    不同进程OSPF路由相互通信
    OSI与TCP/IP
    华为AAA认证详解
    OSPF与静态路由
    [转]那些著名或非著名的iOS面试题(下)
    [转]那些著名或非著名的iOS面试题(中)
  • 原文地址:https://www.cnblogs.com/lanshanxiao/p/7348517.html
Copyright © 2020-2023  润新知