Java学习之泛型

来源:互联网 发布:四川农业大学网络教育 编辑:程序博客网 时间:2024/06/03 19:10

Java泛型学习

一. 什么是泛型
泛型既类型参数化,是指通过在定义类和接口时
二. 为什么要使用泛型
1.在集合框架中通过定义集合元素类型参数,在编译时进行类型检查,防止在运行时出现类型转化异常,看如下代码片

List list=new ArrayList();            list.add(1);        list.add(2);        list.add("Java");        for (int i = 0; i <list.size(); i++) {            int a=(int) list.get(i);        }

上述代码在一个集合中同时添加了String和int两种数据类型,在JDK1.5没有引入泛型之前,所有添加的数据都会被当做Object类型处理,因此当取出数据时会发生 java.lang.ClassCastException类型转化异常。

2.通过动态的传递类型参数实现程序的可扩展性,看一个JDBC的例子`
操作用户数据的Dao:

public class UserDao {    //在这里初始化DataSource    public BookDao() {        // TODO Auto-generated constructor stub    }    //获取所有用户    @Override    public List<User> retrieveAll(){        // TODO Auto-generated method stub        return users;    }    //获取指定用户    @Override    public User retrieveById(int id){        // TODO Auto-generated method stub        return user;    }    //插入用户数据    @Override    public void insert(User user){        // TODO Auto-generated method stub    }    //删除指定数据    @Override    public void deleteById(int id){        // TODO Auto-generated method stub    }    //更新数据    public int update(Book newBook) {            // TODO Auto-generated method stub    }    }    //在这里关闭数据库连接,游标和数据处理对象    private void close(){    }}

上面这个Dao中定义了增删改查的方法,并且在构造器中初始化DataSource对象,还定义了关闭数据库资源的方法。假如说程序中拥有n个业务数据对象,则需要定义n个这样的Dao,那么会造成构造器中初始化DataSource的代码和关闭数据库链接的代码大量的重复。可以通过模板模式抽取一个BaseDao来封装公用的操作,因为每个Dao对应不同的JavaBean类,所以只能使用泛型来动态的在编译的时候替换JavaBean的类型,以下是我的提取结果`

public abstract class BaseDao<T> {    protected DataSource dataSource;    //初始化DataSource对象    public BaseDao() {        // TODO Auto-generated constructor stub    }    public abstract List<T> retrieveAll() ;    public abstract T retrieveById(int id);    public abstract boolean insert(T t) ;    public abstract boolean deleteById(int id);}

三.使用通配符
首先看一段代码

public static void reverse(List<?> list) {        int size = list.size();        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {            for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)                swap(list, i, j);        } else {            ListIterator fwd = list.listIterator();            ListIterator rev = list.listIterator(size);            for (int i=0, mid=list.size()>>1; i<mid; i++) {                Object tmp = fwd.next();                fwd.set(rev.previous());                rev.set(tmp);            }        }    }
List<String> list=new ArrayList<String>();for(int i=0;i<5;i++){     list.add("第"+i+"个元素");}

这个Collections操作集合工具类中,反转集合中数据元素的方法。该方法的形参声明为List <?> 这个?便是通配符。假入在这里使用List<Object> list,当我们调用该方法传递List<String>类型实参时,无法编译通过,因为List<String>不是List<Object>的子类,尽管String是Object的子类,因为List<String>和List<Object>在编译时会对形参进行替换,会生成同一份字节码文件.

三.设定类型通配符的上限和下限

public class Book<T extends Number>{        private String name;        private T price;        public void setName(String name) {            this.name = name;        }        public void setPrice(T price) {            this.price = price;        }        public String getName() {            return name;        }        public T getPrice() {            return price;        }}  Book<Object>   book=new Book<Object>();   

我们定义了一个Book类,这个类的price属性只能是Number类型本身或者Number的子类。然后我们初始化一个Book对象

 Book<Object>    book=new Book<Object>();  //编译错误 Bound mismatch: The type Object is not a valid substitute for the bounded parameter <T extends Number> of the type Book<T>

