一、泛型的理解
1、泛型的概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
2、泛型的引入背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList <E>这个就是类型参数,即泛型。
二、泛型在集合中的使用
1、在集合中使用泛型之前的例子
在引入泛型之前,在集合中可能出现两个问题:
①问题一:无法约束存储数据的类型。
②问题二:强转时可能出现ClassCastException。
//在集合中加入泛型之前
@Test
public void test1(){
List list = new ArrayList();
//存储学生成绩
list.add(78);
list.add(89);
list.add(91);
list.add(82);
//问题一:无法约束存储数据的类型
list.add("Tom");
for(Object obj : list){
//问题二:强转时可能出现ClassCastException
int studentScore = (Integer) obj;
System.out.println(studentScore);
}
}
图示:
2、在集合中使用泛型例子
在集合中添加了泛型后,就避免了上面的两个问题
①避免了问题一,在编译时就会监测传入的数据类型。
②避免了问题二:强转问题。
//在集合中加入泛型
@Test
public void test2(){
ArrayList<Integer> list = new ArrayList<>();
list.add(78);
list.add(88);
list.add(91);
list.add(54);
//避免了问题一,在编译时就会监测传入的数据类型
// list.add("Tom");
//遍历方式1:
// for(Integer score : list){
// //避免了强转问题。
// int stuScore = score;
// System.out.println(stuScore);
// }
//遍历方式2:
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer stuScore = iterator.next();
System.out.println(stuScore);
}
}
//在Map中使用泛型
@Test
public void test3(){
HashMap<String, Integer> map = new HashMap<>();
map.put("Tom",89);
map.put("Jerry",70);
map.put("Jack",65);
map.put("Rose",97);
// map.put(123,"Bob");
Set<Map.Entry<String, Integer>> entry = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> e = iterator.next();
String key = e.getKey();
Integer value = e.getValue();
System.out.println(key + "====" + value);
}
}
图示:
3、集合中使用泛型总结
①集合类及集合接口在jdk5.0后都改为带泛型的结构。
②在实例化集合时,可以指明使用的泛型。
③指明泛型以后,凡是在集合类或接口中凡是定义类或实现接口时,内部(属性、方法、构造器等结构)使用了类的泛型,那么都指定为实例化的泛型。
比如:add(E e);—>add(Integer e);
④泛型的类型都是类,不能使用基础数据类型,当需要使用基本数据类型时,可以使用包装类代替。
⑤在没指明泛型时,系统默认使用java.lang.Object类型。
三、自定义泛型结构
1、引入
1.1、泛型的声明
interface List<T> 和 class GenTest<K,V> 其中,T,K,V不代表值,而是表示类型。这里使用任意字母都可以。常用T表示,是Type的缩写。
1.2、泛型的实例化
一定要在类名后面指定类型参数的值(类型)。如:
List<String> strList = new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator();
T只能是类,不能用基本数据类型填充。但可以使用包装类填充。
把一个集合中的内容限制为一个特定的数据类型,这就是generics背后的核心思想。
2、自定义泛型类、泛型接口
2.1、泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
2.2、泛型类的构造器如下:public GenericClass(){}。而下面是错误的:public GenericClass<E>(){}
2.3、实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
2.4、泛型不同的引用不能相互赋值。尽管在编译时ArrayList<String>和ArrayList<Integer>是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
2.5、泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
2.6、如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
2.7、jdk1.7,泛型的简化操作:ArrayList<Fruit> flist = new ArrayList<>();
2.8、泛型的指定中不能使用基本数据类型,可以使用包装类替换。
2.9、在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
2.10、异常类不能是泛型的。
2.11、不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
2.12、父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
①子类不保留父类的泛型:按需实现
②子类保留父类的泛型:泛型子类 结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自 己的泛型。
代码举例:
public class Order<T> {
String orderName;
int orderId;
T orderT;
public Order(){
//错误的
// T[] arr = new T[10];
//正确写法
T[] arr = (T[]) new Object[10];
}
public Order(String orderName,int orderId,T orderT){
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
//以下的三个方面方法都不是泛型方法
public T getOrderT (){
return orderT;
}
public void setOrderT(T orderT){
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
//静态方法中不能调用泛型结构
// public static void show(T orderT){
// System.out.println(orderT);
// }
public static void show(){
//try-catch结构不能调用泛型
// try{
//
// }catch(T t){
//
// }
}
//泛型方法:在方法中出现了泛型,并且方法的泛型与类声明的泛型参数不相同。
//换句话说,泛型方法所在的类是不是泛型类都无所谓。
//泛型方法时可以声明为静态的,因为泛型的类型是调用方法时确定的,并非实例化类时确定的
public static <E> List<E> copyToArrayList(E[] arr){
List<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
public class SubOrder extends Order<Integer>{//SubOrder:不是泛型类
public static <E> List<E> copyToArrayList(E[] arr){
List<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
}
public class SubOrder1<T> extends Order<T>{//SubOrder1<T>:仍然是泛型类
}
测试:
public class GenericTest1 {
@Test
public void test1(){
//如果使用了泛型的类,在实例化的时候没有确定泛型的类型,那么此泛型类型默认为Object类的。
//不建议这样使用,如果创建类使用了泛型,建议在实例化时指明泛型类型。
// Order o1 = new Order();
// o1.setOrderT(123);
// o1.setOrderT("ABC");
Order<String> order = new Order<>("Tom",1002,"order:Tom");
order.setOrderT("order:AA");
}
@Test
public void test2(){
//由于子类继承了带泛型的父类时,指明了泛型的类型。则在子类实例化时不需要再声明泛型的类型。
SubOrder o = new SubOrder();
o.setOrderT(123);
SubOrder1<Float> sub2 = new SubOrder1<>();
sub2.setOrderT(12.3f);
}
@Test
public void test3(){
ArrayList<String> list1 = null;
ArrayList<Integer> list2 = null;
//泛型不同的引用不能相互赋值。
// list1 = list2;
Person p1 = null;
Person p2 = null;
p1 = p2;
}
@Test
public void test4(){
Order<String> order = new Order<>();
Integer[] arr = new Integer[]{1,2,3,4};
//泛型方法在调用时,指明泛型参数的类型。与泛型类的类型不相干
List<Integer> list = order.copyToArrayList(arr);
System.out.println(list);
}
}
3、自定义泛型方法
方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型 方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
泛型方法的格式: [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
//泛型方法:在方法中出现了泛型,并且方法的泛型与类声明的泛型参数不相同。
//换句话说,泛型方法所在的类是不是泛型类都无所谓。
//泛型方法时可以声明为静态的,因为泛型的类型是调用方法时确定的,并非实例化类时确定的
public static <E> List<E> copyToArrayList(E[] arr){
List<E> list = new ArrayList<>();
for(E e : arr){
list.add(e);
}
return list;
}
四、泛型在继承上的体现
1、泛型在继承方面的体现
虽然类A是类B的父类,但是G<A>与G<B>不具有子父类的关系,即类A和类B在结构上是并列的
补充:类A是类B的父类,A<G>和B<G>是父类关系
测试:
@Test
public void test1(){
Object obj = new Object();
String str = new String();
obj = str;
Object[] obj1 = null;
String[] str1 = null;
obj1 = str1;
ArrayList<Object> list1 = null;
ArrayList<String> list2 = null;
//此时list1与list2并不具有子父类关系
// list1 = list2;
show(list1);
show1(list2);
Date date = new Date();
// str = date;
}
public void show(List<Object> list){
}
public void show1(List<String> list){
}
@Test
public void test2(){
List<Object> list1 = null;
ArrayList<Object> list2 = null;
list1 = list2;
List<Object> list3 = new ArrayList<>();
}
五、通配符的使用
1、使用类型通配符:?
比如:List<?> ,Map<?,?>
List<?>是List<String>、List<Object>等各种泛型List的父类。
2、读取List<?>的对象
读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
3、写入list中的元素
写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。唯一的例外是null,它是所有类型的成员。
/*
2.通配符的使用
通配符:?
类A是类B的父类,G<A>和G<B>是没有关系的,但是它们有共同的父类G<?>
*/
@Test
public void test3(){
List<Object> list1 = null;
ArrayList<String> list2 = null;
List<?> list = null;
list = list1;
list = list2;
//编译通过
// print(list1);
// print(list2);
//**************
List<String> list3 = new ArrayList<>();
list3.add("AA");
list3.add("DD");
list = list3;
//添加(写入):对于List<?>的结构,不进行添加操作,除了add(null);以外
// list.add("bb");
// list.add("?");
list.add(null);
//获取(读取):允许读取操作,获取的数据的类型为Object类。
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
}
4、有限制条件的使用通配符
①通配符指定上限
上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
<? extends Number> (无穷小 , Number]
②通配符指定下限
下限super:使用时指定的类型不能小于操作的类,即>=
<? super Number> [Number , 无穷大)
代码测试:
public class Person {
}
public class Student extends Person{
}
/*
3.有限制条件的通配符的使用
? extends A:
G<? extends A>:可以作为G<A>和G<B>的父类,其中B是A的子类。
? super A:
G<? super A>:可以作为G<A>和G<B>的父类,其中B是A的父类。
*/
@Test
public void test4(){
List<? extends Person> list1 = new ArrayList<>();
List<? super Person> list2 = new ArrayList<>();
List<Person> list3 = new ArrayList<>();
List<Student> list4 = new ArrayList<>();
List<Object> list5 = new ArrayList<>();
list1 = list3;
list1 = list4;
//编译不通过
// list1 = list5;
list2 = list3;
//编译不通过
// list2 = list4;
list2 = list5;
//读取数据
list1 = list4;
Person person = list1.get(0);
list2 = list3;
Object object = list2.get(0);
//添加数据
//编译不通过
// list1.add(new Person());
//编译通过
list2.add(new Person());
list2.add(new Student());
}