Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是
参数化类型
,也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法,然后在使用/调用时传入具体的类型
(类型实参)。Java 泛型也是一种语法糖,
在编译阶段完成类型的转换的工作
,避免在运行时强制类型转换而出现 ClassCastException 类型转化异常
。
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
List<String> stringArrayList = new ArrayList<String>(); Map<String, String> map = new HashMap<String, String>();
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。
java 中泛型标记符:
泛型类在类名后面添加类型参数声明部分。泛型类的类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
定义格式:
public class 类名 <泛型类型1, 泛型类型2,...> { private 泛型类型 var; private 泛型类型 method(){ } }
注意事项:
泛型类型必须是引用类型
,不能是基本数据类型。
例子:
// 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 public class Box<T> { // t这个成员变量的类型为T,T的类型由外部指定 private T t; public void add(T t) { this.t = t; } // 泛型方法getKey的返回值类型为T,T的类型由外部指定 public T getKey() { return t; } public static void main(String[] args) { // 在实例化泛型类时,必须指定T的具体类型 // 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型 Box<Integer> integerBox = new Box<Integer>(); Box<String> stringBox = new Box<String>(); integerBox.add(new Integer(10)); stringBox.add(new String("菜鸟教程")); System.out.printf("整型值为 :%d\n\n", integerBox.getKey()); System.out.printf("字符串为 :%s\n", stringBox.getKey()); } }
定义的泛型类不一定要传入泛型类型实参。
如果在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制
,此时泛型才会起到本应起到的限制作用。如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型
。
例:Box box = new Box("111111"); Box box1= new Box(4444); Box box2 = new Box(55.55); Box box3 = new Box(false); Log.d("泛型测试","key is " + box.getKey()); Log.d("泛型测试","key is " + box1.getKey()); Log.d("泛型测试","key is " + box2.getKey()); Log.d("泛型测试","key is " + box3.getKey()); /** 运行结果: D/泛型测试: key is 111111 D/泛型测试: key is 4444 D/泛型测试: key is 55.55 D/泛型测试: key is false */
泛型接口与泛型类的定义及使用基本相同,泛型接口常被用在各种类的生产器中。
定义格式:
//定义一个泛型接口 public interface Generator<T> { public T next(); }
1、当实现泛型接口的类,未传入泛型实参时
/** * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中 * 即: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; } }
2、当实现泛型接口的类,传入泛型实参时
/** * 传入泛型实参时: * 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T> * 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。 * 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型 * 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。 */ public class FruitGenerator implements Generator<String> { private String[] fruits = new String[]{"Apple", "Banana", "Pear"}; @Override public String next() { Random rand = new Random(); return fruits[rand.nextInt(3)]; } }
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
声明格式:
/** * 泛型方法的基本介绍 * @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 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_1(T t){ System.out.println(t.toString()); } //在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。 //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。 public <E> void show_3(E t){ System.out.println(t.toString()); } //在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。 public <T> void show_2(T t){ System.out.println(t.toString()); } } public static void main(String[] args) { Apple apple = new Apple(); Person person = new Person(); GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>(); //apple是Fruit的子类,所以这里可以 generateTest.show_1(apple); //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person //generateTest.show_1(person); //使用这两个方法都可以成功 generateTest.show_2(apple); generateTest.show_2(person); //使用这两个方法也都可以成功 generateTest.show_3(apple); generateTest.show_3(person); } }
类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是 List< String>,List< Integer> 等所有 List<具体类型实参> 的父类。
例如:
import java.util.*; public class GenericTest { public static void getData(List<?> data) { System.out.println("data :" + data.get(0)); } public static void main(String[] args) { List<String> name = new ArrayList<String>(); List<Integer> age = new ArrayList<Integer>(); List<Number> number = new ArrayList<Number>(); name.add("icon"); age.add(18); number.add(314); getData(name); getData(age); getData(number); } /** 输出结果为: data :icon data :18 data :314 */ }
因为 getData() 方法的参数是 List<?> 类型的,所以 name,age,number 都可以作为这个方法的实参,这就是通配符的作用。
extends 通配符用来限定泛型的上限。比如一个新的需求,我们希望方法接收的 List 集合限定在数值类型内(float、integer、double、byte 等),不希望其他类型可以传入(比如字符串)。
import java.util.*; public class GenericTest { public static void getUperNumber(List<? extends Number> data) { System.out.println("data :" + data.get(0)); } public static void main(String[] args) { List<String> name = new ArrayList<String>(); List<Integer> age = new ArrayList<Integer>(); List<Number> number = new ArrayList<Number>(); name.add("icon"); age.add(18); number.add(314); //getUperNumber(name);//1 getUperNumber(age);//2 getUperNumber(number);//3 } /** 输出结果: data :18 data :314 */ }
在 //1 处会出现错误,因为 getUperNumber() 方法中的参数已经限定了参数泛型上限为 Number,所以泛型为 String 是不在这个范围之内,所以会报错。
类型通配符下限通过形如 List<? super Number> 来定义,表示类型只能接受 Number 及其上层父类类型,如 Object 类型的实例。
(1)使用泛型可以避免强制类型转换
,也可以避免运行期就抛出的 ClassCastException 异常。
(2)在使用泛型时,要注意变量声明的泛型类型要匹配传递给实际对象的类型, Java 7 及以后的版本中,构造方法中可以省略泛型类型,推荐直接省略
。
(3)泛型也是可以继承的
。