再谈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);

编译会报错:

这表明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。

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集合框架中也有使用到: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后端技术栈(t-j20120622),作者:Java后端技术栈

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-10

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring的bean创建实例详解

    IoC容器,又名控制反转,全称为Inverse of Control,其是Spring最为核心的一个组件,其他的组件如AOP,Spring事务等都是直...

    田维常
  • 浅谈MySQL自增锁

    最近在工作中遇到很多使用MySQL自带的autoincrement函数作为发号器,在实际使用中当并发比较小的时候还没有问题,一旦并发增加就会出现很多问题,特此进...

    田维常
  • 再谈泛型java---上

    可以看得出来,每次从list里取数据的时候,需要强制转换,所以这里就很容易报异常:ClassCastException.

    田维常
  • Spring: (一) -- 春雨润物之 核心IOC

    作为一个Java人,想必都或多或少的了解过Spring。对于其优势也能道个一二,诸如方便解耦、支持AOP编程、支持声明式事务、方便测试等等。...

    宋先生
  • 微信小程序8种数据通信的方式

    数据通信在开发中是必不可少的一个环节,也是我们必须掌握的知识。知道得越多的数据通信方式,实现业务会更加得心应手。

    WahFung
  • PS把照片处理成扫描文件

    1.打开PS,复制背景,得到图层 1。 2.对图层 1使用滤镜:滤镜-模糊-高斯模糊,半径100像素。

    似水的流年
  • pytest按tag动态挑选测试用例

    pytest是个成熟全套的 python 自动化测试工具。单元测试,功能测试、接口自动化测试中均可应用;与 unittest相比,它能支持更多、更全面的功能,同...

    用户5521279
  • 行业案例 | 证券行业如何在交易中保证信息沟通安全性?

    ? 腾讯企点 公众号ID:qidianonline 关注 ? ? 随着互联网的飞速发展,证券金融行业迎来了新的机遇,也面临着新的挑战。金融信息安全越来越受到...

    腾讯企点
  • 使用python实现RESTful API服务器端的思路

    最近这些年,REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了。 REST的六个特性 Client...

    Python中文社区
  • 腾讯优图实验室高级研究员线上开讲 | 人工智能领域的人才培养和实践

    由信息技术新工科产学研联盟主办的“人工智能教育线上公开课”第四期,腾讯优图实验室高级研究员彭湃博士将分享在人工智能领域的人才培养和实践经验,欢迎各位扫描下面的...

    腾讯高校合作

扫码关注云+社区

领取腾讯云代金券