泛型的使用
一,泛型概述
关于泛型,先来说几句集合。都知道集合是可以存储任意对象,当我们创建一个集合时如果没有声明它的存储类型,那该集合便自动提升为Object类型。请参看如下代码:
public class GenericDemo {
public static void main(String[] args) {
Collection coll = new ArrayList();
coll.add("abc");
coll.add("hello");
coll.add(5);//由于集合没有做任何限定,任何类型都可以给其中存放
// 使用迭代器遍历集合
Iterator it = coll.iterator();
while(it.hasNext()){
//需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
String str = (String) it.next();
System.out.println(str.length());
}
}
}
毫无疑问,以上代码会报错,首先从代码上看在集合中存储了数值类型,字符串类型。但是在使用迭代器遍历时,取出的是String类型的,那么对于数值类型来说就必然会报错。
那么以上问题该怎么解决呢?通常对于Collection集合来说,同一个集合对象只保存一种数据类型。如下代码所示:
// 存储字符类型
Collection<String> stringCollection = new ArrayList<>();
// 存储数值类型
Collection<Integer> integerCollection = new ArrayList<>();
// 存储精度类型
Collection<Double> doubleCollection = new ArrayList<>();
也就是说在我们初始化集合的时候,就已经将存储存储类型固定。但是按照Java的灵活性来说,这是显然不够的,比如预先并不确定要存储的类型。因此在JDK5之后新增了泛型的语法,使我们的程序显得更为灵活。
泛型:在方法或者类(接口)中预先的使用某种未知的数据类型。
提示:在我们创建对象的时候,如果没有明确指出一种数据类型,那么编译器会默认为Object类。
1.1,泛型的意义
那说了这么多,泛型又能解决什么问题呢?针对上面的案例分析:
1,可以将ClassCastException异常由运行期转到编译期异常。
2,避免强制类型的转换。
请看如上代码,在我们初始化集合时指定String数据类型,此时再存储数值类型就会在编译期报错,那么泛型又将如何运用呢。先别急,我们先看看集合中是如何定义的。
在Array List集合源码中看出,它的存储类型是一个E,这是什么呢?
- E,Elements:元素
- T,Type:类型
这就是泛型,因为我们对于集合来说它们并不知道我们要存储是什么类型,因此这就是泛型的意义。
提示:泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
二,泛型类
编写格式:
修饰符 class 类名称<泛型变量>{}
我们还是以集合为例,请看如下集合源代码实现,以add方法作为参考。
可以看出add方法接收的数据类型为E,与我们上一节图片中所示Array List源码实现是一致的,因为Array List接收的参数类型就是E。说这么多有些啰嗦,现在先定义一个泛型类。
public class GenericClass<T> {
private T key;
public void setKey(T key){
this.key = key;
}
public T getKey(){
return key;
}
}
public class GenericDemo {
public static void main(String[] args) {
GenericClass<String> genericString = new GenericClass<>();
// 存储字符类型
genericString.setKey("这是一个泛型类");
// 获取数据
String stringKey = genericString.getKey();
System.out.println(stringKey);
// 存储数值类型
GenericClass<Integer> genericInteger = new GenericClass<>();
// 存储数值1
genericInteger.setKey(1);
// 获取数值
Integer integerKey = genericInteger.getKey();
System.out.println(integerKey);
}
}
三,泛型方法
编写格式:
修饰符 <泛型变量> 返回值类型 方法名称(参数){}
例如定义泛型方法:
public class GenericMethod {
// 定义泛型方法
public <T> void menthod(T t) {
System.out.println(t.getClass());
}
// 返回实例化对象
public <T> T getGenericMethod(Class<T> t) throws IllegalAccessException, InstantiationException {
return t.newInstance();
}
public static void main(String[] args) throws Exception {
GenericMethod genericMethod = new GenericMethod();
genericMethod.menthod("abc");
Object method = genericMethod.getGenericMethod(Class.forName("com.api.generic.User"));
System.out.println(method);
}
}
四,泛型接口
编写格式:
修饰符 interface 接口名称<泛型变量>{}
定义泛型接口:
public interface GenericInterface<T> {
void add(T t);
T getT();
}
如果泛型接口的实现类仍不确定,则还可以接着使用泛型。
public class GenericInterfaceImpl<T> implements GenericInterface<T> {
@Override
public void add(T t) {
}
@Override
public T getT() {
return null;
}
}
只有在创建对象的时候,要确定泛型的数据类型。
GenericInterfaceImpl<String> genericInterface = new GenericInterfaceImpl<>();
genericInterface.add("Hello");
五,泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
通配符基本使用
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
此时只能接收数据,不能往该集合中存储数据。
举个例子大家理解使用即可:
public static void main(String[] args) {
Collection<Intger> list1 = new ArrayList<Integer>();
getElement(list1);
Collection<String> list2 = new ArrayList<String>();
getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型
注意:泛型不存在继承关系 Collection<Object> list = new ArrayList<String>();这种是错误的。
六,泛型上下限
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。
泛型的上限:
-
格式:
类型名称 <? extends 类 > 对象名称
-
意义:
只能接收该类型及其子类
泛型的下限:
-
格式:
类型名称 <? super 类 > 对象名称
-
意义:
只能接收该类型及其父类型
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类
public static void main(String[] args) {
Collection<Integer> list1 = new ArrayList<Integer>();
Collection<String> list2 = new ArrayList<String>();
Collection<Number> list3 = new ArrayList<Number>();
Collection<Object> list4 = new ArrayList<Object>();
getElement(list1);
getElement(list2);//报错
getElement(list3);
getElement(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
七,泛型擦除
在泛型的使用中还有一项重要的概念,就是泛型擦除。
这里就不再分享了,有一篇写的很好的博客关于泛型擦除的,有兴趣的可以去看看。
转载自:https://blog.csdn.net/briblue/article/details/76736356
八,总结
总体来说泛型的出现为我们的代码带来很多便利,使程序更加灵活。并且在官方文档中提出如果能使用泛型就尽量使用泛型,提高代码的可读性。
这次关于泛型的总结相对来说并不深入,只是一些简单的描述。最后,在本文中的内容如有不适之处,欢迎多多指点。
感谢阅读!