一、泛型概述
在没有泛型之前,一旦把一个对象放到Java集合中,集合就会忘记对象的类型,把所有对象都当做Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换,这种类型转换不仅使代码臃肿,而且很容易引起ClassCastExeception异常。在Java5引入泛型机制后,在集合接口、类后增加一对尖括号,在括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象,在编译时检查集合中元素的类型,如果试图向集合中添加不满足要求类型的对象,编译器就会提示错误。增加泛型后,可以使程序更加简洁,程序更加健壮(Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastExeception异常)。
Java5以后,引入了“参数化类型(parameterized type)”的概念,允许程序在创建集合时指定集合元素的类型,Java的参数化类型被称为泛型(Generic)。
注:泛型只在编译时起作用,即使用了泛型后,对象的编译时类型改变,但实际运行时类型没有改变。
二、泛型入门
1.不使用泛型的缺点
public class Test {public static void main(String[] args) {List mylist = new ArrayList();//创建List类型集合mylist.add("A");mylist.add(100);mylist.add(true);for (int i = 0; i < mylist.size(); i++) {Object o = mylist.get(i);String s = (String) o;//编译无问题,但运行时报错System.out.println(s);}}}//java.lang.ClassCastException异常
//不使用泛型机制,分析程序存在的缺点class Animal{public void move(){System.out.println("移动...");//父类自带方法}}class Cat extends Animal{public void catchMouse(){System.out.println("猫抓老鼠...");//子类特有方法}}class Bird extends Animal{public void fly(){System.out.println("鸟儿在飞...");//子类特有方法}}public class Test {public static void main(String[] args) {//创建一个只想保存Animal对象的ListList myList = new ArrayList();myList.add(new Cat());myList.add(new Bird());myList.add("A");myList.add(1);//还是可以把其他类型放进集合,因为不使用泛型时,//Java把集合元素都当作Object类型处理//遍历集合,取出每个Animal,让它moveIterator it = myList.iterator();while (it.hasNext()){//没有这个语法,通过迭代器取出的对象是Object类型//Animal obj = it.next();Object obj = it.next();//obj.move();obj里没有move()方法,不能直接调用,需要向下转型。if (obj instanceof Animal){Animal animal = (Animal) obj;animal.move();}}//遍历集合,取出Cat抓老鼠,Bird让它飞Iterator it1 = myList.iterator();while (it1.hasNext()){Object obj = it1.next();if (obj instanceof Cat){Cat c = (Cat) obj;c.catchMouse();}if (obj instanceof Bird){Bird b = (Bird) obj;b.fly();}}}}
2.使用泛型后改进的程序
class Animal{public void move(){System.out.println("移动...");//父类自带方法}}class Cat extends Animal{public void catchMouse(){System.out.println("猫抓老鼠...");//子类特有方法}}class Bird extends Animal{public void fly(){System.out.println("鸟儿在飞...");//子类特有方法}}public class Test {public static void main(String[] args) {//使用List<Animal>后,List集合里只能保存Animal对象List<Animal> myList = new ArrayList<Animal>();myList.add(new Cat());myList.add(new Bird());//myList.add("A");//报错//myList.add(1);//报错//Iterator<Animal>表示迭代器迭代的是Animal类型Iterator<Animal> it = myList.iterator();while (it.hasNext()) {Animal obj = it.next();//迭代器返回的是Animal类型obj.move();//不需要强转,可以直接调用move方法}//遍历集合,取出Cat抓老鼠,Bird让它飞Iterator it1 = myList.iterator();while (it1.hasNext()){Object obj = it1.next();if (obj instanceof Cat){Cat c = (Cat) obj;c.catchMouse();}if (obj instanceof Bird){Bird b = (Bird) obj;b.fly();}}}}
3.泛型优缺点
3.1 优点
- 集合中存储的元素更加统一。
从集合中取出的元素类型是泛型指定的类型,不需要进行大量的向下转型。
3.2 缺点
导致集合中存储的元素缺乏多样性。大多数业务中,集合中元素类型比较统一,所以泛型特性比大家所认可。
4.自动类型推断机制(钻石表达式、菱形语法)
public class Test {public static void main(String[] args) {//Java7以前,使用带泛型的接口、类定义变量时,在构造器后面也必须带泛型List<String> strList = new ArrayList<String>();Map<String, Integer> scores = new HashMap<String, Integer>();//Java7以后,Java允许在构造器后不需要带完整的泛型信息,只给出<>即可List<String> strList1 = new ArrayList<>();Map<String, Integer> scores1 = new HashMap<>();}}
三、深入泛型
1.泛型接口、类
1.1 泛型类、接口定义语法
class/interface 类名/接口名<泛型标识,泛型标识,...>{private 泛型标识 变量名;public 泛型标识 方法名(泛型标识 形参){//方法体}}
泛型标识只是个形参名,理论上可以随便自定义,常用E、T、K、V。
1.2 泛型使用语法
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();//Java7之后可以进行自动类型推断,在new对象时具体数据类型可以省略类名<具体的数据类型> 对象名 = new 类名<>();
1.3 自定义泛型类、接口
下面是Java5改写后的List接口、Iterator接口、Map的代码片段。
//定义接口时指定了一个泛型形参,该形参名为Epublic interface List<E>{//在该接口里,E可作为类型使用void add(E x);Iterator<E> interator();}public interface Iterator<E>{E next();boolean hasNext();}//定义接口时指定了两个泛型形参,其形参名为K,Vpublic interface Map<K, V>{//在该接口里,K,V可作为类型使用Set<K> keySet();V put(K key, V value);}
泛型实质:允许在定义接口、类时声明泛型形参,泛型形参在整个接口、类体内可当成类型使用。
public class Test<泛型形参随便写>{private 泛型形参随便写 x;void fun(泛型形参随便写 o){System.out.println(o);}public static void main(String[] args) {Test<String> str = new Test<>();str.fun("abc");}}
1.4 泛型注意事项
使用泛型类时,如果没有指定具体的数据类型,默认类型是Object。
- 泛型的数据类型只能是类类型,不能是基本数据类型。
不管为泛型形参传入哪一种数据类型实参,本质上都是同一个类,所以在静态方法、静态初始化块或者静态变量(它们都是类相关的)的声明和初始化中不允许使用泛型形参。
List<int> list = new List<int>();//报错,不能是基本数据类型
泛型类型在逻辑上可以看作是多个不同的类型,但本质上仍是同一类型。即同一泛型类,根据不同数据类型创建的对象,本质上还是同一类型—泛型类类型。
public class Test<泛型形参随便写>{private 泛型形参随便写 x;void fun(泛型形参随便写 o){System.out.println(o);}public static void main(String[] args) {Test<String> a = new Test<>();Test<Integer> b = new Test<>();System.out.println(a.getClass());System.out.println(b.getClass());System.out.println(a.getClass() == b.getClass());}}//class com.sundegan.Test//class com.sundegan.Test//true
1.5 泛型类派生子类
子类也是泛型类,子类和父类的泛型标识要保持一致
class ChildGeneric<T> extends Generic<T>class ChildGeneric<T,K,V> extends Generic<T>//子类进行泛型扩展,至少有一个泛型标识和父类一致
子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>
1.6 泛型接口的使用
实现类不是泛型类,接口要明确数据类型
实现类也是泛型类,实现类和泛型的接口数据类型要一致
public interface Test<T> {T getKey();}//1.实现类不是泛型类,泛型接口需明确数据类型,如不明确则默认为Object类型class A implements Test<String>{@Overridepublic String getKey() {return "Hello";}}//2.实现类是泛型类,泛型标识需保持一致class B<T,E> implements Test<T>{private T key;private E value;@Overridepublic T getKey() {return key;}public E getValue() {return value;}}
2.泛型方法
在定义类、接口时可以使用泛型形参,在该类的方法定义和成员变量定义、接口的方法定义中,这些泛型形参可以被当成普通类型来用。在定义类、接口时如果没有定义泛型形参,在定义方法时也可以自己定义泛型形参,这就是泛型方法。
2.1 泛型方法定义
修饰符 <T,E,K,V,... > 返回值类型 方法名(形参列表){ 方法体... }修饰符和返回值类型之间的
非常重要,可以理解为声明此方法为泛型方法。 - 只有声明了
的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法。 表明将使用泛型类型T,此时才可以在方法中使用泛型类型T。 public class Test<E> { //泛型方法 public <T> void getValue(ArrayList<T> list){ for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } public static void main(String[] args) { Test<Integer> t = new Test(); ArrayList<String> strList = new ArrayList<>(); strList.add("a"); strList.add("b"); strList.add("c"); t.getValue(strList);//泛型方法的调用,类型是在调用方法时来指定的。 } }public class Test { //静态泛型方法,采用多个泛型类型 public static <E,T,K> void printType(E e, T t, K k){ System.out.println(e + "\t" + e.getClass().getSimpleName()); System.out.println(t + "\t" + t.getClass().getSimpleName()); System.out.println(k + "\t" + k.getClass().getSimpleName()); } public static void main(String[] args) { Test.printType(100, "Java", true);//调用时确定具体类型,非常灵活 } }2.2 可变参数泛型方法
public <E> void print(E... e) { for (E e1 : e) { System.out.println(e1); } }public class Test { //可变参数泛型方法 public static <E> void print(E... e) { for (E e1 : e) { System.out.println(e1); } } public static void main(String[] args) { Test.print(1,2,3); Test.print("a","b","c"); } }2.3 注意事项
泛型方法的具体数据类型是由调用该方法时确定的,和泛型类的数据类型无关系。
泛型方法的泛型类型独立于泛型类,泛型方法的泛型标识和泛型类的泛型标识无关系,两者独立,因此,一个泛型方法可以操作多种数据类型,具有多样性,更加灵活。
public class Test<E> { //泛型方法,即使和泛型类的泛型标识一样,但两者相互独立 public <E> void getValue(ArrayList<E> list){ for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } //泛型类的成员方法 public void show(E e){ System.out.println(e); } public static void main(String[] args) { Test<Integer> t = new Test(); ArrayList<String> strList = new ArrayList<>(); strList.add("a"); strList.add("b"); strList.add("c"); t.getValue(strList);//泛型方法的调用,类型是由调用方法时来指定的。 } }泛型类的成员方法,在其中使用的泛型数据类型和泛型类数据类型是一致的,由泛型类的数据类型确定。
3.类型通配符
类型通配符是指使用“?”代替具体的类型实参,所以,类型通配符是类型实参,而不是类型形参,可以代替任意类型。
3.1 类型通配符的上限
类/接口<? extends 实参类型>表示该类/接口的泛型类型只能是这个实参类型或该实参类型的子类类型。
3.2 类型通配符的下限
类/接口<? super 实参类型>表示该类/接口的泛型类型只能是这个实参类型或该实参类型的父类类型。
3.3 设定泛型形参的上限
Java不仅允许在使用通配符形参时设定上限,而且可以在定义泛型形参时设定上限,表示传给该泛型形参的实际类型要么是该上限类型,要么是该上限类型的子类类型。
public class Test<T extends Number>{ public static void main(String[] args){ Test<Integer> i = new Test<>(); Test<Double> d = new Test<>(); //Test<String> s = new Test<>();//String不是Number的子类型,编译错误4.类型擦除与转换
泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型有关的信息会被擦除,我们称之为类型擦除。



5.泛型与数组
可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象。
可以通过java.lang.reflect.Array的newInstance(Class
, int)创建T[ ]数组。 public class Test { public static void main(String[] args) { //不能直接创建泛型数组对象 //ArrayList<String>[] list = new ArrayList<String>[5]; ArrayList[] list = new ArrayList[5]; ArrayList<String>[] strList = list; ArrayList<Integer> intList = new ArrayList<>(); intList.add(1); list[0] = intList; String s = strList[0].get(0);//ClassCastException异常 } } //ClassCastException尽量不要使用泛型数组,使用泛型集合代替泛型数组。
