0%

泛型

泛型

★第十五章 泛型

  • 泛型是一种表示数据类型的数据类型
  1. 泛型又称参数化类型,是 Jdk5.0 出现的新特性,用以解决数据类型的安全性问题。

  2. 在类声明或实例化时只要指定好需要的具体类型即可。

  3. Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常。同时,代码更加简洁、健壮。

  4. 泛型的作用是:可以在类声明时通过一个标识(<E><T> 或其它)表示类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型

    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
    28
    29
    30
    31
    32
    33
    34
    35
    package com.f.chapter15.generic;

    /**
    * @author fzy
    * @date 2023/6/24 19:39
    */
    public class Generic01 {
    public static void main(String[] args) {
    //传入 String 类型给 E,则 Person 类中所有为 E 的地方都可以看作被 String 替代
    Person<String> p1 = new Person<String>("hello");
    p1.show(); //class java.lang.String

    //传入 Integer 类型给 E,则 Person 类中所有为 E 的地方都可以看作被 Integer 替代
    Person<Integer> p2 = new Person<Integer>(10);
    p2.show(); //class java.lang.Integer
    }
    }

    class Person<E> { //这里的 E 就是泛型
    //用 E 表示 s 的数据类型,该数据类型是在定义 Person 对象的时候指定的,
    // 即在编译期间,就可以确定 E 是什么类型
    E s;

    public Person(E s) { //E 也可以是参数类型
    this.s = s;
    }

    public E getS() { //E 也可以是返回类型
    return s;
    }

    public void show(){
    System.out.println(s.getClass());
    }
    }
  • 使用泛型的好处:
    1. 编译时,检查添加元素的类型,提高了安全性。
    2. 减少了类型转换的次数,提高效率。
    3. 不再提示编译警告。

泛型的语法

  • 泛型的声明:interface 接口<T>{}class 类<K,V>{}

    注意:

    1. T、K、V 不代表值,而是表示类型。
    2. 任何字母都可以,通常用 T 表示,TType 的缩写。
  • 泛型的实例化:要**在类名后面指定类型参数的值(即类型)**,如:

    1. List<String> strList = new ArrayList<String>();
    2. Iterator<Customer> iterator = customers.iterator();
    3. HashMap<String, Student> students = new HashMap<String, Student>();
    4. Set<Map.Entry<String, Student>> entries = students.entrySet();

泛型使用细节

  1. interface List<T>{}public class HashSet<E>{} 等等。

    T、E 只能是引用类型,而不能是基本类型。

    List<Integer> list = new ArrayList<Integer>(); -> 正确的

    List<int> list = new ArrayList<int>(); -> 错误的

  2. 在指定了泛型的具体类型后,可以传入该类型或者其子类类型

    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
    28
    29
    30
    31
    32
    33
    34
    35
    package com.f.chapter15.generic;

    /**
    * @author fzy
    * @date 2023/6/28 20:42
    */
    public class GenericDetail {
    public static void main(String[] args) {
    //1. 因为aDog的泛型指定为A类型,所以在构造器中传入A对象,编译通过
    Dog<A> aDog = new Dog<A>(new A());

    //2. 因为bDog的泛型指定为A类型,又因为B是A的子类,所以在构造器中传入B对象,编译也通过
    Dog<A> bDog = new Dog<A>(new B());

    //3. 因为cDog的泛型指定为A类型,但是C和A没有关系,所以在构造器中传入C对象,编译错误
    //Dog<A> cDog = new Dog<A>(new C());
    }
    }

    class A {
    }

    class B extends A {
    }

    class C {
    }

    class Dog<E> {
    E e;

    public Dog(E e) {
    this.e = e;
    }
    }
  3. 泛型使用形式:

    List<Integer> list = new ArrayList<Integer>();

    在实际开发中,往往简写,即用下面这种形式:

    List<Integer> list = new ArrayList<>(); ,因为编译器会进行类型推断,知道右边 <> 中的类型就是左边 <> 中的类型。

  4. List list = new ArrayList(); 等价于 List<Object> list = new ArrayList<Object>();

自定义泛型

