- 背景
- 什么是Java泛型
- Java泛型机制有什么用
- 怎么用
有许多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类。
public class Container {
private String key;
private String value;
public Container(String k, String v) {
key = k;
value = v;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Container类保存了一对key-value键值对,但是类型是定死的,也就说如果我想要创建一个键值对是String-Integer类型的,当前这个Container是做不到的,必须再自定义。那么这明显重用性就非常低。
当然,我可以用Object来代替String,并且在Java SE5之前,我们也只能这么做,由于Object是所有类型的基类,所以可以直接转型。但是这样灵活性还是不够,因为还是指定类型了,只不过这次指定的类型层级更高而已,有没有可能不指定类型?有没有可能在运行时才知道具体的类型是什么?
所以,就出现了泛型。
public class Container<K, V> {
private K key;
private V value;
public Container(K k, V v) {
key = k;
value = v;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
泛型的命名规范
为了与java关键字区别开来,java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:
- E — Element,常用在java Collection里,如:List
,Iterator ,Set - K,V — Key,Value,代表Map的键值对
- N — Number,数字
- T — Type,类型,如String,Integer等等
- S,U,V etc. - 2nd, 3rd, 4th 类型,和T的用法一样
泛型参数的界限
有时候,泛型类型只能是某一部分类型,比如操作数据的时候,你会希望是Number或其子类类型。这个想法其实就是给泛型参数添加一个界限。其定义形式为:
<T extends BoundingType>
此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的”extends“表示的子类型,不等同于继承。
泛型方法
一个基本的原则是:无论何时,只要你能做到,你就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛化,那么应该有限采用泛型方法。下面来看一个简单的泛型方法的定义:
public class Main {
public static <T> void out(T t) {
System.out.println(t);
}
public static void main(String[] args) {
out("findingsea");
out(123);
out(11.11);
out(true);
}
} 可以看到方法的参数彻底泛化了,这个过程涉及到编译器的类型推导和自动打包,也就说原来需要我们自己对类型进行的判断和处理,现在编译器帮我们做了。这样在定义方法的时候不必考虑以后到底需要处理哪些类型的参数,大大增加了编程的灵活性。
定义泛型方法时,必须在返回值前边加一个
一般来说编写java泛型方法时,返回值类型和至少一个参数类型应该是泛型,而且类型应该是一致的,如果只有返回值类型或参数类型之一使用了泛型,这个泛型方法的使用就大大的限制了,基本限制到跟不用泛型一样的程度。
第一种:
public static <T extends CommonService> T getService(Class<T> clazz) {
T service = (T) serviceMap.get(clazz.getName());
if (service == null) {
service = (T) ServiceLocator.getService(clazz.getName());
serviceMap.put(clazz.getName(), service);
}
return service;
}
第二种:
public static <T> T getService(Class<? extends CommonService> clazz) {
T service = (T) serviceMap.get(clazz.getName());
if (service == null) {
service = (T) ServiceLocator.getService(clazz.getName());
serviceMap.put(clazz.getName(), service);
}
return service;
} 对第一种泛型方法使用:
public class Main {
public static void main(String[] args) {
NoticeService noticeService=CommonService.getService(NoticeService.class);//正确的使用第一种泛型方法,不会出现编译错误。
NoticeService noticeService=CommonService.getService(UserService.class);//不正确的使用第一种泛型方法,会出现编译错误。
}
}
对第二种泛型方法使用:
public class Main {
public static void main(String[] args) {
NoticeService noticeService=CommonService.getService(NoticeService.class);//正确的使用第二种泛型方法,不会出现编译错误,逻辑也正确,运行时不会出现异常。
NoticeService noticeService=CommonService.getService(UserService.class);//不正确的使用第二种泛型方法,不会出现编译错误,但逻辑不正确,运行时会出现异常,危险!
}
}
第一种泛型方法:返回值和参数值的类型是一致,推荐使用; 第二种泛型方法:返回值和参数值的类型不是一致,请谨慎或避免使用。
现在有一个问题:我应该什么时候使用泛型方法,什么时候使用通配符类型呢?–参数跟返回值是有有关系
为了明白这个问题的答案,我们来看看Collection库里的几个方法:
interface Collection
在这里我们也可以用泛型方法:
interface Collection
但是,类型参数T在containsAll和addAll两个方法里面都只是用了一次。返 回类型并不依赖于类型参数或其他传递给该方法的实参(这种是只有一个实参的简单 情况)。这就告诉我们类型实参是用于多态的,它的作用只是对不同的调用可以有一 系列的实际的实参类型。如果是那样的话,就应该使用通配符,通配符就是设计来支 持灵活的子类型的,这也是我们这里所要表述的东西。
泛型方法允许类型参数用于表述一个或多个的实参类型对方法或及其返回类型的 依赖关系。如果没有那样的一个依赖关系的话,泛型方法就不应用使用。
使用通配符带来的灵活性得要付出一定的代价;
代价就是现在在方法里面不能对象插入元素(集合 )