Java泛型
一、 泛型(Generic Programming)
作用:编写的代码可以被很多不同类型的对象所重用
- 泛型类: ArrayList,HashSet,HashMap等
- 泛型方法:Collections.binerySearch,Arrays.sort等
- 泛型接口:List,Iterator等
- 泛型的本质:参数化类型,避免类型转换,代码可复用
- 同类:
- C++的模板(Template)
- C#的泛型
二、自定义泛型类
-
泛型类设计
-
具有泛型变量的类
-
在类名后用
<T>
代表引入类型- 多个字母表示多个引入类型,如
<T,U>
等(对于字母选择的特殊说明:有些字母是约定俗成的,例如数据结构中的ArrayList<E>
,E代表Element,HashMap<K,V>
,K表示Key,V表示Value;自定义泛型类常量常用T,T表示Template) - 引入类型可以修饰成员变量/局部变量/参数/返回值
- 没有专门的Template关键字
示例:
/** * <T>引入类型可以修饰什么 */ public class Demo1<T> { //成员变量 private T data1; private T data2; //参数 public Demo1(T data1,T data2){ this.data1 = data1; this.data2 = data2; } //返回值 public T getData1() { return data1; } }
- 多个字母表示多个引入类型,如
-
-
泛型类调用
-
传入具体的类
Demo1<Integer> d1 = new Demo1<Integer>(1,2);
Demo1<Integer> d1 = new Demo1<>(1,2);
示例:
public class Demo2 { public static void main(String[] args) { Demo1<Integer> d1 = new Demo1<Integer>(1,2); int data1 = d1.getData1(); int data2 = d1.getData2(); System.out.println(data1 + "," + data2); Demo1<Integer> d2 = new Demo1<>(1,2); Demo1<Integer> d3 = getReverse(d2); System.out.println(d3.getData1() + "," + d3.getData2()); } //交换两个数据的值 //static后的第一个<T>是泛型方法制定的变量类型。 Demo1<T>代表的是返回值 public static <T> Demo1<T> getReverse(Demo1<T> d) { return new Demo1<T>(d.getData2(),d.getData1()); } }
-
-
泛型方法
-
具有泛型参数的方法
-
该方法可以在普通类/泛型类中
-
泛型方法的引入类型
<T>
位于修饰符后,返回类型前示例:
/** * 泛型方法 */ class ArrayUtil{ public static <T> T getMiddle(T...a){ return a[a.length/2]; } } public class Demo3 { //引入类型写在方法名的前面 String s1 = ArrayUtil.<String>getMiddle("abc","def","ghi"); //def //可以从后面的参数进行推断,此方法不推荐 Integer i1 = ArrayUtil.getMiddle(1,2,3); //只要不是全空的就行,否则无法推断是什么类型 String s2 = ArrayUtil.getMiddle("abc","def",null); //error 会先寻找共同的超类(Number),在进行转型 //Integer i2 = ArrayUtil.getMiddle(1,2.5f,3L); }
-
-
泛型接口
-
和泛型类相似,在类名后面加
<T>
-
T用来指定方法的返回值和参数
-
实现接口的时候,指定类型
-
T也可以是一个泛型类
示例:
/** * 泛型接口 */ //泛型接口 interface Calculator<T>{ public T add(T operator1,T operator2); } //泛型接口的子类 //实现接口的时候,要指定类型 class IntegerCalculator implements Calculator<Integer>{ @Override public Integer add(Integer operator1, Integer operator2) { return operator1 + operator2; } } public class Demo4 { public static void main(String[] args) { //当普通类使用 IntegerCalculator c1 = new IntegerCalculator(); System.out.println(c1.add(1,2)); //new出来的对象转型为父接口类型对象 Calculator<Integer> c2 = new IntegerCalculator(); System.out.println(c2.add(1,2)); } }
-
-
小结:
- 泛型类:整个类被泛型化,包括属性和方法
- 泛型方法:方法的返回值和参数类型被泛型化
- 泛型接口:子类的方法被泛型化
三、泛型类型限定
-
有时候,在特定场合下,需要对类进行限定(使用某些特定方法)
-
<T extends Comparable>
约定T必须是Comparable的子类 -
extends固定,后面可以多个,用&拼接,如
<T extends Comparable & Serializable>
-
extends限定可以有多个接口,但是只能一个类,且类必须排第一位
-
逗号隔开多个参数,
<T extends File&Clonceable,U extends Serializable>
示例:
/** * 泛型类型的限定 */ class Person implements Comparable{ int age; public Person(int age) { this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Person{" + "age=" + age + '}'; } //通过年龄进行比较 @Override public int compareTo(Object o) { Person p = (Person)o; if (this.age > p.getAge()){ return 1; } if(this.age == p.getAge()){ return 0; } return -1; } } public class Demo5 { public static void main(String[] args) { Person p1 = new Person(15); Person p2 = new Person(33); Person p3 = new Person(25); System.out.println(getMin(p1, p2, p3)); } //因为要进行比较选出最小的,所以需要限定传入的泛型类继承Comparable接口 static <T extends Comparable> T getMin(T...a){ if(null == a || a.length <= 0){ return null; } T min = a[0]; for (int i=1;i<a.length;i++){ if(min.compareTo(a[i]) > 1){ min = a[i]; } } return min; } }
-
泛型类之间的继承
pair<S>和pair<T>
没有任何关系,无论S和T之间是什么关系- 泛型类可以扩展实现其他的类,如
ArrayList<T>
实现List<T>
-
上限界定符(为了弥补上面的不足)
-
Pair<? extends S>
,表示Pair能接受的参数类型是S自身或者子类-
只能Get/不能Set,编译器只能保证出来的类型,但是不保证放入的对象是什么类型
-
例子(B、C是A的子类):
Pair<? extends A>代表Pair<A>,Pair<B>,Pair<C>等 因为 ? extends A,所以 getA()的时候肯定可以转型到A void setA(? extends A) //未知具体的类型,错误,例如B放到C里面 就像类与类之间通俗的例子:护士和学生都是人,但是护士不能强转成学生,学生也不能强转成护士
-
-
-
下限界定符
Pair<? super S>
,表示Pair能接受的参数类型是S自身或超类- 只能Set/不能Get,编译器保证放入的是S本身或超类,但是不保证取出来是什么类型
-
泛型PECS原则
- Producer Extends,Consumer Super
- 要从泛型类读取类型T的数据,并且不能写入,可以使用**?extens通配符**;(Producer Extends,泛型类是生产者,往外输出东西)
- 如果要想泛型类写入T类型数据,并且不需要读取,可以使用**?super通配符**;(Consumer Super,泛型类是消费者,往外增加东西)
- 如果既想写入又想读出,那就不同通配符
-
无限定类型的泛型
Pair<T>
,原始类型Pair<?>
无限定通配符,表示任意类型-
get方法:不能确定出来的是什么类型,只能赋值给Object
-
set方法:无法放入任何对象,甚至是Object
-
四、泛型实现的本质和约束
泛型是JDK1.5之后引入的新特性,并且JDK的版本是向后兼容的,即低版本的class文件可以在高版本的JDK上运行,因此,JVM里面没有泛型对象,而是采用擦除技术,只有普通的类和方法。
-
类型擦除
擦除泛型变量,替换为原始类型.
- 无限定替换成Object
-
例:
Public class Pair<T>{ private T first; public T getFirst(){ return first; } }
下面是替换后的:
Public class Pair<Object>{ private Object first; public Object getFirst(){ return first; } }
-
- 有限定则为第一个类型
-
例:
Public class Pair<T extends Comparable & Serializable>{ private T first; public T getFirst(){ return first; } }
替换后的:
Public class Pair<Comparable>{ private Comparable first; public Comparable getFirst(){ return first; } }
- 类型擦除后,为了保证类型的安全性,需要自动进行类型转换
-
例:
泛型变量翻译:
Fruit a = fruits.getFirst(); //上面的语句会被翻译成下面的语句 Object a1 = fruits.getFirst(); //获取出来是Object Fruit a = (Fruit)a1; //转型
泛型方法翻译:
public void setFirst(T first){ this.first = first; } //在编译以后,字节码中类型被擦除掉了,T变成了Object类型
重载泛型方法翻译(自动桥方法)
//父类中:Pair<T>类中有一个setFirst方法,传入的参数是T类型,经过翻译后,T会被替换成Object类型 //我们将父类的这个方法看作是下面这样的形式: public setFirst(Object o){} //子类的方法是 public setFirst(Integer i){}
在子类变异后的class文件中会有两个setFirst方法,一个参数类型是Integer,一个参数类型是Object,参数类型为Object的方法会调用参数类型是Integer的方法。这样一来,如果调用这个方法的是子类对象,那么会调用参数类型为Integer的,如果是父类对象,会先调用参数类型为Object的,在通过父类的方法调用参数类型为Integer的。
-
泛型的约束
- 不能使用基本(8种)类型来实例化泛型(通俗的说T必须用对象实例化)
- 运行时类查询只使用与原始类型
- 不能创建参数化类型的数组
- 可变参数警告
- 不能实例化类型变量
- 不能构造泛型数组
- …
五、Java类型协变和逆变
-
形式化定义:
A、B是类型,f( . )表示类型转换,≤表示继承关系,如A≤B,表示A继承于B
- f( . )是协变的,如果如A≤B,有f(A) ≤ f(B)
- f( . )是逆变的,如果如A≤B,有f(B) ≤ f(A)
- f( . )是不变的,当上述两种条件都不成立,则f(A)与 f(B)没有关系
- f( . )是双变的,如果如A≤B,有f(A) ≤ f(B)与f(B) ≤ f(A)同时成立
-
Java数据类型变化
-
Java数组是协变的String是Object的子类,String[]是Object[]的子类
-
Java的(原始的)泛型是不变的,
List<String>
和List<Object>
没有关系 -
泛型可以采用通配符,支持协变和逆变(PECS原则)
//A B C三类 B继承A C继承B ArrayList<? extends A> list1 = new ArrayList<B>(); //协变 ArrayList<? super B> list1 = new ArrayList<B>(); //逆变
-