1、 为什么要使用泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
在javaSE5.0之前,java泛型程序设计是使用继承实现的。ArrayList类只维护一个Object引用的数组
public class ArrayList//之前的
{
public Object get(int i){......}
public void add(Object o){......}
......
private Object[] elementData;
}
|
但是这样有两个问题:
1. 当获取一个值时必须进行强制类型转换
2. 没有错误检查,可以向数组列表中添加任何类的对象
但是这样很容易出现问题,比如:
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>(); 这是错误的
}
|
类型限定可以在泛型类、泛型接口和泛型方法中使用,但是需要注意:
1、不管该限定是类还是接口,统一使用关键字 extends
2、 可以使用&符号给出多个限定:
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
}
|
参考了
1、
为什么要使用泛型
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?
顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
在javaSE5.0之前,java泛型程序设计是使用继承实现的。ArrayList类只维护一个Object引用的数组
public class ArrayList//之前的
{
public Object get(int i){......}
public void add(Object o){......}
......
private Object[] elementData;
}
|
但是这样有两个问题:
1. 当获取一个值时必须进行强制类型转换
2. 没有错误检查,可以向数组列表中添加任何类的对象
但是这样很容易出现问题,比如:
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>(); 这是错误的
}
|
类型限定可以在泛型类、泛型接口和泛型方法中使用,但是需要注意:
1、不管该限定是类还是接口,统一使用关键字 extends
2、 可以使用&符号给出多个限定:
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
}
|