1、 为什么要使用泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
在javaSE5.0之前,java泛型程序设计是使用继承实现的。ArrayList类只维护一个Object引用的数组
public class ArrayList//之前的 { public Object get(int i){......} public void add(Object o){......} ...... private Object[] elementData; } |
public static void main(String[] args) { ArrayList list = new ArrayList();
list.add("qewr"); list.add(12); list.add(100L); list.add(3.1415f); //这里因为不知道取值的类型,很容易出现错误 String str = (String) list.get(1); } } |
但如果使用泛型,可以提供一个类型参数来更好地解决。:如 ArrayList<String> list = new ArrayList<String>(); 已经限定输入的参数是String这就使代码具有更好的可读性。
2、 定义简单泛型类
泛型类(generic class)就是具有一个或多个类型变量的类,简单来说就是在类的后面加上<T>, T是类型参数
// 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Pair<T> { private T value; public Pair() { } public Pair(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } } /** * 功能描述 : 泛型的类型参数只能是类类型的,不能是简单类型的 * @param : [] * @return : */ private static void test4() { Pair<String> pair = new Pair<>(); pair.setValue("重庆火锅"); System.out.println(pair.getValue()); Pair pair1 = new Pair("123"); System.out.println(pair1.getValue()); Pair pair2 = new Pair(1024); System.out.println(pair2.getValue()); //Pair<int> pair3 = new Pair<>(); }
|
Pair类引入了一个类型变量T,用尖括号<>括起来,并放在类名的后面。泛型类可以有多个类型变量。例如,可以定义Pair类,其中第一个域和第二个域使用不同的类型:
public class Pair<T,U>{......}
注意:类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。(需要时还可以用临近的字母U和S)表示“任意类型”。
3、泛型方法
private static void test6() { String[] arr = {"赵","钱","孙","李","周"}; String middle = getMiddle(arr); System.out.printf(middle); } public static <T> T getMiddle(T... args){ return args[args.length/2]; } public class Model<T> { private T value; public Model() { } public Model(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } /** * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E" * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。 public E setValue2(E value){ this.value = value } */ /** * 功能描述 : 类里面的静态方法, E 是使用该方法的时候传入的参数类型,和上面的T没有关联 * @param : [message] * @return : */ public static <E>E getMessage(E message){ System.out.println("传入的数据是"+message); return message; } // cannot be referenced from a static context // 因为泛型类中的泛型参数的实例化是在定义对象的时候指定额,而静态变量和静态方法不需要使用对象就可以调用 // 这里对象还没有创建,不清楚这个泛型参数是什么类型 // public static T say(T t){ // System.out.println("传入的消息"+t); // } }
|
注意:类型变量放在修饰符(这里是 public static)的后面,返回类型的前面。泛型方法可以定义在普通类中,也可以定义在泛型类中。
4、泛型接口的定义和使用
// //定义一个泛型接口 interface Animal<T,U>{ void show(T t,U u); } /** * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中 * 即:class Mouse<T> implements Animal<T>{ * 如果不声明泛型,如:class Mouse implements Animal<T>,编译器会报错:"Unknown class" */ class Mouse implements Animal<String,Integer>{ @Override public void show(String t, Integer u) { } } class Cat<T,U> implements Animal<T,U>{ @Override public void show(T name, U age) { System.out.print("这是一只懒猫,它叫"+name); System.out.println(" 已经"+age+"岁"); } } class Dog<T,U> implements Animal<T,U>{ @Override public void show(T name, U age) { System.out.print("这是一只斗牛犬,它叫"+name); System.out.println(" 已经"+age+"岁"); } }
|
5、类型变量的限定
/** * 功能描述 :? 通配符的边界 */ public static void example(){ //通配符的上边界 //Pair<? extends 类型1> pair = new Pair<类型2>(); //类型1 指定一个数据类型,那么类型2就只能是类型1或者是类型1的子类 Pair<? extends Number> pair = new Pair<Integer>(); // UUID 和 Number 没有关系 // Pair<? extends Number> pair2 = new Pair<UUID>(); 这是错误的 //通配符的下边界 //Pair<? super 类型1> pair = new Pair<类型2>(); //类型1 指定一个数据类型,那么类型2就只能是类型1或者是类型1的父类 Pair<? super Number> pair11 = new Pair<Object>(); //Pair<? super Number> pair12 = new Pair<Integer>(); 这是错误的 }
|
类型限定可以在泛型类、泛型接口和泛型方法中使用,但是需要注意:
public static <T extends Comparable & Serializable> T get(T t1, T t2) |
3、 如果限定既有接口也有类,那么类必须只有一个,并且放在首位
public static <T extends Object &Comparable & Serializable> T get2(T t1, T t2) |
6 类型擦除
6.1 概述
Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
如在代码中定义List<Object>和List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别
6.2 代码
private static void test0() { List<Integer> list1 = new ArrayList<>(); list1.add(123); // list1.add("qewqe"); List<String> list2 = new ArrayList<String>(); list2.add("王虎"); list2.get(0); if (list1.getClass().equals(list2.getClass())){ System.out.println("两者相同"); System.out.println(list1.getClass()); System.out.println(list2.getClass()); } System.out.println("*************************"); // 这个是证明泛型只是在编译的时候起作用,对于编译后的操作无法验证 try { list1.getClass().getMethod("add",Object.class).invoke(list1,"hello"); for (int i = 0; i < list1.size(); i++) { System.out.println(list1.get(i)); } } catch (Exception e) { e.printStackTrace(); } }
|
6.3原始类型
原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
6.4原始类型代码
//原始类型Object class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } //Pair的原始类型为: class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
|
因为在Pair<T>中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair,如Pair<String>或Pair<Integer>,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object。ArrayList<Integer>被擦除类型后,原始类型也变为Object,所以通过反射我们就可以存储字符串了。
/** * 功能描述 : 在调用泛型的时候可以指定泛型也可以不指定泛型 * 在不指定泛型的时候,泛型变量的类型为该方法中的几种类型的同一父类的最小级 * 在指定泛型的时候,该方法的几种类型必须是该泛型的实例的类型或者其子类 * @param : [] * @return : void */ private static void example1() { //不指定泛型的时候 ArrayList list = new ArrayList(); list.add(1); list.add("123"); list.add(new Date()); Object l1 = list.get(0); Integer t1 = GenericTest.test(1, 2); //两个参数都是Integer,所以T 为Integer Number t2 = GenericTest.test(1, 1.2);//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number Serializable t3 = GenericTest.test(1, "asdf");//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级 //指定泛型的时候 Integer test = GenericTest.<Integer>test(1, 2);//指定了Integer,所以只能为Integer类型或者其子类 //GenericTest.<Integer>test(1, 1.3);//编译错误,指定了Integer,不能为Float Number c = GenericTest.<Number>test(1, 2.2); //指定为Number,所以可以为Integer和Float }
|
参考了
Java泛型类型擦除以及类型擦除带来的问题
java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一
1、 为什么要使用泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
在javaSE5.0之前,java泛型程序设计是使用继承实现的。ArrayList类只维护一个Object引用的数组
public class ArrayList//之前的 { public Object get(int i){......} public void add(Object o){......} ...... private Object[] elementData; } |
public static void main(String[] args) { ArrayList list = new ArrayList();
list.add("qewr"); list.add(12); list.add(100L); list.add(3.1415f); //这里因为不知道取值的类型,很容易出现错误 String str = (String) list.get(1); } } |
但如果使用泛型,可以提供一个类型参数来更好地解决。:如 ArrayList<String> list = new ArrayList<String>(); 已经限定输入的参数是String这就使代码具有更好的可读性。
2、 定义简单泛型类
泛型类(generic class)就是具有一个或多个类型变量的类,简单来说就是在类的后面加上<T>, T是类型参数
// 此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Pair<T> { private T value; public Pair() { } public Pair(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } } /** * 功能描述 : 泛型的类型参数只能是类类型的,不能是简单类型的 * @param : [] * @return : */ private static void test4() { Pair<String> pair = new Pair<>(); pair.setValue("重庆火锅"); System.out.println(pair.getValue()); Pair pair1 = new Pair("123"); System.out.println(pair1.getValue()); Pair pair2 = new Pair(1024); System.out.println(pair2.getValue()); //Pair<int> pair3 = new Pair<>(); }
|
Pair类引入了一个类型变量T,用尖括号<>括起来,并放在类名的后面。泛型类可以有多个类型变量。例如,可以定义Pair类,其中第一个域和第二个域使用不同的类型:
public class Pair<T,U>{......}
注意:类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。(需要时还可以用临近的字母U和S)表示“任意类型”。
3、泛型方法
private static void test6() { String[] arr = {"赵","钱","孙","李","周"}; String middle = getMiddle(arr); System.out.printf(middle); } public static <T> T getMiddle(T... args){ return args[args.length/2]; } public class Model<T> { private T value; public Model() { } public Model(T value) { this.value = value; } public T getValue() { return value; } public void setValue(T value) { this.value = value; } /** * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E" * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。 public E setValue2(E value){ this.value = value } */ /** * 功能描述 : 类里面的静态方法, E 是使用该方法的时候传入的参数类型,和上面的T没有关联 * @param : [message] * @return : */ public static <E>E getMessage(E message){ System.out.println("传入的数据是"+message); return message; } // cannot be referenced from a static context // 因为泛型类中的泛型参数的实例化是在定义对象的时候指定额,而静态变量和静态方法不需要使用对象就可以调用 // 这里对象还没有创建,不清楚这个泛型参数是什么类型 // public static T say(T t){ // System.out.println("传入的消息"+t); // } }
|
注意:类型变量放在修饰符(这里是 public static)的后面,返回类型的前面。泛型方法可以定义在普通类中,也可以定义在泛型类中。
4、泛型接口的定义和使用
// //定义一个泛型接口 interface Animal<T,U>{ void show(T t,U u); } /** * 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中 * 即:class Mouse<T> implements Animal<T>{ * 如果不声明泛型,如:class Mouse implements Animal<T>,编译器会报错:"Unknown class" */ class Mouse implements Animal<String,Integer>{ @Override public void show(String t, Integer u) { } } class Cat<T,U> implements Animal<T,U>{ @Override public void show(T name, U age) { System.out.print("这是一只懒猫,它叫"+name); System.out.println(" 已经"+age+"岁"); } } class Dog<T,U> implements Animal<T,U>{ @Override public void show(T name, U age) { System.out.print("这是一只斗牛犬,它叫"+name); System.out.println(" 已经"+age+"岁"); } }
|
5、类型变量的限定
/** * 功能描述 :? 通配符的边界 */ public static void example(){ //通配符的上边界 //Pair<? extends 类型1> pair = new Pair<类型2>(); //类型1 指定一个数据类型,那么类型2就只能是类型1或者是类型1的子类 Pair<? extends Number> pair = new Pair<Integer>(); // UUID 和 Number 没有关系 // Pair<? extends Number> pair2 = new Pair<UUID>(); 这是错误的 //通配符的下边界 //Pair<? super 类型1> pair = new Pair<类型2>(); //类型1 指定一个数据类型,那么类型2就只能是类型1或者是类型1的父类 Pair<? super Number> pair11 = new Pair<Object>(); //Pair<? super Number> pair12 = new Pair<Integer>(); 这是错误的 }
|
类型限定可以在泛型类、泛型接口和泛型方法中使用,但是需要注意:
public static <T extends Comparable & Serializable> T get(T t1, T t2) |
3、 如果限定既有接口也有类,那么类必须只有一个,并且放在首位
public static <T extends Object &Comparable & Serializable> T get2(T t1, T t2) |
6 类型擦除
6.1 概述
Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型概念的首要前提是理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除。
如在代码中定义List<Object>和List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型附加的类型信息对JVM是看不到的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别
6.2 代码
private static void test0() { List<Integer> list1 = new ArrayList<>(); list1.add(123); // list1.add("qewqe"); List<String> list2 = new ArrayList<String>(); list2.add("王虎"); list2.get(0); if (list1.getClass().equals(list2.getClass())){ System.out.println("两者相同"); System.out.println(list1.getClass()); System.out.println(list2.getClass()); } System.out.println("*************************"); // 这个是证明泛型只是在编译的时候起作用,对于编译后的操作无法验证 try { list1.getClass().getMethod("add",Object.class).invoke(list1,"hello"); for (int i = 0; i < list1.size(); i++) { System.out.println(list1.get(i)); } } catch (Exception e) { e.printStackTrace(); } }
|
6.3原始类型
原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
6.4原始类型代码
//原始类型Object class Pair<T> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } } //Pair的原始类型为: class Pair { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
|
因为在Pair<T>中,T 是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java语言之前的已经实现的样子。在程序中可以包含不同类型的Pair,如Pair<String>或Pair<Integer>,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object。ArrayList<Integer>被擦除类型后,原始类型也变为Object,所以通过反射我们就可以存储字符串了。
/** * 功能描述 : 在调用泛型的时候可以指定泛型也可以不指定泛型 * 在不指定泛型的时候,泛型变量的类型为该方法中的几种类型的同一父类的最小级 * 在指定泛型的时候,该方法的几种类型必须是该泛型的实例的类型或者其子类 * @param : [] * @return : void */ private static void example1() { //不指定泛型的时候 ArrayList list = new ArrayList(); list.add(1); list.add("123"); list.add(new Date()); Object l1 = list.get(0); Integer t1 = GenericTest.test(1, 2); //两个参数都是Integer,所以T 为Integer Number t2 = GenericTest.test(1, 1.2);//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级,为Number Serializable t3 = GenericTest.test(1, "asdf");//这两个参数一个是Integer,以风格是Float,所以取同一父类的最小级 //指定泛型的时候 Integer test = GenericTest.<Integer>test(1, 2);//指定了Integer,所以只能为Integer类型或者其子类 //GenericTest.<Integer>test(1, 1.3);//编译错误,指定了Integer,不能为Float Number c = GenericTest.<Number>test(1, 2.2); //指定为Number,所以可以为Integer和Float }
|