谈谈Java的泛型,逆变与协变、PECS



 1、泛型是什么,为什么需要使用泛型?

4nQ思考者日记网-束洋洋个人博客
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。4nQ思考者日记网-束洋洋个人博客

Java语言引入泛型的好处是安全简单。可以将运行时错误提前到编译时错误。4nQ思考者日记网-束洋洋个人博客
在java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。4nQ思考者日记网-束洋洋个人博客

4nQ思考者日记网-束洋洋个人博客
在java中随处可见泛型,比如常用的jdk里面的。4nQ思考者日记网-束洋洋个人博客
泛型接口:List、Map4nQ思考者日记网-束洋洋个人博客
泛型类:LinkedList、ArrayList、HashMap、TreeMap,他们都实现了泛型接口4nQ思考者日记网-束洋洋个人博客
泛型方法:Collections类中的sort方法,如下:4nQ思考者日记网-束洋洋个人博客
public static <T> void sort(List<T> list, Comparator<? super T> c)4nQ思考者日记网-束洋洋个人博客

定义泛型时的约束,分为2种:4nQ思考者日记网-束洋洋个人博客

  • 泛型协变:<T extends K>,表示T必须是类K的子类,或者接口K的实现类
  • 泛型逆变:<T super K>,表示T必须是类K的父类,或类K实现的接口

后面我们会举例讲解。4nQ思考者日记网-束洋洋个人博客

泛型中的标记符4nQ思考者日记网-束洋洋个人博客
可以由26个大写英文字母,主要几种:4nQ思考者日记网-束洋洋个人博客

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • ? - 表示不确定的java类型

当然,你也可以自己随便使用26个其中一个,不过为了规范,还是使用有特定含义的标记符。4nQ思考者日记网-束洋洋个人博客

2、泛型怎么用?

这里主要讲解,协变和逆变。4nQ思考者日记网-束洋洋个人博客

有个程序员类4nQ思考者日记网-束洋洋个人博客

package cn.com.shuyangyang.generic;

public class Coder {

	private String name;
	
	private String professional;
	
	public Coder(String name, String professional) {
		super();
		this.name = name;
		this.professional = professional;
	}

//……省略getter和setter方法

	@Override
	public String toString() {
		return "Coder [name=" + name + ", professional=" + professional + "]";
	}
}

有个程序员级别类,继承程序员类4nQ思考者日记网-束洋洋个人博客

package cn.com.shuyangyang.generic;

public class CoderLevel extends Coder {

	private int level;

	public CoderLevel(String name, String professional, int level) {
		super(name, professional);
		this.level = level;
	}

	//……省略getter和setter方法

	@Override
	public String toString() {
		return "CoderLevel [name=" + this.getName() + ", professional=" + this.getProfessional() + ", level=" + this.level
				+ "]";
	}
}4nQ思考者日记网-束洋洋个人博客

 4nQ思考者日记网-束洋洋个人博客

来个装饰类:4nQ思考者日记网-束洋洋个人博客

package cn.com.shuyangyang.generic;

import java.util.List;

public class Decorator<T> {

	public void addDecorator(List<T> itemList, T t) {
		itemList.add(t);
	}

	public void doDecorator(List<T> itemList) {
		for (T t : itemList) {
			System.out.println(t);
		}
	}
}

 现在来测试4nQ思考者日记网-束洋洋个人博客

package cn.com.shuyangyang.generic;

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

public class TestGeneric {

	public static void main(String[] args) {
		Decorator<Coder> decoratorA = new Decorator<Coder>();
		List<Coder> listA = new ArrayList<Coder>();
		Coder javaCoder = new Coder("Jack","JavaCoder");
		Coder phpCoder = new Coder("Tom","PHPCoder");
		decoratorA.addDecorator(listA, javaCoder);
		decoratorA.addDecorator(listA, phpCoder);
		decoratorA.doDecorator(listA);
	}
}

