【Java】【重要特性】详解泛型
一、什么是泛型
泛型就是把确定类型的工作推迟到创建对象或调用方法的时候才去确定的特殊的类型。
Java泛型设计的原则是:只要在编译期没有出现警告,那么运行期就不会出现ClassCastException类型转换异常了。
把类型当作参数传递的是参数化类型。
二、为什么出现泛型
因为早期Java是使用Object来代表任意类型的,但是向下转型需要强转,会出现安全问题。使用了泛型之后,就不需要强制转换了,代码更加简洁,程序更加健壮。简单来说就是将运行时可能出现的问题,尽可能的在编译期就发现。
三、泛型的基础
1.定义泛型类:就是把泛型定义在类上,在创建使用该类的时候,才把类型明确下来,这样的话,在使用的时候声明的是什么类型,该类就代表什么类型了,后面就不需要强制转换了,只要在编译期没有出现问题,运行时就不会出现异常了。
2.定义泛型方法:在类上定义的泛型,在类的方法中也可以使用。将方法的参数使用泛型代替就成了泛型方法了。
3.方法的返回值也可以使用泛型来声明,创建时传进来的是什么类型,返回值就是什么类型。
四、泛型类的子类
1.子类明确泛型类的类型参数时
把泛型定义在接口上,子类实现接口的时候将明确的类型代替泛型。
2.子类不明确泛型类的类型参数时
把泛型定义在接口上,子类实现接口的时候也要一样定义泛型
值得注意的是:
(1)继承实现类要是重写父类的方法,则返回值的类型需要跟父类一样
(2)类上声明的泛型只对非静态成员有效
(3)泛型中的指定参数类型是没有继承关系的,比如<Object>和<String>并没有丝毫关系。
五、泛型的类型通配符(?和T)
1.为什么需要类型通配符呢?
当我们使用泛型的时候,我们希望泛型中指定的参数类型具有通用性,比如<类型符号>,这个“类型符号”可以是String类型的,也可以是int类型的,具体则根据使用创建对象时确定的类型,由于<Object>和<String>,<int>是没有关系的,因此出现了类型通配符。
?通配符表示可以匹配任意类型,任意的java类都可以匹配。例如:
public void test(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
T通配符跟?通配符都差不多只是习惯原则不一样,哪该什么时候使用T或?呢,可以按照标准原则来使用(也是程序的规范):
(1)如果参数之间的类型有依赖关系,或者返回值与参数之间有依赖关系就使用T通配符
(2)如果没有依赖关系,使用?通配符
值得注意的是:
当泛型类使用类型通配符的时候,只能调用对象与类型无关的方法,不能调用与具体类型有关的方法,就像上面的list对象中的方法add()是无法调用的,应为调用add()方法需要将元素插入禁区,而现在都不知道对象具体参数类型是什么,会报错的。
2.设定通配符上限
使用<? extends type>,来限制泛型的类型上限范围,传进来的参数只能是type类或者其子类,例如<? extends Number> 通配符只能匹配Number类或者其子类。即只能操作数字了。
3.设定通配符下限
使用<? Super type>,来限制泛型的类型下限范围,传进来的参数只能是type类或者其子类,对于不涉及强制类型转换的时候,这个就很实用。
无论是设定了通配符上限还是下限,都是不能操作与对象有关的方法,只要涉及到了通配符,它的类型都是不确定的。
4.通配符上下限与集合的关系
<? extends T> 实现了泛型的协变,表现在泛型集合中,适合从集合中读取元素,不能添加元素(确切地说不能add出除null之外的对象,包括Object)。
<?super T> 实现了泛型的逆变,表现在泛型结合中,适合向集合中添加元素,不适合读取元素。
上界 <? extend Fruit> ,表示所有继承Fruit的子类,但是具体是哪个子类,无法确定,所以调用add的时候,要add什么类型,谁也不知道。但是get的时候,不管是什么子类,不管追溯多少辈,肯定有个父类是Fruit,所以,我都可以用最大的父类Fruit接着,也就是把所有的子类向上转型为Fruit。
下界 <? super Apple>,表示Apple的所有父类,包括Fruit,一直可以追溯到老祖宗Object 。那么当我add的时候,我不能add Apple的父类,因为不能确定List里面存放的到底是哪个父类。但是我可以add Apple及其子类。因为不管我的子类是什么类型,它都可以向上转型为Apple及其所有的父类甚至转型为Object 。但是当我get的时候,Apple的父类这么多,我用什么接着呢,除了Object,其他的都接不住。
所以,归根结底可以用一句话表示,那就是编译器可以支持向上转型,但不支持向下转型。具体来讲,我可以把Apple对象赋值给Fruit的引用,但是如果把Fruit对象赋值给Apple的引用就必须得用cast。
import java.util.ArrayList;
import java.util.List;
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
//上界
List<? extends Fruit> flistTop = new ArrayList<Apple>();
flistTop.add(null);
//add Fruit对象会报错
//flist.add(new Fruit());
Fruit fruit1 = flistTop.get(0);
//下界
List<? super Apple> flistBottem = new ArrayList<Apple>();
flistBottem.add(new Apple());
flistBottem.add(new Jonathan());
//get Apple对象会报错
//Apple apple = flistBottem.get(0);
}
}
六、泛型擦除
泛型是提供给javac编译期使用的,在编译期编译的时候,会处理完带有泛型信息的程序,之后生成的java class文件就不会带有泛型信息,这样就可以挡住向集合容器中插入非法类型数据了,编译期在源代码级别上做了阻拦,仅限于指定类型输入;这样就不会影响到后面程序的正常运行了,这个过程称之为“泛型擦除”。
因为泛型是JDK1.5之后被提出来的,为了兼容JDK1.5以下的集合,所以当把带有泛型特征的集合赋值给老版本的集合时候,会把泛型给擦除掉了。泛型的类型信息是被擦除掉了,但还是会保留类型参数的上限。
七,泛型的应用
JDBC的时候经常使用的DAO层设计。
- 点赞
- 收藏
- 关注作者
评论(0)