Java使用泛型的困顿

一、前言

  平时比较少接触泛型,之前也略微看过,自己也动手写过一些easy的demo,感觉也没啥难度,所以也就没怎么去深究,近来偶然的兴头去翻了翻相关的知识,突然发现,我勒个去,为嘛上面说的某些点我半天反应不过来?!毕业快三年了,这个盲点必须扫掉~~~本文不针对泛型做过多的举例,只是对于其中比较难以理解的部分做解读,不了解泛型的童鞋,自行百度~

二、正文

  先说说什么是泛型,泛型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。(这边需要说明一点:泛型方法和泛型类没有必然联系,普通类里面也可以有泛型方法)~

  从我的角度,曾经有困惑过我的问题有两个,其一:List<Father>和List<Son>之间的关系(Son是Father的子类);其二:为什么“List<? extends SomeClass> a”引用的对象只能使用get方法,不能add方法(编译报错,不过add(null)可以,后面会讲到),为什么“List<? superSomeClass> a”引用的对象只能使用add方法,不能使用get方法?

  先说说第一个问题,算了,感觉这样说不下去了,还是要介绍下“泛型”出现的背景,听说在JDK1.5之前,Java泛型程序设计是用继承来实现的,因为Object类是所用类的基类,所以只需要维持一个Object类型的引用即可,就比如ArrayList只维护一个Object引用的数组:

public class ArrayList//JDK1.5之前的
{
    public Object get(int i){......}
    public void add(Object o){......}
    ......
    private Object[] elementData;
}

这样会有两个问题:

1、没有错误检查,可以向数组列表中添加类的对象

2、在取元素的时候,需要进行强制类型转换

这样,很容易发生错误,比如:

/**jdk1.5之前的写法,容易出问题*/
ArrayList arrayList1=new ArrayList();
arrayList1.add(1);
arrayList1.add(1L);
arrayList1.add("asa");
int i=(Integer) arrayList1.get(1);//因为不知道取出来的值的类型,类型转换的时候容易出错        

  所以,在JDK1.5之后,加入了泛型来解决类似的问题,例如在ArrayList中使用泛型:

 /** jdk1.5之后加入泛型*/
 ArrayList<String> arrayList2=new ArrayList<String>();  //限定数组列表中的类型
 //      arrayList2.add(1); //因为限定了类型,所以不能添加整形
 //      arrayList2.add(1L);//因为限定了类型,所以不能添加整长形
 arrayList2.add("asa");//只能添加字符串
 String str=arrayList2.get(0);//因为知道取出来的值的类型,所以不需要进行强制类型转换

  还要明白的是,泛型特性是向前兼容的。尽管 JDK 5.0 的标准类库中的许多类,比如集合框架,都已经泛型化了,但是使用集合类(比如 HashMap 和 ArrayList)的现有代码可以继续不加修改地在 JDK 1.5 中工作。

针对问题一解答:

  Father是Son的父类,那么List<Father>会是List<Son>的父类吗?答案:不是!对于初学者来说,这个结论感觉就是辣眼睛~要理解这个问题,就先需要理解泛型出现的背景,很大程度上是为了避免强制类型转换导致的不兼容问题。(我这边是使用List这个类来做说明,其实完全可以建一个泛型类Tes<T>,然后以Test<Father>和Test<Son>来举例,初学者不必纠结于此,只是List是大家熟知,所以以此举例)~我们知道List里面有两个方法一个是get,一个是add,这两个方法的声明如下形式:

public abstract interface List<E> extends Collection<E>
{
    ....................
    public abstract E get(int paramInt);
    public abstract boolean add(E paramE);
    ....................
}

  我们可以举个实际的例子来说明下,假设List<Number>是List<Integer>和List<Float>的父类:

import java.util.ArrayList;
import java.util.List;

public class TestMain {
    public static void main(String[]args) throws Exception{
        List<Integer> intList = new ArrayList<Integer>();
        intList.add(10);
        List<Number> numberList = intList;//此处编译报错//numberList.get(0); ------1
    }
}

  那么在“1”处,得到的结果是Number还是Integer呢?由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换,显然,这与泛型的理念矛盾,因此,SomeClass<Father>不能视为SomeClass<Son>的父类(特别说明:Fahter<T>是Son<T>的父类,这个和现在的结论不矛盾)~

