类型通配符

一、类型通配符

当声明一个方法时,某个形参的类型是一个泛型类或泛型接口类型,但是在声明方法时,又不确定该泛型实际类型,可以考虑使用类型通配符。

先来看下面一个案例

import java.util.ArrayList;
import java.util.List;

/*
泛型中通配符

常用的 T,E,K,V,?

?无界通配符,表示不确定的 java 类型,代表任意类型,有位置有求
  需要传入具体类型的位置,不能单独使用
  声明,泛型类,泛型接口,泛型方法时不能单独使用

T (type) 表示具体的一个java类型
K V (key value) 分别代表java键值中的Key Value
E (element) 代表Element

上界通配符 < ? extends E>  传入的类型,可以是当前类型以及当前类型的孩子
下界通配符 < ? super E>    传入的类型,可以使当前类型,也可以是当前类型的父类

?和 T 的区别
* */
public class Demo1 {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        list1.add("项羽");
        list1.add("刘邦");
        list1.add("张饵");
        test1(list1);

    }

    public static void test1(List c){
        for (int i = 0; i <c.size() ; i++) {
            System.out.println(c.get(i));
        }
    }
}

上面的方法执行是没有问题的,但是此处使用 List 接口时没有传入实际类型参数,这将引起泛型警告。如何消除这个警告呢?

  方式一:

public class Demo1 {
    public static void main(String[] args) {
        List<Object> list1 = new ArrayList<>();
        list1.add("项羽");
        list1.add("刘邦");
        list1.add("张饵");
        test1(list1);

    }

    public static void test1(List<Object> c){
        for (int i = 0; i <c.size() ; i++) {
            System.out.println(c.get(i));
        }
    }
}

这样的形参太局限了,只能传入 List<Object> 类型的实参。

  方式二:声明一个泛型方法

public class Demo1 {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        list1.add("项羽");
        list1.add("刘邦");
        list1.add("张饵");
        test1(list1);

    }

    public static<T> void test1(List<T> c){
        for (int i = 0; i <c.size() ; i++) {
            System.out.println(c.get(i));
        }
    }
}

该方法需要声明泛型形参T。

  方式三:使用类型通配符

public class Demo1 {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        list1.add("项羽");
        list1.add("刘邦");
        list1.add("张饵");
        test1(list1);

    }

    public static void test1(List<?> c){
        for (int i = 0; i <c.size() ; i++) {
            System.out.println(c.get(i));
        }
    }
}

 那么方式二的泛型方法 test1() 和方式三的 test1() 使用类型通配符有什么区别?

   方式三方法带通配符的List仅表示它可以接受指定了任意泛型实参的List,并不能把元素加入其中,例如如下代码将会引起编译错误:


public class Demo1 {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("项羽");
list1.add("刘邦");
list1.add("张饵");
test1(list1,"陈余");

}

public static void test1(List<?> c,String string){
c.add(string);
}
}
 

因为我们不知道上面程序中c集合里元素的类型,所以不能向其中添加对象,除了null对象,因为它是所有引用数据类型的实例。

    方式二方法带泛型的List,表示该集合的元素类型是T,因此允许T系列的对象加入其中,例如如下代码是可行的:

public class Demo1 {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        list1.add("项羽");
        list1.add("刘邦");
        list1.add("张饵");
        test1(list1,"陈余");

    }

    public static<T> void test1(List<T> c, T string){
        c.add(string);
        for (int i = 0; i <c.size() ; i++) {
            System.out.println(c.get(i));
        }
    }
}

即如果不涉及添加元素到带泛型的集合中,那么两种方式都可以,如果涉及到添加元素到带泛型的集合中,使用类型通配符<?>的不支持

二、设定类型通配符的上限

  当直接使用 List<?> 这种形式时,即表明这个 List 集合接收泛型实参指定为任意类型的 List。但有时候不想这样,只希望接收某些类型的 List。

  Demo:一个图形的抽象父类 Graphic,两个子类 Circle和Rectangle,想定义一个方法,可以打印不同图形的面积。

import java.util.ArrayList;
import java.util.List;

public class Demo2 {
    public static void main(String[] args) {
        Graphic g1 = new Graphic("yuan",2,1);
        Graphic g2 = new Graphic("fang",3,2);
        List<Graphic> list1 = new ArrayList<>();
        list1.add(g1);
        list1.add(g2);
        printArea(list1);

    }

    public static void printArea(List<Graphic> graphics){
        for(Graphic g:graphics){
            g.getArea();
        }
    }
}


class Graphic{
    String name;
    int lengths;
    int wides;

    public Graphic() {
    }

    public Graphic(String name, int lengths, int wides) {
        this.name = name;
        this.lengths = lengths;
        this.wides = wides;
    }

    public void getArea(){
        if (name.equals("fang")){
            int area=lengths*wides;
            System.out.println("父类面积是:"+area);
        }else if (name.equals("yuan")){
            Double area=Double.valueOf(lengths)*3.14;
            System.out.println("父类面积是:"+area);
        }
    }
}

但是,List<Graphic> 的形参只能接收 List<Graphic>的实参,如果想要接收 List<Circle>,List<Graphic>的集合,可以使用List<?>

 

package day19;

import java.util.ArrayList;
import java.util.List;

public class Demo2 {
    public static void main(String[] args) {
        Graphic g1 = new Graphic("yuan",2,1);
        Graphic g2 = new Graphic("fang",3,2);
        Circle c1 = new Circle("yuan",3.0);
        List<Graphic> list1 = new ArrayList<>();
        list1.add(g1);
        list1.add(g2);
        list1.add(c1);
        printArea(list1);

    }

    public static void printArea(List<?> graphics){
        for(Object obj:graphics){
            Graphic g=(Graphic)obj;
            g.getArea();
        }
    }
}


