1.概述
-
泛型,即"参数化类型"。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递参数。就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参)。然后在使用/调用时传入具体的类型(类型实参)
-
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为了一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
-
代码演示
List arrayList = new ArrayList(); arrayList.add("aaaa"); arrayList.add(100); for(int i = 0;i < arrayList.size() ;i++){ String item = (String)arrayList.get(i); Log.d("泛型测试","item = "+ item) }
-
毫无疑问,程序的运行结果以崩溃结束
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
-
ArrayList可以存放任意类型,为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
List<String> arrayList = new ArrayList<String>(); .... //arrayList.add(200);在编译阶段,编译器就会报错
-
2.特性
-
泛型只在编译阶段有效
List<String> stringArrayList = new ArrayList<String>(); List<Integer> integerArrayList = new ArrayList<Integer>(); Class classStringArrayList = stringArrayList.getClass(); Class classIntegerArrayList = integerArrayList.getClass(); if(classStringArrayList.equals(classIntegerArrayList)){//true System.out.println("泛型测试:类型相同"); }
- 通过上面的例子可以证明,在编译之后程序会采取泛型化的措施,也就是Java中的泛型,只在编译阶段有效,在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦除,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法,也就是说,泛型信息不会进入到运行时阶段
- 对此总结成一句话:泛型类型在逻辑上看似是多个不同的类型,实际上都是相同的基本类型
3.泛型的使用
- 泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
3.1泛型类
-
泛型类型用于类的定义中,被称为泛型类,通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map
泛型类的最基本写法
class 类名称<泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{ private 泛型标识 /*成员变量类型*/ param; .... }
-
实例
-
举例普通的泛型类
/** * * 此处T可以随便写为任意标识,常见的如T E K V等形式的参数常用于表示泛型 * 在实例化泛型类时,必须指定T的具体类型 */ public class Generic<T> { //key这个成员变量的类型为T,T的类型由外部指定 private T key; public Generic(T key){ //泛型构造方法形参key的类型也为T,T类型由外部指定 this.key = key; } public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定 return key; } }
/* 泛型的类型参数只能时类类型(包括自定义类),不能时简单的类型 传入的实参类型与泛型的类型参数相同,即为Integer */ Generic<Integer> integerGeneric = new Generic<Integer>(123456); Generic<String> stringGeneric = new Generic<String>("key_value"); System.out.println(integerGeneric.getKey());//123456 System.out.println(stringGeneric.getKey());//key_value
####注意############## //泛型的类型参数只能是类类型,不能是简单类型 //不能对确定的泛型类型使用instanceof操作,如下面的操作是非法的,编译时会出错 if(ex_num instanceof Generic<Number>){ }
-
3.2泛型接口
-
泛型接口和泛型类的定义及使用基本相同,泛型接口常被用在各种类的生产器中
//定义一个泛型接口 public interface Generator<T>{ public T next(); }
-
当实现泛型接口的类,未传入泛型实参时
/** * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中 * 即:class FruitGenerator<T> implements Generator<T> * 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:Unknown class */ class FruitGenerator<T> implements Generator<T>{ @Override public T next(){ return null; } }
3.3泛型方法
-
在java中,泛型类定义非常简单,但是泛型方法就比较复杂
//尤其是我们见到的大多数泛型类中的成员方法都使用了泛型,有些甚至泛型类中也包含泛型方法。
-
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型
/** *泛型方法的基本介绍 * @param tClass 传入的泛型类 * @param <T> T 返回值为T类型 * @return * @throws IllegalAccessException * @throws InstantiationException * * 说明: * 1)public与返回值中间<T>非常重要,可以理解为声明此方法为泛型方法 * 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法 * 3)<T>声明该方法将使用泛型类型T,此时才可以在方法中使用类型T * 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T K E V等形式的参数用于表示泛型 */ public <T> T genericMethod(Class<T> tClass) throws IllegalAccessException, InstantiationException { T instance = tClass.newInstance(); return instance; }
-
泛型方法的基本用法
public class GenericTest { //泛型类 public class Generic<T>{ private T key; public Generic(T key) { this.key = key; } /** * 该方法中使用了泛型,但是这并不是一个泛型方法,这只是类中一个普通的成员方法,只不过 * 它的返回值是在声明泛型类已经声明过的泛型,所以该方法可以继续使用T这个泛型 * @return */ public T getKey() { return key; } /** * 这个方法显然是有问题的,在编译器会给这样的报错信息:cannot reslove symbol E * 因为在这个类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别 * @param key * * public void setKey(E key) { * this.key = key; * } */ /** * 这个才是真正的泛型方法 * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T * 这个T可以出现在这个泛型方法的任意位置 * 泛型的数量可以为任意多个 * public <T,K> K showKeyName(Generic<T> container){} * @param container * @param <T> * @return */ public <T> T showKeyName(Generic<T> container){ System.out.println("container key:"+container.getKey()); //不恰当,只为说明泛型的特性 T test = container.getKey(); return test; } /** * 这是一个普通方法,只是使用了Generic<Number>这个泛型做了形参而已 * @param obj */ public void showKeyValue1(Generic<Number> obj ){ System.out.println(obj.getKey()); } /** * 这也是一个普通方法,只不过使用了?泛型通配符 * @param obj */ public void showKeyValue2(Generic<?> obj){ System.out.println(obj.getKey()); } } }
-
类中的泛型方法
-
当然这并不是泛型方法的全部,泛型方法可以出现在任何地方和任何场景中使用,但是有一种情况是非常特殊的,当泛型方法出现在泛型类中时。
public class GenericFruit { class Fruit{ @Override public String toString() { return "fruit"; } } class Apple extends Fruit{ @Override public String toString() { return "apple"; } } class Person{ @Override public String toString() { return "person"; } } class GenerateTest<T>{ public void show(T t){ System.out.println(t.toString()); } /** * 在泛型类中声明了一个泛型方法,使用了泛型E,这种泛型E可以为任意类型,可以类型与T相同,也可以不同 * 由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明,编译器也能正常识别方法中识别的泛型 * @param e * @param <E> */ public <E> void show_1(E e){ System.out.println(e.toString()); } /** * 在泛型类中声明一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型 * @param t * @param <T> */ public <T> void show_2(T t){ System.out.println(t.toString()); } } public static void main(String[] args){ GenericFruit genericFruit = new GenericFruit(); Apple apple = genericFruit.new Apple(); Person person = genericFruit.new Person(); GenerateTest<Fruit> fruitGenerateTest = genericFruit.new GenerateTest<Fruit>(); //apple是Fruit的子类,所以这里可以 fruitGenerateTest.show(apple);//apple /**编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person * fruitGenerateTest.show(person); */ //使用这两个方法都可以成功 fruitGenerateTest.show_1(apple);//apple fruitGenerateTest.show_1(person);//person //使用这两种方法都可以成功 fruitGenerateTest.show_2(apple);//apple fruitGenerateTest.show_2(person);//person } }
-
-
泛型方法和可变参数
public <T> void printMsg(T... args){ for(T t:args){ System.out.println("泛型测试:"+t) } }
-
静态方法与泛型
-
静态方法有一种情况需要注意一下,那就是类中的静态方法使用方向:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上
-
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法
public class StaticGenerator<T> { /** * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法) * 即使静态方法要使用泛型类中已经声明过的泛型也不可以 * public static void show(T t){ * System.out.println("泛型"+t); * } * * 编译器会提示错误信息:'Basic.annotation.StaticGenerator.this' cannot be referenced from a static context */ public static <T> void show(T t){ System.out.println("泛型:"+t); } }
-
-
泛型方法的总结
- 泛型方法能使方法独立于类而产生变化,一下使一个基本的指导原则:
- 无论何时,如果你能做到,你就该尽量使用泛型方法,也就是说,如果使用泛型方法将整个类泛型化, 那么就应该使用泛型方法。
- 对于一个static的方法,无法访问泛型类型的参数,所以static方法要使用泛型能力, 使其成为泛型方法
- 泛型方法能使方法独立于类而产生变化,一下使一个基本的指导原则:
3.4泛型通配符
-
Integer是number的一个子类,同时在特性性章节中验证过Generic与Generic实际上是相同的一种基本类型,通过提示信息我们可以看到Generic不能被看作为Generic的子类,由此可以看出:同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类型实例是不兼容
Generic<Number> numberGeneric = new Generic<Number>(12345678); Generic<Integer> integerGeneric = new Generic<Integer>(123456); /** * showKeyValue(integerGeneric); * 这个方法编译器会为我们报错:Generic<java.lang.Integer> cannot be applied to Generic<java.lang.number> */ showKeyValue(numberGeneric); public static void showKeyValue(Generic<Number> obj){ System.out.println(obj.getKey()); }
-
同一个方法,不能为了处理不同的类型而新建一个方法,需要一个逻辑上可以表示同时是Generic和Generic父类的引用类型,由此类型通配符应运而生
/** * 类型通配符一般是使用?代替具体的类型实参,注意了,此处'?'是类型实参,而不是类型形参, * @param obj */ public static void showKeyValue(Generic<?> obj){ System.out.println(obj.getKey()); }
4.泛型上下边界
-
在使用泛型的时候,传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或者某种类型的子类
-
泛型添加上边界,即传入的类型实参必须使指定类型的子类型
public class Generstion<T> { public static void main(String[] args) { /** *前提是泛型类使用Generic<T extends Number> * Generic<String> stringGeneric = new Generic<String>("1111111"); * 编译器会报错:String不是Number的子类 */ Generic<Integer> integerGeneric = new Generic<Integer>(2222); Generic<Float> floatGeneric = new Generic<>(1234.5f); Generic<Double> generic = new Generic<Double>(1234.56); /** *前提是泛型类使用Generic<T> * showKeyValue(stringGeneric); * 编译器会报错:String不是Number的子类 */ showKeyValue(integerGeneric); showKeyValue(floatGeneric); showKeyValue(generic); } public static <T> void showKeyValue(Generic<? extends Number> obj){ System.out.println("泛型测试:"+obj.getKey()); } } class Generic<T>{ private T key; public Generic(T key) { this.key = key; } public T getKey() { return key; } }
-
在泛型方法中添加上下边界限制的时候,必须声明与返回值之间的上添加上下边界,即在泛型声明的时候添加
/**
* 在泛型方法中添加上下边界限制的时候,必须声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
* public <T> T showKeyName(Generic<T extends Number> container){};编辑器会报错:'Unexpected bound
* @param container
* @param <T>
* @return
*/
public <T extends Number> T showKeyName(Generic<T> container){
System.out.println("container key:"+container.getKey());
T test = container.getKey();
return test;
}
- 【总结】 泛型的上下边界添加,必须与泛型的声明在一起
5.泛型数组
5.1实例
public class GenericArray<T> {
public static void main(String[] args) {
List<String> string = new ArrayList<>();
string.add(new String("123"));
string.add(new String("1234"));
/**
* 不被允许创建
* List<String>[] strings = new ArrayList<String>[10];
*/
//通配符创建泛型数组是可以的
List<?>[] ls = new ArrayList<?>[10];
//这样创建也可以
List<String>[] ls_2 = new ArrayList[10];
/**
* 由于jvm泛型的擦除机制,在运行时是不知道泛型信息的,所以oa[1]赋上一个ArrayList而不会出现异常,但是在取出数据
* 的时候却要做一次类型转换,所以会出现ClassCastException,如果可以进行泛型数组的声明,上面说的这种情况在编译期将
* 不会出现任何的警告和错误,只有在运行时才会出错,而对泛型数组的声明进行限制,对于这样的情况,可以在编译器提示代码有
* 类型安全问题,比没有任何提示要强很多
*/
Object o = ls;
Object[] oa = (Object[]) o;
oa[1] = string;
/**
* 数组类型不可以是类型变量,除非时采用通配符的方式;对于通配符的方式,最好取出的数据是要做显式的类型转换的
*/
String o1 = (String) ls[1].get(1);
System.out.println(o1);
}
}