泛型

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可以随便写为任意标识,常见的如TEKV等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定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表示集合的元素类型,KV分别表示关键字与值的类型。(需要时还可以用临近的字母US)表示任意类型

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>等类型,在编译后都会变成ListJVM看到的只是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(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类型了,原始类型都是ObjectArrayList<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,所以可以为IntegerFloat
}

 

 

参考了 

Java泛型类型擦除以及类型擦除带来的问题

java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一

 

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可以随便写为任意标识,常见的如TEKV等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定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表示集合的元素类型,KV分别表示关键字与值的类型。(需要时还可以用临近的字母US)表示任意类型

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>等类型,在编译后都会变成ListJVM看到的只是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类型了,原始类型都是ObjectArrayList<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,所以可以为IntegerFloat
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2019-10-25 17:40  烟雨蒙尘  阅读(328)  评论(0编辑  收藏  举报