class Graphic{
    String name;
    int lengths;
    int wides;

    public Graphic() {
    }

    public Graphic(String name, int lengths, int wides) {
        this.name = name;
        this.lengths = lengths;
        this.wides = wides;
    }

    public void getArea(){
        if (name.equals("fang")){
            int area=lengths*wides;
            System.out.println("父类面积是:"+area);
        }else if (name.equals("yuan")){
            Double area=Double.valueOf(lengths)*3.14;
            System.out.println("父类面积是:"+area);
        }
    }
}

class Circle extends Graphic{
    String name;
    Double lengths;

    public Circle() {
    }

    public Circle(String name, Double lengths) {
        this.name = name;
        this.lengths = lengths;
    }

    @Override
    public void getArea() {
        Double area = lengths*3.14;
        System.out.println(area);
    }

}

但是这样有两个问题,一个是 List<?> 可以接收任意类型,不仅仅图形,第二个是需要强制类型转换。

  为了解决这个问题,Java 允许设定通配符的上限:

<? extends Type>,这个通配符表示它必须是Type本身,或是Type的子类。

 如:

import java.util.ArrayList;
import java.util.List;

public class Demo2 {
    public static void main(String[] args) {
        Graphic g1 = new Graphic("yuan",2,1);
        Graphic g2 = new Graphic("fang",3,2);
        Circle c1 = new Circle("yuan1",3.0);
        List<Graphic> list1 = new ArrayList<>();
        list1.add(g1);
        list1.add(g2);
        list1.add(c1);
        printArea(list1);

    }

    public static void printArea(List<? extends Graphic> graphics){
        for(Graphic g:graphics){
            g.getArea();
        }
    }
}


class Graphic{
    String name;
    int lengths;
    int wides;

    public Graphic() {
    }

    public Graphic(String name, int lengths, int wides) {
        this.name = name;
        this.lengths = lengths;
        this.wides = wides;
    }

    public void getArea(){
        if (name.equals("fang")){
            int area=lengths*wides;
            System.out.println("父类面积是:"+area);
        }else if (name.equals("yuan")){
            Double area=Double.valueOf(lengths)*3.14;
            System.out.println("父类面积是:"+area);
        }
    }
}

class Circle extends Graphic{
    String name;
    Double lengths;

    public Circle() {
    }

    public Circle(String name, Double lengths) {
        this.name = name;
        this.lengths = lengths;
    }

    @Override
    public void getArea() {
        Double area = lengths*3.14;
        System.out.println(area);
    }

}

与前面的完全相同,因为不知道这个受限制的通配符的具体类型,所以不能把 Graphic 对象或其子类对象加入到这个泛型集合中。

    public static void printArea(List<? extends Graphic> graphics){
        for(Graphic g:graphics){
            graphics.add(new Circle("yuan1",3.0));//编译错误,因为不知道?的具体类型
            g.getArea();
        }
    }
}

如果需要将 Graphic 对象或其子类对象加入这个泛型集合,那么就只能用泛型方法。

三、设定通配符的下限

  假设自己实现一个工具方法:实现将 src 集合里元素复制到 dest 集合中的功能,因为 dest 集合需要接受 src 的所有元素,所以 dest 集合元素的类型应该是 src 集合元素的父类。为了表示两个参数之间的类型依赖,考虑同时使用通配符、泛型形参类实现该方法。

  Demo:

package day19;

import java.util.ArrayList;
import java.util.Collection;

public class Demo3 {

    public static void main(String[] args) {
        ArrayList<String> src = new ArrayList<>();
        src.add("贾宝玉");
        src.add("秦钟");
        src.add("薛宝钗");

        ArrayList<Object> dest = new ArrayList<>();
        Object last=copy(dest,src);
        System.out.println(last);
    }

    public static <T> T copy(Collection<T> dest,Collection<? extends T> src){
        T last=null;
        for (T t:src){
            dest.add(t);
            last=t;
        }
        return last;
    }
}

表面上看这个实现没有问题,实际上有一个问题,当遍历src元素的类型是不确定(但可以肯定是T的子类),程序只能用T来笼统的表示,所以返回值类型是T

发现返回的T是Object,也就是说,程序在复制集合元素的过程中,丢失了src集合元素的类型String。

 

对于上面的copy方法,可以这样理解两个集合参数之间的依赖关系:不管src集合元素的类型是什么,只要dest集合元素类型与src的元素类相同或是它的父类即可。

为了表示这种约束关系,Java允许设定通配符的下限:

<? super Type>,这个通配符表示它必须是Type本身或是Type的父类。

代码

import java.util.ArrayList;
import java.util.Collection;

public class Demo3 {

    public static void main(String[] args) {
        ArrayList<String> src = new ArrayList<>();
        src.add("贾宝玉");
        src.add("秦钟");
        src.add("薛宝钗");

        ArrayList<Object> dest = new ArrayList<>();
        String last=copy(dest,src);
        System.out.println(last);
    }

    public static <T> T copy(Collection<? super T> dest,Collection<T> src){
        T last=null;
        for (T t:src){
            dest.add(t);
            last=t;
        }
        return last;
    }
}

注意:只有类型通配符才可以设定下限,泛型形参是不能设定下限的

四、泛型方法与方法重载

因为泛型既允许设定通配符的上限,也允许设定通配符的下限,如果在一个类里包含这样两个方法定义,会报错:

public static <T> T copy(Collection<? super T> dest,Collection<T> src){
        //....省略代码
    }

    public static <T> T copy(Collection<T> dest,Collection<? extends T> src){
        //....省略代码
    }

 

posted @ 2022-01-12 11:55  从此重新定义啦  阅读(102)  评论(0编辑  收藏  举报