1.泛型的概念
所谓泛型,就是允许在定义类, 接口 时通过一个"标识"表示类中某个属性的类型或者某个方法的返回值或形参类型.这个类型参数将在使用时确定.
2.举例
(1). 集合类在设计阶段/声明阶段不能确定这个容器到底存的是什么对象,所以在JDK5.0版本以前,只能把元素类型设计为Object类.JDK5.0时java引入了参数化类型的概念,允许我们在创建集合时指定集合元素的类型.(如List<String>表示该List只能存放String类型的数据.)
使用集合存储数据时,除了元素的类型不确定,其他部分都是确定的.
(2). java.lang.Comparable接口和java.util.Comparator接口,是用于比较对象大小的接口.这两个接口只是限制了当一个对象大于另一个对象时返回正整数,等于返回0,否则返回负整数.但并不确定是什么类型的对象比较大小.jdk5.0之前只能用Object类型表示,即麻烦(可能需要强制类型转换)又不安全(可能比较的两个对象并不是同一类型).故jdk5.0给它们增加了泛型.
3.集合在未使用泛型前可能存在的问题
- 类型不安全.add(Object obj)形参类型是Object,意味着任何类型的对象都可以被放入到集合中.
- 需要强转操作,繁琐.还可能出现异常.
4.使用说明
(1). 我们在声明完自定义泛型类后,可以在类的内部(如属性,构造器,方法)使用类的泛型.
public class Order<T>{//泛型类 T t;//属性 public Order() { } public Order(T t) {//构造器 this.t = t; } public T show() { return t;//方法 } }
(2). 我们在创建自定义泛型类的对象时,可以指明泛型参数类型.一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型.
(3). 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型都按照Object处理,但不等价于Object.
下列是ArrayList的源码,也涉及到了泛型.
(4). 泛型的指定类型必须使用引用数据类型,不能使用基本数据类型,此时只能使用包装类.
Order<int> o = new Order<>();//错 Order<Integer> o = new Order<>();//正确
(5). 除了创建泛型类的对象外,子类继承泛型类时,实现类实现泛型接口时,也可以确定泛型结构的泛型参数.如果我们在给泛型提供子类时,子类也不确定泛型的类型,则继续可以使用泛型参数,我们还可以在现有父类的泛型参数的基础上,新增泛型参数.
public class SubOrder<T1, T2> extends Order<String>{ T1 data1; T2 data2; }
5.注意事项
(1). 泛型类可能有多个参数,可以将多个参数都放到尖括号内,如(<T1, T2, T3>).
(2). 从jdk7.0开始,可以简化泛型操作 :
Order<String> o =new Order<String>();//以前
Order<String> o =new Order<>()//jdk7.0之后.
(3). 如果泛型结构是一个接口或抽象类,则不可以创建对象.//这里其实是接口/抽象类的性质.
(4). 不能使用new T[](因为此时new对象的时候并不知道T的类型,所以无法为其分配内存),但可以使用
T[] element =(T[]) new Object[10];//强制类型转换
(5). 在类/接口上声明的泛型,在本类或本接口中代表某种类型,但不可以在静态方法中使用泛型.(原因也很简单,因为静态方法随着类的加载而加载,而泛型的类型等到创建对象时才能确定,此时泛型的类型还是未知的)
(6). 异常类是不可以带泛型的.
6.泛型方法
这些并不是泛型方法,因为此时的T是泛型类的泛型参数.
//这些都不是泛型方法 public T method3() { //方法体 } public void method4(T t) { //方法体 }
正确写法
权限修饰符 <E> 返回值类型 method(参数列表){
//方法体
//<E>表明E是泛型方法的泛型参数.
}
例 :
public class GenericityMethhodTest<T> { public static <E> void method1(E[] e, int a, int b) { E temp; temp = e[a]; e[a] = e[b]; e[b] = temp; } public static <E> void method2(E[] e) { int j = e.length - 1; for (int i = 0; i < (e.length) / 2; i++) { E temp; temp = e[i]; e[i] = e[j]; e[j] = temp; j--; } } public static void main(String[] args) { Integer[] array = {1, 2, 3, 4, 5}; method1(array, 0, 4); for (Integer i : array) { System.out.println(i); } System.out.println("******************"); method2(array); for (Integer i : array) { System.out.println(i); } } }
说明 :
- 声明泛型方法时,一定要添加泛型参数<E>.
- 泛型方法在调用时,需要指明其具体的类型.
- 泛型类型是可以根据需要来声明为static.则需要在通过类调用该静态方法时指明泛型参数类型.
- 泛型方法所属的类是否是一个泛型类,其实都可以,因为可能泛型方法的泛型参数与泛型类的泛型参数并不一样.
7.泛型在继承性上的体现
(1). 类superA是A的父类,则G<superA>与G<A>的关系.
例:
public class Genericity { public static void main(){ G<String> g1 = new G<>(); G<Object> g2 = new G<>(); //如果二者是有子父类关系,则可以通过多态体现 //此处报错,说明G<String>与G<Object>不具有子父类关系 //实际上,二者是不同的,同一个级别的类 //g2 = g1; } } class G<E>{ int age; E name; }
(2). 类superA是A的父类,则superA<E>与A<E>之间的关系.
例 :
public class Genericity { public static void main(String[] args) { G<String> g3 = null; T<String> g4 = null; //G是T的父类 //此处没有报错,说明G<String>是T<String>的父类 g3 = g4; } } class G<E>{ int age; E name; } class T<E> extends G<E>{ }
8.通配符?
(1). 在阅读源码过程中,经常会看到通配符的使用.
举例 :
G<?> g1 = null; G<String> g2 = null; //父类的引用指向子类的对象,即可以将G<?>看作是G<String>类型的父类, g1 = g2;
(2). 读写数据的特点(以集合ArrayList为例)
ArrayList<?>可以看作是ArrayList<A>的父类
范围 : (-∞, +∞)
ArrayList<?> a1 = null; ArrayList<String> a2 = new ArrayList<>(); //父类的引用指向子类的对象 a1 = a2; a2.add("AAA"); a2.add("BBB"); a2.add("CCC"); //a1可以通过get方法获取值 //说明读取数据是允许的 Object o1 = a1.get(0); //而且读取的值的类型为Object类型 Object o2 = a1.get(1); //此处报错,说明写入数据是不允许的,但可以有特例 //a1.add("DDD"); //可以写入null; a1.add(null); for(Object i : a1) { System.out.println(i); } 控制台 AAA BBB CCC null
(3). 有条件限制的通配符
比如
List<? extends A> : (-∞, A]
List<? super A> : [A, +∞]
例 : A是B的父类,测试List<? extends A>
@Test public void test() { List<? extends A> l1 = null; List<A> l2 = new ArrayList<>(); l1 = l2; l2.add(new A()); l2.add(new B()); //但可以进行可读操作,且get()方法返回值的类型是A,而集合中的元素为A或A的子类的对象,可体现多态性 A a1 = l1.get(0); A a2 = l1.get(1); //报错,List<? extends A>不可以进行可写操作,但可以添加nill // l1.add(new A()); // l1.add(new B()); l1.add(null); }
测试List<? super A>
@Test public void test2() { List<? super A> l1 = null; List<A> l2 = new ArrayList<>(); l2.add(new A()); l2.add(new B()); //未报错,说明List<? super A>可以进行可读操作 //而且get()返回值的类型是Object Object object = l1.get(0); //未报错,可以添加A类及其子类B l1.add(new A()); l1.add(new B()); //报错,但不能添加除上的其他对象 //l1.add(new Object()); }