Java泛型总结之定义泛型接口、类和类型通配符
2016-11-09 14:25
603 查看
Java泛型总结之定义泛型接口、类和类型通配符
前言:在前面总结Collection接口时常常会用到泛型知识,以及在之前对Okhttp3进行封装时用上了泛型,封装需要泛型是因为工具类需要有通用性,适合各种自定义类的数据传入,所以需要有泛型思想。所以,我决心把泛型知识都梳理一遍,方便自己也尽量给同仁们一些帮助。(一)初识泛型
(一).为什么要使用泛型?我们看下面这个例子:public class GenericTest { public static void main(String[] args) { List list = new ArrayList(); list.add("Hello"); list.add("Hi"); list.add(100); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); // 出现异常 System.out.println("name:" + name); } } }
对于集合List,里面元素默认为Object类,但是在循环过程中,List对加入的元素原始类型全给忘记了,只记得它们是Object类。所以在
String name = (String) list.get(i);这行代码中,将Integer类型的数据错转为String,引发了ClassCastException,类型转换异常。
由此我们发现:当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型仍然为其本身类型。
(二).使用泛型
从Java5以后,Java引入了参数化类型,允许程序在创建集合时指定集合元素的类型。
public class GenericTest { public static void main(String[] args) { /* List list = new ArrayList(); list.add("Hello"); list.add("Hi"); list.add(100); */ List<String> list = new ArrayList<String>(); list.add("Hello"); list.add("Hi"); //list.add(100); // 1 提示编译错误 for (int i = 0; i < list.size(); i++) { String name = list.get(i); // 2 System.out.println("name:" + name); } } }
采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。
(三).定义泛型接口、类
我们来看一下List接口、Iterator接口、Map的源码片段
//定义接口时指定一个类型形参,该形参名为E public interface List<E>{ //在该接口中,E可作为类型使用 //下面方法可以使用E作为参数类型 boolean add(E e); Iterator<E> iterator(); } //定义该接口时指定了两个类型形参,其形参名为K,V public interface Map<K,V>{ //在该接口中K,V完全可以作为类型使用 Set<K> keySet(); V put(K key,V value); }
我们看一个最简单的泛型小例子:
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); System.out.println("name:" + name.getData()); } } class Box<T> { private T data; public Box() { } public Box(T data) { this.data = data; } public T getData() { return data; } }
由上面例子可知,允许在定义接口、类时声明类型形参,类型形参在整个接口,类体内都可当成类型使用。
(四).从泛型类派生子类
当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类。但是当用这些接口、父类时不能再包含类型参数。如下面代码为错:
//Apple类不能跟类型形参 public class A extends Apple<T>{}
必须改为如下代码:
//使用Apple类时,没有为T形参传入实际的类型参数 public class A extends Apple<String>{}
(五)并不存在泛型类
先看一个简单例子:
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box System.out.println(name.getClass() == age.getClass()); // true } }
由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
【对此总结成一句话】:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型,不管泛型的类型形参传入哪一种类型实参,对于java来说,它们依然被当做一个类来处理,在内存中也只占用一块内存来处理。
(六).类型通配符
我们先来看这样一个片段
public void test(List<Object> c){ for(Object o : c ){ system.out.println(o); } }
上面代码看似没问题:
List<String> strList = new ArrayList<>(); test(strList);
上面程序发生编译错误,说明List《String 》对象无法被当成List《String》对象,也就是说List《String 》并不是List《String》对象的子类。
为了表示各种泛型List的父类,可以使用类型通配符。类型通配符是一个问号。类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); Box<Number> number = new Box<Number>(314); getData(name); getData(age); getData(number); } public static void getData(Box<?> data) { System.out.println("data :" + data.getData()); } }
(七).设定类型通配符的上限
在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。
public class GenericTest { public static void main(String[] args) { Box<String> name = new Box<String>("corn"); Box<Integer> age = new Box<Integer>(712); Box<Number> number = new Box<Number>(314); getData(name); getData(age); getData(number); //getUpperNumberData(name); // 1 getUpperNumberData(age); // 2 getUpperNumberData(number); // 3 } public static void getData(Box<?> data) { System.out.println("data :" + data.getData()); } public static void getUpperNumberData(Box<? extends Number> data){ System.out.println("data :" + data.getData()); } }
(八).设定类型形参的上限
Java不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类。
public class Apple<T extends Number>{ T col; public void static main(String[] args){ Apple<Integer> ai = new Apple<>(); Apple<Double> ad = new Apple<>(); //下面代码出现编译错误,下面代码试图把String类型传给T参数 Apple<String> as = new Apple<>(); } }
相关文章推荐
- Java泛型方法定义及泛型类型推断
- Java泛型方法定义及泛型类型推断
- 泛型通配符、定义泛型类型
- Java泛型总结(集合泛型、方法泛型、通配符)
- java泛型-自定义泛型方法与类型推断总结
- 获取在接口或者类上定义的泛型类型
- Java泛型方法定义及泛型类型推断
- [疯狂Java]泛型:类型参数多态问题、类型通配符(?)、类型通配符的上下限、类型参数的上限(类、接口)
- java泛型总结(类型擦除、伪泛型、陷阱)
- 泛型接口的实现方式一:在子类的定义上声明泛型类型
- Java泛型--泛型实例--标识接口的定义
- [Java 10 泛型] 泛型通配符 Info<?> i = new Info<String>(); 在程序中定义没有方法的接口,称之为标识接口
- 获取在接口或者类上定义的泛型类型
- Java基础(21):泛型—泛型的定义、用法与类型通配符的使用方式
- java泛型的使用(五)在接口中定义泛型
- java泛型总结(类型擦除、伪泛型、陷阱)
- Java泛型方法定义及泛型类型推断
- [Java 10 泛型] 泛型通配符 Info<?> i = new Info<String>(); 在程序中定义没有方法的接口,称之为标识接口
- java泛型学习-自定义泛型方法与类型推断总结
- 黑马程序员--Java基础加强--12.利用反射操作泛型I【与反射+泛型相关的接口类型综述】【Type】【ParameterizedType】【个人总结】