类型通配符
一、类型通配符
当声明一个方法时,某个形参的类型是一个泛型类或泛型接口类型,但是在声明方法时,又不确定该泛型实际类型,可以考虑使用类型通配符。
先来看下面一个案例
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){ //....省略代码 }