一,java泛型简介
java泛型避免了运行期错误,在编译时对代码进行了检查。在新代码中我们不应该使用原生态类型,否则就失掉了泛型在安全性和表述性方面的所有优势。
泛型不像数组,可以支持协变。比如Apple是Fruit的子类型。但是List不是List的子类型。这个问题怎么解决,我们将在五六小节详细说明。
再一方面,java里是没有真正的泛型的,所以java的泛型也叫做伪泛型。泛型在运行期都会擦除。这个知识点我们将在泛型擦除这一小节详细介绍。
二,学会使用泛型
举一个effective版中的一个简单例子。
首先,这是一个简单的堆栈实现,代码如下
package stackDemo;
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULLT_INITIAL_CAPACITY = 16;
public Stack(Object[] elements) {
elements = new Object[DEFAULLT_INITIAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if(size == 0){
throw new EmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
public void ensureCapacity() {
if(elements.length == size){
elements = Arrays.copyOf(elements,2*size+1);
}
}
}
将类泛型化的第一个步骤是给它的声明添加一个或者多个类型参数。下一步是用相应的类型参数替换所有的Object类型。
修改后,代码如下
package stackDemo;
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack {
private E[] elements;
private int size = 0;
private static final int DEFAULLT_INITIAL_CAPACITY = 16;
public Stack(E[] elements) {
elements = new E[DEFAULLT_INITIAL_CAPACITY];
}
public void push(E e){
ensureCapacity();
elements[size++] = e;
}
public E pop() {
if(size == 0){
throw new EmptyStackException();
}
E result = elements[--size];
elements[size] = null;
return result;
}
public void ensureCapacity() {
if(elements.length == size){
elements = Arrays.copyOf(elements,2*size+1);
}
}
}
这里呢,编译器会产生一个错误
elements = new E[DEFAULLT_INITIAL_CAPACITY];
stackDemo/Stack.java:13
因为我们不能创建不可具体化类型的数组,数组是不支持泛型的。effective java一书中给出了两种解决方案,这里只写第二种。
将elements域的类型从E[]改为Object[]。
然后将pop方法改动如下
@SuppressWarnings("unchecked")
E result = (E)elements[--size];
三,泛型方法
首先,泛型方法能够独立于类而产生变化,是否是泛型方法与其所在的类是否是泛型没关系。
其次,无论何时,只要能够做到,就应该尽量使用泛型方法。另外,对于static方法而言,无法访问泛型类的类型参数。所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
简单举两个泛型方法的例子来加深大家的理解
public static void union(Set s1,Set s2){
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
public static T max(List list){
Iterator i = list.iterator();
T result = i.next();
while(i.hasNext()){
T t = i.next();
if(t.compareTo(result) > 0){
result = t;
}
}
return result;
}
四,泛型擦除
泛型技术在C#和java中有着根本性的分歧。C#里面的泛型无论是在源码中或是运行期的CLR中,都是切实存在的。List与List就是不同的类型。它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型称为真实泛型。
java语言中的泛型则不同,它只在程序源码中存在,在编译后的字节码文件中就已经替换为原来的原生类型。所以java语言的泛型技术本质上来说就是一颗语法糖。
泛型擦除,一般来说类型参数将会被替换为Object类型,但如果是下面这种情况
Class A
那么泛型类型参数将擦除到它的第一个边界。(可能会有多个边界)
再一点,泛型擦除的代价是显著的,因为它不能显示地引用运行时类型的操作中,例如转型,instance of和new 表达式。因为所有关于参数的类型信息都丢失了。所以我们写代码时必须时刻提醒自己,我们只是看起来好像拥有有关参数的类型信息而已。
擦除既然有缺陷,java也相应的对它进行了补偿
例如,我们不能用instanceof来判断类型,因为其类型信息已被擦除。我们可以转而使用动态的isInstance()。
例子如下
class Building{}
class House extends Building{}
public classs ClassTypeCapture{
Class kind;
public ClassTypeCapture(Class kind){
this.kind = kind;
}
public boolean f(Object arg){
return kind.isInstance(arg);
}
public static void main(String[] args){
ClassTypeCapture ctt1 = new ClassTypeCapture(Building.class);
system.out.println(cttq1.f(new Building()));
system.out.println(cttq1.f(new House()));
}
true
true
}
java中对于new T()这种尝试将无法实现,一部分原因是因为擦除,另一部分原因是因为编译器不能验证T具有默认(无参)构造器。但是在C#中,这种操作很自然,并且很安全。
java中的解决方案是传递一个工厂对象,并使用它来创建新的实例。最方便的工厂对象就是Class对象,因此如果使用类型标签,那么你就可以使用newInstance()来创建这个类型的新对象。
class ClassAsFactory{
T x;
public ClassAsFactory(Class kind){
try{
x = kind.newInstance();
}catch(Exception e){
throw new RuntimeException(e);
}
}
}
class Employee{}
public class InstantiateGenericType{
public static void main(String[] args){
ClassAsFactory fe = new ClassAsFactory(Employee.class);
}
}
再一个方式是使用显示的工厂,并限制其类型,这种方式就是类似Spring中的FactoryBean。实现如下
Interface Factory{
T create();
}
class Foo2{
private T x;
public > foo2(F factory){
x = factory.create();
}
//........
}
class IntegerFactory implements Factory{
public Integer create(){
return new Integer(0);
}
}
class Widget{
public static class Factory implements Factory{
pulic Widget create(){
return new Widget();
}
}
}
public class FactoryConstraint{
public static void main(String[] args){
new Foo2(new IntegerFactory());
new Foo2(new Widget.Factory());
}
}
五,泛型边界
边界使得你可以在泛型的类型参数上设置限制条件,因为泛型擦除,所以无界泛型参数只可以调用Object的方法。但是,如果能够将这个参数限制为某个类型子集,那么你就可以用这些类型子集来调用方法。为了执行这种限制,Java泛型重用了extends关键字。
package com.zy.test;
import java.awt.Color;
public class BasicBounds {
public static void main(String[] args) {
Solid solid = new Solid(new Bounded());
System.out.println(solid.color());
System.out.println(solid.getY());
System.out.println(solid.weight());
}
}
interface HasColor {
java.awt.Color getColor();
}
class Colored {
T item;
Colored(T item) {
this.item = item;
}
T getItem() {
return item;
}
java.awt.Color color() {
return item.getColor();
}
}
class Dimension {
public int x, y, z;
}
class ColoredDimension {// 类和接口都是放进来
T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
ColoredDimension(T item) {
this.item = item;
}
java.awt.Color color() {
return item.getColor();
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
}
interface Weight {
int weight();
}
class Solid {
T item;
Solid(T item) {
this.item = item;
}
public T getItem() {
return item;
}
java.awt.Color color() {
return item.getColor();
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
int weight() {
return item.weight();
}
}
class Bounded extends Dimension implements HasColor, Weight {
@Override
public int weight() {
return 0;
}
@Override
public Color getColor() {
return null;
}
}
六,泛型通配符
通配符分为三种
上界通配符,下界通配符,无界通配符
先讲有界通配符,简单来说,有这么两点
1.上界 extends T>不能往里存,只能往外取
2.下界 super T>往外取只能赋值给Object变量,不影响往里存
我们在本文一开始就说过,泛型和数组不一样,是不支持协变的。而通配符则能够在两个类型之间建立某种向上的转型关系。它能够给我们以最大限度的灵活性。
那我们怎样去使用有界通配符呢?在effective java中给出了一种简单高效的方法 名为"PECS",即 producer- extends ,consumer -super。
换句话说,如果参数化类型表示一个T生产者,就使用 extends T>;如果它表示一个T消费者,就使用 super T>。
举个例子
public static Set union (Set s1,Set s2){
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
s1和s2着两个参数都是生产者,根据PECS规则,这个声明应该是
public static Set union (Set extends E> s1,Set extends E> s2)
书中提到,Comparable始终是消费者,因此使用时始终应该是Comparable super T>优先于Comparable。对于Comparator也一样。
举例如下
public static > T max(List extends T> list){
Iterator extends T> t = list.iterator();
T result = t.next();
while (t.hasNext()){
T i = t.next();
if(i.compareTo(result) > 0){
result = i;
}
}
return result;
}