一. 泛型概念的提出(为什么需要泛型)?
首先,我们看下下面这段简短的代码:
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类型了。
类型擦除
正确理解泛型概念的首要前提是理解类型擦除(type erasure)。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();
}
注:泛型接口的应用
一个大型网站的后台往往使用多个数据表,可以将一些公共的操作如数据的增删改以及保存等放在一个泛型的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) {
}
}
注: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处调用正常。
类型通配符上限通过形如Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。
1.Java中的数组支持协变类型,即如果方法参数是数组T,而S是T的子类,则方法也可以使用参数S。对于泛型类则没有这个特性。
为了弥补这个不足,Java推出了通配符类型参数。
。
3.通配符可以利用"extends"关键字来设置取值上限,如:<? extends Number>,参数类型要求继承Number
4.通配符可以设置取值下限,如:<?super Number>,参数类型要求是Number的父类
5.通配符可有多个"界限",如:实现多个接口,在接口间用&分隔。
五.话外篇
虽然Java泛型简单的用 extends 统一的表示了原有的 extends 和 implements 的概念,但仍要遵循应用的体系,Java 只能继承一个类,但可以实现多个接口,所以你的某个类型需要用 extends 限定,且有多种类型的时候,只能存在一个是类,并且类写在第一位,接口列在后面,也就是:<T extends SomeClass & interface1 & interface2 & interface3>
这里的例子仅演示了泛型方法的类型限定,对于泛型类中类型参数的限制用完全一样的规则,只是加在类声明的头部,如:
public class Demo<T extends Comparable & Serializable>{
//T类型就可以用Comparable声明的方法和Seriablizable所拥有的特性了
}
最后再强调一点,就是泛型最重要的作用就是提高了代码的安全性,因为它能够在编译期对代码进行检查,从而避免了很多在运行期强转类型发生的异常。了解了泛型出现的目的,相信你也就知道该怎么使用泛型了吧!