概述:
泛型即编写的代码可以被很多不同的对象使用,通常有泛型类(ArrayLis、HashSet、HashMap)泛型方法(Collection.binarySearch,Array.sort)、泛型接口(List、Iterator),
Iterator是一个轻量级的迭代器,iterator()要求容器返回一个Iterator,而hasNext()用来检测是否还含有元素:
ArrayList<String> stringList =new ArrayList<String>();
stringList.add("string1");
stringList.add("string2");
stringList.add("string3");
ArrayList<Integer> intList =new ArrayList<Integer>();
intList.add(1);
intList.add(2);
intList.add(3);
Iterator<String> iterString =stringList.iterator();
while(iterString.hasNext()){
System.out.println(iterString.next());
}
Iterator<Integer> iterInt =intList.iterator();
while (iterInt.hasNext()){
System.out.println(iterInt.next());
}
泛型类
泛型类是指具有泛型变量的类,在类名后用<T>表示引入类型,如果有多个字母的话代表引入多种类型<T,U>,具体用哪个字母表示并没有明确的规定,只不过我们都会遵从默认的规则。引入的类型可以用来修饰成员变量、局部变量、参数、返回值。
public class Interval <T> {
private T lower;
private T upper;
public Interval(T lower,T upper){
this.upper =upper;
this.lower =lower;
}
public T getLower() {
return lower;
}
}
对泛型类调用,在我们的菱形语法中,可以根据左侧的值退出右侧的类型,所以右侧尖括号中的类型可以省略不写:
public static void main(String[] args) {
Interval<Integer> v1 =new Interval<Integer>(1,2);
Interval<Integer> v2 =new Interval<>(3,4);
System.out.println("v1:"+v1.getLower()+","+v1.getUpper()+"; v2:"+v2.getLower()+","+v2.getUpper());
System.out.println("经过高低值交换后的情况 "+"v1:"+getReverse(v1).getLower()+","+getReverse(v1).getUpper()+"; v2:"+getReverse(v2).getLower()+","+getReverse(v2).getUpper());
}
public static <T> Interval<T> getReverse(Interval<T> interval){
return new Interval<>(interval.getUpper(),interval.getLower());
}
泛型方法
泛型方法中的<T>的位置在修饰符之后,返回类型之前,在上面代码中的getReveerse()中,以第一个<T>代表的就是整个方法的一个泛型,Interval<T>代表的是返回值类型。如果要是我们的方法中不止有一个类型U、T,而返回值中只有一个类型T,那么我们就应该写成:
public static <U,T> Interval<T> getReverse(Interval<T> interval,U temp){
System.out.println(temp.toString());
return new Interval<>(interval.getUpper(),interval.getLower());
}
泛型方法调用的具体示例:
public class ArrayUtil {
public static <T> T getMiddle(T... a){
return a[a.length/2];
}
}
// 如果菱形语法可以推断出数据的类型,那么方法前的<>可以省略不写
String s1 =ArrayUtil.<String>getMiddle("abc","def","ghi");
Integer i1 = ArrayUtil.getMiddle(1,2,3);
// 空值是被允许的
String s2 =ArrayUtil.<String>getMiddle("abc","def",null);
// 如果没有指定方法的类型,而传进去的类型又不一致,JDK会先寻找共同类,再进行转型,会存在报错
// i2报错,i3不报错;牵扯到类型转化
Integer i2 =ArrayUtil.getMiddle(1,2.5f,3L);
Object i3 =ArrayUtil.getMiddle(1,2.5f,3L);
泛型接口
泛型接口的写法与泛型类是相似的,我们在实现类中指定具体的实现类型,在泛型接口的实例中,应当注意这句代码,以及他背后的实现思路: Calculator<Interval<Integer>> c =new IntervalCalculator();
public interface Calculator<T> {
Interval<Integer> add(Interval<Integer> operand1, Interval<Integer> operand2);
}
public class Interval <T> {
private T lower;
private T upper;
public Interval(T lower,T upper){
this.upper =upper;
this.lower =lower;
}
public T getLower() {
return lower;
}
public T getUpper() {
return upper;
}
}
public class IntervalCalculator implements Calculator<Interval<Integer>>{
public static void main(String[] args) {
// 注意这种表达形式
Calculator<Interval<Integer>> c =new IntervalCalculator();
Interval<Integer> operand1 = new Interval<>(1,2);
Interval<Integer> operand2 = new Interval<>(3,4);
System.out.println("lower:"+c.add(operand1,operand2).getLower()+",upper:"+c.add(operand1,operand2).getUpper());
}
@Override
public Interval<Integer> add(Interval<Integer> operand1, Interval<Integer> operand2) {
Integer lower =operand1.getLower()+operand2.getLower();
Integer upper =operand1.getUpper()+operand2.getUpper();
Interval<Integer> operandTemp =new Interval<>(lower,upper);
return operandTemp;
}
}
自定义的泛型设计中,泛型类是整个类都被泛化,这其中包括变量和方法。泛型接口泛化的是子类的方法,泛型方法是指方法被泛化,其中包括返回值与参数。
泛型的限定
我们先假设类A集成与类B,且A、B均为泛型类/接口,类C继承与类D,但是C、D都是普通的类/接口。
在上面假设的基础上A<D>继承与B<D>,但是A<C>,A<D>之间是没有任何关系的,即A、B可能存在集成关系,他们都属于泛型类或者接口。但是作为泛型类型的C、D放在泛型类或接口之后他们之间是没有任何关系的。我了使得他们可能存在一些关系,我们可以通过A<T extend C>来指定这个泛型中的类型都是C的子类或C,也就是说A所能接受的范围应该是C的子类或C;A< T super C>来表明泛型中的类型都应该是C的父类或者C,也就是说A能就收的都是C的父类或者C。我们管第一种的叫做上线界定符,第二种叫做下限界定符。
泛型中有一个PECS(Producer Extend,Consumer Super)原则:要从泛型中读取数据但是不能写入,这时候我们可以使用 ? extends 通配符,泛型是生产者,往外输出东西;往泛型中放入数据但是不能输出,这时候我们使用 ?super 通配符,泛型是消费者,往内增加东西。如果即想写入又想读出,那么就不能使用通配符。
原始类型的<T>与无限定通配符的<?>的不同之处在于前者在编译的时候类型就是固定的,而后者是不固定的。
类型变化关系
类型变化关系分为协变、逆变、不变、双变:
Java的数组是协变的,原始泛型是不变的,但是可以通过通配符的形式类实现协变或者逆变。< ? extends A>支持协变,< ? super B>支持逆变
在JDK1.4重写方法的参数和返回值是要求一样的,但是到了JDK 1.5+ 重写方法只要求参数是一样的,返回值是可以协变的。