第九章.泛型

泛型入门:

  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

posted @ 2017-08-12 16:39  lanshanxiao  阅读(284)  评论(0编辑  收藏  举报