建议先阅读我前面分享过的《再谈Java泛型---上》。




类型通配符




先来看一段代码:


private void test(List list) {	
   for (int i = 0; i < list.size(); i++) {	
       System.out.println(list.get(i));	
   }	
}


这段代码没毛病,只是编译的时候会出现泛型警告,于是想到一个方案:


private void test(List<Object> list) {	
   for (int i = 0; i < list.size(); i++) {	
         System.out.println(list.get(i));	
      }	
}


表面上看起来没什么问题,这个方法声明确实没有任何问题,但问题是调用该方法传入的实际参数时可能不是我们所期望的,例如:


List<String> strings=new ArrayList<>();	
 new LessJDK5Demo().test(strings);


编译会报错:

再谈Java泛型---下_泛型

这表明List<String>和List<Object>没什么关系,并不是很多人想象的他们之间存在父子关系。


注意



如果Man是Person的一个子类型,而G是具有泛型声明的类或接口,那么G<Man>并不是G<Person>的子类型!!!


1


使用通配符


将上面的例子改成通配符:


private void test(List<?> list) {	
   for (int i = 0; i < list.size(); i++) {	
         System.out.println(list.get(i));	
    }	
}


这个问号(?)被称为通配符,它的元素类型可以匹配任何类型。但是得小心这个匹配任何类型,以下代码就编译通不过:


List<?> strings=new ArrayList<String>();	
 strings.add(new Object())


因为程序无法确定strings集合中元素的类型,所以不能添加对象。


2


设置通配符上限


public class Person {	
    private int info;	
    public Person(int info) {	
        this.info = info;	
    }	
}


public class Sub extends Person {	
    public Sub(int info) {	
        super(info);	
    }	
}


private static void test(List<Person> peoples){	
     peoples.forEach(person -> {	
        System.out.println(person);	
      });	
 }


因为List<Sub>不是List<Person>的子类,所以以下代码是编译通不过的:


List<Sub> subs=new ArrayList<>();	
test(subs);//这一行编译通不过


为了能让上面编译通过,有一种办法,那就是通配符的上限,改造test方法入参:


private static void test(List<? extends Person> peoples){	
        peoples.forEach(person -> {	
            System.out.println(person);	
        });	
}


由此可一推断出,对于类似List<?>这样的通配符而言,期上限就是Object。


对于指定通配符上限的泛型,相当于通配符上限是Object。


再谈Java泛型---下_List_02


3


通配符的下限


通配符的下限与通配符的上限是相反的,格式如下:


private static void test(List<? super Sub> peoples) {	
        peoples.forEach(person -> {	
            System.out.println(person);	
        });	
}


采用<? super 下限类型>


List<Person> subs = new ArrayList<>();	
   test(subs);//编译通过	
   List<Object> subs = new ArrayList<>();	
    test(subs);//编译通过


可以顶一个Sub的子类的证明:


public class SubSub extends Sub {	
    public SubSub(int info) {	
        super(info);	
    }	
}


再谈Java泛型---下_List_03

可以看得出编译不通过,由此证明上面的说法是正确的。

关于通配符的使用,在Java集合框架中也有使用到:java.util.TreeMap中


public class TreeMap<K,V>{	
  //下限通配符	
  private final Comparator<? super K> comparator;	
  //下限通配符	
  public TreeMap(Comparator<? super K> comparator) {	
        this.comparator = comparator;	
  }	
  //上限通配符	
  public TreeMap(Map<? extends K, ? extends V> m) {	
        comparator = null;	
        putAll(m);	
  }	
  //....其他代码就不贴了	
}



4


设定泛型形参的上限


代码:


public class Apple<T extends Number> {	
    T t;	
}


Apple<Integer> apple=new Apple<>();	
Apple<Double> apple1=new Apple<>();	
//下面这一行编译不通过	
Apple<String> str=new Apple<>();


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




泛型方法




1


定义泛型方法


假设需要实现这一一个方法:该方法负责将一个Object数组的所有元素添加到一个Colletion集合中,考虑采用如下代码来实现:


private  static void fromArrayToColletion(Object [] os, Collection<Object> cs){	
     for (Object object:os) {	
         cs.add(object);	
     }	
}


上面方法没问题,但是考虑到如果使用String[] 和List<String>作为入参就会编译报错。这里显然不能用通配符Colletion<?>,因为Java不允许把对象放进一个未知类型的集合中。

为了解决上述问题,java1.5版本提供了泛型方法,所谓泛型方法就是在声明方法时定义一个或多个泛型形参,格式如下:


修饰符 <T, S> 返回值类型  方法名(形参列表){	
 //方法体.....	
}


修正上面的例子:


private static <T> void fromArrayToColletion(T[] objects,	
                          Collection<T> collection) {	
      for (T object : objects) {	
        collection.add(object);	
       }	
 }


调用案例:


public static void main(String[] args) { 	
        String[] a = {"java后端技术栈", "大中华"};	
        List<String> la = new ArrayList<>();	
        fromArrayToColletion(a, la);	

	
        Integer[] b = {1, 9};	
        List<Integer> lb = new ArrayList<>();	
        fromArrayToColletion(b, lb); 	
}


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


private static <T> void fromArrayToCollection(Collection<T>  from, Collection<T> to) {	
       for (T object : from) {	
            to.add(object);	
       }	
 }	
public static void main(String[] args) {	
       List<String> l = new ArrayList<>();	
       List<Object> s = new ArrayList<>();	
       //编译报错	
       fromArrayToCollection(l, s);	
 }


为了避免上述问题,我们可以改写一下上面的方法:


private static <T> void fromArrayToCollection(Collection<? extends T>  from, Collection<T> to) {	
        for (T object : from) {	
            to.add(object);	
        }	
    }	
public static void main(String[] args) {	
        List<String> l = new ArrayList<>();	
        List<Object> s = new ArrayList<>();	
        //正常编译成功	
        fromArrayToCollection(l, s);	
 }


那么到底是何时使用泛型方法?

何时使用类型通配符呢?


2


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


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


public interface Collection<E> {	
boolean containsAll(Collection<?> c);	
boolean addAll(Collection<? extends E> c);	
}


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


public interface Collection<E> {	
<T> boolean containsAll(Collection<?> c);	
<T extends E> boolean addAll(Collection<T> c);	
}


<T extends E>是泛型形式,这时定义泛型形参时设定了上限。

上面两个方法中泛型形参T只是用了一次,泛型形参T产生的唯一效果是可以在不同的调用点传入不同实际类型。对于这种情况,应该使用通配符;通配符就是被设计用来支持灵活的子类化的。泛型方法允许泛型形参被用来表示一个或多个参数之间的类型依赖关系,或者方法返回值与防范参数之间的依赖关系。如果没有这样的关系依赖类型,就不应该使用泛型方法。

类型通配符和泛型方法一个很明显的区别:

类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但是泛型方法中的泛型形参必须在对应方法中显示声明。


3


JDK1.8改进的类型推断


改进了泛型方法的类型推断能力,类型推断主要有两个方面:

  1. 可以通过调用方法的上下文来推断泛型的目标类型。
  2. 可在方法调用链中,将推断得到的泛型类型传递到最后一个方法。

到此,本次Java泛型已经分享完毕。

万丈高楼从地起!!!  加油