在阅读Java源码时配到了泛型的使用。本着知其然知其所以然的原则,上网搜索了资料学习。总结如下:
什么是泛型
泛型即类型参数化。
通俗易懂的讲就是,类,方法所操作的类型被指定为一个参数,在用到的时候再指定具体的类型。
public class Test {
public static void main(String[] args){
Box<String> sbox = new Box<String>();//应用时指定数据类型为String
Box<Integer> ibox = new Box<Integer>();
}
}
//申明时操作的数据类型被指定为T
class Box<T> {
private T data;
public Box() {
}
public Box(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
泛型的意义
个人认为泛型的意义有2个:
-数据类型的检查
-代码的复用
- 类型检查
下面的代码中List未限定数据类型,默认处理的为object。所以下面的列子中可以同时往里添加String和Integer而不会在编译过程中报错。但是在运行过程Integer无法强制转换成String,会报出如下错误。
public class Test {
public static void main(String[] args){
List list = new ArrayList();
list.add("111");
list.add(222);
for (int i = 0; i < list.size() ; i ++){
String value = (String)list.get(i);
//java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
System.out.println(value);
}
}
}
如果指定类型,下面注释的代码就会报错。将运行时的错误提前到编译时暴露出来总是有益的。
public class Test {
public static void main(String[] args){
List<String> list = new ArrayList<String>();
list.add("111");
// list.add(222);
for (int i = 0; i < list.size() ; i ++){
String value = (String)list.get(i);
System.out.println(value);
}
}
}
- 代码的复用
如第一个例子代码,一个泛型类。可以处理String类型数据,也能处理Integer类型数据而不必再新建一个类。
自定义泛型,泛型类,泛型接口和泛型方法
-如示例一中的Box类即是最简单的泛型类了。
-泛型接口
interface Number<T>{
public void SelfAdd(T number);
}
-泛型方法
class Type{
//泛型方法
public <T> T getType(T x){
return x;
}
}
在使用泛型时,我们会指定具体的数据类型。那么相同的泛型类指定不同的数据类型后生成的对象,其类型是否还一致呢?
public class Test {
public static void main(String[] args){
Box<String> sbox = new Box<String>();
Box<Integer> ibox = new Box<Integer>();
System.out.println(sbox.getClass() == ibox.getClass()); //true
}
}
通过如上例子,我们可以知道。即使指定了不同的数据类型,生成对象后其类型还是相同的。
所以在逻辑上sbox和ibox可以看成是不同的类型,实际上是用一个基本类型即Box。
类型通配符
既然我们定义了泛型,总是要使用他的。但是泛型的具体数据类型不同,我们总不可能为每一种用到的数据类型申明一个同样的操作。如下代码所示:
public static void main(String[] args){
Box<String> sbox = new Box<String>("Name");
Box<Integer> ibox = new Box<Integer>(123);
ShowData(sbox);
ShowData(ibox);//编译错误
}
public static void ShowData(Box<String> box){
System.out.println(box.getData());
}
我们申明了方法ShowData
处理Box<String>
。因应Box<String>
与Box<Integer>
并无继承派生关系,我们不能够直接用ShowData
处理Box<Integer>
。从这似乎不符合多态性。
因此我们需要一中方法能同时表示Box<String>
和Box<Integer>
。类型通配符应运而生。
类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>
在逻辑上是Box<Integer>
、Box<Number>
…等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。
public static void main(String[] args){
Box<String> sbox = new Box<String>("Name");
Box<Integer> ibox = new Box<Integer>(123);
ShowData(sbox);//输出Name
ShowData(ibox);//输出123
}
public static void ShowData(Box<?> box){
System.out.println(box.getData());
}
泛型中的类型限定
我们有如下列子:
public static<T> T Compare(T t1,T t2){
if (t1.compareTo(t2) > 0 ) //编译报错
return t1;
else
return t2;
}
由于在编码阶段,我们未指定具体类型所以其默认为object类型出来。object类型中并未有compareTo方法,所以会编译报错。
在这种情况下,我们需要对泛型参数做些限定。总所周知,实现了 Comparable接口的方法都有compareTo方法。所以可以做如下限定:
public static<T extends Comparable> T Compare(T t1,T t2){
if (t1.compareTo(t2) > 0 )
return t1;
else
return t2;
}
类型限定有如下注意点:
1. 不管该限定是类还是接口,统一都使用关键字 extends
2. 可以使用&符号给出多个限定
3. 如果限定既有接口也有类,那么类必须只有一个,并且放在首位置
–通配符的上下边界:
当我们需要限定通配符为某一族中的一部分时,就需要使用通配符的上下边界限定了。如下例子:
public class Test {
public static void main(String[] args){
Box<GrandFather> Gf = new Box<GrandFather>(new GrandFather());
Box<Father> Fa = new Box<Father>(new Father());
Box<Child> Ch = new Box<Child>(new Child());
print1(Gf);//编译错误
print1(Fa);
print1(Ch);
print2(Gf);
print2(Fa);
print2(Ch);//编译错误
}
public static void print1(Box<? extends Father> b1){
}
public static void print2(Box<? super Father> b2){
}
}
class Box<T extends GrandFather>{
private T data;
public Box() {
}
public Box(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
class GrandFather{
}
class Father extends GrandFather{
}
class Child extends Father{
}
由上诉例子中我们可以知道
extends: 界定上边界,即泛型方法只接受Father以及其子类。
super:界定下边界,即泛型方法只接受Father以及其父类。
限定通配符总是包括自己。