针对问题二的解答:

  看下下面这段代码(摘录别人的,详见“参考链接”部分),注释里面说的已经非常清楚,最核心的一句话就是为了确保类型能够兼容

  先看看这行代码:List<? extends Pet> list= new ArrayList<Dog>(),很多人可能会问,我这边不是已经指定泛型的具体类型为Dog吗?但是从编译器的反馈来看,只知道左边方括号内的内容“? extends Pet”,所以编译器只知道是某种Pet的子类,但是并不知道具体的子类是什么,无法确保在运行时类型兼容(这个我是这样理解的,在运行时,必定会根据右边的表达式知道泛型的实际类型为Dog,如果在代码里面使用add(new DogOther()),DogOther也是Pet的一个子类,这边add的动作相当于将DogOther类型的对象实例,赋值给Dog类型的引用,DogOther和Dog并不兼容,那么运行时必定报错,所以不允许),所以不允许使用add函数~那为什么又允许get呢?首先我们要明白,既然此处不能add,那么如果list有值必定是之前就有一个有确定值的List对象实例假设是list01,这个list01的泛型必定是确定的,然后list01赋值给list,在使用list.get的时候返回的值应该是一个确定类型的对象,所以可以使用get~

  再来看看List<? super Dog> list2 = new ArrayList<Dog>(),同样,在编译期间,编译器并不知道泛型的具体类型是什么,只是知道是Dog类型或者其超类,如果在运行时,可以知道泛型的类型为Dog,在调用list2.add(new PettyDog())的时候,相当于将PettyDog的对象实例,赋值给Dog,根据里氏替换原则,这样是允许的,所以这边使用add是合法的~那为什么此处调用get报错呢?这是因为add方法可以往list2里面设置很多Dog或者Dog类型的子对象,那么在调用get的时候返回的到底是什么呢?因为有多种可能,又要进行类型判断,且进行强制类型转换,显然,这与泛型的理念又发生矛盾~

public class TestMain {
    public static void main(String[] args) {
        Item<Dog> item = new Item<Dog>(new Dog());
        item.get();
        Item<? extends Pet> item2 = new Item<Dog>(new Dog());
        item2.get().out();
        /**知道某种限制,但是不知道具体是什么**/
        List<? extends Pet> list = new ArrayList<Dog>();
        /**
         * The method add(capture#2-of ? extends Pet) in the type List<capture#2-of ? extends Pet>
         *  is not applicable for the arguments (Dog)
         * list.add(new Dog());
         * 从错误代码得知,编译期list的类型是List<? extends Pet>,所以通配符还是个未知类型;
         * 只知道是某种Pet的子类,但是并不知道具体的子类是什么,无法确保类型兼容;
         * 即使list.add(new Object()); (Object类型与Pet的子类不兼容) 也是不可以的;
         * list.add(null);这个可以;所以对于使用通配符的泛型,只能“取”不能“加”;
         * 编译器只允许加null;因为只有 Pet的子类 = null;等式成立;
         */
        //list.get(0);
        List<? super Dog> list2 = new ArrayList<Dog>();
        /**
         * 这关系到泛型的边界(bounds),list通配符使用的是上边界,无法确定具体的类;
         * 这里list2用的是下边界,取放的是Dog某种超类,但是并不知道具体的超类是什么;
         * 我们只知道Dog的子类可以跟Dog超类类型兼容
         * 所以这种形式的通配符只能“存”不能“取”
         */
        list2.add(new Dog());
        /**
         * list2.add(new Object());
         */
        list2.add(new PettyDog());
        /**ClassCastException**/
        //list2.add((Dog) new Run());
    }
}
interface Pet {
    public void out();
}
class Item<T extends Pet> {
    T item;
    public Item(T item) {
        this.item = item;
    }
    public T get() {
        return item;
    }
    public void put(T item) {
    }
}
class Run {

}
class Dog extends Run implements Pet {
    @Override
    public void out() {
        System.out.println("The Dog!");
    }
    public void getMsg(Item<?> item) {
        item.get();
        /**
         *The method put(capture#3-of ?) in the type Item<capture#3-of ?>
         * is not applicable for the arguments (Item<capture#4-of ?>)
         * capture:占位符被称为这个特殊通配符的捕获
         * item.put(item);
         */
    }
}
class PettyDog extends Dog {
}

三、参考链接

http://www.cnblogs.com/ljxe/p/5521840.html

http://blog.csdn.net/lonelyroamer/article/details/7864531

https://my.oschina.net/u/782865/blog/198906

时间: 2024-10-12 20:52:42

Java使用泛型的困顿的相关文章

