Java学习之泛型

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();
        }
输出:T
interface 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值