一 为什么我们需要泛型?
public class NonGeneric {
public int addInt(int x, int y) {
return x + y;
}
public float addFloat(float x, float y) {
return x + y;
}
//double
public static void main(String[] args) {
NonGeneric nonGeneric = new NonGeneric();
System.out.println(nonGeneric.addInt(1, 2));
System.out.println(nonGeneric.addFloat(1f, 2f));
}
}
public class NonGeneric2 {
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("chric");
list.add("OK");
list.add(100);
for (int i = 0; i < list.size(); i++) {
String name = list.get(i); // 1
System.out.println("name:" + name);
}
}
}
//
java: 对于add(int), 找不到合适的方法
方法 java.util.Collection.add(java.lang.String)不适用
(参数不匹配; int无法转换为java.lang.String)
方法 java.util.List.add(java.lang.String)不适用
(参数不匹配; int无法转换为java.lang.String)
- 适用于多种数据类型执行相同的代码
- 泛型中的类型在使用时指定,不需要强制类型转换
1.1 泛型简介
之前关于类的学习中我们知道,一个类中可以定义它的属性以及方法,在那里我们定义类的属性时不同的属性我们采用的是不同的数据类型,这就要求我们对每一个数据的类型进行声明操作。但是我们想到这样一个问题,如果这个类有无数个属性的时候我们怎么对这个类的属性进行定义呢?显然,将这个类的所有属性进行枚举型的定义是不现实的,因此这就要求我们找到属性定义的共同特性。这时Object出现在了我们面前,用这个关键字可以对不同类型的属性进行定义接收,但是这样我们也不知道具体的属性的类型,在涉及类型转换时也很容易出现错误,并且这钟错误一般出现在运行阶段。为了解决这个难题,让代码拥有更高的可读性与更高的安全性,java中提出了泛型的概念。
泛型的本质是数据类型的参数化,这么说显得比较抽象。其实我们可以这样理解,泛型就是用一个指定的标识符来代表具体的数据类型,将具体的数据类型转换为参数,在创建类、接口或者方法时,我们都用指定的标识符来代替具体的数据类型,这样就免去同一个类、接口或者方法对不同的数据类型进行操作时需要重新编写程序的问题。在使用由泛型创建的方法、接口或者类时,只要我们进行一定程度的说明,系统就会自动判断数据的类型,这样也免去了类型转换时可能出现的错误,提高了代码的安全性。
所以其实上,泛型与我们之前学的带参方法有着异曲同工之妙。在带参方法中,我们用形式参数来代表传入方法的具体数据,但是在方法创建时我们并不知道传入的实际参数是什么,只有在使用这个方法的时候才知道要传入一个什么样的数据。泛型也是这样,我们用一个标识符来代表在类、方法或者接口中可能出现的数据类型,但具体会出现什么样的数据类型只有在使用一个方法、实例化一个对象或者实现一个接口的时候才知道。
除了上面谈到的关于泛型的理解的问题,我们还要注意到,虽然程序员在编写程序的时候用了泛型参数来定义类型,但是在编译的时候泛型会被去掉,编译结束后类型参数会被替换成Object。涉及到类型转换时,任然是普通的强制类型转换。我们把这种类型参数在编译后消失的现象叫做类型擦除。虽然目前通过反编译手段任然能够看到编译前的类型参数,但是着并不代表编译后生成的class字节码文件中存在着泛型中的类型信息。
1.2 泛型的定义
从理论上来说,泛型可以用任何一个符合规则的标志符来定义,但是那样会有很大概率造成不小的麻烦,因此一般来说我们会用java中指定的几个标识符来定义泛型。这些标识符分别是:E、T、K、V、N、?。
这些标识符每一个都代表了不同的含义。
E——Element,在容器中使用,代表的是容器中的元素。
T——Type,表示的是普通的java类。
K——Key,表示键,例如Map中的Key键。
V——Value,表示值。
N——Number,表示数值类型。
?——表示不确定的java类型。
不难发现,N代表的是Number类,而在包装类中我们提到过,Number类是六种数值类型对应的包装类的父类,也就是说定义泛型时只能用引用类型,不能使用基本数据类型。
二 泛型类和泛型接口
2.1 泛型类
public class NormalGeneric<K> {
private K data;
public NormalGeneric() {
}
public NormalGeneric(K data) {
this.data = data;
}
public K getData() {
return data;
}
public void setData(K data) {
this.data = data;
}
public static void main(String[] args) {
NormalGeneric<String> normalGeneric = new NormalGeneric<>();
normalGeneric.setData("OK");
//normalGeneric.setData(1);
System.out.println(normalGeneric.getData());
NormalGeneric normalGeneric1 = new NormalGeneric();
normalGeneric1.setData(1);
normalGeneric1.setData("dsf");
}
}
public class NormalGeneric2<T,K> {
private T data;
private K result;
public NormalGeneric2() {
}
public NormalGeneric2(T data) {
this();
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public NormalGeneric2(T data, K result) {
this.data = data;
this.result = result;
}
public K getResult() {
return result;
}
public void setResult(K result) {
this.result = result;
}
public static void main(String[] args) {
NormalGeneric2<String,Integer> normalGeneric2 = new NormalGeneric2<>();
normalGeneric2.setData("OK");
System.out.println(normalGeneric2.getData());
normalGeneric2.setResult(23);
System.out.println(normalGeneric2.getResult());
}
}
2.2 泛型接口
在泛型接口Igeneric的实现接口IgenericIme中,我们给定了泛型T的具体类型为String,这时如果通过实现类IgenericIme来创建对象,那么创建的对象中的数据类型就是String类。在这里就必须说到实例化对象的不同方式——通过接口实例化对象以及通过实现类实例化对象。在通过泛型接口的的实现类来实例化对象的时候,它的实例化方式与普通类的实例化没有区别,比如在下面演示代码中的igenericIme。但是当我们用泛型接口来实例化对象的时候就要特别注意,这种实例化方式要指定泛型所代之的类型并且还要指定特定的实现类,它的语法格式为:泛型接口名称<泛型指代的类型> 对象名称 = new 泛型接口的实现类名称();。就像演示代码中的对象igeneric以及对象igeneric1一样,并且要注意,尖括号中的泛型指代的类型要和接口的实现类中的保持一致,如果将对象igeneric中的泛型指代的名称输入为Integer,那么因为和后面的实现类中的泛型不同,所以程序无法通过编译。
public interface Generator<T> {
public T next();
}
public class ImplGenerator<T> implements Generator<T> {
private T data;
@Override
public T next() {
return data;
}
}
class Test {
public static void main(String[] args) {
ImplGenerator<String> stringImplGenerator = new ImplGenerator<>();
}
}
public class ImplGenerator2 implements Generator<String> {
@Override
public String next() {
return "OK";
}
}
class TestGenerator {
public static void main(String[] args) {
ImplGenerator2 implGenerator2 = new ImplGenerator2();
}
}
2.2 泛型方法
静态泛型方法与非静态泛型方法
在之前说的泛型类以及泛型接口的部分能够发现,我们定义在类和接口上的泛型在它们的方法中仍然可以使用,但是有时我们需要的仅仅是在一个指定的方法上使用泛型而不需要在整个类和接口上使用泛型,因此泛型方法的概念就被提出了。泛型方法指的是将方法中的参数类型定义为泛型,以便在调用时接收不同的参数类型的现象。泛型方法可以分为非静态泛型方法和静态泛型方法。在定义泛型方法时,参数类型一般放到返回值前面,并且需要说明的是如果返回值类型不是void的情况下,返回值类型仍然定义为泛型。此外,调用泛型方法时不需要像泛型类和泛型接口那样指定泛型的类型,系统能够自动判断出泛型的类型,因此泛型方法的调用和普通方法没有区别。
- 非静态泛型方法的语法结构:
public <泛型表示符号> void 方法名(泛型表示符号 形式参数){}
public <泛型表示符号> 泛型表示符号 方法名(泛型表示符号 形式参数){}
- 静态泛型方法的语法够:
public static <泛型表示符号> void 方法名(泛型表示符号 形式参数){}
public static <泛型表示符号> 泛型表示符号 方法名(泛型表示符号 形式参数){}
对于静态泛型方法和非静态泛型方法,除了在定义的格式上存在的细微区别外,还要特别注意,静态泛型方法时无法访问类上定义的泛型的,也就是说如果我们定义了一个泛型类,在这个类中定义静态泛型方法时必须在static后面定义泛型,而不能直接使用类上定义的泛型。
泛型方法与可变参数
在泛型方法中和普通方法一样,传入的参数也可以是任意类型,包括像数组这种可变参数依旧可以传入方法,不过在泛型方法中用泛型定义可变参数的类型时要采用特殊的语法结构进行说明,它的具体语法格式为:
public (static) 泛型表示符号 泛型表示符号(void) 方法名 (泛型表示符号. . . 可变参数名称){}
在这个语法结构中要特别注意泛型表示符号与可变参数名称之间的符号. . . ,它是用来区分普通参数和可变参数的标志。
public class GenericMethod {
public <T> T genericMethod(T...a){
return a[a.length/2];
}
public void test(int x,int y){
System.out.println(x+y);
}
public static void main(String[] args) {
GenericMethod genericMethod = new GenericMethod();
genericMethod.test(23,343);
System.out.println(genericMethod.<String>genericMethod("mark","av","lance"));
System.out.println(genericMethod.genericMethod(12,34));
}
}
public class GenericMethod2 {
//这个类是个泛型类,在上面已经介绍过
public class Generic<T>{
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 = key;
// }
}
/**
* 这才是一个真正的泛型方法。
* 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
* 这个T可以出现在这个泛型方法的任意位置.
* 泛型的数量也可以为任意多个
* 如:public <T,K> K showKeyName(Generic<T> container){
* ...
* }
*/
//这也不是一个泛型方法,这就是一个普通的方法,
// 只是使用了Generic<Number>这个泛型类做形参而已。
public void show(Generic<Number> obj){
}
/**
* 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
* 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
*/
// public <T,E> T show(E ab){
// //
// }
/**
* 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
* 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
* 所以这也不是一个正确的泛型方法声明。
}
*/
// public void show(T obj){
//
// }
public static void main(String[] args) {
}
}
public class GenericMethod3 {
static class Fruit{
@Override
public String toString() {
return "fruit";
}
}
static class Apple extends Fruit{
@Override
public String toString() {
return "apple";
}
}
static class Person{
@Override
public String toString() {
return "Person";
}
}
static 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<>();
generateTest.show_1(apple);
//generateTest.show_1(person);
generateTest.show_2(apple);
generateTest.show_2(person);
generateTest.show_3(apple);
generateTest.show_3(person);
}
}
三 限定类型变量
public static <T> T min(T a, T b) {
if (a.comapareTo(b) > 0) {
return a;
} else {
return b;
}
}
public static <T extends Comparable> T min(T a, T b) {
if (a.compareTo(b) > 0) {
return a;
} else {
return b;
}
}
public static <T extends ArrayList & Comparable> T min(T a, T b) {
if (a.compareTo(b) > 0) {
return a;
} else {
return b;
}
}
public static <T extends Serializable & Comparable> T min(T a, T b) {
if (a.compareTo(b) > 0) {
return a;
} else {
return b;
}
}
四 泛型中的约束和局限性
public class Restrict<T> {
private T data;
//不能实例化类型变量
// public Restrict() {
// this.data = new T();
// }
// 泛型类的静态上下文中类型变量失效
//静态域或者方法里不能引用类型变量
//private static T instance;
//静态方法 本身是泛型方法就行
//private static <T> T getInstance(){}
public static void main(String[] args) {
// 不能用基本类型实例化类型参数 这种不允许
// Restrict<double> restrict = new Restrict<>();
//Restrict<double>
Restrict<Double> restrict = new Restrict<>();
// 运行时类型查询只适用于原始类型
// if(restrict instanceof Restrict<Double>) 这种不允许
// if(restrict instanceof Restrict<T>) 这种不允许
Restrict<String> restrictString = new Restrict<>();
System.out.println(restrict.getClass() == restrictString.getClass());
System.out.println(restrict.getClass().getName());
System.out.println(restrictString.getClass().getName());
// 不能创建参数化类型的数组
Restrict<Double>[] restrictArray; // 可以
//Restrict<Double>[] restricts = new Restrict<Double>[10]; // 不允许
// ArrayList<String>[] list1 = new ArrayList<String>[10]; // 不允许
ArrayList<String>[] list2 = new ArrayList[10]; //可以
}
}
public class ExceptionRestrict {
// 不能捕获泛型类的实例
/*泛型类不能extends Exception/Throwable*/
//private class Problem<T> extends Exception;
/*不能捕获泛型类对象*/
// public <T extends Throwable> void doWork(T x){
// try{
//
// }catch(T x){
// //do sth;
// }
// }
// 但是这样可以:
public <T extends Throwable> void doWorkSuccess(T x) throws T{
try{
}catch(Throwable e){
throw x;
}
}
}
4.1 不能用基本类型实例化类型参数
// 不能用基本类型实例化类型参数 Restrict<double>这种不允许
// Restrict<double> restrict = new Restrict<>();
//Restrict<double>
Restrict<Double> restrict = new Restrict<>();
4.2 运行时类型查询只适用于原始类型
// 运行时类型查询只适用于原始类型
// if(restrict instanceof Restrict<Double>) 这种不允许
// if(restrict instanceof Restrict<T>) 这种不允许
Restrict<String> restrictString = new Restrict<>();
System.out.println(restrict.getClass() == restrictString.getClass());
System.out.println(restrict.getClass().getName());
System.out.println(restrictString.getClass().getName());
4.3 泛型类的静态上下文中类型变量失效
不能在静态域或方法中引用类型变量。因为泛型是要在对象创建的时候才知 道是什么类型的,而对象创建的代码执行先后顺序是 static 的部分,然后才是构 造函数等等。所以在对象初始化之前 static 的部分已经执行了,如果你在静态部 分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,因为这个时候类还 没有初始化。
// 泛型类的静态上下文中类型变量失效
//静态域或者方法里不能引用类型变量
//private static T instance;
//静态方法 本身是泛型方法就行
//private static <T> T getInstance(){}
4.4 不能创建参数化类型的数组
// 不能创建参数化类型的数组
Restrict<Double>[] restrictArray; // 可以
//Restrict<Double>[] restricts = new Restrict<Double>[10]; // 不允许
// ArrayList<String>[] list1 = new ArrayList<String>[10]; // 不允许
ArrayList<String>[] list2 = new ArrayList[10]; //可以
4.5 不能实例化类型变量
//不能实例化类型变量
// public Restrict() {
// this.data = new T();
// }
4.6 不能捕获泛型类的实例
// 不能捕获泛型类的实例
/*泛型类不能extends Exception/Throwable*/
//private class Problem<T> extends Exception;
/*不能捕获泛型类对象*/
// public <T extends Throwable> void doWork(T x){
// try{
//
// }catch(T x){
// //do sth;
// }
// }
// 但是这样可以:
public <T extends Throwable> void doWorkSuccess(T x) throws T{
try{
}catch(Throwable e){
throw x;
}
}
五 泛型类型的继承规则
public class Employee {
private String firstName;
private String secondName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSecondName() {
return secondName;
}
public void setSecondName(String secondName) {
this.secondName = secondName;
}
}
public class Worker extends Employee {
}
public class Pair<T> {
private T one;
private T two;
public T getOne() {
return one;
}
public void setOne(T one) {
this.one = one;
}
public T getTwo() {
return two;
}
public void setTwo(T two) {
this.two = two;
}
private static <T> void set(Pair<Employee> p){
}
public static void main(String[] args) {
//Pair<Employee>和Pair<Worker>没有任何继承关系
Pair<Employee> employeePair = new Pair<>();
Pair<Worker> workerPair = new Pair<>();
Employee employee = new Worker();
//Pair<Employee> employeePair2 = new Pair<Worker>();
Pair<Employee> pair = new ExtendPair<>();
set(employeePair);
//set(workerPair);
}
/*泛型类可以继承或者扩展其他泛型类,比如List和ArrayList*/
private static class ExtendPair<T> extends Pair<T>{
}
}
//Pair<Employee>和Pair<Worker>没有任何继承关系
Pair<Employee> employeePair = new Pair<>();
Pair<Worker> workerPair = new Pair<>();
Employee employee = new Worker();
//Pair<Employee> employeePair2 = new Pair<Worker>(); // 不允许
Pair<Employee> pair = new ExtendPair<>();
/*泛型类可以继承或者扩展其他泛型类,比如List和ArrayList*/
private static class ExtendPair<T> extends Pair<T>{
}
六 通配符类型
public class GenericType<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
public class Food {
}
public class Fruit extends Food {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
public class Apple extends Fruit {
}
public class Orange extends Fruit {
}
public class HongFuShi extends Apple {
}
public class WildChar{
public static void print(GenericType<Fruit> p){
System.out.println(p.getData().getColor());
}
public static void use(){
GenericType<Fruit> a = new GenericType<>();
print(a);
GenericType<Orange> b = new GenericType<>();
//print(b); // 这样不允许
}
}
Provided:GenericType<Orange>
6.1 ? extends X
public class WildChar{
public static void print2(GenericType<? extends Fruit> p){
System.out.println(p.getData().getColor());
}
public static void use2(){
GenericType<Fruit> a = new GenericType<>();
print2(a);
GenericType<Orange> b = new GenericType<>();
print2(b);
//print2(new GenericType<Food>()); // 不允许
GenericType<? extends Fruit> c = new GenericType<>();
Apple apple = new Apple();
Fruit fruit = new Fruit();
//c.setData(apple); // 不允许
//c.setData(fruit); // 不允许
Fruit x = c.getData();
}
}
// print2(new GenericType<Food>()); // 不允许Required type:GenericType<? extends Fruit>
Provided:GenericType<Food>// c.setData(apple); // 不允许Required type:capture of ? extends Fruit
Provided:Apple// c.setData(fruit); // 不允许Required type:capture of ? extends Fruit
Provided:Fruit
6.2 ? super X
public class WildChar{
public static void printSuper(GenericType<? super Apple> p){
System.out.println(p.getData());
}
public static void useSuper(){
GenericType<Fruit> fruitGenericType = new GenericType<>();
GenericType<Apple> appleGenericType = new GenericType<>();
GenericType<HongFuShi> hongFuShiGenericType = new GenericType<>();
GenericType<Orange> orangeGenericType = new GenericType<>();
printSuper(fruitGenericType);
printSuper(appleGenericType);
// printSuper(hongFuShiGenericType);
// printSuper(orangeGenericType);
//表示GenericType的类型参数的下界是Apple
GenericType<? super Apple> x = new GenericType<>();
x.setData(new Apple());
x.setData(new HongFuShi());
//x.setData(new Fruit()); // 不允许
Object data = x.getData();
}
}
//x.setData(new Fruit()); // 不允许Required type:capture of ? super Apple
Provided:Fruit
6.3 无限定的通配符 ?
七 虚拟机是如何实现泛型的?
public class Conflict {
public static String method(List<String> stringList){
System.out.println("List");
return "OK";
}
public static Integer method(List<Integer> stringList){
System.out.println("List");
return 1;
}
/*
* Signature (弱记忆)
*
* ? super xxxx
*
* */
}