前言 本身是打算接着写JMM、JCStress,然后这两个是在公司闲暇的时候随手写的,没有推到Github上,但写点什么可以让我获得宁静的感觉,所性就从待办中拎了一篇文章,也就是这篇泛型。这篇文章来自于我朋友提出的一个问题,比如我在一个类里面声明了两个方法,两个方法只有返回类型是int,一个是Integer,像下面这样,能否通过编译: public class DataTypeTest { public int sayHello(){
return 0;
}
public Integer sayHello(){
return 1;
}
In any nontrivial software project, bugs are simply a fact of life. Careful planning, programming, and testing can help reduce their pervasiveness, but somehow, somewhere, they'll always find a way to creep into your code. This becomes especially apparent as new features are introduced and your code base grows in size and complexity. 在任何不平凡的软件工程,bug都是不可避免的事实。仔细的规划、变成、测试可以帮助减少它们的普遍性,但不知何时,不知何地,它们总会找到一种方式渗入你的代码。随着新功能的引入和代码量的增长,这一点变得尤为明显。
Fortunately, some bugs are easier to detect than others. Compile-time bugs, for example, can be detected early on; you can use the compiler's error messages to figure out what the problem is and fix it, right then and there. Runtime bugs, however, can be much more problematic; they don't always surface immediately, and when they do, it may be at a point in the program that is far removed from the actual cause of the problem. 幸运的是,一些bug更容易发现相对其他类型的bug,例如,编译时的bug可以在早期发现; 你可以使用编译器给出的错误信息来找出问题所在,然后在当时就解决它。然而运行时的bug就要麻烦的多,它们并不总是立即复现出来,而且当它们复现出来的时候,可能是在程序的某个点上,与问题的实际原因相去甚远。 Generics add stability to your code by making more of your bugs detectable at compile time. 泛型可以增加你的代码的稳定性,让更多错误可以在编译时被发现。
In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs. The difference is that the inputs to formal parameters are values, while the inputs to type parameters are types. Code that uses generics has many benefits over non-generic code: 简而言之,泛型可以使得在定义类、接口和方法时可以将类型作为参数。就像在方法中声明形式参数一样,类型参数提供了一种方式,让你可以在不同的输入使用相同的代码。不同之处在于,形式参数输入的是值,而类型参数的输入是类型。使用泛型的代码相对于非泛型的代码有很多优点:
Stronger type checks at compile time. A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.
Elimination of casts. The following code snippet without generics requires casting: 消除转换,下面代码片段是没有泛型所需的转换
List list = new ArrayList(); list.add("hello world"); String s = (String) list.get(0); 复制代码
When re-written to use generics, the code does not require casting: 当我们用泛型重写, 代码就不需要类型转换
List list = new ArrayList(); list.add("hello world"); String s = list.get(0); 复制代码
Enabling programmers to implement generic algorithms. 使得程序员能够通用(泛型)算法。 By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read. 用泛型,程序员能够可以在不同类型的集合上工作,可以被被定制,并且类型是安全的,更容易阅读。
简单总结一下,引入泛型的好处,将类型当做参数,可以让开发者可以在不同的输入使用相同的代码,我的理解是,提升代码的可复用性,在编译时执行更强的类型检查,消除类型转换,用泛型实现通用的算法。那该怎么使用呢? 泛型如何使用 Hello World 上面我们提到泛型是类型参数,那我们如何传递给一个类,类型呢,类似于方法,我们首先要声明形式参数,它跟在类名后面,放在<>里面,在里面我们可以声明接收几个类型参数,如下所示: class name<T1, T2, ..., Tn> {} 复制代码 下面是一个简单的泛型使用示例: public class Car{
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
Car<Integer> car = new Car<>();
car.setData(1);
Integer result = car.getData();
}
} 复制代码 在没有泛型之前,我们的代码如果想实现这样的效果就只能用Object,在使用的时候进行强制类型转换像下面这样: public class Car{
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static void main(String[] args) {
Car car = new Car();
car.setData(1);
Integer result = (Integer) car.getData();
}
E- 元素 广泛被Java集合框架所使用 K - key N - 数字 Y - 类型 V - 值 S,U,V etc - 2nd, 3rd, 4th types
原始类型(Raw Type) 泛型类和泛型接口没有接收类型参数的名字,拿上面的Car类举例, 为了给传递参数类型,我们在创建car对象的时候就会给一个正常的类型: Car car = new Car<>(); 复制代码 如果未提供类型参数,你将创建一个Car的原始类型: Car car = new Car(); 复制代码 因此,Car是泛型类Car的原始类型,然而非泛型类、接口就不是原始类型。现在我们有一个类叫Dog, 这个Dog类不接收类型参数, 如下代码参数: class Dog{
Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors. 所谓泛型方法指的就是方法上引入参数类型的方法,这与声明泛型类似。但是类型参数的范围仅于声明的范围。允许静态和非静态方法,也允许泛型构造函数。
下面是一个泛型静态方法: // 例子来自于: The Java™ Tutorials public class Pair<K,V> {
private K key;
private V value;
// 泛型构造函数
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
// getters, setters etc.
} 复制代码 U extends Number,compare接收的参数只能是Number或Number子类的实例,extends后面跟上界。我们传入String类型,编译就会不通过: // IDE 中会直接报错 CompareUtil.compare("2"); 复制代码 说到这里想起了《数学分析》上的确界原理: 任一有上界的非空实数集必有上确界(最小上界);同样任一有下界的非空实数集必有下确界(最大下界)。 当我们限制了泛型的上界,那我们就可以在泛型方法里面调用上界类的方法, 像下面这样: public static boolean compare(U u){ u.intValue(); return false; } 复制代码 但有的时候一个上界可能还不够,我们希望有多个上界:
复制代码 Java中虽然不支持多继承,但是可以实现多个接口,但是如果多个上界中某个上界是类,那么这个类一定要出现在第一个位置,如下所示: class A {} interface B {} interface C {} class D 复制代码 如果A不在第一个位置,就会编译报错。 有界类型参数和泛型方法 有界类型参数是实现通用算法的关键,思考下面一个方法,该方法计算数组中大于指定元素elem的元素数量, 我们可能这么写: public static int countGreaterThen(T[] anArray,T elem){
int count = 0;
for (T t : anArray) {
if (t > elem ){
count++;
}
}
return count;
} 复制代码 但由于你没有限制泛型参数的范围,上面的方法报错原因也很简单,原因在于操作符号(>)只能用于基本数据类型,比如short,int,double,long,float,byte,char。对象之间不能使用(>),但这些数据类型都有包装类,包装类都实现了Comparable接口,我们就可以这么写: public static int countGreaterThen(T[] anArray,T elem){ int count = 0; for (T t : anArray) {
In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype. 在泛型代码中 ,?被称为通配符,代表未知类型。通配符可以在各种情况下使用: 作为参数、字段或局部变量的类型;有时作为返回类型(尽管更好的编程实际是更具体的)。通配符从不用作泛型方法的调用,泛型类示例创建或父类型的类型参数。 《Java Tutorial》
其实看到这块的时候,我对这个通配符是有点不了解的,我将这个符号理解为和T、V一样的泛型参数名,但是我用?去取代T的时候,发现IDEA里面出现了错误提示。那代表?号是特殊的一类泛型符号,有专门的含义。 假如我们想制作一个处理List的方法,我们希望限制集合中的元素只能是Number的子类,我们看了上面的有界类型参数就可能会很自然的写出下面的代码: public static int processNumberList(List anArray) {
// 省略处理逻辑
return 0;
} 复制代码 但有了通配符之后,事实上我们可以这么声明: public static int processNumberList(List<? extends Number> numberList ) {
return 0;
} 复制代码 事实上编译器会认为这两个方法是一样的,IDEA上会给出提示是:
'processNumberList(List<? extends Number>)' clashes with 'processNumberList(List)'; both methods have same erasure 两个方法拥有相同的泛型擦除
我们将在下文专门讨论泛型擦除 , 我们这里还是熟悉泛型的基本使用。 ? extends Number 复制代码 这种语法我们称之为上界类型通配符(Upper Bounded Wildcards),表示的是传入的List中的元素只能是Number实例、或Number子类型的实例。在遍历中可以调用上界的方法。 下界通配符 有上界通配符对应的就有下界通配符,上界通配符限制的是传入的类型必须是限制类型或限制类型的子类型,而下界类型则限制传入类型是限制类型或限制类型的父类型。举个例子,你只想传入的类型是List,List, List
If you are writing a method that can be implemented using functionality provided in the Object class.
如果你正在编写的方法可以用Object类提供的方法进行实现。
When the code is using methods in the generic class that don't depend on the type parameter. For example, List.size or List.clear. In fact, Class<?> is so often used because most of the methods in Class do not depend on T.
我们将Number理解为牛亚科,Integer理解为羚羊亚科,那所有羚羊亚科的下级科都是牛亚科的下级科,所有牛亚科的上机科目都是羚羊亚科的上级科目。这样理解似乎更自然。 通配符捕获和辅助方法 在某些情况下,编译器会尝试推断通配符的类型。例如一个List被定为List<?>,编译器执行表达式的时候,编译器会从代码中推断出一个具体的类型。这种情况被称为通配符捕获。大部分情况下,你都不需要担心通配符捕获的问题,除非你看到包含"捕获" 这一短语的错误信息。通配符错误通常发生在编译器: public class WildcardError {
An "in" variable is defined with an upper bounded wildcard, using the extends keyword.
入参用上界通配符,使用extends关键字。
An "out" variable is defined with a lower bounded wildcard, using the super keyword.
输出变量用下界通配符, 使用super关键字
In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard.
如果需要使用入参可以使用定义在Object类中的方法时,使用无界通配符。
In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard.
当代码需要将变量同时用作输入和输出时,不要使用无界通配符。
泛型擦除 Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. 泛型被引入Java, 在编译时提供了强类型检查,支持了通用泛型编程。 To implement generics, the Java compiler applies type erasure to:
Java选择用泛型擦除实现泛型
Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.