当我们传入Object类型实参时,发生编译错误,Object不是一个有效的代替对于边界参数<T extends Number>,因此在这个只能使用Integer,Float等Number类型的子类,这个Number便是通配符的上限。

    public static <T> void fill(List<? super T> list, T obj) {        int size = list.size();        if (size < FILL_THRESHOLD || list instanceof RandomAccess) {            for (int i=0; i<size; i++)                list.set(i, obj);        } else {            ListIterator<? super T> itr = list.listIterator();            for (int i=0; i<size; i++) {                itr.next();                itr.set(obj);            }        }    }

这是Collections工具类用指定元素填充指定集合的方法,其中List<? super T> list, T obj,使用super表示集合中的元素类型只能是目标元素类型的父类。

  Book<Integer>  book=new Book<Integer>();      List<Square>  list=new ArrayList<Square>();      Rectangle rectangle=new Rectangle();      Collections.fill(list, rectangle);   class Shape{}   class Rectangle extends Shape{}        class Square extends Rectangle{}

以上代码我们定义了三个类,其中Rectangle继承自Shape,Square继承自Rectangle,当我们在测试代码中向fill中传入元素为Square类型,目标元素为Rectangle类型的实参时,不能编译通过,因为Square是Rectangle的子类,而不是Square的父类。
四.使用泛型方法
现在定义一个方法向一个集合中添加一个元素

public  <T> void addData(Collection<T> list,T t){        list.add(t);    }

当方法参数中包含类型参数使,需要在方法修饰符和返回值之间添加类型形参,多个类型形参之间用逗号隔开。
五.擦除和转换
首先看这样一个问题:如何把一个字符串类型的数据插入到List泛型参数为Integer类型中。`

 List<Integer> list=new ArrayList<Integer>();          //list.add(e)          Class<List<Integer>> clazz=(Class<List<Integer>>) list.getClass();          try {            Method method=clazz.getDeclaredMethod("add", Object.class);                method.invoke(list, "张三");                method.invoke(list, "张三");                method.invoke(list, "张三");                method.invoke(list, "张三");          } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }          for (int i = 0; i < list.size(); i++) {                      Object object=list.get(i);                      System.out.println(object);        }`

通过以上代码,使用反射技术便可以通过泛型检查,插入字符串类型的数据,由此可以得出结论。
1.泛型检查只在编译期有效
2.clazz.getDeclaredMethod(“add”, Object.class);通过这行代码可以推断出从源文件到字节码阶段把源文件中的add(E e) 中的e替换成立Object
,进而可知把ArrayList中的类型形参E替换为了实参Object。
3.能把String插入List中的根本原因是因为在List集合中存储元素的是 Object[] elementData;这样一个数组元素类型是Object的数组,
因此只要跳过编译期的类型检查,便可以插入各种类型的数据,并且没有类型不对应异常,因为所有类型都是Object的子类。
泛型参数的赋值原则:
在定义类型形参时使用通配符的上限:

public class Book<T extends Interface2&Interface1>{        private String name;        private T price;        public void setName(String name) {            this.name = name;        }        public void setPrice(T price) {            this.price = price;        }        public String getName() {            return name;        }        public T getPrice() {            return price;        }}

这是一个简单的JavaBean类, 其中Interface1和Interface2是两个接口,Book<T extends Interface2&Interface1>,这中写法中上限可以是
两个接口或者是第一个是接口第二个是类,但不能是两个都是类,或者第一个是接口第二个是类。说明Java允许统配符的允许设置多个接口上限不允许有多个类上限,并且类上限必须位于接口上限之前。

 Class<Book> class1=Book.class;         TypeVariable<Class<Book>>[] typeParameters = class1.getTypeParameters();        TypeVariable<Class<Book>> typeVariable=typeParameters[0];        System.out.println(typeVariable);        try {        Field field=    class1.getDeclaredField("price");         Type type=field.getType();         System.out.println(type);        } catch (NoSuchFieldException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } catch (SecurityException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }输出:Tinterface com.sunjinxi.spring.test.Interface2       

