Java泛型那些事儿(一)
一、为什么我们需要泛型?
我们先用一段代码来说明使用泛型的必要性:
public class CountBean {
public boolean compareInt(int x, int y) {
return x > y;
}
public boolean compareFloat(float x, float y) {
return x > y;
}
public static void main(String[] args) {
//示例代码一
CountBean countBean = new CountBean();
boolean result1 = countBean.compareInt(2, 3);
boolean result2 = countBean.compareFloat(2.4f, 3.6f);
System.out.println("result1=" + result1);
System.out.println("result2=" + result2);
//示例代码二
List list = new ArrayList();
list.add("第一代码");
list.add("blog");
list.add(100);
}
}
如上所述,示例1没有使用泛型,我们要写两个功能一样的函数compareInt()和compareFloat()。如果使用泛型,我们可以避免写功能相同的代码。示例2没有声明List的类型,添加了不同的数据类型的数据,并且没有提示错误,但在运行时会抛出异常,这是我们不想看到的。如果使用泛型,则可以避免上述的两个问题。下面是使用泛型的代码:
public class CountBean {
public <T extends Number & Comparable> boolean compare(T x, T y) {
/**
* x>y 返回1
* x=y 返回0
* x<y 返回-1
*/
return x.compareTo(y) == 1;
}
public static void main(String[] args) {
//示例代码一
CountBean countBean = new CountBean();
boolean result1 = countBean.compare(2, 3);
boolean result2 = countBean.compare(2.4f, 3.6f);
System.out.println("result1=" + result1);
System.out.println("result2=" + result2);
//示例代码二
List<String> list = new ArrayList();
list.add("第一代码");
list.add("blog");
//指定了List的泛型类型为String,所以传入数据类型会报错
list.add(100);
}
}
综上所述,泛型适用于多种数据类型执行相同的代码;使用泛型可以指定数据类型,不需要进行强制类型转换,而使用错误的数据类型在编译时就会报出异常,避免发生运行时异常。
二、泛型类、泛型接口、泛型方法解析
- 泛型类的定义
泛型类的定义和普通的类定义没有很大的不同,只是在类名后面添加了<T>,其中T可以是其他的任意字母。大概有以下几种情况:
示例1:
public class PersonBean<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
示例2:
public class PersonBean<T extends Number> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
示例3:
public class PersonBean<T extends Number & Comparable> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
示例4:
public class PersonBean<K,T extends Number & Comparable> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
示例1时最基本的泛型类定义,没有什么特别的。示例2限定了使用该泛型类的场景,必须是Number或者其Number的子类才能使用,如下图所示,传入Integer可以使用,传入String就会报异常:
示例3在示例2的基础上继承了接口Comparable。需要说明的是,在示例3这种情况下,必须将类写在前面,将接口写在&后面,否则编译异常,如下图所示:
示例4在示例3的基础上增加了一个泛型,说明泛型不是只能一个,你愿意的话,可以增加很多个,同样的,你也可以&多个接口,如下图:
其中Number是抽象类,必须放在第一位,且只能有一个类,因为Java是单继承,多实现的。这也是可以&多个接口的原因。
- 泛型接口的定义
泛型接口的定义和普通接口的定义差不多,规则和上面的泛型类一样,只是需要将关键字class改成interface。泛型接口也可以被别的泛型接口继承,扩展更多的功能。 - 泛型方法的辨析
泛型方法不是特指定义在泛型类的方法。像下面的这种都不是泛型方法:
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
上面的两个方法,只是返回泛型或者使用泛型作为参数,都不算泛型方法。泛型方法必须使用<T>才算,其中T可以上其他的大写字母。例如,下面几种情况都属于泛型方法:
public <T> T test(T t) {
return t;
}
public <K, T> T test(K k, T t) {
return t;
}
public <T extends Number> T test(T t) {
return t;
}
public <T extends View & Comparable> T test(T t) {
return t;
}
public <K, T extends View & Comparable> T test(K k, T t) {
return t;
}
泛型方法还是比较容易辨别的,但是和泛型类一起使用,初学者容易造成困惑,比如下面的代码:
public class PhoneBean<T> {
public void setData(T t){
//...
}
public <T> void setInfo(T t){
//...
}
public <K> void setUser(K k){
//...
}
}
首先初学者都会认为setData(T t)和setInfo(T t)与PhoneBean<T>是同一个T,实际上,只有setData(T t)与PhoneBean<T>是同一个,而setInfo(T t)使用了<T>进行重新定义,和setData(T t)与PhoneBean<T>的T没有任何关系。其次,setUser(K k)也是新定义的泛型方法,它不必和泛型类的泛型名一致。最后,如果将setUser改名setInfo,那会报异常,即使泛型名不一样,因为他们本质上是一样的泛型。异常如下图所示: