Java从入门到精通!第十二天(泛型)
十二、泛型(Generic)
1.为什么要有泛型
(1)集合容器类在声明阶段不能够确定这个容器实际存放的是什么,在JDK1.5 之前全部当成 Object 类型处理,在 JDK1.5 之后可以使用泛型来解决问题,我们可以把元素类型设计成一个参数,这个类型参数就是泛型,比如:Collection<E>,List<E>,ArrayList<E>等。
2.泛型概念
(1)所谓泛型,就是允许在定义类或接口的时候,通过一个标志来表示类中的某个属性或某个方法参数或方法的返回值类型,这个类型参数将在使用的时候确定(即传入实际的类型参数,也称为类型实参),类似于方法的形参和实参。
(2)泛型的语法:
(3)从 JDK1.5 之后,Java引入了“类型化参数”的概念,允许我们在创建集合的时候再指定集合元素的类型,比如 List<String > 表示该集合只能接收 String 类型的元素,JDK1.5 改写了集合框架中所有的接口和类,为这些接口和类增加了泛型的支持,从而可以在创建集合对象的时候传入实际的类型参数(类型实参)。
示例:不加泛型
由于没有加泛型,所有元素被当成 Object 处理,所以会有安全性问题,比如 get 元素之后进行强转就可能出现类型转换异常。
示例2:加上泛型,会把集合中的元素限定为只能是某种类型,此时没有转换异常,是安全的。
示例3:向上转型也可以向作用域其他类型一样作用域泛型
3.自定义泛型接口
(1)泛型的声明
比如:
(2)泛型的实例化
一定要在类名的后面指明泛型参数的实际类型,比如:
其中泛型 T 只能是类类型,不能是基本数据类型填充,但是可以是包装类,将一个集合中的类型限定为一个特定的数据类型,这就是泛型的核心思想。
示例1:自定义泛型
package com.edu.gen;
public class GenericDemo4 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//GenDemo<String> gen1 = new GenDemo<String>();
//在JDK1.8之后,上面的语句等效于:
GenDemo<String> gen1 = new GenDemo();//实际传递的类型是 String,那么就限定了该对象的内部的类型参数为 String
gen1.setT("第一个泛型是 String 类型");
String t = gen1.getT();
System.out.println(t);
GenDemo<Integer> gen2 = new GenDemo();//实际传递的类型是 Integer,那么就限定了该对象的内部的类型参数为 Integer
gen2.setT(20000);
Integer t2 = gen2.getT();
System.out.println(t2);
}
}
class GenDemo<T> {
private T t;//泛型可以作为成员变量的类型
//public GenDemo<T>() { //注意:构造方法中是不能指定泛型类型的
// TODO Auto-generated constructor stub
//}
public GenDemo() {
// TODO Auto-generated constructor stub
}
public GenDemo(T t) {
this.t = t;
}
//泛型还可以作为方法参数以及返回值的类型
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
@Override
public String toString() {
return "GenDemo [t=" + t + "]";
}
}
示例2:带两个类型参数的泛型
package com.edu.gen;
public class GenericDemo5 {
public static void main(String[] args) {
// TODO Auto-generated method stub
GenClass<String, Integer> gen = new GenClass();
gen.setK("键");
gen.setV(20000);
System.out.println(gen);//默认会调用它的 toString() 方法
}
}
class GenClass<K,V> {
private K k;
private V v;
public GenClass() {
// TODO Auto-generated constructor stub
}
public GenClass(K k, V v) {
this.k = k;
this.v = v;
}
public K getK() {
return k;
}
public void setK(K k) {
this.k = k;
}
public V getV() {
return v;
}
public void setV(V v) {
this.v = v;
}
@Override
public String toString() {
return "GenClass [k=" + k + ", v=" + v + "]";
}
}
示例3:使用泛型能够在编译期间就能检测出错误,而不是运行期间
修改一下:加上泛型
4.使用泛型的注意事项
(1)泛型类可能有多个类型参数,此时应该将类型参数放在尖括号中,比如<T1,T2,T3>
(2)泛型类的构造函数不能有泛型声明,即不能是如下格式:
public GenClass<E>(){},应该改为:public GenClass(){}
(3)不同的泛型不能相互赋值
(4)泛型如果不指定,将被擦除,泛型对应的类全部按照 Object 类型处理,但不等价于 Object,使用泛型的经验:要用泛型就一路用,要不用,一路都不用
(5)泛型不能使用基本数据类型,但是可以使用包装类
(6)在类/接口上声明泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型,非静态方法的参数类型和返回值类型,但是不能在静态方法中使用泛型。
(7)异常类不能使用泛型
示例:
5.泛型在继承关系中的使用
(1)父类有泛型,子类可以选择保留泛型也可以选择指定泛型的类型
示例:
6.泛型方法
(1)方法也可以是泛型方法,不管此时的类是否是泛型类,在泛型方法中可以定义泛型参数(类型参数),此时,参数的类型就是传入数据的类型
(2)泛型方法的语法:
示例:泛型方法的使用,将数组中的元素添加到集合中
package com.edu.gen;
import java.util.ArrayList;
import java.util.Collection;
public class GenericMethodDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//类型参数是 Integer
Integer[] a1 = {1,2,3,4,5,6};//自动装箱
Collection<Integer> c1 = new ArrayList<Integer>();//向上转型
fromArray2Collection(a1, c1);
for (Integer i : c1) {
System.out.print(i + " ");
}
System.out.println();
//泛型为 String
String[] a2 = {"张飞","关羽","赵云","诸葛亮","刘备"};
Collection<String> c2 = new ArrayList<String>();
fromArray2Collection(a2, c2);
for (String name : c2) {
System.out.print(name + " ");
}
//注意:使用泛型方法的时候,传递的泛型类型不一致,会出现编译错误
// fromArray2Collection(a1, c2); //编译错误
}
//泛型方法
private static <T> void fromArray2Collection(T[] a, Collection<T> c){
for (T t : a) {
c.add(t);
}
}
}
(3)泛型方法中的继承
泛型方法中的泛型可以使用继承机制,语法:
只要是类本身或类的子类或接口的实现类都可以作为泛型参数的实参传递进去
示例:
7.使用类型通配符
(1)比如List<?>,Map<?,?>,其中List<?>可以是List<String>,List<Object>等等各种泛型的父类
(2)读取List<?>的对象中的list元素的时候,永远是安全的,因为不管list中的元素是什么类型,它包含的都是Object(我们可以将?想象成Object类型)
(3)写入List<?>的时候会出现问题,唯一例外的就是添加null元素
示例:
? 不能用于泛型方法中,返回值前面:
? 也不能用于类的泛型声明:
? 也不能用于创建对象上:
8.有限制的通配符
(1)通配符指定上限:使用extends关键字,使用的时候指定泛型类型必须是某个类或接口以及它的子类或实现类,比如
(2)通配符指定下限:使用super关键字,使用时指定的类型必须是某个类的父类或接口,例如:
示例:
package com.edu.gen;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class GenericDemo10 {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Person> list1 = new ArrayList<Person>();
for (int i = 0; i < 6; i++) {
list1.add(new Person("姓名" + i, 20 + i));
}
printCollection(list1);
List<Emp> list2 = new ArrayList<Emp>();
for (int i = 0; i < 6; i++) {
list2.add(new Emp("姓名" + i, 20 + i, 10000 + i));
}
printCollection(list2);
}
//限定了泛型只能是 Person 或其子类
public static void printCollection(Collection<? extends Person> coll) {
Iterator<? extends Person> iter = coll.iterator();
while(iter.hasNext()) {
Person p = iter.next();
System.out.println(p);
}
}
}
class Person {
private String name;
private int age;
public Person() {
// TODO Auto-generated constructor stub
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
class Emp extends Person {
private int sal;
public Emp() {
// TODO Auto-generated constructor stub
}
public Emp(String name, int age, int sal) {
super(name, age);
this.sal = sal;
}
public int getSal() {
return sal;
}
public void setSal(int sal) {
this.sal = sal;
}
@Override
public String toString() {
return "Emp [sal=" + sal + ", getName()=" + getName() + ", getAge()=" + getAge() + "]";
}
}
示例2:同时 <? super Emp>就跟上例是反过来的过程,同学们自己去实现。
9.总结泛型中的通配符
(1)<?>表示通配符,类型可以是任何类型,所以不能确定具体写入的是什么类型,数据只能读不能写,例如 List<?> list;list.add("") 是会报错的。
(2)上界<? extends Fruit>,可以传Fruit本身或其子类
(3)下界<? super Apple>,可以传Apple本身或其父类
(4)上界和下界的特点
1) 上界的 list 只能 get,不能 add(确切地说不能 add 除 null 之外的对象,包括Object)
2)下界的list只能 add,不能 get。
示例:
分析:
上界<? extends Fruit> ,表示所有继承 Fruit 的子类,但是具体是哪个子类,无法确定,所以调用 add 的时候,要 add 什么类型,谁也不知道。但是 get 的时候,不管是什么子类,不管追溯多少辈,肯定有个父类是 Fruit,所以,我都可以用最大的父类 Fruit 接着,也就是把所有的子类向上转型为 Fruit。
下界<? super Apple>,表示 Apple 的所有父类,包括 Fruit,一直可以追溯到老祖宗 Object 。那么当我 add 的时候,我不能 add Apple的父类(Fruit),因为不能确定List里面存放的到底是哪个父类。但是我可以add Apple及其子类。因为不管我的子类是什么类型,它都可以向上转型为Apple及其所有的父类甚至转型为Object 。但是当我get 的时候,Apple 的父类这么多,我用什么接着呢,除了Object,其他的都接不住。
所以,归根结底可以用一句话表示,那就是编译器可以支持向上转型,但不支持向下转型。具体来讲,我可以把Apple 对象赋值给 Fruit 的引用,但是如果把 Fruit 对象赋值给 Apple 的引用就必须得用 cast。
示例3:泛型的嵌套
package com.edu.gen;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class GenericDemo12 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//泛型的嵌套
Map<String, List<Zone>> cities = new HashMap<String, List<Zone>>();
//生成区域
List<Zone> zones = new ArrayList<Zone>();
zones.add(new Zone("天府新区"));
zones.add(new Zone("双流区"));
zones.add(new Zone("青羊区"));
zones.add(new Zone("武侯区"));
zones.add(new Zone("锦江区"));
zones.add(new Zone("金牛区"));
zones.add(new Zone("高新西区"));
zones.add(new Zone("高新区"));
zones.add(new Zone("龙泉驿区"));
zones.add(new Zone("青白江区"));
zones.add(new Zone("新都区"));
zones.add(new Zone("新津区"));
zones.add(new Zone("成华区"));
zones.add(new Zone("郫都区"));
cities.put("成都市", zones);
//遍历,获取 Map 的 Entry 对象组成的 Set 集合
Set<Entry<String, List<Zone>>> entrySet = cities.entrySet();
Iterator<Entry<String, List<Zone>>> iter = entrySet.iterator();
while(iter.hasNext()) {
Entry<String, List<Zone>> entry = iter.next();
String key = entry.getKey();
List<Zone> value = entry.getValue();
System.out.println(key + "---" + value);
}
}
}
class Zone {//区域
private String name;
public Zone() {
// TODO Auto-generated constructor stub
}
public Zone(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Zone [name=" + name + "]";
}
}