java 泛型使用详解 + 总结
2015-12-25 16:02
656 查看
一. 泛型概念的提出(为什么需要泛型)?
首先,我们看下下面这段简短的代码:1 public class GenericTest { 2 3 public static void main(String[] args) { 4 List list = new ArrayList(); 5 list.add("qqyumidi"); 6 list.add("corn"); 7 list.add(100); 8 9 for (int i = 0; i < list.size(); i++) { 10 String name = (String) list.get(i); // 1 11 System.out.println("name:" + name); 12 } 13 } 14 }
定义了一个List类型的集合,先向其中加入了两个字符串类型的值,随后加入一个Integer类型的值。这是完全允许的,因为此时list默认的类型为Object类型。在之后的循环中,由于忘记了之前在list中也加入了Integer类型的值或其他编码原因,很容易出现类似于//1中的错误。因为编译阶段正常,而运行时会出现“java.lang.ClassCastException”异常。因此,导致此类错误编码过程中不易发现。
在如上的编码过程中,我们发现主要存在两个问题:
1.当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
2.因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,否则很容易出现“java.lang.ClassCastException”异常。
那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。
二.什么是泛型?
泛型的定义
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。看着好像有点复杂,首先我们看下上面那个例子采用泛型的写法。
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 /* 5 List list = new ArrayList(); 6 list.add("qqyumidi"); 7 list.add("corn"); 8 list.add(100); 9 */ 10 11 List<String> list = new ArrayList<String>(); 12 list.add("qqyumidi"); 13 list.add("corn"); 14 //list.add(100); // 1 提示编译错误 15 16 for (int i = 0; i < list.size(); i++) { 17 String name = list.get(i); // 2 18 System.out.println("name:" + name); 19 } 20 } 21 }
采用泛型写法后,在//1处想加入一个Integer类型的对象时会出现编译错误,通过List<String>,直接限定了list集合中只能含有String类型的元素,从而在//2处无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。
结合上面的泛型定义,我们知道在List<String>中,String是类型实参,也就是说,相应的List接口中肯定含有类型形参。且get()方法的返回结果也直接是此形参类型(也就是对应的传入的类型实参)。下面就来看看List接口的的具体定义:
1 public interface List<E> extends Collection<E> { 2 3 int size(); 4 5 boolean isEmpty(); 6 7 boolean contains(Object o); 8 9 Iterator<E> iterator(); 10 11 Object[] toArray(); 12 13 <T> T[] toArray(T[] a); 14 15 boolean add(E e); 16 17 boolean remove(Object o); 18 19 boolean containsAll(Collection<?> c); 20 21 boolean addAll(Collection<? extends E> c); 22 23 boolean addAll(int index, Collection<? extends E> c); 24 25 boolean removeAll(Collection<?> c); 26 27 boolean retainAll(Collection<?> c); 28 29 void clear(); 30 31 boolean equals(Object o); 32 33 int hashCode(); 34 35 E get(int index); 36 37 E set(int index, E element); 38 39 void add(int index, E element); 40 41 E remove(int index); 42 43 int indexOf(Object o); 44 45 int lastIndexOf(Object o); 46 47 ListIterator<E> listIterator(); 48 49 ListIterator<E> listIterator(int index); 50 51 List<E> subList(int fromIndex, int toIndex); 52 }
我们可以看到,在List接口中采用泛型化定义之后,<E>中的E表示类型形参,可以接收具体的类型实参,并且此接口定义中,凡是出现E的地方均表示相同的接受自外部的类型实参。
自然的,ArrayList作为List接口的实现类,其定义形式是:
1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable { 3 4 public boolean add(E e) { 5 ensureCapacityInternal(size + 1); // Increments modCount!! 6 elementData[size++] = e; 7 return true; 8 } 9 10 public E get(int index) { 11 rangeCheck(index); 12 checkForComodification(); 13 return ArrayList.this.elementData(offset + index); 14 } 15 16 //...省略掉其他具体的定义过程 17 18 }
由此,我们从源代码角度明白了为什么//1处加入Integer类型对象编译错误,且//2处get()到的类型直接就是String类型了。
类型擦除
正确理解泛型概念的首要前提是理解类型擦除(typeerasure)。 Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。如在代码中定义的List<Object>和List<String>等类型,在编译之后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方式与C++模板机制实现方式之间的重要区别。
很多泛型的奇怪特性都与这个类型擦除的存在有关,包括:
泛型类并没有自己独有的Class类对象。比如并不存在List<String>.class或是List<Integer>.class,而只有List.class。
静态变量是被泛型类的所有实例所共享的。对于声明为MyClass<T>的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass<String>还是new MyClass<Integer>创建的对象,都是共享一个静态变量。
泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException<String>和MyException<Integer>的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。
类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如T
get()方法声明就变成了Object get();List<String>就变成了List。接下来就可能需要生成一些桥接方法(bridge
method)。这是由于擦除了类型之后的类可能缺少某些必须的方法。
泛型的好处
使用泛型的好处:强类型检查。在编译时就可以得到类型错误信息。
避免显式强制转换。
方便实现通用算法。
泛型的规则
使用泛型的规则:泛型的类型参数只能是类类型(类型的封转类Integer,Char,Double等,也包括自定义类),不能是简单类型(int,
char,double)。
同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的,不存在父子类关系,不同版本的泛型实际上都是同一种基本类型。 <public class Example
{ public void print(List<Integer> integers) {} public void print(List<Double> doubles) {} }>两个print方法会被认为同一类中的相同方法,因此编译错误:Erasure of method print(List<Integer>) is the same as another method in type Example。
泛型的类型参数可以有多个,比如 public class Pair<T, V> { ....} 。
静态字段的类型不能为类型参数,public class Box<T> { private static T object;
// compile-time error}。
不能直接创建类型参数变量的数组,只能通过反射创建,因此 List<Integer>[] arrayOfLists
= new List<Integer>[2]; // compile-time error 。
泛型的参数类型可以使用extends和super关键字,例如<T extends superclass>、<T extends B1 & B2 & B3> 、<T super
Number> 习惯上称为“有界类型”。
泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String")。
三.自定义泛型接口、泛型类、泛型方法、自定义泛型数组
从上面的内容中,大家已经明白了泛型的具体运作过程。也知道了接口、类和方法也都可以使用泛型去定义,以及相应的使用。是的,在具体使用时,可以分为泛型接口、泛型类和泛型方法。自定义泛型接口、泛型类和泛型方法与上述Java源码中的List、ArrayList类似。如下,我们看一个最简单的泛型类和方法定义:
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 System.out.println("name:" + name.getData()); 7 } 8 9 } 10 11 class Box<T> { 12 13 private T data; 14 15 public Box() { 16 17 } 18 19 public Box(T data) { 20 this.data = data; 21 } 22 23 public T getData() { 24 return data; 25 } 26 27 }
在泛型接口、泛型类和泛型方法的定义过程中,我们常见的如T、E、K、V等形式的参数常用于表示泛型形参,由于接收来自外部使用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 8 System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box 9 System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box 10 System.out.println(name.getClass() == age.getClass()); // true 11 12 } 13 14 }
由此,我们发现,在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最基本的类型(本实例中为Box),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,在于Java中的泛型这一概念提出的目的,导致其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
泛型接口
1.在Java中除了可以定义类和方法,还可以定义泛型接口。泛型接口的作用和普通接口一样,只是它的实用性更强。对于很多具体类型通用的方法,可以将其提取到一个泛型接口中,再编写一个泛型类实现这个接口即可。2.定义泛型接口和定义泛型类是相似的,直接在接口名称后面加上<T>即可。T就是泛型类型参数,可以是多个。
3.在实现此接口时要注意,实现类的泛型参数和接口的泛型参数要相匹配。
package com.xhj.generics.used.ginterface; /** * 定义一个泛型接口 * * @author XIEHEJUN * */ public interface GenericComparableInterface { public <T extends Comparable<T>> T getMax(T[] array); } 实现泛型接口 package com.xhj.generics.used.ginterface; public class GenericComparableImp implements GenericComparableInterface { @Override public <T extends Comparable<T>> T getMax(T[] array) { if(array==null||array.length==0){ return null; }else{ T max = array[0]; for (int i = 0; i < array.length; i++) { if(max.compareTo(array[i])<0){ max = array[i]; } } return max; } } public static void main(String[] args) { GenericComparableImp gci = new GenericComparableImp(); String[] strs = { "您好!我是和佑b,来自和佑博客园", "您好!我是和佑a,来自和佑博客园", "您好!我是和佑c,来自和佑博客园" }; System.out.println("最小的类对象实例为:" +gci.getMax(strs)); } }
注:泛型接口的应用
一个大型网站的后台往往使用多个数据表,可以将一些公共的操作如数据的增删改以及保存等放在一个泛型的DAO接口中定义,
在针对使用的持久层技术,编写此DAO的实现类,这些对于每一个持久化的对象,直接继承这个实现类,再去实现特有方法即可。
泛型类
原有的普通类定义public class Box { public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } private Object object;
该类可以传给它任何你想要的对象,比如对象String,Integer等,也可以传入自定义的一些对象。但是调用getObject方法返回的对象需要显式的强转为传入的类型,才能使用原来类型的一些方法。
泛型类定义
public class Box<T> { public T getObject() { return object; } private T object; public void setObject(T object) { this.object = object; } }
因此使用泛型构建该类可以方便在实例化Box对象时,必须要给其指定一种类型,String,Integer或者自定义的类,并且调用getObject方法并不需要进行强转就可以使用该类型的方法。
同样也可以一个类中传入多个类型参数。例如下面的Pair对象
public class Pair<T, V> { private T key; private V value; public Pair(T key, V value) { this.key = key; this.value = value; } public T getKey() { return key; } public V getValue() { return value; }
使用方法如下:
Pair<Integer, String> one = new Pair<Integer, String>(1, "one"); Pair<String, String> hello = new Pair<String, String>("hello", "world");
泛型方法
在Java中,不仅可以声明泛型类,还可以声明泛型方法:1.使用<T>格式来表示泛型类型参数,参数个数可多个;
2.类型参数列表要放在访问权限修饰符、static和final之后;
3.类型参数列表要放在返回值类型、方法名称、方法参数之前。
package com.xhj.generics.used.entity; /** * 用户实体类 * * @author XIEHEJUN * */ public class User { private String userName; private String userId; private int userAge; private String userAddress; private String gende; private long userTell; public User() { super(); } public User(String userName, String userId, int userAge, String userAddress, String gende, long userTell) { this.userName = userName; this.userId = userId; this.userAge = userAge; this.userAddress = userAddress; this.gende = gende; this.userTell = userTell; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserId() { return userId; } public int getUserAge() { return userAge; } public void setUserAge(int userAge) { this.userAge = userAge; } public String getUserAddress() { return userAddress; } public void setUserAddress(String userAddress) { this.userAddress = userAddress; } public String getGende() { return gende; } public void setGende(String gende) { this.gende = gende; } public long getUserTell() { return userTell; } public void setUserTell(long userTell) { this.userTell = userTell; } @Override public String toString() { return "User{" + "\n\tuserId =" +userId+ "\n\tuserName =" + userName + "\n\tuserAge =" + userAge + "\n\tgende =" + gende + "\n\tuserAddress =" + userAddress + "\n\tuserTell =" + userTell + "\n\t}"; } }
泛型数据访问操作类
package com.xhj.generics.used.dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.List; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanListHandler; /** * 数据库操作类,定义增删改查等操作方法 * * @author XIEHEJUN * */ public class GenericQuery { private static String URL = "jdbc:oracle:thin:@192.168.100.13:1521:SIGMA"; private static String DRIVRR = "ojdbc6"; private static String USER = "PCD_Online_V2"; private static String PASSWORD = "password"; private static Connection con; /** * 获取数据库连接 * * @return */ public static Connection getConnecton() { DbUtils.loadDriver(DRIVRR); try { con = DriverManager.getConnection(URL, USER, PASSWORD); } catch (SQLException e) { System.out.println("连接失败"); } return con; } /** * 查询数据 * * @param sql * SQL语句 * @param type * 实体类类型 * @return */ @SuppressWarnings("unchecked") public static <T> List<T> query(String sql, Class<T> type) { QueryRunner qr = new QueryRunner(); List<T> list = null; try { list = (List<T>) qr.query(getConnecton(), sql, new BeanListHandler( type)); } catch (SQLException e) { System.out.println("SQL语句不正确"); e.printStackTrace(); }finally{ DbUtils.closeQuietly(con); } return list; } /** * 更新数据--增/删/改 */ public static void queryUpdate(String sql) { QueryRunner qr = new QueryRunner(); try { qr.update(getConnecton(), sql); } catch (SQLException e) { e.printStackTrace(); }finally{ DbUtils.closeQuietly(con); } } }
业务操作类
package com.xhj.generics.used.service; import java.util.List; import com.xhj.generics.used.dao.GenericQuery; import com.xhj.generics.used.entity.User; /** * 调用数据库操作方法,对数据进行增删改查等操作 * * @author XIEHEJUN * */ public class Service { /** * 插入数据 * * @param user */ public static void update(User user) { String sql = "insert into XHJUSER values('" + user.getUserId() + "','" + user.getUserName() + "','" + user.getUserAge() + "','" + user.getGende() + "','" + user.getUserAddress() + "','" + user.getUserTell() + "')"; GenericQuery.queryUpdate(sql); } /** * 修改数据 * * @param sql */ public static void update(String sql) { GenericQuery.queryUpdate(sql); } /** * 查询数据 * * @param user * @param sql */ public static void select(User user, String sql) { List<User> list = GenericQuery.query(sql, User.class); System.out.println("表中数据有:"); for (int i = 0; i < list.size(); i++) { System.out.println(i + "号对象属性值为:" + list.get(i)); } } /** * 删除数据 * * @param user */ public static void delete(String sql) { GenericQuery.queryUpdate(sql); } }
测试类:
package com.xhj.generics.used.main; import com.xhj.generics.used.entity.User; import com.xhj.generics.used.service.Service; /** * 测试类 * * @author XIEHEJUN * */ public class Test { public static void main(String[] args) { User user = new User("B", java.util.UUID.randomUUID().toString(), 12, "湖南", "女", 1213344455); Service.update(user); String sql = "select * from XHJUser where username = 'B'"; Service.select(user, sql); sql = "update XHJuser set username = 'D' where userid ='1ab3ee1b-1c52-43d2-8df5-ffefb92c9c5c'"; Service.update(sql); sql = "delete from XHJuser where username = 'C'"; Service.delete(sql); } }
注:泛型类与泛型方法的重要区别
1.在使用泛型类时,需要注意不能将泛型参数类型用于静态域和静态方法中,而对于泛型方法则可以是静态的。
2.这种区别主要是"擦除"产生的。由于在泛型方法中已经指明了参数的具体类型,故即使发生擦除,也不会丢失。
泛型化方法与最小值
1.在Java中除了数值可以比较大小外,任何实现了Comparable接口的类的实例,都可以比较大小。
2.在比较类的对象是,需要限制比较的对象实现Comparable接口即:<T extends Comparable>
3.当泛型参数类型被限制为接口的子类型时,也使用extends关键字。
代码实例:
package com.xhj.generics.used; /** * 利用泛型比较类对象实例大小 * * @author XIEHEJUN * */ public class GenericComparable { /** * 比较并获取最小类对象实例 * * @param array * @return */ public static <T extends Comparable<T>> T getMin(T[] array) { if (array.length == 0 || array == null) { return null; } else { T min = array[0]; for (int i = 0; i < array.length; i++) { if (min.compareTo(array[i]) > 0) { min = array[i]; } } return min; } } public static void main(String[] args) { String[] strs = { "您好!我是和佑b,来自和佑博客园", "您好!我是和佑a,来自和佑博客园", "您好!我是和佑c,来自和佑博客园" }; System.out.println("最小的类对象实例为:" + getMin(strs)); } }
注:1.compareTo()方法先是逐步比较ASCII码,若是此时仍无法得出结果,再比较其长度
2.泛型类型参数的限定一般有两种情况:
a.小于某一个"范围"
b.大于某一个"范围"
范围即可以是一个类,也可以是一个接口,还可以是类和接口的组合,对于组合来说,需要将类放在第一位,并且用&分隔。
自定义泛型化数组类
1.在Java虚拟机中并没有泛型类型的对象,所有有关泛型的信息都被擦除了。这虽然可以避免C++语言的模版代码膨胀问题,但是也引起了其他问题。如:不能直接创建泛型数组等。2.Java中的泛型不支持实例化类型变量。
3.通过Java的反射机制创建一个泛型化数组newInstance(Class<?> componentType,int length)
package com.xhj.generics.used; import java.lang.reflect.Array; /** * 利用Java反射机制泛型化数组 * * @author XIEHEJUN * * @param <T>数组类型 */ public class GenericsArray<T> { private T[] array; private int size; /** * 泛型化数组构造函数 * * @param type * 数组类型 * @param size * 数组长度 */ @SuppressWarnings("unchecked") public GenericsArray(Class<T> type, int size) { array = (T[]) Array.newInstance(type, size); this.size = size; } /** * 向泛型化数组添加元素 * * @param index * @param item */ public void put(int index, T item) { if (index >= 0 && index < size) { array[index] = item; } } /** * 根据数组下标获取相应值 * * @param index * @return */ public T get(int index) { if (index >= 0 && index < size) { return array[index]; } return null; } /** * 将泛型化数组打印输出 * * @param t */ public void printService(T[] t) { put(0, t[0]); System.out.println("添加的元素为:" + get(0)); put(1, t[1]); System.out.println("添加的元素为:" + get(1)); put(2, t[2]); System.out.println("添加的元素为:" + get(2)); } public static void main(String[] args) { System.out.println("向泛型化数组添加String元素"); GenericsArray<String> gStrArray = new GenericsArray<String>( String.class, 3); String[] strs = { "您好!", "我叫和佑!", "我喜欢Java!" }; gStrArray.printService(strs); System.out.println("\n向泛型化数组添加Integer元素"); GenericsArray<Integer> gIntArray = new GenericsArray<Integer>( Integer.class, 3); Integer[] arrays = { 10, 52, 32 }; gIntArray.printService(arrays); } }
总结:
Java泛型的局限性
1.不能使用基本类型作为其类型参数;
2.不能抛出或捕获泛型类型的实例、
3.不能直接使用泛型数组、
4.不能实例化类型变量
5.对于某些不足,可以通过Java的反射机制进行弥补。
四.类型通配符
接着上面的结论,我们知道,Box<Number>和Box<Integer>实际上都是Box类型,现在需要继续探讨一个问题,那么在逻辑上,类似于Box<Number>和Box<Integer>是否可以看成具有父子关系的泛型类型呢?为了弄清这个问题,我们继续看下下面这个例子:
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<Number> name = new Box<Number>(99); 6 Box<Integer> age = new Box<Integer>(712); 7 8 getData(name); 9 10 //The method getData(Box<Number>) in the type GenericTest is 11 //not applicable for the arguments (Box<Integer>) 12 getData(age); // 1 13 14 } 15 16 public static void getData(Box<Number> data){ 17 System.out.println("data :" + data.getData()); 18 } 19 20 }
我们发现,在代码//1处出现了错误提示信息:The method getData(Box<Number>) in the t ype GenericTest is not applicable for the arguments (Box<Integer>)。显然,通过提示信息,我们知道Box<Number>在逻辑上不能视为Box<Integer>的父类。那么,原因何在呢?
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<Integer> a = new Box<Integer>(712); 6 Box<Number> b = a; // 1 7 Box<Float> f = new Box<Float>(3.14f); 8 b.setData(f); // 2 9 10 } 11 12 public static void getData(Box<Number> data) { 13 System.out.println("data :" + data.getData()); 14 } 15 16 } 17 18 class Box<T> { 19 20 private T data; 21 22 public Box() { 23 24 } 25 26 public Box(T data) { 27 setData(data); 28 } 29 30 public T getData() { 31 return data; 32 } 33 34 public void setData(T data) { 35 this.data = data; 36 } 37 38 }
这个例子中,显然//1和//2处肯定会出现错误提示的。在此我们可以使用反证法来进行说明。
假设Box<Number>在逻辑上可以视为Box<Integer>的父类,那么//1和//2处将不会有错误提示了,那么问题就出来了,通过getData()方法取出数据时到底是什么类型呢?Integer? Float? 还是Number?且由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换。显然,这与泛型的理念矛盾,因此,在逻辑上Box<Number>不能视为Box<Integer>的父类。
好,那我们回过头来继续看“类型通配符”中的第一个例子,我们知道其具体的错误提示的深层次原因了。那么如何解决呢?总部能再定义一个新的函数吧。这和Java中的多态理念显然是违背的,因此,我们需要一个在逻辑上可以用来表示同时是Box<Integer>和Box<Number>的父类的一个引用类型,由此,类型通配符应运而生。
类型通配符一般是使用 ? 代替具体的类型实参。注意了,此处是类型实参,而不是类型形参!且Box<?>在逻辑上是Box<Integer>、Box<Number>...等所有Box<具体类型实参>的父类。由此,我们依然可以定义泛型方法,来完成此类需求。
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 Box<Number> number = new Box<Number>(314); 8 9 getData(name); 10 getData(age); 11 getData(number); 12 } 13 14 public static void getData(Box<?> data) { 15 System.out.println("data :" + data.getData()); 16 } 17 18 }
有时候,我们还可能听到类型通配符上限和类型通配符下限。具体有是怎么样的呢?
在上面的例子中,如果需要定义一个功能类似于getData()的方法,但对类型实参又有进一步的限制:只能是Number类及其子类。此时,需要用到类型通配符上限。
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 Box<Number> number = new Box<Number>(314); 8 9 getData(name); 10 getData(age); 11 getData(number); 12 13 //getUpperNumberData(name); // 1 14 getUpperNumberData(age); // 2 15 getUpperNumberData(number); // 3 16 } 17 18 public static void getData(Box<?> data) { 19 System.out.println("data :" + data.getData()); 20 } 21 22 public static void getUpperNumberData(Box<? extends Number> data){ 23 System.out.println("data :" + data.getData()); 24 } 25 26 }
此时,显然,在代码//1处调用将出现错误提示,而//2 //3处调用正常。
类型通配符上限通过形如[b]Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反[/b],在此不作过多阐述了。
1.Java中的数组支持协变类型,即如果方法参数是数组T,而S是T的子类,则方法也可以使用参数S。对于泛型类则没有这个特性。
为了弥补这个不足,Java推出了通配符类型参数。
2.使用通配符"?"可以让泛型在实际应用中更加的灵活,如果只指定了<?>,而没有extends,则默认是允许Object及其下的任何Java类,也就是任意类。
3.通配符可以利用"extends"关键字来设置取值上限,如:<? extends Number>,参数类型要求继承Number
4.通配符可以设置取值下限,如:<?super Number>,参数类型要求是Number的父类
5.通配符可有多个"界限",如:实现多个接口,在接口间用&分隔。
五.话外篇
虽然Java泛型简单的用 extends 统一的表示了原有的 extends 和 implements 的概念,但仍要遵循应用的体系,Java 只能继承一个类,但可以实现多个接口,所以你的某个类型需要用 extends 限定,且有多种类型的时候,只能存在一个是类,并且类写在第一位,接口列在后面,也就是:<Textends SomeClass & interface1 & interface2 & interface3>
这里的例子仅演示了泛型方法的类型限定,对于泛型类中类型参数的限制用完全一样的规则,只是加在类声明的头部,如:
public class Demo<T extends Comparable & Serializable>{ //T类型就可以用Comparable声明的方法和Seriablizable所拥有的特性了 }
最后再强调一点,就是泛型最重要的作用就是提高了代码的安全性,因为它能够在编译期对代码进行检查,从而避免了很多在运行期强转类型发生的异常。了解了泛型出现的目的,相信你也就知道该怎么使用泛型了吧!
相关文章推荐
- Java IO系列4 字节流之PushbackInputStream
- Eclipse快捷键 10个最有用的快捷键
- Java.lang.IllegalStateException Activity has been destroyed(Fragment+ViewPage)
- java中的NIO
- 《Java与模式》之适配器模式
- java.lang.IncompatibleClassChangeError:可以考虑是否是jar包冲突
- Java Mail(三):Session、Message详解
- 【Java】spring-MVC 使用中莫名的400解决方法
- myeclipse使用前的基本配置
- springmvc学习笔记--Interceptor机制和实践
- eclipse快捷键(转)
- 使用eclipse 创建一个简单的java项目(helloWorld)
- springMVC两种方式实现多文件上传及效率比较
- JDK5个小工具
- java读取.properties文件
- 庆丰包子铺自助点餐系统
- java int与integer的区别
- Java魔法堂:注解用法详解——@SuppressWarnings(转)
- Java中对文件的解压缩
- java基础之枚举用法