自定义泛型类
  • 基本语法:

    1
    2
    3
    class 类名<T,R,...> {	//...表示可以有多个泛型
    成员
    }
  • 使用细节:

    1. 普通成员可以使用泛型(属性、方法)。

      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
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      package com.f.chapter15.customgeneric;

      /**
      * @author fzy
      * @date 2023/6/29 19:54
      */
      public class CustomGeneric_ {
      public static void main(String[] args) {

      }
      }

      //1. Tiger后面跟着泛型,所以Tiger为自定义泛型类
      //2. T、R、M为泛型标识符,一般是单个大写字母
      //3. 泛型标识符可以有多个
      class Tiger<T, R, M> {
      String name;
      T t;
      R r;
      M m;

      public Tiger(String name, T t, R r, M m) {
      this.name = name;
      this.t = t;
      this.r = r;
      this.m = m;
      }

      public String getName() {
      return name;
      }

      public void setName(String name) {
      this.name = name;
      }

      public T getT() {
      return t;
      }

      public void setT(T t) {
      this.t = t;
      }

      public R getR() {
      return r;
      }

      public void setR(R r) {
      this.r = r;
      }

      public M getM() {
      return m;
      }

      public void setM(M m) {
      this.m = m;
      }
      }
    2. 使用泛型的数组,不能初始化。-> 不确定数组的类型,就不知道该开辟多大的内存空间,所以无法初始化。

    3. 静态属性和静态方法中不能使用类的泛型。-> 因为静态是和类相关的,在类加载时,对象还没有创建,而泛型的类型是在对象创建后才确定的,所以如果静态属性和静态方法使用了泛型,就无法确定其在类加载时的具体类型,JVM 无法完成初始化,所以不能使用。

    4. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)。

    5. 如果在创建对象时,没有指定类型,默认为Object

自定义泛型接口
  • 基本语法:

    1
    2
    3
    interface 接口名<T,R,...> {

    }
  • 使用细节:

    1. 接口中,静态成员也不能使用泛型。

    2. 泛型接口的类型,在继承接口或者实现接口的时候确定。

      • 在继承接口时,可以确定泛型类型,也可以继续沿用泛型标识符。
      • 在实现接口时,必须确定泛型类型。
      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
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      package com.f.chapter15.customgeneric;

      /**
      * @author fzy
      * @date 2023/6/29 21:09
      */
      public class CustomGenericInterface {
      public static void main(String[] args) {
      }
      }

      //因为IA继承IUsb接口时,指定了T为String类型,R为Double类型,
      // 所以在类AA实现IA接口时,就会用String替换T,用Double替换R
      class AA implements IA {
      @Override
      public Double get(String s) {
      return null;
      }

      @Override
      public void hi(String s) {

      }

      @Override
      public void run(String s, Double aDouble) {

      }
      }

      //在继承接口时确定泛型类型
      interface IA extends IUsb<String, Double> {
      }

      //在实现接口时确定泛型类型
      class U implements IUsb<Integer, Boolean> {
      @Override
      public Boolean get(Integer integer) {
      return null;
      }

      @Override
      public void hi(Integer integer) {

      }

      @Override
      public void run(Integer integer, Boolean aBoolean) {

      }
      }

      interface IUsb<T, R> {
      R get(T t);

      void hi(T t);

      void run(T t, R r);

      //在jdk8中,可以在接口中使用默认方法,也是可以使用泛型的
      default R method(T t) {
      return null;
      }
      }
      //在继承接口时,可以确定泛型类型,也可以继续沿用泛型标识符
      interface IPhone<E, M> extends IUsb<E, M> { //标识符字母不一定要一模一样

      }
    3. 没有指定类型,默认为 Object

自定义泛型方法
  • 基本语法:

    1
    2
    3
    修饰符 <T,R,...> 返回类型 方法名(参数列表) {

    }
  • 使用细节:

    1. 泛型方法,可以定义在普通类中,也可以定义在泛型类中。

    2. 当泛型方法被调用时,类型会确定。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      package com.f.chapter15.customgeneric;

      /**
      * @author fzy
      * @date 2023/6/29 21:41
      */
      public class CustomGenericMethod {
      public static void main(String[] args) {
      Car car = new Car();
      car.fly("宾利", 100); //当调用方法时,传入参数,编译器就会确定类型
      }
      }

      class Car {
      public void drive() { //普通方法

      }

      public <T, R> void fly(T t, R r) { //泛型方法
      System.out.println(t.getClass());
      System.out.println(r.getClass());
      }
      }
    3. public void eat(E e) {} ,修饰符后面没有 <T,R,... ,说明 eat 方法不是泛型方法,而是使用了泛型。

    4. 泛型方法,即可以使用类声明的泛型,也可以使用自己声明的泛型。

