java泛型

1.泛型的概述

1.1 泛型由来

  • 我们的集合可以存储多种数据类型的元素,那么在存储的时候没有任何问题,但是在获取元素,并向下转型的时候,可能会存在一个错误,而这个错误就是ClassCastException . 很显然,集合的这种可以存储多种数据类型的元素的这个特点,不怎么友好 , 程序存在一些安全隐患,那么为了出来这种安全隐患,我们应该限定一个集合存储元素的数据类型,我们只让他存储统一中数据类型的元素,那么在做向下转型的是就不会存在这种安全隐患了. 怎么限定集合只能给我存储同一种数据类型的元素呢? 需要使用泛型。

1.2 基本概述

  • 是一种把类型明确的工作推迟到创建对象或者调用方法的时候才去明确的特殊的类型。参数化类型,把类型当作参数一样的传递。
  • 泛型的出现减少了很多强转的操作,同时避免了很多运行时的错误,在编译期完成检查类型转化

2.泛型的格式

2.1 泛型格式如下

  • <数据类型> 这里的数据类型只能是引用数据类型
  • <数据类型1 , 数据类型2 , ….>

3.泛型的好处

3.1 好处

  • (1):把运行时期的问题提前到了编译期间
  • (2):避免了强制类型转换
  • (3):优化了程序设计,解决了黄色警告线

4.泛型的使用

4.1 泛型类的概述及使用

  • A:泛型类概述: 把泛型定义在类上
  • B:定义格式: public class 类名<泛型类型1,…>
  • C:注意事项: 泛型类型必须是引用类型

4.2 泛型方法的概述和使用

  • A:泛型方法概述: 把泛型定义在方法上
  • B:定义格式: public <泛型类型> 返回类型 方法名(泛型类型 变量名)
    1
    2
    3
    public <T> void show(T t) {

    }

4.3 泛型接口的概述和使用

  • A:泛型接口概述: 把泛型定义在接口上
  • B:定义格式: public interface 接口名<泛型类型>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 泛型接口的定义格式: 修饰符 interface 接口名<数据类型> {}
*/
public interface Inter<T> {
public abstract void show(T t) ;
}

/**
* 子类是泛型类
*/
public class InterImpl<E> implements Inter<E> {
@Override
public void show(E t) {
System.out.println(t);
}
}


Inter<String> inter = new InterImpl<String>() ;
inter.show("hello") ;

5.泛型高级之通配符

5.1 为什么要使用通配符

  • 通配符的设计存在一定的场景,例如在使用泛型后,首先声明了一个Animal的类,而后声明了一个继承Animal类的Cat类,显然Cat类是Animal类的子类,但是List却不是List的子类型,而在程序中往往需要表达这样的逻辑关系。为了解决这种类似的场景,在泛型的参数类型的基础上新增了通配符的用法。

5.1 <? extends T> 上界通配符

  • 上界通配符顾名思义,<? extends T>表示的是类型的上界【 包含自身】,因此通配的参数化类型可能是T或T的子类。正因为无法确定具体的类型是什么,add方法受限(可以添加null,因为null表示任何类型),但可以从列表中获取元素后赋值给父类型。如上图中的第一个例子,第三个add()操作会受限,原因在于List和List是List<? extends Animal>的子类型。

5.2 <? super T> 下界通配符

  • 下界通配符<? super T>表示的是参数化类型是T的超类型(包含自身),层层至上,直至Object,编译器无从判断get()返回的对象的类型是什么,因此get()方法受限。但是可以进行add()方法,add()方法可以添加T类型和T类型的子类型,如第二个例子中首先添加了一个Cat类型对象,然后添加了两个Cat子类类型的对象,这种方法是可行的,但是如果添加一个Animal类型的对象,显然将继承的关系弄反了,是不可行的。

5.3 <?> 无界通配符

  • 任意类型,如果没有明确,那么就是Object以及任意的Java类了
  • 无界通配符用<?>表示,?代表了任何的一种类型,能代表任何一种类型的只有null(Object本身也算是一种类型,但却不能代表任何一种类型,所以List和List的含义是不同的,前者类型是Object,也就是继承树的最上层,而后者的类型完全是未知的)。

6.可变参数的概述和使用

6.1 概述

  • A:可变参数概述: 定义方法的时候不知道该定义多少个参数
  • B:格式: 修饰符 返回值类型 方法名(数据类型… 变量名){}
  • C:注意事项:
    • a: 这里的变量其实是一个数组
    • b: 如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个

7.关于泛型拓展

7.1 编译器类型检查

* A.编译器类型检查

image

1
2
3
4
* 在引入泛型之后,通过将代码中的“public class Box”更改为“public class Box<T>”来创建泛型类型的声明,而这个声明的背后实质上是引入了可以在类中任何地方使用的类型变量T。如实例4中所示:可以看到,除了新增的泛型类型声明<T>外,所有在原来代码中出现的Object都被类型变量T所替换。
* 乍一看类型变量这个词,感觉有点晦涩难懂,但其实如果仔细思量一番会发现它其实并不难理解,上面的实例4可以理解为“在使用泛型时,可以将类型参数T传递给Box类型本身”,结合Oracle给出的官方定义“泛型的本质是类型参数化”会有更深的理解。
* 在实例5中,在对象声明和初始化的时候,都指定了类型参数T,在场景一种,T为String;在场景二中,T为Integer。这样,在场景二中向IntegerBox中传入String类型的数据“aaaaa”时,程序会报错。实例6中的泛型集合对象的操作也与之类似,在声明了一个List<String>的boxes对象之后,如果向boxes中传入Integer对象11111,程序会报错。
* 可以看到,通过对于泛型的使用,之前的多业务场景中的问题都得到了解决,因为现在在编译阶段就可以解决之前类型不匹配的问题,而不用等到运行时才暴露问题,只要合理使用泛型,就能在很大程度上规避此类风险。对于泛型的使用,这种参数化类型的作用表面上看是声明,背后其实是约定。

