您的位置:首页 > 编程语言 > Java开发

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类型了。


类型擦除

正确理解泛型概念的首要前提是理解类型擦除(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();
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 限定,且有多种类型的时候,只能存在一个是类,并且类写在第一位,接口列在后面,也就是:<T
extends SomeClass & interface1 & interface2 & interface3>

这里的例子仅演示了泛型方法的类型限定,对于泛型类中类型参数的限制用完全一样的规则,只是加在类声明的头部,如:

public class Demo<T extends Comparable & Serializable>{
//T类型就可以用Comparable声明的方法和Seriablizable所拥有的特性了
}


最后再强调一点,就是泛型最重要的作用就是提高了代码的安全性,因为它能够在编译期对代码进行检查,从而避免了很多在运行期强转类型发生的异常。了解了泛型出现的目的,相信你也就知道该怎么使用泛型了吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: