泛型

2017/01/07 Java

泛型

早期的Object类型可以接收任意的对象类型,但是在实际的使用中,会有类型转换的问题。也就存在这隐患,所以Java提供了泛型来解决这个安全问题。

泛型是一种特殊的类型,它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行。 也被称为参数化类型,可以把类型当作参数一样传递过来,在传递过来之前我不明确,但是在使用的时候我就用明确了。 泛型数据类型只能是引用类型

泛型的好处

  • 提高了程序的安全性
  • 将运行期遇到的问题转移到了编译期
  • 省去了类型强转的麻烦

泛型术语

  • 整个称为ArrayList泛型类型
  • ArrayList中的E称为类型变量或类型参数
  • 整个ArrayList称为参数化的类型
  • ArrayList中的Integer称为类型参数的实例或实际类型参数
  • ArrayList中的<>读作typeof
  • ArrayList称为原始类型

泛型通配符

  • 泛型通配符<?>
    • 任意类型,如果没有明确,那么就是Object以及任意的Java类了
  • ? extends E
    • 向下限定,E及其子类
  • ? super E
    • 向上限定,E及其父类
class Animal {

}
class Dog extends Animal {

}
class Cat extends Animal {

}

public class Collection {
	public static void main(String[] args) {
		// 泛型如果明确的写的时候,前后必须一致
		Collection<Object> c1 = new ArrayList<Object>();
		// Collection<Object> c2 = new ArrayList<Animal>();
		// Collection<Object> c3 = new ArrayList<Dog>();
		// Collection<Object> c4 = new ArrayList<Cat>();

		// ?表示任意的类型都是可以的
		Collection<?> c5 = new ArrayList<Object>();
		Collection<?> c6 = new ArrayList<Animal>();
		Collection<?> c7 = new ArrayList<Dog>();
		Collection<?> c8 = new ArrayList<Cat>();

		// ? extends E:向下限定,E及其子类
		// Collection<? extends Animal> c9 = new ArrayList<Object>();
		Collection<? extends Animal> c10 = new ArrayList<Animal>();
		Collection<? extends Animal> c11 = new ArrayList<Dog>();
		Collection<? extends Animal> c12 = new ArrayList<Cat>();

		// ? super E:向上限定,E极其父类
		Collection<? super Animal> c13 = new ArrayList<Object>();
		Collection<? super Animal> c14 = new ArrayList<Animal>();
		// Collection<? super Animal> c15 = new ArrayList<Dog>();
		// Collection<? super Animal> c16 = new ArrayList<Cat>();
	}
	//可以使用&符号多重限定
	<T extends Serializable & Cloneable> void deepClone(T t){
    }

    //异常采用泛型
    private static <T extends Exception> sayHello() throws T{
		try{
		}catch(Exception e){// 这里不能用T
		   throw (T)e;
		}
	}
}

通配符问题

定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,该方法如何定义呢?

//错误方式:
public static void printCollection(Collection<Object> cols) {
	for(Object obj:cols) {
		System.out.println(obj);
	}
	cols.add("string");//没错
	//cols = new HashSet<Date>();//错误
}
// 正确方式:
public static void printCollection(Collection<?> cols) {
	for(Object obj:cols) {
		System.out.println(obj);
	}
	//cols.add("string");//错误,因为它不知自己未来匹配就一定是String
	cols.size();//没错,此方法与类型参数没有关系
	cols = new HashSet<Date>();
}

Collection 中的Object只是说明Collection 实例对象中的方法接受的参数是Object
Collection 是一种具体类型,new HashSet也是一种具体类型,两者没有兼容性问题。

Collection<?> a可以与任意参数化的类型匹配,但到底匹配的是什么类型,只有以后才知道
所以a=new ArrayList和a=new ArrayList都可以,但a.add(new Date())或a.add(“abc”)都不行

Vector<? extends Number> y = new Vector<Integer>();
Vector<Number> x = y;

上面的代码错误,原理与Vector v = new Vector();相似 只能通过强制类型转换方式来赋值。

定义泛型类型

  • 在对泛型类型进行参数化时,类型参数的实例必须是引用类型,不能是基本类型。
  • 当一个变量被声明为泛型时,只能被实例变量、方法和内部类调用,而不能被静态变量和静态方法调用。因为静态成员是被所有参数化的类所共享的,所以静态成员不应该有类级别的类型参数。
public class GenericDao<T> {
	private T field;
	public void save(T obj){}
	public T getById(String id){}
}

类中只有一个方法需要使用泛型,是使用类级别的泛型,还是使用方法级别的泛型?

类级别

泛型擦除

  • Java中的泛型类型(或者泛型)类似于 C++ 中的模板。但是这种相似性仅限于表面,Java 语言中的泛型基本上完全是在编译器中实现,用于编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码,这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。这是因为扩展虚拟机指令集来支持泛型被认为是无法接受的,这会为 Java 厂商升级其 JVM 造成难以逾越的障碍。所以,java的泛型采用了可以完全在编译器中实现的擦除方法。
  • 泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉“类型”信息,使程序运行效率不受影响,对于参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
  • 泛型是编译期状态,所有的泛型都会被编译器擦除,变成Object类型,编译器会在适当的位置增加强转返回正确的数据类型。
System.out.println(new ArrayList<Integer>().getClass().getName());// java.util.ArrayList
System.out.println(new ArrayList<String>().getClass().getName());// java.util.ArrayList

List<String> list = new ArrayList<String>();
list.add("test");
String str = list.get(0);
// 编译后
ArrayList var1 = new ArrayList();
var1.add("test");
String var2 = (String)var1.get(0);

下面这两个方法,编译器会报告错误,它不认为是两个不同的参数类型,而认为是同一种参数类型。

private static void applyGeneric(Vector<String> v){
}

private static void applyGeneric(Vector<Date> v){
}

泛型应用示例

public class GenericDao<E>  {
	public void add(E x){
	}
	
	public E findById(String id){
		return null;
	}
	
	public void delete(E obj){
	}
	
	public void delete(String id){
	}	
	
	public void update(E obj){
	}
	
	public static <E> void update2(E obj){
	}
	
	public E findByUserName(String name){
		return null;
	}
	public Set<E> findByConditions(String where){
		return null;
	}
}
// 泛型DAO
public abstract class DaoBaseImpl<T> implements DaoBase<T> {
	protected Class<T> clazz;
	public DaoBaseImpl() {
		Type type = this.getClass().getGenericSuperclass();
		ParameterizedType pt = (ParameterizedType) type;
		this.clazz = (Class) pt.getActualTypeArguments()[0];
		System.out.println("clazz = " + this.clazz);
	}
}
public class ArticleDaoImpl extends DaoBaseImpl<Article> implements ArticleDao {
}
static <E> void swap(E[] a, int i, int j) {//必须是引用类型
	E t = a[i];
	a[i] = a[j];
	a[j] = t;
}

下面的代码会报错误吗?

没有。编译器逐行编译,所以编译时期没有错误,运行时期则不能保证

Vector v1 = new Vector<String>(); //参数化类型赋值给原始类型
Vector<Object> v = v1;// 原始类型赋值给参数化类型

以上概念总结于传智播客Java基础课程

Search

    Post Directory