完整输出,一个程序员集合,里面有Jack和Tom2个人,一个搞Java,一个搞php,问题,现在又来一批有级别的程序员(CoderLevel类)需要添加到程序员集合类中。4nQ思考者日记网-束洋洋个人博客

01.jpg4nQ思考者日记网-束洋洋个人博客

我们看到程序员级别集合B无法加入到程序员集合中??我们要说,程序员级别类不是继承自程序员类吗,我们想象中肯定是可以加入到程序员集合A中的啊。4nQ思考者日记网-束洋洋个人博客
因为装饰类T传入了Coder类,而T不知道CoderLevel继承了Coder类,所以直接给你个异常:The method doDecorator(List<Coder>) in the type Decorator<Coder> is not applicable for the arguments (List<CoderLevel>)4nQ思考者日记网-束洋洋个人博客

我们修改下装饰类Decorator4nQ思考者日记网-束洋洋个人博客

public void addDecorator2(List<? super T> itemList, T t) {
		itemList.add(t);
	}

	public void doDecorator2(List<? extends T> itemList) {
		for (Object t : itemList) {
			System.out.println(t);
		}
	}

 4nQ思考者日记网-束洋洋个人博客

现在发现编译正常,可以添加了。4nQ思考者日记网-束洋洋个人博客

Decorator<CoderLevel> decoratorB = new Decorator<CoderLevel>();
		List<CoderLevel> listB = new ArrayList<CoderLevel>();
		CoderLevel coderLevel = new CoderLevel("Seteven","C++Coder",1);
		decoratorB.addDecorator2(listB, coderLevel);
		decoratorA.doDecorator2(listB);

 4nQ思考者日记网-束洋洋个人博客

当A ≦ B时,如果有f(A) ≦ f(B),那么f叫做协变;4nQ思考者日记网-束洋洋个人博客
当A ≦ B时,如果有f(B) ≦ f(A),那么f叫做逆变;
4nQ思考者日记网-束洋洋个人博客
如果上面两种关系都不成立则叫做不可变。4nQ思考者日记网-束洋洋个人博客

 4nQ思考者日记网-束洋洋个人博客

PECS

4nQ思考者日记网-束洋洋个人博客
PECS指“Producer Extends,Consumer Super”。换句话说,如果参数化类型表示一个生产者,就使用<? extends T>;如果它表示一个消费者,就使用<? super T>。4nQ思考者日记网-束洋洋个人博客
可能你看了上面还是稀里糊涂的,那到底上面时候用协变,什么时候用逆变呢?继续看个例子:4nQ思考者日记网-束洋洋个人博客

package cn.com.shuyangyang.generic;

public class Fruit {

	private String color;
	
	private String type;

	//省略getter与setter方法……
}

package cn.com.shuyangyang.generic;
public class Apple extends Fruit {

}

package cn.com.shuyangyang.generic;
public class RedApple extends Apple {

}

//定义一个水果集合,将一堆苹果放进去
List<Apple> apples = new ArrayList<Apple>();
List<Fruit> fruits = apples;//这里不允许放入
		
//让一堆苹果可以放进水果里
List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> fruits = apples;
Fruit fruit = fruits.get(0);//读取,由于编译器知道它总是Fruit的子类,所以这里可以直接获取Fruit
fruits.add(null);//这里不允许写入,只能为null
		
//让水果篮中放入苹果及苹果的子类,红苹果……
List<Fruit> fruits = new ArrayList<Fruit>();
List<? super Apple> apples = fruits;
apples.add(new Apple());
apples.add(new RedApple());
apples.add(new Fruit()); //异常,只允许加入Apple及其子类,其他超类不允许添加
apples.add(new Object());//由于编译器并不知道List的内容究竟是Apple的哪个超类,因此不允许加入任何特定的超类
Object object = apples.get(0);//读取:编译器在不知道什么类型的情况下只能返回Object

