泛型
因为泛型使用较多的场景是在集合中,我们以集合为例来说说泛型。java集合的一个缺点就是,我们放入一个东西之后,他并不知道这个东西的数据类型。如何理解?看下面的代码。
List list=new ArrayList();
list.add("Sherry");
list.add(18);
这段代码编译、运行是没有任何问题的,但会报警告(下面会介绍)。现在将两个元素放入集合list中,当往外读的时候就有问题了,因为集合并不知道哪个对象具体是什么数据类型的。
当我们从集合中读数据时,因为不知道具体的数据类型,经常会遇到类型转换的问题,比如:
//输出
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
报错信息:
Exception in thread “main” java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at com.ysx.test.GenericTest.main(GenericTest.java:16)
如何解决
上图将不同类型的元素放入各自的集合中,这时我们进行读写操作时就不需要考虑是否需要类型转换,如何转,是否可以转换成功等问题了。
解决的思路很简单,就是将类型抽象出来作为参数。在定义集合的时候就说明是用来存放何种类型的。
将上面的例子改造下,改造之后代码就不报警告了。
List<String> listStr=new ArrayList<String>();
listStr.add("Sherry");
listStr.add("123");
for (int i = 0; i < listStr.size(); i++) {
System.out.println(listStr.get(i));
}
Tips:
因为
List<String> listStr=new ArrayList<String>();
中ArrayList后面的String显得有些多余,java7之后就不需要写完整信息了。写成List<String> listStr=new ArrayList<>();
就可以了。
书上的定义:
泛型,就是允许在定义类、接口、方法时使用类型形参,这个形参将在声明变量、创建对象、调用方法时动态的指定。
回顾下List的接口定义:
public interface List<E> extends Collection<E> {
}
在定义接口的时候指定了类型形参E,因此,我们在使用的时候才可以直接在List后面加参数类型。
Collection和Map可以说是集合的父接口,其他都是对他们的继承或者实现,我们可以说java集合对支持泛型提供了良好的基础。
当然,我们可以不需要把泛型加在类或接口上,而是直接加到某个方法上,即泛型方法。
泛型方法
先看个例子:
public class GenericMethodTest {
//泛型方法
public static <T> void Test(List<T> c) {
for (int i = 0; i < c.size(); i++) {
System.out.println(c.get(i));
}
}
public static void main(String[] args) {
// List<Object>
List<Object> obj = new ArrayList<>();
obj.add(2);
obj.add("dd");
Test(obj);
// List<Stromg>
List<String> strList = new ArrayList<>();
strList.add("a");
strList.add("b");
Test(strList);
}
}
在该泛型方法中,使用了T类型形参,他的位置应该在修饰符(public、static、final等)之后,在返回值之前。
泛型方法和泛型类区别归根到底是方法和类,比如:作用域等。
泛型方法还有一个好朋友——类型通配符,他们经常被拿来作比较。
类型通配符
当我们使用一个泛型类或方法时,如果不传入实际类型参数,就会给出黄色警告。
比如:
public void test(List c){
for(int i=0;i<c.size();i++){
System.out.println(c.get(i));
}
}
在例子中,List c 并没有指定具体的类型,这个集合中需要放入的类型并不确定,我们无法加上任意一种形式。这时,使用通配符“?”就可以避免这个问题。
修改一下代码:
public void test(List<?> c){
for(int i=0;i<c.size();i++){
System.out.println(c.get(i));
}
}
使用类型通配符之后,警告就不见了。但因为集合中的类型是不确定的,我们只可以进行简单的查询操作,没办法添加元素,除了添加null情况。
有人会问了,那这通配符有什么用啊,集合的常用操作都实现不了,加不加有什么用啊?别急,他的精髓还在后面……
设定上限
List<?>代表元素类型未知的List集合,他的元素可以匹配任何类型。更多时候,我们希望他只匹配某一类泛型的父类。
我们以MiniCat-》Cat-》Animal为例,三者之间是继承关系。
public static void test3(List<? extends Cat> cats) {
for (Cat c : cats) {
System.out.println(c.getName());
}
}
下面我们开始调用,上面例子中的意思是Cat类以及其子类可以放入List集合中,我们来试一下他的父类Animal。
public static void main(String[] args) {
List<Animal> a = new ArrayList<Animal>();
a.add(new Animal("pig", "white"));
a.add(new Animal("apple", "white"));
test3(a);
}
此时,编译报错,报错信息为:
之后,将Animal改为Cat类,顺利执行。
下限
下限的设定使用<? super Class>
public static void test4(List<? super Cat> cats) {
for (Object obj: cats) {
Cat c = (Cat) obj;
System.out.println(c.getName());
}
}
这代表下限是Cat类,满足条件的是Cat的父类,这时如果传入MiniCat类,仍旧会报上面的错。
比较
大多数情况下都可以使用泛型方法来代替类型通配符。
通配符支持灵活的子类化。
若方法中的参数或返回值之间有类型依赖关系,应该使用泛型方法。
有时候,他们两者也结合使用。