一,泛型机制介绍及为何要使用泛型
泛型机制是在Java SE5.0中增加的,使用泛型机制编写的程序代码要比那些杂乱地使用object变量,然后再进行强制转换的代码具有更好的安全性和可读性。泛型对于集合类尤其有效,例如,ArrayList就是一个无处不在的集合类。
集合可以存储任何类型的对象,但是存储一个对象到集合后,集合会“忘记”这个对象的类型,当该对象从集合中取出时,这个对象的编译类型就变成object类型。那么在取出元素时,如果进行强制类型转换就很容易抛出异常。
泛型,可以用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递 。
1.1例子:集合中不使用泛型
package cn.xaomifeng1010;
import java.util.ArrayList;
import java.util.List;
public class Teee {
public static void main(String[] args) {
List list = new ArrayList();
list.add(225);
list.add(250);
list.add(520);
//1.没有使用泛型,任何Object及其子类的对象都可以添加进来
list.add(new String("AA"));
for(int i = 0;i < list.size();i++){
//2.强转为int型时,可能报ClassCastException的异常
int score = (Integer)list.get(i);
System.out.println(score);
}
}
}
运行后,报错
分析:上面的例子中,向List集合中存入了4个元素,分别是3个int类型和一个字符串类型。在取出这些元素时,都将他们强制转换成了Integer类型,由于String类型不能转换为Integer类型,因此在运行时抛出类转换异常,为了解决这个问题,Java中引用了“参数化类型”(parameterized type)这个概念,即泛型,它可以限定方法操作的类型,在定义集合类时,使用“<参数化类型>“的方法指定该类中方法操作的类型
1.2例子:在集合中使用泛型
package cn.xaomifeng1010;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericType {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
list.add(225);
list.add(250);
list.add(520);
// list.add("AA");
// for(int i = 0;i < list.size();i++){
// int score = list.get(i);
// System.out.println(score);
// }
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
运行后结果
例子二:集合中使用泛型
package cn.xaomifeng1010;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class GenericTypeDemo2 {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put("AA", 78);
map.put("BB", 87);
map.put("DD", 98);
Set<Map.Entry<String,Integer>> set = map.entrySet();
for(Map.Entry<String,Integer> o : set){
System.out.println(o.getKey() + "--->" + o.getValue());
}
}
}
运行结果
使用泛型之后,限定了集合存储元素的类型,该写后的程序,往集合中添加元素时只能添加制定类型的元素,例如 ArrayList<Integer>(),限定只能添加int类型数据,如果添加string类型就会报错(非法参数),这样限定元素类型避免了程序运行时发生出错,避免了在程序中进行强制类型转换。
二、自定义泛型类:应用
自己在创建类和接口时也可以根据需要自定义创建泛型类或者泛型接口。
2.1 定义泛型类:
例如:
package cn.xaomifeng1010;
import java.util.List;
public class DAO<T> {
public void add(T t){
}
public T get(int index){
return null;
}
public List<T> getForList(int index){
return null;
}
public void delete(int index){
}
}
/*继承泛型类或者泛型接口时,可以指定泛型的具体类型,本例子中指定了DAO泛型类型为Integer,也可以不指定类型
* 继续使用T类型,T表示任意类型
*/
class CustomerDAO extends DAO<Integer> {
}
测试泛型类
package cn.xaomifeng1010;
public class TestCustomerDAO {
public static void main(String[] args) {
CustomerDAO c = new CustomerDAO();
c.add(556);
c.get(0);
}
}
【注意点】
1.对象实例化时不指定泛型,默认为:Object。
2.泛型不同的引用不能相互赋值。
3.加入集合中的对象类型必须与指定的泛型类型一致。
4.静态方法中不能使用类的泛型。
5.如果泛型类是一个接口或抽象类,则不可创建泛型
类的对象。
6.不能在catch中使用泛型
7.从泛型类派生子类,泛型类型需具体化
2.2 定义泛型接口:
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
例如:
public interface MyGenericInterface<E>{
public abstract void add(E e);
public abstract E getE();
}
使用格式:
2.2.1 在由类实现接口时确定泛型类型:
例如:
public class MyImp1 implements MyGenericInterface<String> {
@Override
public void add(String e) {
// 省略...
}
@Override
public String getE() {
return null;
}
}
此时,泛型E的值就是String类型。
2.2.2 在类实现接口时,不指定泛型的具体类型
此时接口和实现类都是泛型,实现类为泛型类
例如:
public class MyImp2<E> implements MyGenericInterface<E> {
@Override
public void add(E e) {
// 省略...
}
@Override
public E getE() {
return null;
}
}
直到创建对象时,确定泛型的类型
/*
* 使用
*/
public class GenericInterface {
public static void main(String[] args) {
MyImp2<String> my = new MyImp2<String>();
my.add("aa");
}
}
三、泛型与继承的关系
A类是B类的子类,G是带泛型声明的类或接口。那么G<A>不是G<B>的子类!通常G<A>与G<B>没有什么联系。
四、通配符类型
4.1 无限定通配符
A类是B类的子类,G是带泛型声明的类或接口。则G<?> 是G<A>、G<B>的父类!
①以List<?>为例,能读取其中的数据。因为不管存储的是什么类型的元素,其一定是Object类的或其子类的。
①以List<?>为例,不可以向其中写入数据。因为没有指明可以存放到其中的元素的类型!唯一例外的是:null
具体例子说明:
泛型类型中只有一个通配符?,是使用的无限定通配符,例如Pair<?>。乍看起来,似乎和原始的Pair类型一样,实际上,有很大不同。类型Pair<?>有以下方法:
?getFirst();
void setFirst(?);
getFirst的返回值只能赋给一个Object。setFirst方法不能被调用,甚至不能用Object调用。Pair<?>和Pair本质的不同在于:可以用任意Object对象嗲用原始Pair类的setObject方法。
但是可以调用setFirst(null)。
为什么要使用这么脆弱的类型?它对于许多简单的操作非常有用。例如,下面这个方法将用来测试一个pair是否包含一个null引用,它不需要实际的类型。
public static boolean hasNulls<Pair<?> p){
return p.getFirst()==null || p.getSecond()==null;
}
通过将hasNulls转换成泛型方法,可以避免使用通配符类型:
public static <T> boolean hsaNulls(Pair<T> p)
但是带有通配符的版本可读性更高。
4.2 有限定的通配符(通配符的子类限定)
List<? extends A> :可以将List<A>的对象或List<B>的对象赋给List<? extends A>。其中B 是A的子类(A类的子类都可以作为List<? extends A>的元素类型)。
可以总结为泛型的上限:只能接收该类型及其子类
注意extends后边的A可以是类,也可以是接口,而且A如果是接口时,可以有多个接口限定,之间用“&”分割,例如:
4.3 通配符的超类型限定
List<? super A>:可以将List<A>的对象或List<B>的对象赋给List<? super A>。其中B 是A的父类(只有A类的超类即父类可以作为List<? super A>的元素类型)
可以总结为泛型的下限:只能接收该类型及其父类型
五,含有泛型的方法
5.1 在泛型类中定义的含有泛型的方法:
public class DAO<T> {
public void add(T t){
}
public T get(int index){
System.out.println(index);
return null;
}
public List<T> getForList(int index){
System.out.println(index);
return null;
}
public void delete(int index){
}
public T update(T t) {
System.out.println(t);
return t;
}
}
注意:因为在声明类时已经定义了<>泛型,所以方法中就不需要定义泛型(即方法的修饰符后边不需要加<T>了),方法中的返回类型是泛型,方法参数是可以为泛型,也可以为其他确定类型的。
测试:
package cn.xiaomifeng1010.test;
public class DAOdemoTest {
public static void main(String[] args) {
DAO<String> dao=new DAO<String>();
dao.getForList(5);
dao.get(7);
dao.update("xiaomifeng");
}
}
运行结果:
5.2 在普通类中定义含有泛型的方法:
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
例如:
package cn.xiaomifeng1010.test;
public class MyGenericMethod {
public <T> void show(T t) {
System.out.println(t.getClass());
}
public <T> T show2(T t) {
return t;
}
}
注意:这里和上边不同,因为这里是普通类,没有定义过泛型,所以在方法的修饰符后边就有了尖括号里面定义的泛型(即<T>),后边跟返回类型void(无返回)和T(返回T类型)。
测试,在创建对象后,调用方法时给定具体类型:
package cn.xiaomifeng1010.test;
public class GenericMethodDemo {
public static void main(String[] args) {
MyGenericMethod test2=new MyGenericMethod();
test2.show("123");
test2.show(123);
test2.show(123.23);
System.out.println(test2.show2("xiaomifeng1010"));
}
}
运行结果:
文中小部分内容引用自《Java核心技术卷一 第10版》作者:Cay S.Horstmann