疯狂Java讲义_08_泛型

泛型的传参

注意
若类 Base 是类 Derived 的基类(父类),那么数组类型 Base[] 是 Derived[] 的基类(父类)。但是集合类型 List<Base> 不是 List<Derived> 的基类(父类)。

若函数里的参数使用基类接受所有的派生类,怎么做?

例如:函数里的参数要接受所有的List类,包括 List<Integer>, List<String> 等。

public class Test01 {
	
	public static void main(String[] args) {

		ArrayList<String> strList = new ArrayList<String>();
		strList.add("good");
		strList.add("man");
		strList.add("helo");
		testGenericParameter(strList);
	}
// 形参类型 可以使用  List (会有警告),List<?> (推荐,没有警告), 不可以使用 List<Object>
	private static void testGenericParameter(List<?> list) {
		for (int i = 0; i < list.size(); i++) {
			System.out.println(list.get(i));
		}
	}
}	

例如:Shape 是基类,Circle、Rectangle 是派生类
只想要接受所有Shape派生类的入参

//  ? extends Shape   表示 所有从Shape派生出的类
void testGenericParameter(List<? extends Shape> list) {

}
  • ? extends Shape 表示 所有 Shape 的派生类
  • ? super Shape 表示所有 Shape 的基类(父类)

类型通配符的上限

List<? extends Shape> 是受限制通配符的例子,此处的问号(? )代表一个未知的类型,就像前面看到的通配符一样。但是此处的这个未知类型一定是Shape的子类型(也可以是Shape本身),因此可以把Shape称为这个通配符的上限(upper bound)。
类似地,由于程序无法确定这个受限制的通配符的具体类型,所以不能把Shape对象或其子类的对象加入这个泛型集合中。例如,下面代码就是错误的
在这里插入图片描述

与使用普通通配符相似的是,shapes.add()的第二个参数类型是 ? extends Shape,它表示Shape未知的子类,程序无法确定这个类型是什么,所以无法将任何对象添加到这种集合中。
简而言之,这种指定通配符上限的集合,只能从集合中取元素(取出的元素总是上限的类型或其子类),不能向集合中添加元素(因为编译器没法确定集合元素实际是哪种子类型)。
对于更广泛的泛型类来说,指定通配符上限就是为了支持类型型变。比如Foo是Bar的子类,这样A<Foo> 就相当于A<? extends Bar> 的子类,可以将A<Foo> 赋值给A<? extends Bar> 类型的变量,这种型变方式被称为协变

对于协变的泛型而言,它只能调用泛型类型作为返回值类型的方法(编译器会将该方法返回值当成通配符上限的类型);而不能调用泛型类型作为参数的方法。 口诀是:协变只出不进!

提示:没有指定通配符上限的泛型类,相当于通配符上限是Object 。例如 List<?> 表示 类型上限是Object

类型通配符的下限

除可以指定通配符的上限之外,Java也允许指定通配符的下限,通配符的下限用<? super 类型> 的方式来指定,通配符下限的作用与通配符上限的作用恰好相反。
指定通配符的下限就是为了支持类型型变。比如Foo是Bar的子类,当程序需要一个A<? super Foo> 变量时,程序可以将A<Bar> 、A<Object> 赋值给A<? super Foo> 类型的变量,这种型变方式被称为逆变
对于逆变的泛型集合来说,编译器只知道集合元素是下限的父类型,但具体是哪种父类型则不确定。因此,这种逆变的泛型集合能向其中添加元素(因为实际赋值的集合元素总是逆变声明的父类),从集合中取元素时只能被当成Object类型处理(编译器无法确定取出的到底是哪个父类的对象)。

对于逆变的泛型而言,它只能调用泛型类型作为参数的方法;而不能调用泛型类型作为返回值类型的方法。 口诀是:逆变只进不出!

设自己实现一个工具方法:实现将src集合中的元素复制到dest集合的功能,因为dest集合可以保存src集合中的所有元素,所以dest集合元素的类型应该是src集合元素类型的父类。
对于上面的copy()方法,可以这样理解两个集合参数之间的依赖关系:不管src集合元素的类型是什么,只要dest集合元素的类型与前者相同或者是前者的父类即可,此时通配符的下限就有了用武之地。
下面程序采用通配符下限的方式来实现该copy()方法。

public class TestGenericType {
	public static void main(String[] args) {
		ArrayList<Number> list1 = new ArrayList<Number>();
		ArrayList<Integer> list2 = new ArrayList<Integer>();
		list2.add(1);
		list2.add(2);
		list2.add(3);
		Integer last = copy(list1, list2);//①
		System.out.println(list1);
	}
	
	// dest 集合里的元素的类型必须是 src 集合元素的类型 或 其父类
	public static <T> T copy(Collection<? super T> dest, Collection<T> src) {
		T last = null;
		for (T ele : src) {
			last = ele;
			//逆变的泛型集合添加元素是安全的
			dest.add(ele);
		}
		return last;
	}
}

使用这种语句,就可以保证程序的①处调用后推断出最后一个被复制的元素类型是Integer,而不是笼统的Number类型。

实际上,Java集合框架中的TreeSet< E> 有一个构造器也用到了这种设定通配符下限的语法,如下所示。

    public TreeSet(Comparator<? super E> comparator) {
        //...
    }

正如前一章所介绍的,TreeSet会对集合中的元素按自然顺序或定制顺序进行排序。如果需要TreeSet对集合中的所有元素进行定制排序,则要求TreeSet对象有一个与之关联的Comparator对象。上面构造器中的参数comparator就是进行定制排序的Comparator对象。
Comparator接口也是一个带泛型声明的接口

public interface Comparator<T> {
    int compare(T o1, T o2);
}

通过这种带下限的通配符的语法,可以在创建TreeSet对象时灵活地选择合适的Comparator。
假定需要创建一个TreeSet<String> 集合,并传入一个可以比较String大小的Comparator,这个Comparator既可以是Comparator<String> ,也可以是Comparator<Object> 只要尖括号里传入的类型是String的父类型(或它本身)即可。

	private static void test02() {
		// 既可以使用 new Comparator<Object> 因为 Object 是 String 的父类,也可以使用 Comparator<String> 作为构造器的参数
		TreeSet<String> set1 = new TreeSet<String>(new Comparator<Object>() {
			@Override
			public int compare(Object o1, Object o2) {
				return o1.hashCode() > o2.hashCode() ? 1 :
					o1.hashCode() < o2.hashCode() ? -1 : 0;
			}
		});
		set1.add("hello");
		set1.add("wa");
		TreeSet<String> set2 = new TreeSet<String>(new Comparator<String>() {
			@Override
			public int compare(String o1, String o2) {
				return o1.length() > o2.length() ? -1 :
					o1.length() < o2.length() ? 1 : 0;
			}
		});
		set2.add("hello");
		set2.add("wa");
		System.out.println(set1);
		System.out.println(set2);
	}

通过使用这种通配符下限的方式来定义TreeSet构造器的参数,就可以将所有可用的Comparator作为参数传入,从而增加了程序的活性。当然,不仅TreeSet有这种用法,TreeMap也有类似的用法,具体的请查阅Java的API文档。

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值