Java 泛型总结

泛型指参数化类型的能力,可以定义带泛型类型的类或方法,随后编译器会用具体的类型来替换它。

在泛型产生之前,像集合的存取都是靠强制类型转换:

	public class ArrayList {
		public Object get(int i) {...}
		public void add(Object o) {...}
		...
	}
String fileName = (String) names.get(0);
这样没有进行错误检查,在运行时可能发生类型转换错误,编程时必须小心。

泛型提供了类型参数,放在一对尖括号中。像<T>这样被称为 形式泛型类型(formal generic type),随后可以用一个实际具体类型(actual concrete type)来替换,这个过程叫泛型实例化(generic instantiation)。

ArrayList<String> names = new ArrayList<String>();
类型参数String说明了List中只存放String对象,当调用get的时,不需要手动进行类型转换,编译器会帮我们完成这个过程,并返回String。当调用add时,编译器会进行检查,传入非String类型的参数,编译不能通过。

泛型的加入,使得能够在编译时而不是在运行时检测出类型错误。提高代码的可靠性和可读性。

注意, 泛型必须是引用类型,不能是基本类型,int, double这种需要用Integer,Double等替换。


泛型类: 定义一个类为泛型类型,需要将类型参数放在类名之后。

public class Pair<T> {
	private T first;
	private T second;
	
	public T getFirst() {
		return first;
	}
	public void setFirst(T first) {
		this.first = first;
	}
	public T getSecond() {
		return second;
	}
	public void setSecond(T second) {
		this.second = second;
	}
}

类型参数命名规则:E-集合,K-Key,V-Value,T-任意类型。


泛型方法:泛型方法既可以定义在普通类中,也可以定义在泛型类中。

定义一个方法为泛型类型,需要将类型参数放在方法返回类型之前:

	public static <T> T getMiddle(T[] a) {
		return a[a.length / 2];
	}
调用这个方法:

public class Test {
	public static void main(String[] args) {
		
		String names[] = {"A", "B", "C"};
		String middle = Test.<String>getMiddle(names);
	}
	...

类型参数也可以省略:

public class Test {
	public static void main(String[] args) {
		
		String names[] = {"A", "B", "C"};
		String middle = getMiddle(names);
	}
	...


类型变量的限定:
将泛型指定为另外一种泛型的子类型,这种泛型类型称为受限泛型(bounded)

<T extends Bounding Type> T和绑定类型可以是类,也可以是接口。

一个类型变量或者通配符可以有多个限定:

T extends Comparable & Serializable    

&分隔限定类型,逗号分隔类型变量:

public class Interval<T extends Comparable & Serializable , M extends Comparable> {


类型擦除:

虚拟机中没有泛型,只有普通的类和方法。虽然在代码中制定了泛型参数类型,但是执行的时候,虚拟机会执行类型擦除。

每个泛型类型都有其对应的原始类型,原始类型的名字就是删去类型参数后的泛型类型的名称。对于类/方法的类型变量,如果它有限定类型,则替换为限定类型,否则替换为Object。

类的类型擦除:

Pair<T>的原始类型:

public class Pair {
	private Object first;
	private Object second;
	
	public Object getFirst() {
		return first;
	}
	public void setFirst(Object first) {
		this.first = first;
	}
	public Object getSecond() {
		return second;
	}
	public void setSecond(Object second) {
		this.second = second;
	}
}
因为T没有限定类型,所以用Object替换。

另一个例子:

public class Interval<T extends Comparable & Serializable> {
	private T lower;
	private T upper;

	public Interval(T first, T second) {
		if (first.compareTo(second) < 0) {
			lower = first;
			upper = second;
		} else {
			lower = second;
			upper = first;
		}
	}
}
类型擦除后:

public class Interval {
	private Comparable lower;
	private Comparable upper;

	public Interval(Comparable first, Comparable second) {
		if (first.compareTo(second) < 0) {
			lower = first;
			upper = second;
		} else {
			lower = second;
			upper = first;
		}
	}
}
如果调换Comparable 和Serializable的顺序,原始类型会用Serializable替换T,在方法体内会执行Comparable  的强制类型转换,所以为了提高效率,一般应该将没有方法的接口放在最后。


方法的类型擦除:

	public static <T extends Comparable> T min(T[] a) {		
	}
擦除后:

