• 泛型


    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
    }

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    理解python多个参数*args
    物联网MQTT 协议测试
    python 自动化测试人工智能
    Django 初识
    算法排序
    python教程笔记GUI wxpython
    python入门教程学习笔记#3 基础部分
    python入门教程学习笔记#1 安装准备
    2012-2013 Northwestern European Regional Contest (NWERC 2012)
    2017 Benelux Algorithm Programming Contest (BAPC 17)
  • 原文地址:https://www.cnblogs.com/zhuguangzhe/p/11739298.html
Copyright © 2020-2023  润新知