您的位置:首页 > 编程语言 > Java开发

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<>();

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  泛型 类型通配符