文章目录
- Day16 泛型
- 学习目标
- 1 泛型的概念
- 1.1 没有泛型的问题
- 1.2 泛型的引入
- 1.2 泛型的好处
- 1.3 泛型的定义
- 2 泛型类与泛型接口
- 2.1 使用核心类库中的泛型类/接口
- 案例一:Collection集合相关类型
- 案例二:Comparable接口
- 2.2 自定义泛型类与泛型接口
- 语法格式
- 案例一:自定义泛型类
- 案例二:自定义泛型接口
- 2.3 小结
- 3 泛型方法
- 3.1 泛型方法的调用
- 3.2 自定义泛型方法
- 3.3 泛型类与泛型方法的区别
- 1、<泛型变量>声明位置不同
- 2、<泛型变量>使用的范围不同
- 4 类型变量的上限与泛型的擦除
- 4.1 <类型变量>的上限
- 案例1:定义泛型类的<类型变量>时指定上限
- 案例2:定义泛型方法的<类型变量>时指定上限
- 4.2 泛型擦除与泛型上限
- 5 类型通配符
- 5.1 类型通配符
- 5.2 类型通配符的三种使用形式
- 5.3 泛型变量T与通配符?区别
- 1、T可以单独使用,而?必须依赖于泛型类或泛型接口使用
- 2、T只能在声明时指定上限,?可以指定上限和下限
- 3、同一个方法中多个T代表相同的类型,多个?没有关联性
- 4、T是可以确定的类型,?是不能确定的类型
Day16 泛型
学习目标
- 能够理解泛型的好处和意义
- 能够在使用集合相关API时正确指定泛型
- 能够使用其他泛型类、泛型接口
- 能够认识泛型方法
- 能够使用泛型定义类、接口、方法
- 能够理解泛型上限作用
- 能够阐述泛型通配符的作用
- 能够识别通配符的上下限
1 泛型的概念
1.1 没有泛型的问题
例如
(1)在设计集合类型时,只能确定集合用来装对象,但是无法确定装什么类型的对象,即集合的元素类型未知,
(2)在设计比较器接口时,只能确定两个对象比较大小的结果是正整数、父整数、零,但是无法确定是两个什么类型的对象比较大小。
这些都必须要在集合被创建时或比较器接口被实现时才能确定。这就造成了两个问题:
(1)为了让集合和比较器类型得以顺利实现,就把元素类型设计为Object,那么在使用时,编译器就无法进行更加具体的类型检查====> 类型安全问题==
(2)为了调用元素对象非Object类的方法,不得不向下转型====> 代码繁琐的问题==
package com.atguigu.nogeneric;
import java.util.ArrayList;
public class TestNoGeneric {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("hello");
list.add("java");
list.add(1);//编译器不进行类型检查
for (Object o : list) {
String s = (String) o;//需要向下转型,繁琐
System.out.println(s +"的长度:" + s.length());
}
}
}
package com.atguigu.nogeneric;
public class Circle implements Comparable{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle [radius=" + radius + "]";
}
@Override
public int compareTo(Object o) {
Circle c = (Circle) o;//向下转型,繁琐
return Double.compare(this.radius, c.radius);
}
}
1.2 泛型的引入
类比1:生活中的启发:
例如:生产瓶子的厂家,一开始并不知道我们将来会用瓶子装什么,我们什么都可以装,但是有的时候,我们拿到一些瓶子准备装东西时,想要限定这些瓶子分别只能用来装什么,这样我们不会装错,而取东西的时候也可以放心的取,无需再三思量。我们生活中是在瓶子上“贴标签”,这样就轻松解决了问题,在Java中是否也可以在使用集合或比较器等类型时,再给它们“贴标签”呢?
类比2:参数的启发
在Java中我们在声明方法时,当在完成方法功能时如果有未知的数据需要参与,这些未知的数据需要在调用方法时才能确定,我们把这样的数据通过形参表示,在方法体中用这个形参名来代表那个未知的数据,而调用者在调用时,通过实参对应的传入值就可以了。
类似于上面的类比,JDK1.5设计了泛型的概念。泛型即为“把类型当成参数传递的一种机制”。例如:java.lang.Comparable接口和java.util.Comparator接口,其中就是类型变量,在使用时再给类型变量T传递一个具体的类型。
public interface Comparable<T>{
int compareTo(T o) ;
}
public interface Comparator<T>{
int compare(T o1, T o2) ;
}
1.2 泛型的好处
如果有了泛型并使用泛型,那么既能保证安全,又能简化代码。
因为把不安全的因素在编译期间就排除了;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。
package com.atguigu.usegeneric;
public class Circle implements Comparable<Circle>{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle [radius=" + radius + "]";
}
@Override
public int compareTo(Circle o) {
return Double.compare(this.radius, o.radius);
}
}
package com.atguigu.usegeneric;
import java.util.Comparator;
public class CircleComparator implements Comparator<Circle> {
@Override
public int compare(Circle o1, Circle o2) {
//不再需要强制类型转换,代码更简洁
return Double.compare(o1.getRadius(), o2.getRadius());
}
}
package com.atguigu.usegeneric;
import java.util.ArrayList;
public class TestUseGeneric {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("java");
// list.add(1);//编译器进行类型检查
for (String s : list) {
System.out.println(s +"的长度:" + s.length());
}
}
}
1.3 泛型的定义
<类型>这种语法形式就叫泛型。
- 是类型变量(Type Variables),而是代表未知的数据类型,我们可以指定为,,
等。相当于把某个具体的类型泛化为一般的类型,所以称为泛型。 - 类比方法的参数的概念,我们可以把,称为类型形参,将
称为类型实参,有助于我们理解泛型;
2 泛型类与泛型接口
我们把类名或接口名后面带、、<K,V>等的类或接口称为泛型类或泛型接口。
2.1 使用核心类库中的泛型类/接口
自从JDK1.5引入泛型的概念之后,对之前核心类库中的API做了很大的修改,例如:集合框架集中的相关接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等等。
下面以Collection、ArrayList集合以及Iterator迭代器为例演示,泛型类与泛型接口的使用。
案例一:Collection集合相关类型
(1)创建一个Collection集合(暂时创建ArrayList集合对象),并指定泛型为
(2)添加5个[0,100)以内的整数到集合中,
(3)使用foreach遍历输出5个整数,
(4)使用集合的removeIf方法删除偶数,为Predicate接口指定泛型
(5)再使用Iterator迭代器输出剩下的元素,为Iterator接口指定泛型。
package com.atguigu.genericclass.use;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Random;
import java.util.function.Predicate;
public class TestNumber {
public static void main(String[] args) {
Collection<Integer> coll = new ArrayList<Integer>();
Random random = new Random();
for (int i = 1; i <= 5 ; i++) {
coll.add(random.nextInt(100));
}
System.out.println("coll中5个随机数是:");
for (Integer integer : coll) {
System.out.println(integer);
}
coll.removeIf(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer % 2 == 0;
}
});
System.out.println("coll中删除偶数后:");
Iterator<Integer> iterator = coll.iterator();
while(iterator.hasNext()){
Integer number = iterator.next();
System.out.println(number);
}
}
}
案例二:Comparable接口
(1)声明矩形类Rectangle,包含属性长和宽,属性私有化,提供有参构造、get/set方法、重写toString方法,提供求面积和周长的方法。
(2)矩形类Rectangle实现java.lang.Comparable接口,并指定泛型为,重写int compareTo(T t)方法,按照矩形面积比较大小,面积相等的,按照周长比较大小。
(3)在测试类中,创建Rectangle数组,并创建5个矩形对象
(4)调用Arrays的sort方法,给矩形数组排序,并显示排序前后的结果。
package com.atguigu.genericclass.use;
public class Rectangle implements Comparable<Rectangle>{
private double length;
private double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double area(){
return length * width;
}
public double perimeter(){
return 2 * (length + width);
}
@Override
public String toString() {
return "Rectangle{" +
"length=" + length +
", width=" + width +
",area =" + area() +
",perimeter = " + perimeter() +
'}';
}
@Override
public int compareTo(Rectangle o) {
int compare = Double.compare(area(), o.area());
return compare != 0 ? compare : Double.compare(perimeter(),o.perimeter());
}
}
package com.atguigu.genericclass.use;
import java.util.Arrays;
public class TestRectangle {
public static void main(String[] args) {
Rectangle[] arr = new Rectangle[4];
arr[0] = new Rectangle(6,2);
arr[1] = new Rectangle(4,3);
arr[2] = new Rectangle(12,1);
arr[3] = new Rectangle(5,4);
System.out.println("排序之前:");
for (Rectangle rectangle : arr) {
System.out.println(rectangle);
}
Arrays.sort(arr);
System.out.println("排序之后:");
for (Rectangle rectangle : arr) {
System.out.println(rectangle);
}
}
}
2.2 自定义泛型类与泛型接口
当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型。
语法格式
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 父接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【extends 父接口们】{
}
案例一:自定义泛型类
例如:我们要声明一个学生类,该学生包含姓名、成绩,而此时学生的成绩类型不确定,为什么呢,因为,语文老师希望成绩是“优秀”、“良好”、“及格”、“不及格”,数学老师希望成绩是89.5, 65.0,英语老师希望成绩是’A’,‘B’,‘C’,‘D’,‘E’。那么我们在设计这个学生类时,就可以使用泛型。
package com.atguigu.genericclass.define;
public class Student<T>{
private String name;
private T score;
public Student() {
super();
}
public Student(String name, T score) {
super();
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getScore() {
return score;
}
public void setScore(T score) {
this.score = score;
}
@Override
public String toString() {
return "姓名:" + name + ", 成绩:" + score;
}
}
@Test
public void test01(){
//语文老师使用时:
Student<String> stu1 = new Student<String>("张三", "良好");
//数学老师使用时:
//Student<double> stu2 = new Student<double>("张三", 90.5);//错误,必须是引用数据类型
//Student<Double> stu2 = new Student<Double>("张三", 90);//错误,90是int,不能自动装箱为Double
Student<Double> stu2 = new Student<Double>("张三", 90.0);//可以
Student<Double> stu3 = new Student<Double>("张三", 90D);
//英语老师使用时:
Student<Character> stu4 = new Student<Character>("张三", 'C');
//错误的指定
//Student<Object> stu5 = new Student<String>();//错误的
}
案例二:自定义泛型接口
案例需求:
1、定义一个计算器接口Calculator<T,R>,T代表操作数的类型,R代表计算结果的类型
- 包含两个数计算的方法caculate,要求操作数的类型相同,但具体类型不确定,计算结果可能与操作数的类型不同。
- 包含求两个数最大值的方法max,要求操作数的类型相同,结果与操作数的类型也相同
2、编写实现类实现计算器接口
- 两个Integer整数相加及最大值,
- 相加结果用Long表示
- 返回两个整数中更大的那个,如果一样大,就返回第1个
- 两个String相加及最大值,
- 相加结果仍然是String,
- 返回两个字符串中更长的字符串,如果一样长,就返回第1个
public interface Calculator<T, R> {
R calculate(T t1, T t2);
T max(T t1, T t2);
}
package com.atguigu.generic.classinterface;
import org.junit.Test;
public class TestGenericInterface {
@Test
public void test1() {
System.out.println(Integer.MAX_VALUE);
Calculator<Integer, Long> c = new Calculator<Integer, Long>() {
@Override
public Long calculate(Integer t1, Integer t2) {
return (long) t1 + t2;
}
@Override
public Integer max(Integer t1, Integer t2) {
return t1 >= t2 ? t1 : t2;
}
};
Long sum1 = c.calculate(65536, 65536);
Long sum2 = c.calculate(Integer.MAX_VALUE, Integer.MAX_VALUE);
Integer max = c.max(1, 2);
System.out.println("sum1 = " + sum1);
System.out.println("sum2 = " + sum2);
System.out.println("max = " + max);
}
@Test
public void test2() {
Calculator<String, String> c = new Calculator<String, String>() {
@Override
public String calculate(String t1, String t2) {
return t1 + t2;
}
@Override
public String max(String t1, String t2) {
return t1.length() >= t2.length() ? t1 : t2;
}
};
String sum = c.calculate("hello", "world");
String max = c.max("hello", "java");
System.out.println("sum = " + sum);
System.out.println("max = " + max);
}
}
2.3 小结
1、<类型变量列表>:可以是一个或多个类型变量,一般都是使用单个的大写字母表示。例如:、<K,V>等。
2、<类型变量列表>中的类型变量不能用于静态成员上。
3、在同一个类或接口中同一个类型变量代表同一种数据类型
4、<实际类型参数>必须是引用数据类型,不能是基本数据类型
5、可以在创建泛型类的对象时指定<类型变量>对应的<实际类型>
(1)指定泛型实参时左右两边必须一致
(2)JDK1.7支持自动类型推断的简写形式:ArrayList list= new ArrayList<>();
6、子类继承泛型父类时,子接口继承泛型父接口、或实现类实现泛型父接口时,
(1)可以指定<类型变量>对应的<实际类型>,此时子类或实现类不再是泛型类
package com.atguigu.genericclass.define;
//ChineseStudent不再是泛型类
public class ChineseStudent extends Student<String>{
public ChineseStudent() {
super();
}
public ChineseStudent(String name, String score) {
super(name, score);
}
}
public class Rectangle implements Comparable<Rectangle>
(2)用子类/子接口的类型变量指定父类或父接口的类型变量,子类/子接口的类型变量可以和原来字母一样,也可以换一个字母,此时子类、子接口、实现类仍然是泛型类或泛型接口
public interface Iterable<T>
public interface Collection<E> extends Iterable<E> //E:Element元素
public interface List<E> extends Collection<E>
public class ArrayList<E>extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, Serializable
3 泛型方法
在方法的返回值类型前面声明了等,该方法就是泛型方法。
泛型方法在调用时,由实参的类型确定泛型方法类型变量的具体类型。
3.1 泛型方法的调用
在java.util.Arrays数组工具类中,有很多泛型方法,例如:
-
public static List asList(T… a):添加任意个任意类型的对象到List集合中
-
public static T[] copyOf(T[] original, int newLength):复制任意对象数组,新数组长度为newLength。
如果没有泛型,只能用Object[]数组,那么对象数组复制后只能返回Object[]数组,就太麻烦了。
package com.atguigu.method;
import java.util.Arrays;
import java.util.List;
public class TestArrays {
public static void main(String[] args) {
String[] arr = {"java", "world", "hello"};
String[] strings = Arrays.copyOf(arr, arr.length * 2);
System.out.println(Arrays.toString(strings));
List<String> list = Arrays.asList("java", "world", "hello");
System.out.println(list);
}
}
3.2 自定义泛型方法
我们除了在类名或接口名后面声明泛型的<类型变量>之外, 还可以在方法的返回值类型前面为这个方法单独声明泛型的<类型变量>,这个方法可以是静态方法,也可以是非静态方法。
语法格式:
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//...
}
示例代码:
我们编写一个集合工具类,实现将多个元素都添加到一个Collection集合中。
package com.atguigu.method;
import java.util.Collection;
public class MyCollections {
public static <T> void addAll(Collection<T> coll, T... args){
for (T t : args) {
coll.add(t);
}
}
}
package com.atguigu.method;
import java.util.ArrayList;
public class MyCollectionsTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
MyCollections.addAll(list, "hello","world","java");
System.out.println(list);
}
}
3.3 泛型类与泛型方法的区别
1、<泛型变量>声明位置不同
- 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明(是声明不是单纯的使用)了类型变量的方法称为泛型方法
【修饰符】 <类型变量列表> 返回值类型 方法名(【形参列表】)【throws 异常列表】{
//...
}
例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
....
}
- 声明类或接口时,在类名或接口名后面声明类型变量,我们把这样的类或接口称为泛型类或泛型接口
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 父接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【implements 父接口们】{
}
例如:
public class ArrayList<E>
public interface Map<K,V>{
....
}
2、<泛型变量>使用的范围不同
- 在类或接口名后面声明的<泛型变量>在整个类中都可以使用,而且同名的<泛型变量>代表的类型是相同。
- 在方法返回值类型前面声明的<泛型变量>仅限于当前方法使用,和其他方法同名的<泛型变量>代表的类型是无关的。
package com.atguigu.different;
public class TestDifferent1{
public static void main(String[] args) {
Demo<String> demo = new Demo<>();
demo.m1("hello");
demo.m2("world");
Example example = new Example();
example.m1("hello");
example.m2(666);
}
}
/*
同一个Demo对象的m1和m2的T类型是有关联的,是同一种类型
*/
class Demo<T> {
void m1(T t1){
System.out.println("t1 = " + t1);
}
void m2(T t2){
System.out.println("t2 = " + t2);
}
}
/*
同一个Example对象的m1和m2的T类型是无关的,独立的
*/
class Example{
<T> void m1(T t1){
System.out.println("t1 = " + t1);
}
<T> void m2(T t2){
System.out.println("t2 = " + t2);
}
}
4 类型变量的上限与泛型的擦除
4.1 <类型变量>的上限
当在声明类型变量时,如果不希望这个类型变量代表任意引用数据类型,而是某个系列的引用数据类型,那么可以设定类型变量的上限。
语法格式:
<类型变量 extends 上限>
如果有多个上限
<类型变量 extends 上限1 & 上限2>
如果多个上限中有类有接口,那么只能有一个类,而且必须写在最左边。接口的话,可以多个。
如果在声明<类型变量>时没有指定任何上限,默认上限是java.lang.Object。
案例1:定义泛型类的<类型变量>时指定上限
例如:我们要声明一个两个数算术运算的工具类,要求两个数必须是Number数字类型,并且实现Comparable接口。
package com.atguigu.limmit;
import java.math.BigDecimal;
import java.math.BigInteger;
public class NumberTools<T extends Number & Comparable<T>>{
private T a;
private T b;
public NumberTools(T a, T b) {
super();
this.a = a;
this.b = b;
}
public T getSum(){
if(a instanceof BigInteger){
return (T) ((BigInteger) a).add((BigInteger)b);
}else if(a instanceof BigDecimal){
return (T) ((BigDecimal) a).add((BigDecimal)b);
}else if(a instanceof Byte){
return (T)(Byte.valueOf((byte)((Byte)a+(Byte)b)));
}else if(a instanceof Short){
return (T)(Short.valueOf((short)((Short)a+(Short)b)));
}else if(a instanceof Integer){
return (T)(Integer.valueOf((Integer)a+(Integer)b));
}else if(a instanceof Long){
return (T)(Long.valueOf((Long)a+(Long)b));
}else if(a instanceof Float){
return (T)(Float.valueOf((Float)a+(Float)b));
}else if(a instanceof Double){
return (T)(Double.valueOf((Double)a+(Double)b));
}
throw new UnsupportedOperationException("不支持该操作");
}
public T getSubtract(){
if(a instanceof BigInteger){
return (T) ((BigInteger) a).subtract((BigInteger)b);
}else if(a instanceof BigDecimal){
return (T) ((BigDecimal) a).subtract((BigDecimal)b);
}else if(a instanceof Byte){
return (T)(Byte.valueOf((byte)((Byte)a-(Byte)b)));
}else if(a instanceof Short){
return (T)(Short.valueOf((short)((Short)a-(Short)b)));
}else if(a instanceof Integer){
return (T)(Integer.valueOf((Integer)a-(Integer)b));
}else if(a instanceof Long){
return (T)(Long.valueOf((Long)a-(Long)b));
}else if(a instanceof Float){
return (T)(Float.valueOf((Float)a-(Float)b));
}else if(a instanceof Double){
return (T)(Double.valueOf((Double)a-(Double)b));
}
throw new UnsupportedOperationException("不支持该操作");
}
}
测试类
package com.atguigu.limmit;
public class NumberToolsTest {
public static void main(String[] args) {
NumberTools<Integer> tools = new NumberTools<Integer>(8,5);
Integer sum = tools.getSum();
System.out.println("sum = " + sum);
Integer subtract = tools.getSubtract();
System.out.println("subtract = " + subtract);
}
}
案例2:定义泛型方法的<类型变量>时指定上限
我们编写一个数组工具类,包含可以给任意对象数组进行从小到大排序,调用元素对象的compareTo方法比较元素的大小关系。要求数组的元素类型必须是java.lang.Comparable接口类型。
package com.atguigu.limmit;
public class MyArrays {
public static <T extends Comparable<T>> void sort(T[] arr){
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length-i; j++) {
if(arr[j].compareTo(arr[j+1])>0){
T temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
}
测试类
package com.atguigu.limmit;
import com.atguigu.generic.Circle;
import java.util.Arrays;
public class MyArraysTest {
public static void main(String[] args) {
int[] arr = {3,2,5,1,4};
// MyArrays.sort(arr);//错误的,因为int[]不是对象数组
String[] strings = {"hello","java","chai"};
MyArrays.sort(strings);
System.out.println(Arrays.toString(strings));
Circle[] circles = {new Circle(2.0),new Circle(1.2),new Circle(3.0)};
// MyArrays.sort(circles); //编译报错
}
}
4.2 泛型擦除与泛型上限
当使用参数化类型的类或接口时,如果没有指定泛型,那么会怎么样呢?
会发生泛型擦除,自动按照最左边的第一个上限处理。如果没有指定上限,上限即为Object。
package com.atguigu.limmit;
import java.util.ArrayList;
import java.util.Collection;
public class TestErase {
public static void main(String[] args) {
NumberTools tools = new NumberTools(8,5);
Number sum = tools.getSum();//自动按照Number处理
System.out.println("sum = " + sum);
Number subtract = tools.getSubtract();
System.out.println("subtract = " + subtract);
Collection coll = new ArrayList();
coll.add("hello");
coll.add(1);
for (Object o : coll) {//自动按照Object处理
System.out.println(o);
}
}
}
5 类型通配符
5.1 类型通配符
当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Collection类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 ? 。
package com.atguigu.wild;
import java.util.Collection;
public class MyCollections {
public static void print(Collection<?> coll){
for (Object o : coll) {
System.out.println(o);
}
}
}
package com.atguigu.wild;
import org.junit.Test;
import java.util.Arrays;
public class TestWild {
@Test
public void test01(){
MyCollections.print(Arrays.asList(1,2,3));
MyCollections.print(Arrays.asList("hello","java","world"));
MyCollections.print(Arrays.asList(1.5,2.6,6.3));
}
}
5.2 类型通配符的三种使用形式
类型通配符 ? 有三种使用形式:
- <?>:完整形式为:类名<?> 或接口名<?>,此时?代表任意类型。
- <? extends 上限>:完整形式为:类名<? extends 上限类型> 或接口名<? extends 上限类型>,此时?代表上限类型本身或者上限的子类,即?代表 <= 上限的类型。
- <? super 下限>:完整形式为:类名\<? super 下限类型> 或接口名\<? super 下限类型>,此时?代表下限类型本身或者下限的父类,即?代表>= 下限的类型。
案例:
声明一个集合工具类MyCollections,要求包含:
- public static boolean different(Collection<?> c1, Collection<?> c2):比较两个Collection集合,此时两个Collection集合的泛型可以是任意类型,如果两个集合中没有相同的元素,则返回true,否则返回false。
- public static void addAll(Collection<? super T> c1, T… args):可以将任意类型的多个对象添加到一个Collection集合中,此时要求Collection集合的泛型指定必须>=元素类型。
- public static void copy(Collection<? super T> dest,Collection<? extends T> src):可以将一个Collection集合的元素复制到另一个Collection集合中,此时要求原Collection泛型的类型<=目标Collection的泛型类型。
package com.atguigu.wildcard;
import java.util.Collection;
public class MyCollections {
public static boolean different(Collection<?> c1, Collection<?> c2){
return c1.containsAll(c2) && c2.containsAll(c1);
}
public static <T> void addAll(Collection<? super T> c1, T... args){
for (int i = 0; i < args.length; i++) {
c1.add(args[i]);
}
}
public static <T> void copy(Collection<? super T> dest,Collection<? extends T> src){
for (T t : src) {
dest.add(t);
}
}
}
测试类
package com.atguigu.wildcard;
import java.util.ArrayList;
import java.util.Collection;
public class MyCollectionsTest {
public static void main(String[] args) {
Collection<Integer> c1 = new ArrayList<Integer>();
MyCollections.addAll(c1,1,2,3,4,5);
System.out.println("c1 = " + c1);
Collection<String> c2 = new ArrayList<String>();
MyCollections.addAll(c2,"hello","java","world");
System.out.println("c2 = " + c2);
System.out.println("c1 != c2 " + MyCollections.different(c1, c2));
Collection<Object> c3 = new ArrayList<>();
MyCollections.copy(c3,c1);
MyCollections.copy(c3,c2);
System.out.println("c3 = " + c3);
}
}
5.3 泛型变量T与通配符?区别
1、T可以单独使用,而?必须依赖于泛型类或泛型接口使用
package com.atguigu.generic.wild;
import java.util.ArrayList;
public class TestDifferent1 {
public static <T> void test1(T t){
System.out.println(t);
}
// public static void test2(? t){//错误
public static void test2(ArrayList<?> list){
System.out.println(list);
}
}
2、T只能在声明时指定上限,?可以指定上限和下限
package com.atguigu.generic.wild;
import java.util.ArrayList;
public class TestDifferent2 {
public static <T extends Number> void test1(T t){
System.out.println(t);
}
public static void test2(ArrayList<? extends Number> list){
System.out.println(list);
}
public static void test3(ArrayList<? super Number> list){
System.out.println(list);
}
}
3、同一个方法中多个T代表相同的类型,多个?没有关联性
package com.atguigu.generic.wild;
import java.util.Collection;
public class TestDifferent3 {
public static <T> void test1(Collection<T> c1, Collection<T> c2){
c1.addAll(c2);
//c1和c2的<T>是同一个类型
}
public static <T> void test2(Collection<? super T> c1, Collection<? extends T> c2){
c1.addAll(c2);
//c1和c2的<T>是同一个类型
}
public static void test3(Collection<?> c1, Collection<?> c2) {
// c1.addAll(c2);//报错
//c1和c2的<?>没有关联
}
}
4、T是可以确定的类型,?是不能确定的类型
package com.atguigu.generic.wild;
import java.util.Arrays;
import java.util.Collection;
public class TestDifferent4 {
public static <T> void test1(Collection<T> coll,T t){
coll.add(t);//coll可以添加T或T的子类对象
for (T element : coll) {
System.out.println(element);
}
}
public static void test2(Collection<?> coll){
// coll.add("hello");
// coll.add(1);
// coll.add(1.0);
/*
上面所有添加操作都报错。
为什么?
因为<?>表示未知的类型,集合的元素是不确定的,那么添加任意类型对象都有风险。
void add(E t)方法无法正常使用
因为此时E由?表示,即表示直到add方法被调用时,E的类型仍然不确定,所以该方法无法正常使用
*/
coll = Arrays.asList("hello","java","world");
for (Object o : coll) {
System.out.println(o);
}
}
public static void test3(Collection<? extends Number> coll){
// coll.add(1);
// coll.add(1.0);
// coll.add("hello");
/*
上面所有添加操作都报错。
为什么?
因为<?>表示未知的类型,代表<=Number的任意一种
void add(E t)方法无法正常使用
因为此时<E>由<? extends Number>表示,即表示直到add方法被调用时,E的类型仍然不确定,所以该方法无法正常使用。它可以是<=Number的任意一种类型。
*/
coll = Arrays.asList(1,2,3.0);
for (Object o : coll) {
System.out.println(o);
}
}
public static void test4(Collection<? super Number> coll){
coll.add(1);
coll.add(1.0);
// coll.add("hello");
/*
前两个可以,最后一个不行
<? super Number>代表>=Number类型。最小可能是Number。
//可以添加Number对象或Number子类对象
*/
}
public static void test5(Collection coll){
//coll添加任意类型的对象都可以
coll.add(1);
coll.add(1.0);
coll.add("hello");
for (Object o : coll) {
System.out.println(o);
}
}
}