泛型的定义以及存在意义
泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
例如:GenericClass<T>{}
使用泛型的主要优点是能够在编译时而不是在运行时检测错误。
泛型只在编译阶段有效。看下面的代码:
List<String> stringArrayList = new ArrayList<String>(); List<Integer> integerArrayList = new ArrayList<Integer>(); Class classStringArrayList = stringArrayList.getClass(); Class classIntegerArrayList = integerArrayList.getClass(); if(classStringArrayList.equals(classIntegerArrayList)){ Log.d("泛型测试","类型相同"); }
输出结果:D/泛型测试: 类型相同
。
通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。
Generic generic = new Generic("111111"); Generic generic1 = new Generic(4444); Generic generic2 = new Generic(55.55); Generic generic3 = new Generic(false); Log.d("泛型测试","key is " + generic.getKey()); Log.d("泛型测试","key is " + generic1.getKey()); Log.d("泛型测试","key is " + generic2.getKey()); Log.d("泛型测试","key is " + generic3.getKey()); D/泛型测试: key is 111111 D/泛型测试: key is 4444 D/泛型测试: key is 55.55 D/泛型测试: key is false
命名类型参数
推荐的命名约定是使用大写的单个字母名称作为类型参数。这与 C++ 约定有所不同(参阅 附录 A:与 C++ 模板的比较),并反映了大多数泛型类将具有少量类型参数的假定。对于常见的泛型模式,推荐的名称是:
K —— 键,比如映射的键。
V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。
E —— 异常类。
T —— 泛型。
泛型有三种常用的使用方式:泛型类,泛型接口和泛型方法。
泛型类
一个泛型类(generic class
)就是具有一个或多个类型变量的类。
下文写了一个用泛型定义的栈
为了创建一个字符串堆找,可以使用new GenericStack<String>() 或new GenericStack<>() 。这可能会误导你认为GenericStack 的构造方法应该定义为 public GenericStack<E>()
这是错误
的。它应该被定义为 public GenericStack()
public class GenericStack<E>{ private java.util.ArrayList<E> list=new java.util.ArrayList<>(); public int getSize(){ return list.size; } public T peek(){ return list.get(getList()-1); } public void push(E o){ list.add(o); } public E pop(){ E o = list.get(getList()-1); list.remove(getSize()-1); return o; } public boolean isEmpty(){ return list.isEmpty(); } @Overide public String toString(){ return "stack: "+list.toString(); } }
同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
泛型方法
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
/** * 泛型方法的基本介绍 * @param tClass 传入的泛型实参 * @return T 返回值为T类型 * 说明: * 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。 * 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 * 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。 * 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。 */ public <T> T genericMethod(Class<T> tClass)throws InstantiationException , IllegalAccessException{ T instance = tClass.newInstance(); return instance; }
泛型方法的基本用法
public class GenericTest { //这个类是个泛型类,在上面已经介绍过 public class Generic<T>{ private T key; public Generic(T key) { this.key = key; } //我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。 //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。 //所以在这个方法中才可以继续使用 T 这个泛型。 public T getKey(){ return key; } /** * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E" * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。 public E setKey(E key){ this.key = keu } */ } /** * 这才是一个真正的泛型方法。 * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T * 这个T可以出现在这个泛型方法的任意位置. * 泛型的数量也可以为任意多个 * 如:public <T,K> K showKeyName(Generic<T> container){ * ... * } */ public <T> T showKeyName(Generic<T> container){ System.out.println("container key :" + container.getKey()); //当然这个例子举的不太合适,只是为了说明泛型方法的特性。 T test = container.getKey(); return test; } //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。 public void showKeyValue1(Generic<Number> obj){ Log.d("泛型测试","key value is " + obj.getKey()); } //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符? //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类 public void showKeyValue2(Generic<?> obj){ Log.d("泛型测试","key value is " + obj.getKey()); } /** * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' " * 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。 * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。 public <T> T showKeyName(Generic<E> container){ ... } */ /** * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' " * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。 * 所以这也不是一个正确的泛型方法声明。 public void showkey(T genericObj){ } */ public static void main(String[] args) { } }
声明泛型方法,将泛型类型<E>置于方法头中关键字static中,例如:
public static <E> void print(E[] list)
为了调用泛型,需要将实际类型放在尖括号内作为方法名的前缀,例如:
GenericMethodDemo.<Integer>print(integers);
GenericMethodDemo.<String>print(strings);
示例:对一个对象数组进行排序
要点提示:可以开发一个泛型方法,对一个Comparable对象数组进行排序。
创建一个泛型方法,对一个Comparable对象数组进行排序。这些对象是Comparable接口的实例,使用compareTo()方法进行比较。
public class GenericSort { public static void show() { Integer[] intArray = {new Integer(2),new Integer(4),new Integer(3)}; Double[] doubleArray = {new Double(2.5),new Double(6.4),new Double(3.3)}; Character[] charArray = {new Character('a'),new Character('q'),new Character('c')}; String[] stringArray = {"liu","lu","hhh"}; sort(intArray); sort(doubleArray); sort(charArray); sort(stringArray); System.out.print("sorted integer objects: "); printList(intArray); System.out.print("sorted Double objects: "); printList(doubleArray); System.out.print("sorted char objects: "); printList(charArray); System.out.print("sorted string objects: "); printList(stringArray); } public static <E extends Comparable<E>> void sort(E[] list) { //可以对任何对象类型的数组进行排序 E currentMin; int currentMinIndex; for(int i = 0; i < list.length -1 ;i++) { currentMin = list[i]; currentMinIndex = i; for (int j = i+1 ; j < list.length; j++) { if(currentMin.compareTo(list[j])>0) { currentMin = list[j]; currentMinIndex = j; } } if(currentMinIndex != i) { list[currentMinIndex] = list[i]; list[i] = currentMin; } } } public static void printList(Object[] list) { for(int i = 0; i< list.length ; i++) System.out.print(list[i]+" "); System.out.println(); } }
泛型类型定义为<E extends Comparable <E>>
,这具有两个含义:
首先,它指定E 是Comparable 的子类型;其次,它还指定进行比较的元素是E 类型的。
通配泛型
通配泛型类型有三种形式一一?
、? extends T
或者? super T
,其中T是泛型类型。
第一种形式 ?
称为 非受限通配
(unbounded wildcard) ,它和? extends Object
是一样的。表示任何一种
对象类型。
public static void print(GenericStack <?> stack)
第二种形式 ? extends T
称为 受限通配
(bounded wildcard),表示T 或T 的一个子类型
。
public static double max(GenericStack <? extends Number> stack)
所以max(new GenericStack <Integer/Double>()) 都是合法的
第三种形式 ? super T
称为 下限通配
(Iower-bound wildcard) ,表示T 或T 的一个父类型
。
GenericStack stack1 = new GenericStack<>(); 一个字串栈
GenericStack stack2 = new GenericStack<>(); 一个对象栈
如果要调用下面的add(stack1,stack2)方法,stack2就应该申明为 <? super T>
public static double max(GenericStack<? extends Number> stack) { // 子类型 double max = stack.pop().doubleValue(); while(!stack.isEmpty()) { double value = stack.pop().doubleValue(); if(value>max) max = value ; } return max; } public static <T> void add(GenericStack<T> stack1, GenericStack<? super T> stack2) { while(!stack1.isEmpty()) stack2.push(stack1.pop()); }
详细讲解
public class SuperExtneds { public static void main(String[] args) { List<? extends Apple> list1 = getExtendsAppleList(); Apple first = list1.get(0); list1.add(null); first.sayHello(); //编译错误 //list.add(new Fruit()); //list.add(new Apple()) //list.add(new Object()); List<? super Apple> list2 = getSupperAppleList(); list2.add(new Apple()); list2.add(new HFSApple()); //只能返回obj Object item = list2.get(0); //编译 错误 //Fruit aa1 = list2.get(0); //Apple aa2 = list2.get(0); //HFSApple aa3 = list2.get(0); //Orange aa4 = list2.get(0); //list2.add(new Fruit()); List<Fruit> list3 = new ArrayList<>(); handlerApple(list3); list3.get(0).sayHello(); } public static List<? extends Apple> getExtendsAppleList() { List<Apple> appleList = new ArrayList<>(); appleList.add(new Apple()); appleList.add(new HFSApple()); //编译错误 //return new ArrayList<Fruit>(); return appleList; } public static List<? super Apple> getSupperAppleList() { List<Fruit> fruitList = new ArrayList<>(); fruitList.add(new Fruit()); fruitList.add(new Orange()); //编译错误 //return new ArrayList<HFSApple>(); return fruitList; } public static void handlerApple(List<? super Apple> list) { list.add(new HFSApple()); list.add(new Apple()); //编译报错 //list.add(new Fruit()); //list.add(new Orange()); } } class Fruit { public void sayHello() { System.out.println("hello"); } } class Apple extends Fruit { } class HFSApple extends Apple { } class Orange extends Fruit { }
List<? extends Apple> list1 只能获取某个类型的对象,不能向其中添加Fruit,Orange甚至Apple的实例,这是为什么呢?原来是? expends Apple的含义时Apple或者Apple的子类,这是一个不确定的类型,那么List就不知道到底应该装入什么类型的对象,所以不允许添加任何类型的对象,但是奇怪的事我们可用向其中添加null,因为null表示任何对象.
List<? super Apple> list2能够向其中添加Apple,HFSApple等Apple及其子类,但是不能添加Apple的父类,在获取list2中的对象时只能是Object,这是为什么呢?原来? super Apple表示Apple的或者Apple的父类,有一个有限长度的类型范围,直到Object,既然有范围,就可以在这个范围之上向list2添加Apple或者Apple的子类,但是在获取的时候,因为时一个有限的范围,list2不知道应该返回具体持有的是什么类型,所以只能返回所有类型的父类Objct.
list3 简单表述了? super Apple的一个应用场景,
那么我们做出如下总结:
-
extends 可用于的返回类型限定,不能用于参数类型限定。
-
super 可用于参数类型限定,不能用于返回类型限定。
泛型的擦除和限制
List<Integer> list0 = new ArrayList<>(); List<String> list1 = new ArrayList<>(); System.out.println(list0.getClass()==list1.getClass()); 运行结果:true
泛型不能使用instanceof,因为类型擦除,但是可用动态的调用isInstance()
泛型中不允许使用new T()这样创建新的对象,但是我们可用使用其他方法创建,第一种时使用newInstance(),这种方法必须保证有默认的构造函数;另一种是构造一个工厂对象,由工厂生成对象.
注意
//在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加 //public <T> T showKeyName(Generic<T extends Number> container),编译器会报错:"Unexpected bound" public <T extends Number> T showKeyName(Generic<T> container){ System.out.println("container key :" + container.getKey()); T test = container.getKey(); return test; }
下面的这个例子是不可以的:
List<String>[] ls = new ArrayList<String>[10];
而使用通配符创建泛型数组是可以的,如下面这个例子:
List<?>[] ls = new ArrayList<?>[10];
这样也是可以的:
List<String>[] ls = new ArrayList[10];