3、泛型总结

如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends)4nQ思考者日记网-束洋洋个人博客
如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super)4nQ思考者日记网-束洋洋个人博客
如果既要存又要取,那么就不要使用任何通配符。4nQ思考者日记网-束洋洋个人博客

4、扩展

4nQ思考者日记网-束洋洋个人博客
javatuples (元祖)4nQ思考者日记网-束洋洋个人博客
元组只是一系列对象,不一定以任何方式相互关联。例如:[23,“Saturn”,java.sql.Connection@li734s]可以被认为是包含一个整数,一个String和一个JDBC连接对象的三个元素的tuple。就是如此简便。4nQ思考者日记网-束洋洋个人博客

很可能,您已经为某些项目编写了tuple类,例如:4nQ思考者日记网-束洋洋个人博客
public Triplet <Integer,Integer,Integer> getYearMonthDay(){4nQ思考者日记网-束洋洋个人博客
....4nQ思考者日记网-束洋洋个人博客

...要么...4nQ思考者日记网-束洋洋个人博客
LabelValue <String,Operator> operatorLabelValue = 4nQ思考者日记网-束洋洋个人博客
新的LabelValue <String,Operator>(“> =”,Operator.MORE_OR_EQUAL_TO);4nQ思考者日记网-束洋洋个人博客

...要么...4nQ思考者日记网-束洋洋个人博客
private Map <Pair <String,Integer>,Object> objectsByTwoKeys = ...4nQ思考者日记网-束洋洋个人博客

Triplet <A,B,C >,LabelValue <A,B >和Pair <A,B >都是元组。4nQ思考者日记网-束洋洋个人博客

4nQ思考者日记网-束洋洋个人博客
javatuples为元组提供一到十个元素:4nQ思考者日记网-束洋洋个人博客

  • Unit<A> (1 节点)
  • Pair<A,B> (2 节点)
  • Triplet<A,B,C> (3 节点)
  • Quartet<A,B,C,D> (4 节点)
  • Quintet<A,B,C,D,E> (5 节点)
  • Sextet<A,B,C,D,E,F> (6 节点)
  • Septet<A,B,C,D,E,F,G> (7 节点)
  • Octet<A,B,C,D,E,F,G,H> (8 节点)
  • Ennead<A,B,C,D,E,F,G,H,I> (9 节点)
  • Decade<A,B,C,D,E,F,G,H,I,J> (10 节点)

加上一些非常常见的二元组元组,相当于对,只是为了代码语义:4nQ思考者日记网-束洋洋个人博客

KeyValue <A,B >4nQ思考者日记网-束洋洋个人博客
LabelValue <A,B >4nQ思考者日记网-束洋洋个人博客

4nQ思考者日记网-束洋洋个人博客
所有元组都是:4nQ思考者日记网-束洋洋个人博客

  • 类型安全
  • 不可变
  • 可迭代
  • 序列化
  • Comparable(implements Comparable <Tuple >)
  • 实现equals(...)和hashCode()
  • 实现toString()

具体大家可以看下官网:http://www.javatuples.org/using.html4nQ思考者日记网-束洋洋个人博客

 

(转载本站文章请注明作者和出处 思考者日记网|束洋洋个人博客 ,请勿用于任何商业用途)

『访问 思考者日记网404页面 寻找遗失儿童』

告知
  •     本站90%以上文章均属原创,部分转载已加上原作者出处。 如需转载本站文章请您务必保留本站出处!
  •     打广告评论者请自重,请为广大网友提供一个健康干净的网络空间。
  • 感谢主机屋提供网站空间;
  • 感谢万网阿里云提供域名解析;
  • 感谢EmpireCMS提供CMS系统;
  • 感谢bootstrap展示本站前端页面;
  • 感谢Glyphicons Halflings提供字体;
  • 感谢大家一直以来对本站的喜爱,感谢大家!
近期文章 建议与反馈