一、简介
- 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,
- “任意化”带来的缺点是要做显式的强制类型转换,
- 而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。
- 对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
- 泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
二、泛型
- 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
- 用于解决安全问题,是一个类型安全机制。
- 这种参数类型可以用在类、接口和方法的创建中,
- 分别称为泛型类、泛型接口、泛型方法。
- 泛型是提供给javac编译器使用的
- 如集合中的泛型可以限定输入类型
- 让编译器挡住源程序中的非法输入
- 编译器编译带类型说明的集合时会去掉“类型”信息,使程序运行效率不受影响
- getClass方法的返回值和原始类型完全一样
- 由于编译生成的字节码会去掉泛型的类型信息(泛型擦除),
- 只要能跳过编译器,就可以往某个泛型集合中加入其他类型的数据
- 例如:用反射得到集合,再调用其add方法即可
import java.util.*;
public class GenericTest {
public static void main(String[] args) throws Exception{
ArrayList<String> a1 = new ArrayList<String>();
a1.add("abc");
ArrayList<Integer> a2 = new ArrayList<Integer>();
a2.add(3);
System.out.println(a1.getClass() == a2.getClass());
//通过反射就可以将Integer添加到a1中
a1.getClass().getMethod("add", Object.class).invoke(a1, 5);
System.out.println(a1);
}
}
三、重要结论
- 实验发现上述例子中如果将最后一句改为:System.out.println(a1.get(1)),就会出现 java.lang.ClassCastException
- 通过jad工具反编译class文件得出结论:
- 通过反射可以绕过编译器,向指定了泛型的集合中添加其他类型的元素
- 但是通过get方法调用时如果泛型为String将在get方法前加上强转(String)
- 如果泛型是其他类型,则不会强转
- 如果在get得到元素后还有其他操作,即像get(1).getClass()之类的操作
- 也会加上对应泛型类型的强转
- 也就是说泛型有可能会影响到class文件的内容,并不是完全擦除泛型
四、初步了解泛型
- ArrayList<E>类定义和ArrayList<Integer>类引用中涉及的术语
- 整个称为ArrayList<E>泛型类型
- ArrayList<E>中的E称为类型变量或类型参数
- ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
- ArrayList<Integer>中的<>念typeof
- ArrayList称为原始类型
- 参数化类型与原始类型的兼容性
- 参数化类型可以引用一个原始类型的对象,编译报告警告
- 如:Collection<String> c = new Vector();
- 原始类型可以引用一个参数化类型的对象,编译报告警告
- 如:Collection c = new Vector<String>();
- 参数化类型不考虑类型参数的继承关系
- Vector<String> v = new Vector<Object>();//错误
- Vecotr<Object> v = new Vector<String>();//错误
- 在创建数组实例时,数组的元素不能实用参数化的类型,例如,下面渔具有错误
- Vector vector1[] = new Vector[10];//正确
- Vector<Integer> vector[] = new Vector<Integer>[10];//错误cannot create a generic array of Vector<Integer>
五、规则和限定
- 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
- 泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上称为“有界类型”。
- 泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");
- ?通配符,也可以理解为占位符
- ?extends E:可以接收E类型或者E的子类型。上限 ArrayList(Collection<? extends E> c)
- ?super E:可以接收E类型或者E的父类型。下限 TreeSet(Comparator<? super E> comparator)
import java.util.*;
/**
* 定义一个方法,该方法用于打印出任意集合中的数据
*
* 使用?通配符可以引用其他各种参数化的类型
* ?通配符定义的变量主要用作引用
* 可以调用与参数无关的方法,不能调用与参数化有关的方法
*/
public class GenericTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("abc");
list.add("hjx");
printCllection(list);
ArrayList<Integer> list1 = new ArrayList<Integer>();
list1.add(5);
list1.add(8);
printCllection(list1);
}
private static void printCllection(Collection<?> collection) {
//collection.add("d12");
System.out.println(collection.size());
for(Object obj : collection)
System.out.println(obj);
}
}
想要实现添加等操作参数的方法时,就必须写成泛型T,而不是用通配符?
限定通配符的上边界
Vector<? extends Number> x = new Vector<Integer>();//正确
Vector<? extends Number> x = new Vector<String>();//错误
限定通配符的下边界
Vector<? super Integer> x = new Vector<Number>();//正确
Vector<? super Integer> x = new Vector<Byte>();//错误
六、示例
import java.util.*;
public class GenericTest {
public static void main(String[] args){
HashMap<String,Integer> map = new HashMap<String, Integer>();
map.put("zhangsan", 20);
map.put("lisi", 18);
map.put("wangwu", 16);
printMap(map);
}
private static void printMap(HashMap<String, Integer> map) {
Iterator<Map.Entry<String, Integer>> entrySet = map.entrySet().iterator();
while(entrySet.hasNext())
{
Map.Entry<String, Integer> mapEntry = entrySet.next();
String key = mapEntry.getKey();
Integer value = mapEntry.getValue();
System.out.println(key+":"+value);
}
}
}
七、类型推断
- 编译器判断泛型方法的实际类型参数的过程称为类型推断
- 类型推断是相对于知觉推断的,其实现方法是一个非常复杂的过程
- 根据调用泛型方法时实际传递的参数类型或返回值的类型来推断
- 如果不接收返回值,传入的类型相同就是这个类型,传入的类型不同则取父类的最小交集,见示例1
- 如果要接收返回值,则优先考虑接收变量的类型,并在class文件中加入强转 ,见示例2
- 参数类型的类型推断具有传递性 ,见示例3
示例1
public class GenericTest {
public static void main(String[] args){
add(5,6); //推断为Integer
add(5,3.0); //推断为Number
add(5,""); //推断为Object
}
private static <T> T add(T x,T y)
{
//return x + y; 加法运算可能不适合T类型不能用
return null;
}
}
示例2
public class GenericTest {
public static void main(String[] args)
{
String str = autoConvert("abc");//推断为String
int x = autoConvert(45);//推断为Integer
int x = autoConvert("adf");//推断为Integer,运行时会出现转换异常
autoConvert(45);//推断为Object
System.out.println(str+":"+x);
}
private static <T> T autoConvert(Object obj) {
return (T)obj;
}
}
如果将autoConvert(Object obj)改为autoConvert(T t)
int x = autoConvert(45);//推断为Integer
autoConvert(45);//推断为Integer
int x = autoConvert("adb");//不能通过编译,只能传入Integer,因为类型已经确定为Integer
示例3
static <T> void copy(T[] a,T[] b){
}
copy(new Integer[5],new String[5]);//推断为Object
static <T> void copy(Collection<T> a,T[] b){
}
copy(new Vector<String>(),new Integer[5]);//推断为String,所以编译不能通过,只能传入String数组
八、泛型方法
- 泛型类定义的泛型,在整个类中有效,
- 如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有方法要操作的类型就已经固定了
- 为了让不同方法可以操作不同类型,而且类型还不确定,那就可以将泛型定义在方法上
- 用于放置泛型的类型参数的尖括号应出现在方法的其他所有修饰符之后,返回类型之前
- 按照惯例,类型参数通常用耽搁大写字母表示
- 除了在应用泛型时可以实用extends限定符,在定义泛型时也可以
- 如:public <A extends Annotation> A getAnnotation(Class<A> annotationClass){}
- 并且可以用&来指定多个边界
- 如:<V extends Serializable & Cloneable> void method(){}
- 普通方法、构造方法、静态方法都可以用泛型
- 特殊之处:静态方法不可以访问类上定义的泛型(因为它优先于对象存在)
- 如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上
- 也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但不能用于catch子句中
- 在泛型中可以同时有多个类型参数,用逗号隔开,如:Map<String,Integer>
/**
* 定义方法打印任意类型数据
*/
public class GenericTest {
public static void main(String[] args)
{
print("haha");
print(123);
}
public static <T> void print(T t)
{
System.out.println(t);
}
}
import java.util.*;
/**
* 定义方法交换任意类型数组中的两个元素的位置
*
* 因为泛型类型只能是引用数据类型,所以基本数据类型的数组,操作不了
*/
public class GenericTest {
public static void main(String[] args)
{
String[] strs = {"123","abc","hahh"};
swap(strs,0,1);
System.out.println(Arrays.toString(strs));
Integer[] ints = {2,5,6};
swap(ints,1,2);
System.out.println(Arrays.toString(ints));
}
public static <T> void swap(T[] t,int x,int y)
{
T temp = t[x];
t[x] = t[y];
t[y] = temp;
}
}
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Vector;
public class GenericTest {
public static void main(String[] args) {
String[] strs = {"56","nihao","zhangsan"};
fillArray(strs,"liuliu");
System.out.println(Arrays.toString(strs));
copy1(new String[10],new String[10]);
copy2(new String[10],new Vector<String>());
copy1(new String[10],new Date[10]);
//copy2(new String[10],new Vector<Date>());//因为泛型的传递性,数组应该定义为Date型,编译失败
}
/**
* 把任意类型集合中的数据安全的复制到相应类型的数组中
* @param dest 目标数组
* @param src 源集合
*/
private static <T> void copy2(T[] dest,Collection<T> src) {
int x = 0;
for(T t : src)
{
dest[x] = t;
x++;
}
}
/**
* 把任意类型数组中的数据安全的复制到相应类型的另一个数组中
* @param dest 目标数组
* @param src 源数组
*/
private static <T> void copy1(T[] dest,T[] src) {
for(int x=0;x<src.length;x++)
{
dest[x] = src[x];
}
}
/**
* 该方法可以将任意类型的数组中的所有元素填充为相应类型的某个对象
* @param t 数组
* @param obj 填充的对象
*/
private static <T> void fillArray(T[] t,T obj) {
for(int x=0;x<t.length;x++)
t[x] = obj;
}
}
九、自定义泛型类
class Gen<T>
{
private T ob; //定义泛型成员变量
public Gen(T ob)
{
this.ob = ob;
}
public T getOb()
{
return ob;
}
public void setOb(T ob)
{
this.ob = ob;
}
public void showType()
{
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
public class GenDemo
{
public static void main(String[] args)
{
//定义泛型类Gen的一个Integer版本
Gen<Integer> intOb=new Gen<Integer>(88);
intOb.showType();
int i= intOb.getOb();
System.out.println("value= " + i);
System.out.println("----------------------------------");
//定义泛型类Gen的一个String版本
Gen<String> strOb=new Gen<String>("Hello Gen!");
strOb.showType();
String s=strOb.getOb();
System.out.println("value= " + s);
}
}
十、通过反射获取泛型的实际类型
import java.lang.reflect.*;
import java.util.*;
public class GenericTest {
public static void main(String[] args) throws Exception
{
//想要获得v的泛型的实际类型,要通过一个方法
Vector<Date> v = new Vector<Date>();
Method method = GenericTest.class.getMethod("apply", Vector.class);
Type[] types = method.getGenericParameterTypes();
ParameterizedType type = (ParameterizedType) types[0];
System.out.println(type.getRawType());
System.out.println(type.getActualTypeArguments()[0]);
}
public static void apply(Vector<Date> v)
{
}
}
//按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的
public Type[] getGenericParameterTypes(){}
//Type 是 Java 编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型变量和基本类型
public interface Type
//ParameterizedType 表示参数化类型,如 Collection<String>,从jdk5开始
public interface ParameterizedType extends Type
{
//返回表示此类型实际类型参数的 Type 对象的数组
Type[] getActualTypeArguments();
//返回 Type 对象,表示声明此类型的类或接口
Type getRawType();
}