通过上述代码测试发现Book中T的类型总是被替换为Book<T extends Interface2&Interface1>,中靠近extends的类型,由此可以得出结论在使用形参为通配符上限时,实参为靠近extends关键字的类型。
在定义类型形参时使用通配符的下限:
通过测试,在Java中并不支持这么做。
六.泛型数组
在Java中允许定义List<String>[]这样类型的数组,但是不允许创建ArrayList<String>[]这样的数组,说明Java不支持泛型数组,
因为在字节码阶段并不存在所有的泛型都会被擦除。java的泛型设计规范是如果一段代码在编译时系统没有产生【unchecked】未经检查的转换警告,则程序在运行时不会引发ClassCastExceptin异常。假设Java支持
ArrayList这样的类型,

    1   List<String> [] lists=new ArrayList<String>[4];    2   List<String> [] lists=new ArrayList[4];    3   List<?>[] lists=new ArrayList<?>[4];        Object[] objects=lists;        List<Integer> list1=new ArrayList<Integer>();        list1.add(1);        objects[0]=list1;        //这样的代码不会有警告,但是会触发ClassCastException        String string=lists[0].get(0);        //使用3号代码,需要进行类型判断才能使代码安全         Object target=lists[0].get(0);         if (target instanceof String) {             String string=(String) lists[0].get(0);        }

则上面的1号代码片段不会有任何警告,但是会引发类型转换异常,这是违反泛型的设计原则的,所以这样的代码编译不通过,如果换成2号代码是可以编译通过的,所以引发异常是可能的不违反规则的。3号代码可以编译通过,但是只允许创建没有通配符上限的ArrayList类型,但是同样可能引发类型转换异常,需要做类型判断。

七.最后的总结
泛型只是在编译期做了类型检查,并且在编译为字节码的时候对泛型形参进行替换,根据替换规则可以知道每个包含泛型参数的Java源文件只会生成一份字节码,所以List<String>和List<Integer>会编译成一份字节码,其中泛型形参E会替换成Object,所以获取他们的Class对象也是同一个Class对象。通过使用泛型可以将要可能发生的错误 在编译期进行控制检查,在程序设计中使用泛型也可以增加结构设计的灵活性,减少重复的代码。关于泛型的总结就到这里,下一篇将对Java中的注解进行总结。

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 怀孕30周羊水少怎么办 怀孕30周羊水多怎么办 孕30周羊水少怎么办 拔完智齿嗓子疼怎么办 神经功能性引起的胸闷怎么办 中风后说话不清怎么办 老人吃不进去饭怎么办 老人吃什么就吐怎么办 老人吃了就吐怎么办 老人吃饭噎着了怎么办 胃胀气嗝不出来怎么办 嗓子咽口水都疼怎么办 产后盆底肌肉松弛怎么办 2个月宝宝鼻塞怎么办 人淹死捞不上来怎么办 胶囊卡在胃里怎么办 药卡在气管里怎么办 胶囊药卡在气管怎么办 被胶囊卡在喉咙怎么办 药卡在食道里怎么办 胶囊黏在喉咙里怎么办 要一直卡在喉咙怎么办 胃老是往上反气怎么办 有口气憋在喉咙怎么办 肛裂伤口不愈合怎么办 肛裂口子不愈合怎么办 宝宝胃食道反流怎么办 去角质后脸发红怎么办 红烧肉做的太甜怎么办 红烧排骨太甜了怎么办 唱歌时嗓子有痰怎么办 一唱歌喉咙有痰怎么办 鼻子老是打喷嚏还流鼻涕怎么办 鼻涕流到喉咙里怎么办 鼻塞怎么办怎样让鼻通气 流清鼻涕嗓子疼怎么办 喉咙疼咳嗽有痰怎么办 扁桃体发炎痛得厉害怎么办 腭垂掉下来了怎么办 喉咙干有异物感怎么办 嗓子干有异物感怎么办