	public static Comparable min(Comparable[] a) {		
	}

如果一个方法的返回类型是泛型变量,由于进行了类型擦除,在调用这个方法后,返回值会被进行类型强制转换:

Pair<Employee> p = ...

Employee e = p.getFirst();

擦除getFirst返回类型后将返回Object类型,虚拟机会进行强制类型转换,以上语句,会被翻译为2条虚拟机指令:

1.对原始方法getFrist的调用

2.将返回的Oject类型强制转换为Employee类型


通配泛型类型:

? extends T : 受限通配

现有方法 print输出Employee 的姓名:

public class Employee {
	
	private String name;
	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Employee" + getName();
	}
}
public class Manager extends Employee {
}
public class Test {
	public static void main(String[] args) {
		Employee employee = new Employee();
		employee.setName("joe");
		Employee employee2 = new Employee();
		employee2.setName("Tom");
		
		Pair<Employee> p = new Pair<Employee>();
		p.setFirst(employee);
		p.setSecond(employee2);
		
		print(p);
	}
	
	public static void print(Pair<Employee> p) {
		Employee first = p.getFirst();
		Employee second = p.getSecond();
		System.out.println(first.getName() + " and " + second.getName());
	}
}
尽管Manager 是Employee的子类,但是不能这样调用:
Pair<Manager> p2 = new Pair<Manager>();
		print(p2); //编译不过
这时需要使用通配符,
public static void print(Pair<? extends Employee> p) {

参数表示,任何泛型Pair类型,它的类型参数是Employee的子类。

		Pair<Manager> p2 = new Pair<Manager>();
		Manager manager = new Manager();
		manager.setName("Tim");
		Manager manager2 = new Manager();
		manager2.setName("Hank");
		
		p2.setFirst(manager);
		p2.setSecond(manager2);
		print(p2);

	public static void print(Pair<? extends Employee> p) {
		Employee first = p.getFirst();
		Employee second = p.getSecond();
		System.out.println(first.getName() + " and " + second.getName());
	}

输出: Tim and Hank
注意: Employee first = p.getFirst();可以正常调用,但是setFirst参数不能传入 Employee的实例,而只能传入Manager的实例。

因为类型参数是<? extends Employee>编译器只知道是 Employee的子类型,父类引用子类是合法的,所以可以将getFirst的返回值赋给一个Employee对象,而Employee子类不包含Employee本身,所以在set方法中不能传入Employee的实例。


? super T : 下限通配:

? super Manager 这个通配符限制为Manager及其所有超类型。

这个通配符的行为和上面相反,它可以使用Manager作为方法的参数,但是不能使用返回值:

Pair<Manager> p3 = new Pair<Manager>();
		p3.setFirst(manager);
		p3.setSecond(manager2);
		print2(p3);
	public static void print2(Pair<? super Manager> p) {
		Manager first = (Manager) p.getFirst();
		Manager second = (Manager) p.getSecond();
		System.out.println(first.getName() + " and " + second.getName());
	}
注意这里 p.getFirst()返回的是Object对象,因为 ? super Manager,编译器只知道是 Manager的超类,并不知道具体的类。这段代码只是为了说明问题,实际上是不合适的。

实际应用举例:根据提供的Manager数组参数,提供排序后的Pair对象。

	public static void minMax (Manager[] a, Pair<? super Manager> result) {
		if (a == null || a.length == 0) {
			return;
		}
		Manager min = a[0];
		Manager max = a[0];
		for (int i = 0; i < a.length; i++) {
			if (min.getName().compareTo(a[i].getName()) > 0) {
				min = a[i];
			}
			if (max.getName().compareTo(a[i].getName()) < 0) {
				max = a[i];
			}
		}
		result.setFirst(min);
		result.setSecond(max);
	}

简单说就是,下限通配可以写入,受限通配可以读取。


?:无限定通配:

像Pair<?> 这样的?通配符叫做无限定通配,使用它不需要知道实际的类型:

	public boolean hasNulls(Pair<?> p) {
		return p.getFirst() == null || p.getSecond() == null;
	}
无限定通配符也可以用具体类型变量替代:

	public <T> boolean hasNulls2(Pair<T> p) {
		return p.getFirst() == null || p.getSecond() == null;
	}
但是前者的可读性更强。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值