Java泛型

Java泛型

  • 🧳1. 泛型类的定义🧳
    • 1.1 语法
    • 1.2 简单示例
    • 1.3 加入静态内部类的示例
    • 1.4 加入继承或实现的示例
    • 1.5泛型的上界
    • 1.5.1复杂举例
    • 1.6泛型的下界
    • 🌂2. 泛型类的使用🌂
      • 2.1 语法
      • 2.2 示例
      • 2.3 类型推导(Type Inference)
      • ☂️3. 裸类型(Raw Type) (了解)☂️
        • 3.1 说明
        • 3.2 未检查错误
        • 🧵4. 泛型类的定义-类型边界🧵
          • 4.1 语法
          • 4.2 示例
          • 4.3 复杂一点的示例
          • 🧶5. 类型擦除🧶
          • 👓6. 泛型类的使用-通配符(Wildcards)👓
            • 6.1通配符解决什么问题
            • 6.2 通配符-上界
            • 6.3 通配符-下界
            • 🥼7. 泛型中的父子类型(重要)🥼
            • 🦺8. 泛型方法🦺
              • 8.1 定义语法
              • 8.2 示例
              • 8.3 使用示例-可以类型推导
              • 8.4 使用示例-不使用类型推导
              • 👔9. 泛型的限制👔
              • 👕10. 完整定义一份泛型类支持的搜索树(不使用 Comparator)👕

                大家好,我是晓星航。今天为大家带来的是 Java泛型 相关内容的讲解!😀

                🧳1. 泛型类的定义🧳

                一般的类和方法,只能使用具体的类型:要么是基本的类型,要么是自定义类型。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的舒服就会很大。----来源《Java编程思想》对泛型的介绍。

                泛型实在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。

                1.1 语法

                class 泛型类名称<类型形参列表> {// 这里可以使用类型参数
                }
                class ClassName { }
                
                class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {// 这里可以使用类型参数
                }
                class ClassName extends ParentClass {// 可以只使用部分类型参数
                        }
                

                了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:

                • E 表示 Element
                • K 表示 Key
                • V 表示 Value
                • N 表示 Number
                • T 表示 Type
                • S, U, V 等等 - 第二、第三、第四个类型

                  不能实例化泛型类型的数组

                   public T[] objects = new T[10];//ERROR 1、不能实例化泛型类型的数组
                  

                  1.2 简单示例

                  定义一个泛型类顺序表

                  // 演示泛型目的,没有做错误处理和扩容
                  public class MyArrayList { private E[] array;
                      private int size;
                      public MyArrayList() {// 泛型类型无法直接创建数组,具体的见下面的注意事项
                          array = (E[])new Object[16];
                          size = 0;
                      }
                      // 尾插
                      public void add(E e) { array[size++] = e;
                      }
                      // 尾删
                      public E remove() { E element = array[size - 1];
                          array[size - 1] = null; // 将容器置空,保证对象被正确释放
                          size--;
                          return element;
                      }
                  }
                  

                  1.3 加入静态内部类的示例

                  定义一个泛型类链表

                  public class MyLinkedList { public static class Node { private E value;
                          private Node next;
                          private Node(E e) { value = e;
                              next = null;
                          }
                      }
                      private Node head;
                      private int size;
                      public MyLinkedList() { head = null;
                          size = 0;
                      }
                      // 头插
                      public void pushFront(E e) { Node node = new Node<>(e);
                          node.next = head;
                          head = node;
                          size++;
                      }
                      // 尾插
                      public void pushBack(E e) { if (size == 0) { pushFront(e);
                              return;
                          }
                          Node cur = head;
                          while (cur.next != null) { cur = cur.next;
                          }
                          cur.next = new Node(e);
                          size++;
                      }
                  }
                  

                  1.4 加入继承或实现的示例

                  定义一个泛型类顺序表

                  public interface MyList { // 尾插
                      void add(E e);
                      // 尾删
                      E remove();
                  }
                  
                  public class MyArrayList implements MyList {// TODO: 未完成
                  }
                  

                  1.5泛型的上界

                  在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

                  语法

                  class 泛型类名称<类型形参 extends 类型边界> {...
                  }
                  

                  上述的类型边界就是我们泛型的上界

                  示例

                  public class MyArray {...
                  }
                  

                  只接受 Number 的子类型作为 E 的类型实参

                  MyArray l1; // 正常,因为 Integer 是 Number 的子类型
                  MyArray l2; // 编译错误,因为 String 不是 Number 的子类型
                  
                  error: type argument String is not within bounds of type-variable E
                      MyArrayList l2;
                          ^
                  where E is a type-variable:
                      E extends Number declared in class MyArrayList
                  

                  了解: 没有指定类型边界 E,可以视为 E extends Object

                  不指定边界就默认为Object

                  1.5.1复杂举例

                  public class MyArray> {...
                  }
                  

                  E必须是实现了Comparable接口的

                  1.6泛型的下界

                  泛型没有下界!!!

                  🌂2. 泛型类的使用🌂

                  2.1 语法

                  泛型类<类型实参> 变量名; // 定义一个泛型类引用
                  new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
                  

                  2.2 示例

                  MyArrayList list = new MyArrayList();
                  

                  2.3 类型推导(Type Inference)

                  当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写

                  MyArrayList list = new MyArrayList<>(); // 可以推导出实例化需要的类型实参为 String
                  

                  ☂️3. 裸类型(Raw Type) (了解)☂️

                  3.1 说明

                  裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型

                  MyArrayList list = new MyArrayList();
                  

                  **注意:**我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制

                  下面的类型擦除部分,我们也会讲到编译器是如何使用裸类型的。

                  3.2 未检查错误

                  MyArrayList list = new MyArrayList(); // 自己永远不要这么用
                  

                  上述代码,会产生编译警告

                  Note: Example.java uses unchecked or unsafe operations.
                  Note: Recompile with -Xlint:unchecked for details.
                  

                  可以使用 @SuppressWarnings 注解进行警告压制

                  @SuppressWarnings("unchecked")
                  

                  🧵4. 泛型类的定义-类型边界🧵

                  在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

                  4.1 语法

                  class 泛型类名称<类型形参 extends 类型边界> {...
                  }
                  

                  4.2 示例

                  public class MyArrayList {...
                  }
                  

                  只接受 Number 的子类型作为 E 的类型实参

                  MyArrayList l1; // 正常,因为 Integer 是 Number 的子类型
                  MyArrayList l2; // 编译错误,因为 String 不是 Number 的子类型
                  error: type argument String is not within bounds of type-variable E
                          MyArrayList l2;
                          ^
                  where E is a type-variable:
                          E extends Number declared in class MyArrayList
                  

                  了解: 没有指定类型边界 E,可以视为 E extends Object

                  4.3 复杂一点的示例

                  定义一个泛型类搜索树

                  public class BSTree, V> {...
                  }
                  

                  传入的 K 必须是 Comparable 的,并且是可以和另一个 K 类型做比较的,后边的 K 是对类型参数的使用了

                  🧶5. 类型擦除🧶

                  我们之前已经讲过,泛型是作用在编译期间的一种机制,实际上运行期间是没有这么多类的,那运行期间是什么类型呢?这里就是类型擦除在做的事情。

                  class MyArrayList {// E 会被擦除为 Object
                  }
                  class MyArrayList> {// E 被擦除为 Comprable
                  }
                  

                  总结: 即类型擦除主要看其类型边界而定

                  了解: 编译器在类型擦除阶段在做什么?

                  1. 将类型变量用擦除后的类型替换,即 Object 或者 Comparable
                  2. 加入必要的类型转换语句
                  3. 加入必要的 bridge method 保证多态的正确性

                  👓6. 泛型类的使用-通配符(Wildcards)👓

                  ? 用于在泛型的使用,即为通配符

                  6.1通配符解决什么问题

                  **通配符是用来解决泛型无法协变的问题的,**协变指的就是如果 Student 是 Person 的子类,那么 List 也应该是 List 的子类。但是泛型是不支持这样的父子类关系的。

                  1、泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围。

                  2、或者我们可以这么理解:泛型T就像是个变量,等着你将来传一个具体的类型,而通配符则是一种规定,规定你能传哪些参数。

                  class Alg3 { public static  void print1(ArrayList list) { for (T x: list) { System.out.println(x);
                          }
                      }
                      public static void print2(ArrayList list) { for (Object x: list) { System.out.println(x);
                          }
                      }
                  }
                  

                  print1是泛型T来接受,他可以确定你传过来的是泛型T类型。

                  但是print2使用的是Object来接受,他不能确定你传过来的?是什么类型,所以他选择了使用万能类型Object来接受。

                  示例

                  package www.bit.java.test;
                  class Message { private T message ;
                      public T getMessage() { return message;
                      }
                      public void setMessage(T message) { this.message = message;
                      }
                  }
                  public class TestDemo { public static void main(String[] args) { Message message = new Message() ;
                          message.setMessage("庐山风景真美");
                          fun(message);
                      }
                      public static void fun(Message temp){ System.out.println(temp.getMessage());
                      }
                  }
                  

                  以上程序会带来新的问题,如果现在泛型的类型设置的不是String,而是Integer.

                  public class TestDemo { public static void main(String[] args) { Message message = new Message() ;
                          message.setMessage(99);
                          fun(message); // 出现错误,只能接收String
                      }
                      public static void fun(Message temp){ System.out.println(temp.getMessage());
                      }
                  }
                  

                  我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符"?"来处理

                  范例:使用通配符

                  public class TestDemo { public static void main(String[] args) { Message message = new Message() ;
                          message.setMessage(55);
                          fun(message);
                      }
                      // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
                      public static void fun(Message temp){//temp.setMessage(100); 无法修改!
                          System.out.println(temp.getMessage());
                      }
                  }
                  

                  在"?"的基础上又产生了两个子通配符:

                  ? extends 类:设置泛型上限

                  ? super 类:设置泛型下限

                  6.2 通配符-上界

                  语法

                  只能调用传入参数的子类,不允许调用他们的父类

                  //可以传入的实参类型是Number或者Number的子类
                  

                  示例

                  // 可以传入类型实参是 Number 子类的任意类型的 MyArrayList 因为这里的? 继承了Number。
                  public static void printAll(MyArrayList list) {...
                  }
                  // 以下调用都是正确的
                  printAll(new MyArrayList());
                  printAll(new MyArrayList());
                  printAll(new MyArrayList());
                  // 以下调用是编译错误的
                  printAll(new MyArrayList());
                  printAll(new MyArrayList());
                   

                  此时的list可以引用的子类对象很多,编译器无法确定你具体的类型的。 编译器为了安全起见,此时只允许你进行读取。

                  上述传入的是Number,调用Integer、Double和Number是可以的,但是调用String和Object是不可以的。

                  注意: 需要区分 泛型使用中的通配符上界 和 泛型定义中的类型上界

                  通配符的上界,不能进行写入数据,只能进行读取数据。

                  6.3 通配符-下界

                  语法

                  //代表 可以传入的实参的类型是Integer或者Integer的父类类型
                  

                  示例

                  // 可以传入类型实参是 Integer 父类的任意类型的 MyArrayList
                  public static void printAll(MyArrayList list) { ...
                          }
                  // 以下调用都是正确的
                          printAll(new MyArrayList());
                          printAll(new MyArrayList());
                          printAll(new MyArrayList());
                  // 以下调用是编译错误的
                          printAll(new MyArrayList());
                          printAll(new MyArrayList());
                   

                  假如我们引用Person,我们引用的对象肯定是Person或者Person的父类的集合,我们能够确定此时存储的元素的最小粒度比Person小的都可以。但是,你读取的时候,你知道是读取到的哪个子类吗?

                  添加元素时,只要添加的时Person或者Person的子类就可以。

                  通配符的下界,不能进行读取数据,只能写入数据。

                  为什么

                  Person person = arrayList1.get(0);

                  //Student student = arrayList1.get(0);

                  注:Student是Person的子类

                  不可以取出arrayList的元素?

                  答:因为存放的时候可以通过多态来存放Person的子类,但是取出的时候,只能取出Person或者Person的父类,不能向下引用子类

                  🥼7. 泛型中的父子类型(重要)🥼

                  public class MyArrayList { ... }
                  // MyArrayList 不是 MyArrayList 的父类型
                  // MyArrayList 也不是 MyArrayList 的父类型
                  // 需要使用通配符来确定父子类型
                  // MyArrayList 是 MyArrayList 的父类型
                  // MyArrayList 是 MyArrayList 的父类型
                   

                  理论上来说:Object是所有类的父类

                  为什么上述代码的Object不是Number的父类呢,因为编译完成后我们的类型都被擦除了

                  🦺8. 泛型方法🦺

                  8.1 定义语法

                  方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
                  

                  8.2 示例

                  一般的泛型方法需要new对象,new完对象之后才可以使用我们的Alg方法

                  class Alg> { public T findMax(T[] array) { T max = array[0];
                          for (int i = 0; i < array.length; i++) { if (max.compareTo(array[i]) < 0) { max = array[i];
                              }
                          }
                          return max;
                      }
                  }
                  public class TestDemo { public static void main(String[] args) { Alg alg = new Alg<>();
                          Integer[] array = {1,2,3,4,7,5,6,9,100,97,123};
                          System.out.println(alg.findMax(array));
                          Alg alg1 = new Alg<>();
                          String[] array1 = {"abc","xxhljq","hello"};
                          System.out.println(alg1.findMax(array1));
                      }
                  

                  加入了static之后的泛型方法变为了静态的泛型方法,不需要new对象,直接就可以调用Alg2的方法。

                  class Alg2 { public static >T findMax(T[] array) { T max = array[0];
                          for (int i = 0; i < array.length; i++) { if (max.compareTo(array[i]) < 0) { max = array[i];
                              }
                          }
                          return max;
                      }
                  }
                  public class TestDemo { public static void main(String[] args) { Integer[] array = {1,2,3,4,7,5,6,9,100,97,123};
                          //System.out.println(Alg2.findMax(array));  不写我们的Alg2方法也会自己推导出array的类型是Integer
                          System.out.println(Alg2.findMax(array));
                          String[] array1 = {"abc","xxhljq","hello"};
                          //System.out.println(Alg2.findMax(array1));  不写我们的Alg2方法也会自己推导出array的类型是String
                          System.out.println(Alg2.findMax(array1));
                      }
                  }
                  

                  8.3 使用示例-可以类型推导

                  Integer[] a = { ... };
                          swap(a, 0, 9);
                          String[] b = { ... };
                          swap(b, 0, 9);
                  

                  8.4 使用示例-不使用类型推导

                  Integer[] a = { ... };
                          Util.swap(a, 0, 9);
                          String[] b = { ... };
                          Util.swap(b, 0, 9);
                  

                  👔9. 泛型的限制👔

                  1. 泛型类型参数不支持基本数据类型
                  2. 无法实例化泛型类型的对象
                  3. 无法使用泛型类型声明静态的属性
                  4. 无法使用 instanceof 判断带类型参数的泛型类型
                  5. 无法创建泛型类数组
                  6. 无法 create、catch、throw 一个泛型类异常(异常不支持泛型)
                  7. 泛型类型不是形参一部分,无法重载

                  👕10. 完整定义一份泛型类支持的搜索树(不使用 Comparator)👕

                  import java.util.*;
                  public class BSTree, V> { private static class Entry { private K key;
                          private V value;
                          private Entry left = null;
                          private Entry right = null;
                          private Entry(K key, V value) { this.key = key;
                              this.value = value;
                          }
                          @Override
                          public String toString() { return String.format("{%s=%s}", key, value);
                          }
                      }
                      private Entry root = null;
                      public V get(K key) { Entry cur = root;
                          while (cur != null) { int r = key.compareTo(cur.key);
                              if (r == 0) { return cur.value;
                              } else if (r < 0) { cur = cur.left;
                              } else { cur = cur.right;
                              }
                          }
                          return null;
                      }
                      public V put(K key, V value) { if (root == null) { root = new Entry<>(key, value);
                              return null;
                          }
                          Entry parent = null;
                          Entry cur = root;
                          while (cur != null) { int r = key.compareTo(cur.key);
                              if (r == 0) { V oldValue = cur.value;
                                  cur.value = value;
                                  return oldValue;
                              } else if (r < 0) { parent = cur;
                                  cur = cur.left;
                              } else { parent = cur;
                                  cur = cur.right;
                              }
                          }
                          Entry entry = new Entry<>(key, value);
                          if (key.compareTo(parent.key) < 0) { parent.left = entry;
                          } else { parent.right = entry;
                          }
                          return null;
                      }
                      public V remove(K key) { Entry parent = null;
                          Entry cur = root;
                          while (cur != null) { int r = key.compareTo(cur.key);
                              if (r == 0) { V oldValue = cur.value;
                                  removeEntry(parent, cur);
                                  return oldValue;
                              } else if (r < 0) { parent = cur;
                                  cur = cur.left;
                              } else { parent = cur;
                                  cur = cur.right;
                              }
                          }
                          return null;
                      }
                      private void removeEntry(Entry parent, Entry cur) { if (cur.left == null) { if (cur == root) { root = cur.right;
                              } else if (cur == parent.left) { parent.left = cur.right;
                              } else { parent.right = cur.right;
                              }
                          } else if (cur.right == null) { if (cur == root) { root = cur.left;
                              } else if (cur == parent.left) { parent.left = cur.left;
                              } else { parent.right = cur.left;
                              }
                          } else { Entry gParent = cur;
                              Entry goat = cur.left;
                              while (goat.right != null) { gParent = goat;
                                  goat = goat.right;
                              }
                              cur.key = goat.key;
                              cur.value = goat.value;
                              if (goat == gParent.left) { gParent.left = goat.left;
                              } else { gParent.right = goat.left;
                              }
                          }
                      }
                      public static interface Function { void apply(T entry);
                      }
                      public static  void preOrderTraversal(Entry node, Function> func) { if (node != null) { func.apply(node);
                              preOrderTraversal(node.left, func);
                              preOrderTraversal(node.right, func);
                          }
                      }
                      public static  void inOrderTraversal(Entry node, Function> func) { if (node != null) { inOrderTraversal(node.left, func);
                              func.apply(node);
                              inOrderTraversal(node.right, func);
                          }
                      }
                      public void print() { System.out.println("前序遍历: ");
                          preOrderTraversal(root, (n) -> { System.out.print(n.key + " ");
                          });
                          System.out.println();
                          System.out.println("中序遍历: ");
                          inOrderTraversal(root, (n) -> { System.out.print(n.key + " ");
                          });
                          System.out.println();
                      }
                      public static void main(String[] args) { BSTree tree = new BSTree<>();
                          int count = 0;
                          Random random = new Random(20190915);
                          for (int i = 0; i < 20; i++) { int key = random.nextInt(200);
                              String value = String.format("Value of %d", key);
                              if (tree.put(key, value) == null) { count++;
                              }
                          }
                          System.out.println("一共插入 " + count + " 个结点");
                          tree.print();
                      }
                  }
                  

                  感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