7.2 可读性和灵活性

  • 泛型除了能进行编译器类型检查和规避类型强制转换外,还能有效地提高代码的可读性。如果不使用泛型,当一个不清楚业务场景的人在对集合进行操作时,无法知道list中存储的是什么类型的对象,如果使用了泛型,就能够通过其类型参数判断出当前的业务场景,也增加了代码的可读性,同时也可以大胆地在抽象继承的基础上进行开发了。
  • 泛型使用上的灵活性体现在很多方面,因为它本身实质上就是对于继承在使用上的一种增强。因为泛型在具体工作时,当编译器在编译源码的时候,首先要进行泛型类型参数的检查,检查出类型不匹配等问题,然后进行类型擦除并同时在类型参数出现的位置插入强制转换指令,从而实现泛型。

7.3 泛型方法和泛型类的比较

  • 例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A<T>(){
//泛型类的成员方法,该T受A后面的T的限制
public T memberFunc(){
return null;
}
//泛型方法,这里的T和和类A的T是不同的
public static <T> T genericFunc(T a){
return null;
}
public static void main(String[] args) {
//编译不通过
//Integer i = A<String>().findByUserName("s");

//编译通过
Set<Integer> set= A<String>().findByConditions("s");
}
}
  • 这里Integer i = A<String>().findByUserName("s");会编译报错:

    1
    Error:(35, 61) java: 不兼容的类型: java.lang.String无法转换为java.lang.Integer`
  • 由这个例子可知,泛型方法的T和和类A的T是不同的。

7.4 泛型擦除案例

  • 泛型是提供给javac编译器使用的,限定集合的输入类型,编译器编译带类型说明的集合时会去掉“类型”信息。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class GenericTest {
    public static void main(String[] args) {
    new GenericTest().testType();
    }

    public void testType(){
    ArrayList<Integer> collection1 = new ArrayList<Integer>();
    ArrayList<String> collection2= new ArrayList<String>();

    System.out.println(collection1.getClass()==collection2.getClass());
    //两者class类型一样,即字节码一致

    System.out.println(collection2.getClass().getName());
    //class均为java.util.ArrayList,并无实际类型参数信息
    }
    }
  • 输出

    1
    2
    true
    java.util.ArrayList
  • 使用反射可跳过编译器,往某个泛型集合加入其它类型数据。

    • 只有引用类型才能作为泛型方法的实际参数
    • 例子:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class GenericTest {
      public static void main(String[] args) {
      swap(new String[]{"111","222"},0,1);//编译通过

      //swap(new int[]{1,2},0,1);
      //编译不通过,因为int不是引用类型

      swap(new Integer[]{1,2},0,1);//编译通过
      }

      /*交换数组a 的第i个和第j个元素*/
      public static <T> void swap(T[]a,int i,int j){
      T temp = a[i];
      a[i] = a[j];
      a[j] = temp;
      }
      }
  • 但注意基本类型有时可以作为实参,因为有自动装箱拆箱

    • 例子(编译通过了):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public class GenericTest {
      public static void main(String[] args) {
      new GenericTest().testType();
      int a = biggerOne(3,5);
      //int 和 double,取交为Number
      Number b = biggerOne(3,5.5);
      //String和int 取交为Object
      Object c = biggerOne("1",2);
      }
      //从x,y中返回y
      public static <T> T biggerOne(T x,T y){
      return y;
      }
      }
    • 同时,该例还表明,当实参不一致时,T取交集,即第一个共同的父类。

    • 另外,如果用Number b = biggerOne(3,5.5);改为String c = biggerOne(3,5.5);则编译报错:

      1
      2
      3
      Error:(17, 29) java: 不兼容的类型: 推断类型不符合上限
      推断: java.lang.Number&java.lang.Comparable<? extends java.lang.Number&java.lang.Comparable<?>>
      上限: java.lang.String,java.lang.Object

8.泛型和反射

通过反射获得泛型的实际类型参数

  • 把泛型变量当成方法的参数,利用Method类的getGenericParameterTypes方法来获取泛型的实际类型参数
  • 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class GenericTest {

    public static void main(String[] args) throws Exception {
    getParamType();
    }

    /*利用反射获取方法参数的实际参数类型*/
    public static void getParamType() throws NoSuchMethodException{
    Method method = GenericTest.class.getMethod("applyMap",Map.class);
    //获取方法的泛型参数的类型
    Type[] types = method.getGenericParameterTypes();
    System.out.println(types[0]);
    //参数化的类型
    ParameterizedType pType = (ParameterizedType)types[0];
    //原始类型
    System.out.println(pType.getRawType());
    //实际类型参数
    System.out.println(pType.getActualTypeArguments()[0]);
    System.out.println(pType.getActualTypeArguments()[1]);
    }

    /*供测试参数类型的方法*/
    public static void applyMap(Map<Integer,String> map){

    }

    }
  • 输出结果:

    1
    2
    3
    4
    java.util.Map<java.lang.Integer, java.lang.String>
    interface java.util.Map
    class java.lang.Integer
    class java.lang.String
-------------本文结束,感谢您的阅读-------------
您的支持将鼓励我继续创作!!