泛型是 Java SE5 出现的新特性,泛型的本质是类型参数化或参数化类型,在不建立新的类型的状况下,经过泛型指定的不一样类型来控制形参具体限制的类型。java
通常的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。若是要编写能够应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。git
Java 在引入泛型以前,表示可变对象,一般使用 Object 来实现,可是在进行类型强制转换时存在安全风险。有了泛型后:github
举个例子:编程
public static void method1() {
List list = new ArrayList();
List.add(22);
List.add("hncboy");
List.add(new Object());
for (Object o : list) {
System.out.println(o.getClass());
}
}复制代码
未使用泛型前,咱们对集合能够进行任意类型的 add 操做,遍历结果都被转换成 Object 类型,由于不肯定集合里存放的具体类型,输出结果以下所示。数组
class java.lang.Integer
class java.lang.String
class java.lang.Object复制代码
采用泛型以后,建立集合对象能够明确的指定类型,在编译期间就肯定了该集合存储的类型,存储其余类型的对象编译器会报错。这时遍历集合就能够直接采用明确的 String 类型输出。安全
public static void method2() {
List<String> list = new ArrayList();
list.add("22");
list.add("hncboy");
//list.add(new Object()); 报错
for (String s : arrayList) {
System.out.println(s);
}
}复制代码
泛型能够定义在类、接口、方法中,分别表示为泛型类、泛型接口、泛型方法。泛型的使用须要先声明,声明经过 <符号> 的方式,符号能够任意,编译器经过识别尖括号和尖括号内的字母来解析泛型。泛型的类型只能为类,不能为基本数据类型。尖括号的位置也是固定的,只能在类名以后或方法返回值以前。微信
通常泛型有约定的符号:E 表明 Element,
泛型的表达式有以下几种:函数
将泛型定义在类名后,使得用户在使用该类时,根据不一样状况传入不一样类型。在类上定义的泛型,在实例方法中能够直接使用,不须要定义,可是静态方法上的泛型须要在静态方法上声明,不能直接使用。举个例子:学习
public class Test<T> {
private T data;
public T getData() {
return data;
}
/** 这种写法是错误的,提示 T 未定义 */
/*public static T get() {
return null;
}*/
/** 正确写法,该方法上的 T 和类上的 T 虽然同样,可是是两个指代,能够彻底相同,互不影响 */
public static <T> T get() {
return null;
}
public void setData(T data) {
this.data = data;
}
}复制代码
泛型方法,是在调用方法时指明的具体的泛型类型。虽然类上定义的泛型,实例方法中能够直接使用,可是该方法不属于泛型方法。举个例子:get 方法为泛型方法,并且该程序能编译经过运行,由于尖括号里的每一个元素都指代一种未知类型,能够为任何符号,尖括号里的 String 并不是 java.lang.String 类型,而是做为泛型标识
public class Test {
public static <String, T, Hncboy> String get(String string, Hncboy hncboy) {
return string;
}
public static void main(String[] args) {
Integer first = 666;
Double second = 888.0;
Integer result = get(first, second);
System.out.println(result);
}
}复制代码
? 为泛型非限定通配符,表示类型未知,不用声明,能够匹配任意的类。该通配符只能读,不能写,且不对返回值进行操做。也能够将非限定通配符出现的地方用普通泛型标识,不过使用通配符更简洁。举个例子:
test1() 是经过通配符来输出集合的每个元素的,test2() 和 test1() 的做用同样,只不过将通配符用
public class Test {
public static void test1(List<?> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
public static <T> void test2(List<T> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
public static void test3(List<?> list) {
//list.add(1); capture of ?
//list.add("1"); capture of ?
list.add(null);
}
/*public static ? test4(List<?> list) {
return null;
}*/
public static void test5(List<Object> list) {
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
test1(list);
test2(list);
//test5(list);
}
}复制代码
经过使用泛型通配符能够实现泛型的上下边界 和 ,下面将使用 Number 类以及该类的子类来演示这两种上下型边界,Number 类的关系图以下。
表示类型为 Number 或 Number 的子类, 表示类型为 Integer 或 Integer 的父类,举个例子,method1 方法测试是上边界 Number,因为 arrayList1 和 arrayList2 的泛型都为 Number 或其子类,因此能够插入成功,而 arrayList3 的类型 String 和 Number 无关,所以编译报错。method2 方法测试的是下边界 Integer,因为 arrayList4,arrayList5 和 arrayList7 种的类型 Integer、Object 和 Number 都为 Integer 的父类,因此插入成功,而 arrayList7 的类型 Double,所以插入失败。
public class Generic {
public static void main(String[] args) {
ArrayList<Integer> arrayList1 = new ArrayList<>();
ArrayList<Number> arrayList2 = new ArrayList<>();
ArrayList<String> arrayList3 = new ArrayList<>();
method1(arrayList1);
method1(arrayList2);
//method1(arrayList3);
ArrayList<Integer> arrayList4 = new ArrayList<>();
ArrayList<Object> arrayList5 = new ArrayList<>();
ArrayList<Number> arrayList6 = new ArrayList<>();
ArrayList<Double> arrayList7 = new ArrayList<>();
method2(arrayList4);
method2(arrayList5);
method2(arrayList6);
//method2(arrayList7)
}
public static void method1(ArrayList<? extends Number> arrayList) {
}
public static void method2(ArrayList<? super Integer> arrayList) {
}
}复制代码
泛型接口就是在接口上定义的泛型,当一个类型未肯定的类实现接口时,须要声明该类型。举个例子:
public interface CalcGeneric<T> {
T add(T num1, T num2);
}
public class CalculatorGeneric<T> implements CalcGeneric<T> {
@Override
public T add(T num1, T num2) {
return null;
}
}复制代码
数组是支持协变的,什么是数组的协变呢?举个例子:这段代码中,数组支持以 1 的方式定义数组,由于 Integer 是 Number 的子类,一个 Integer 对象也是一个 Number 对象,因此一个 Integer 的数组也是一个 Number 的数组,这就是数组的协变。虽然这种写法编译时能经过,可是数组实际上存储的是 Integer 对象,若是加入 Double 对象,那么在运行时就会抛出 ArrayStoreException 异常,该种设计存在缺陷。3 方式所示的定义数组方式编译错误,4 所指示的代码才是正确的。泛型是不变的,没有内建的协变类型,使用泛型的时候,类型信息在编译期会被类型擦除,因此泛型将这种错误检测移到了编译器。泛型的设计目的之一就是保证了类型安全,让这种运行时期的错误在编译期就能发现,因此泛型是不支持协变的,如 5 所示的该行代码会有编译错误,
public class Test {
public static void main(String[] args) {
Number[] numbers = new Integer[10]; // 1
// java.lang.ArrayStoreException: java.lang.Double
numbers[0] = new Double(1); // 2
//List<String>[] list = new ArrayList<String>[10]; // 3
List<String>[] list2 = new ArrayList[10]; // 4
//List<Number> list3 = new ArrayList<Integer>(); // 5
}
}复制代码
在泛型内部,没法得到任何有关泛型参数类型的信息,泛型只在编译阶段有效,泛型类型在逻辑上可当作是多个不一样的类型,可是其实质都是同一个类型。由于泛型是在JDK5以后才出现的,须要处理 JDK5以前的非泛型类库。擦除的核心动机是它使得泛化的客户端能够用非泛化的类库实现,反之亦然,这常常被称为"迁移兼容性"。
代价:泛型不能用于显式地引用运行时类型地操做之中,例如转型、instanceof 操做和 new 表达式,由于全部关于参数地类型信息都丢失了。不管什么时候,当你在编写这个类的代码的时候,提醒本身,他只是个Object。catch 语句不能捕获泛型类型的异常。
举个例子:这串代码的运行输出是,所以可见泛型在运行期间对类型进行了擦除。
class java.util.ArrayList
class java.util.ArrayList
true复制代码
public static void method1() {
List<Integer> integerArrayList = new ArrayList();
List<String> stringArrayList = new ArrayList();
System.out.println(integerArrayList.getClass());
System.out.println(stringArrayList.getClass());
System.out.println(integerArrayList.getClass() == stringArrayList.getClass());
}复制代码
将上面的 Java 代码编译成字节码后查看也可看见两个集合都是 java/util/ArrayList
public static method1()V
L0
LINENUMBER 14 L0
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 0
L1
LINENUMBER 15 L1
NEW java/util/ArrayList
DUP
INVOKESPECIAL java/util/ArrayList.<init> ()V
ASTORE 1复制代码
由于在运行期间类型擦除的关系,能够经过反射在运行期间修改集合能添加的类,不过添加后查询该集合会抛出 ClassCastException 异常,代码以下。
public static void method4() throws Exception {
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("hnc");
stringArrayList.add("boy");
System.out.println("以前长度:" + stringArrayList.size());
// 经过反射增长元素
Class<?> clazz = stringArrayList.getClass();
Method method = clazz.getDeclaredMethod("add", Object.class);
method.invoke(stringArrayList, 60);
System.out.println("以后长度:" + stringArrayList.size());
// 存的仍是 Integer 类型
// java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
for (int i = 0; i < stringArrayList.size(); i++) {
System.out.println(stringArrayList.get(i).getClass());
}
}复制代码
泛型在平时的学习中用到的仍是挺多的。
Java 编程思想
码出高效 Java 开发手册
文章同步到公众号和Github,有问题的话能够联系做者。