★泛型的继承和通配符

  1. 泛型不具备继承性。

    List<Object> list = new ArrayList<String>(); -> 错误的

  2. <?>:表示支持任意泛型类型。

  3. <? extends A>:表示支持 A 类以及 A 类的子类,规定了泛型的上限。

  4. <? super A> :表示支持 A 类以及 A 类的父类,不限于直接父类,规定了泛型的下限。

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    package com.f.chapter15.generic;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author fzy
    * @date 2023/6/30 13:33
    */
    public class GenericExtends {
    public static void main(String[] args) {
    List<Object> list1 = new ArrayList<>();
    List<String> list2 = new ArrayList<>();
    List<AA> list3 = new ArrayList<>();
    List<BB> list4 = new ArrayList<>();
    List<CC> list5 = new ArrayList<>();

    //`List<?>`:表示支持任意泛型类型。
    printCollection1(list1);
    printCollection1(list2);
    printCollection1(list3);
    printCollection1(list4);
    printCollection1(list5);

    //List<? extends AA>:表示支持 `AA` 类以及 `AA` 类的子类。
    // 即希望传入的list中的对象的类型为 `AA` 类或 `AA` 类的子类
    //printCollection2(list1); //错误的
    //printCollection2(list2); //错误的
    printCollection2(list3);
    printCollection2(list4);
    printCollection2(list5);

    //List<? super AA>:表示支持 `AA` 类以及 `AA` 类的父类,不限于直接父类。
    // 即希望传入的list中的对象的类型为 `AA` 类或 `AA` 类的父类,不限于直接父类
    printCollection3(list1);
    //printCollection3(list2); //错误的
    printCollection3(list3);
    //printCollection3(list4); //错误的
    //printCollection3(list5); //错误的
    }

    //`<?>`:表示支持任意泛型类型。
    public static void printCollection1(List<?> c) {
    for (Object obj : c) {
    System.out.println(obj);
    }
    }

    //`<? extends A>`:表示支持 `A` 类以及 `A` 类的子类,规定了泛型的上限。
    public static void printCollection2(List<? extends AA> c) {
    for (AA aa : c) {
    System.out.println(aa);
    }
    }

    //`<? super A>` :表示支持 `A` 类以及 `A` 类的父类,不限于直接父类,规定了泛型的下限。
    public static void printCollection3(List<? super AA> c) {
    for (Object obj : c) {
    System.out.println(obj);
    }
    }
    }

    class AA {

    }

    class BB extends AA {

    }

    class CC extends BB {

    }
  • 通配符用在方法的参数列表(即 () )中,表示对传入的参数的泛型类型进行限制。

  • 另一个例子:(看注释应该就明白是什么意思了)

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.f.chapter15.generic;

    import java.util.List;

    /**
    * @author fzy
    * @date 2023/6/30 14:43
    */
    public class GenericExtends2 {
    public static void main(String[] args) {
    P<String> p1 = new P<>(); //T会被替换成String
    P<Integer> p2 = new P<>(); //T会被替换成Integer
    P<List> p3 = new P<List>(); //T会被替换成List
    /*
    * 下面的语句 p1.set(p2) 可以这么看:传入的参数为p2,而p2使用的泛型类型为Integer,
    * 对于P类的set方法,若把 <> 遮掉,则可以看出,要传入的是一个P类的对象,
    * 此时再看 <> 内部,表示传入的这个P类的对象,其使用的泛型类型应为List或其子类,
    * 而p2使用的泛型类型为Integer,不满足 <> 内的要求,所以下面的语句会报错。
    * */
    //p1.set(p2); //错误的
    p1.set(p3); //正确的
    }
    }

    class P<T> {
    private T t;

    public T getT() {
    return t;
    }

    //通配符
    //设置指定类型的范围,超过范围就会报错
    //extends : 指定范围必须是其(这里是List)子类
    public void set(P<? extends List> p) {
    return;
    }

    //super : 指定类型必须是其(这里是List)父类
    public void setSuper(P<? super List> p) {
    return;
    }
    }

JUint

  • JUnit 是一个 Java 语言的单元测试框架。多数 Java 的开发环境都已经集成了 JUnit 作为单元测试的工具。

    • 在要进行单元测试的函数前面加上 @Test
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.f.chapter15.junit_;

    import org.junit.jupiter.api.Test;

    /**
    * @author fzy
    * @date 2023/6/30 15:00
    */
    public class JUnit_ {
    public static void main(String[] args) {

    }

    @Test
    public void m1() {
    System.out.println("m1()方法...");
    }

    @Test
    public void m2() {
    System.out.println("m2()方法...");
    }
    }
---------------The End---------------