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中的注解进行总结。
- java学习之泛型
- java学习之泛型
- java学习之泛型
- java学习之泛型
- java学习之泛型
- Java学习之泛型
- Java学习之泛型
- java学习之泛型
- java 之 泛型学习示例
- java再学习之泛型
- 泛型之Java学习总结
- java学习笔记之泛型
- JAVA学习笔记之泛型接口
- java学习之泛型例子
- Java学习笔记之泛型
- java学习之认识泛型
- java学习笔记之泛型
- java基础学习之泛型
- 让你脑洞大开的MySQL优化技巧
- 算法竞赛入门经典 习题3-2 单词的长度(word)
- Color 递归
- Unix高级编程:库函数与系统调用函数区别、文件锁、进程基础
- mac修改host文件,让你的mac轻松上google
- Java学习之泛型
- github pages + 域名和外网ip访问本地服务器
- bzoj1811mea(不等式)
- RxJava
- 如何把asp.net网站发布到自己的电脑,外网也可以访问
- TCP通信丢包原因总结
- Retrofit
- Unix高级编程:进程的同步、加载新的程序映像、环境变量
- String类的intern方法随笔