Java中泛型的协变

在工作中遇到一个问题,用代码描述如下: package test; import java.util.LinkedList; import java.util.List; public class ListTest {     public void func(List<Base> list) {     }     public static void main(String args[]) {         ListTest lt = new ListTest();         Li

Java 容器 & 泛型:一、认识容器

Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket 容器是Java语言学习中重要的一部分.泥瓦匠我的感觉是刚开始挺难学的,但等你熟悉它,接触多了,也就"顺理成章"地知道了.Java的容器类主要由两个接口派生而出:Collection和Map. 一.Collection vs Collections 首先,Collection 和 Collections 是两个不同的概念.之所以放在一起,是为了更好的比较.Collection是容器层次结构中

Java 泛型 Java使用泛型的意义

Java 泛型 Java使用泛型的意义 @author ixenos 直接意义 在编译时保证类型安全 根本意义 a) 类型安全问题源自可复用性代码的设计,泛型保证了类型安全的复用模板 b) 使用复用性模板时不用手动强制类型转换 三种泛型实现方式的优缺点 C++:模板方式实现,在编译时完全展开并且支持偏特化,类型精度高,代码共享差: Java 5:擦除方式实现,仅用于编译时类型检查,在运行时擦除,向后兼容性好,代码共享好,类型精度非常差: C# 2.0:混合方式实现,在运行时展开特化,类型精度高,

Java 中泛型的全面解析

Java泛型(generics) 是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter).声明的类型参数在使用时用具体的类型来替换.泛型最主要的应用是在JDK 5中的新集合类框架中.对于泛型概念的引入,开发社区的观点是褒贬不一.从好的方面来说,泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时刻类型错误,因为编译器可以在编译时刻就发现很多明显的错误.而从不好的地方来说,为了保证与旧有版本的兼容性,Java泛型的实现上存在着一些不够优雅的

Java 中 泛型的限定

泛型 一般 出现在集合中,迭代器中 也会出现! 泛型 是为了 提高代码的 安全性. 泛型 确保数据类型的唯一性. 在我们常用的容器中,  越是 单一 约好处理啊! 泛型的限定: ? 是通配符 指代 任意类型 泛型的限定上限: <? extends E> 接受 E 或者 E 的子类型. 泛型的限定下限: <?  super   E>  接收  E 或者 E 的父类. 泛型的限定上限 (定义父类 填装子类 类型!) 代码: package stu.love.v; import java

Java中泛型 使用

泛型: 1.5  之后出现  提高安全 1      泛型 确定 集合容器的类型. 2      <> 接收一种数据类型,(引用数据类型) ArrayList<String> lis = new ArrayList<String>() 目的: 将运行时期的 错误 转化到 编译时期,提高了安全性! 3      不需要 强制类型转换.  更加安全! 泛型的 擦除: 泛型在编译时期使用!使用完毕直接擦除. 编译完的时候 不存在 泛型. 好处: 使用了 泛型,不自需要强制类型

java遍历泛型的方法

一.List遍历 Java中List遍历有三种方法来遍历泛型,主要为: 1.for循环遍历 2.iterator遍历 3.foreach遍历 package com.gmail.lsgjzhuwei; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.junit.Test; public class test { //第一种方法:for循环遍历 @Test public

Java核心 --- 泛型

CoreJava 泛型 java泛型的出现避免了强制类型转换,便于代码更好的被阅读 本文的写作参照了张孝祥的泛型介绍:http://www.itcast.cn/news/dbfd20f1/f4b1/412d/9b40/c1a81b8bf1da.shtml 更多疑问请参考:http://www.vaikan.com/java-generics-quick-tutorial/ 1.可以接收类型参数的类型在接受类型参数后变为泛型,但是,虽然是不同的泛型但是还是相同的类型 package com.yuk

Java 之泛型

两种快速理解泛型的描述 1. 数据类型是抽象的,这个类可以处理很多数据类型.里面的T在被实例化的时候可以用任意类型代替. 2.  泛型,想成一个桶,你装水或者装沙子都行,这个桶只负责提供装满水或者装满沙子的功能 . 官方解释: 泛型是程序设计语言的一种特性.允许程序员在强类型程序设计语言中编写代码时定义一些可变部份,那些部份在使用前必须作出指明. 泛型的定义主要有以下两种: 在程序编码中一些包含类型参数的类型,也就是说泛型的参数只可以代表类,不能代表个别对象.(这是当今较常见的定义) 在程序编码