Java泛型
泛型的定义以及存在意义
泛型,即“参数化类型”。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
例如:GenericClass{}
使用泛型的主要优点是能够在编译时而不是在运行时检测错误。
泛型只在编译阶段有效。看下面的代码:
ListstringArrayList = new ArrayList(); ListintegerArrayList = new ArrayList(); 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
public class GenericStack{ private java.util.ArrayListlist=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 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。 * 2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。 * 3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。 * 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。 */ publicT genericMethod(ClasstClass)throws InstantiationException , IllegalAccessException{ T instance = tClass.newInstance(); return instance; }
泛型方法的基本用法
public class GenericTest { //这个类是个泛型类,在上面已经介绍过 public class Generic{ 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可以出现在这个泛型方法的任意位置. * 泛型的数量也可以为任意多个 * 如:publicK showKeyName(Genericcontainer){ * ... * } */ publicT showKeyName(Genericcontainer){ System.out.println("container key :" + container.getKey()); //当然这个例子举的不太合适,只是为了说明泛型方法的特性。 T test = container.getKey(); return test; } //这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic这个泛型类做形参而已。 public void showKeyValue1(Genericobj){ Log.d("泛型测试","key value is " + obj.getKey()); } //这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符? //同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类 public void showKeyValue2(Generic obj){ Log.d("泛型测试","key value is " + obj.getKey()); } /** * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' " * 虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法。 * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。 publicT showKeyName(Genericcontainer){ ... } */ /** * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' " * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。 * 所以这也不是一个正确的泛型方法声明。 public void showkey(T genericObj){ } */ public static void main(String[] args) { } }
声明泛型方法,将泛型类型
public static
为了调用泛型,需要将实际类型放在尖括号内作为方法名的前缀,例如:
GenericMethodDemo.
GenericMethodDemo.
示例:对一个对象数组进行排序
要点提示:可以开发一个泛型方法,对一个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> 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 是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
第三种形式 ? super T 称为 下限通配 (Iower-bound wildcard) ,表示T 或T 的一个父类型。
GenericStack stack1 = new GenericStack<>(); 一个字串栈
GenericStack stack2 = new GenericStack<>(); 一个对象栈
如果要调用下面的add(stack1,stack2)方法,stack2就应该申明为
public static double max(GenericStack stack) { // 子类型 double max = stack.pop().doubleValue(); while(!stack.isEmpty()) { double value = stack.pop().doubleValue(); if(value>max) max = value ; } return max; } public staticvoid add(GenericStackstack1, GenericStack stack2) { while(!stack1.isEmpty()) stack2.push(stack1.pop()); }
详细讲解
public class SuperExtneds { public static void main(String[] args) { List 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 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()); Listlist3 = new ArrayList<>(); handlerApple(list3); list3.get(0).sayHello(); } public static List getExtendsAppleList() { ListappleList = new ArrayList<>(); appleList.add(new Apple()); appleList.add(new HFSApple()); //编译错误 //return new ArrayList(); return appleList; } public static List getSupperAppleList() { ListfruitList = new ArrayList<>(); fruitList.add(new Fruit()); fruitList.add(new Orange()); //编译错误 //return new ArrayList(); return fruitList; } public static void handlerApple(List 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
List
list3 简单表述了? super Apple的一个应用场景,
那么我们做出如下总结:
-
extends 可用于的返回类型限定,不能用于参数类型限定。
-
super 可用于参数类型限定,不能用于返回类型限定。
泛型的擦除和限制
Listlist0 = new ArrayList<>(); Listlist1 = new ArrayList<>(); System.out.println(list0.getClass()==list1.getClass()); 运行结果:true
泛型不能使用instanceof,因为类型擦除,但是可用动态的调用isInstance()
泛型中不允许使用new T()这样创建新的对象,但是我们可用使用其他方法创建,第一种时使用newInstance(),这种方法必须保证有默认的构造函数;另一种是构造一个工厂对象,由工厂生成对象.
注意
//在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的上添加上下边界,即在泛型声明的时候添加 //publicT showKeyName(Genericcontainer),编译器会报错:"Unexpected bound" publicT showKeyName(Genericcontainer){ System.out.println("container key :" + container.getKey()); T test = container.getKey(); return test; }
下面的这个例子是不可以的:
List[] ls = new ArrayList[10];
而使用通配符创建泛型数组是可以的,如下面这个例子:
List[] ls = new ArrayList[10];
这样也是可以的:
List[] ls = new ArrayList[10];