Java泛型
- 👔9. 泛型的限制👔
- 👕10. 完整定义一份泛型类支持的搜索树(不使用 Comparator)👕
- 大家好,我是晓星航。今天为大家带来的是 Java泛型 相关内容的讲解!😀
🧳1. 泛型类的定义🧳
一般的类和方法,只能使用具体的类型:要么是基本的类型,要么是自定义类型。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的舒服就会很大。----来源《Java编程思想》对泛型的介绍。
泛型实在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。
1.1 语法
class 泛型类名称<类型形参列表> { // 这里可以使用类型参数 } class ClassName<T1, T2, ..., Tn> { }
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ { // 这里可以使用类型参数 } class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> { // 可以只使用部分类型参数 }
了解: 【规范】类型形参一般使用一个大写字母表示,常用的名称有:
- 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<E> { 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<E> { public static class Node<E> { private E value; private Node<E> next; private Node(E e) { value = e; next = null; } } private Node<E> head; private int size; public MyLinkedList() { head = null; size = 0; } // 头插 public void pushFront(E e) { Node<E> node = new Node<>(e); node.next = head; head = node; size++; } // 尾插 public void pushBack(E e) { if (size == 0) { pushFront(e); return; } Node<E> cur = head; while (cur.next != null) { cur = cur.next; } cur.next = new Node<E>(e); size++; } }
1.4 加入继承或实现的示例
定义一个泛型类顺序表
public interface MyList<E> { // 尾插 void add(E e); // 尾删 E remove(); }
public class MyArrayList<E> implements MyList<E> { // TODO: 未完成 }
1.5泛型的上界
在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。
语法
class 泛型类名称<类型形参 extends 类型边界> { ... }
上述的类型边界就是我们泛型的上界
示例
public class MyArray<E extends Number> { ... }
只接受 Number 的子类型作为 E 的类型实参
MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型 MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型
error: type argument String is not within bounds of type-variable E MyArrayList<String> 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 extends Comparable<E>> { ... }
E必须是实现了Comparable接口的
1.6泛型的下界
泛型没有下界!!!
🌂2. 泛型类的使用🌂
2.1 语法
泛型类<类型实参> 变量名; // 定义一个泛型类引用 new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
2.2 示例
MyArrayList<String> list = new MyArrayList<String>();
2.3 类型推导(Type Inference)
当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写
MyArrayList<String> list = new MyArrayList<>(); // 可以推导出实例化需要的类型实参为 String
☂️3. 裸类型(Raw Type) (了解)☂️
3.1 说明
裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型
MyArrayList list = new MyArrayList();
**注意:**我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制
下面的类型擦除部分,我们也会讲到编译器是如何使用裸类型的。
3.2 未检查错误
MyArrayList<String> 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<E extends Number> { ... }
只接受 Number 的子类型作为 E 的类型实参
MyArrayList<Integer> l1; // 正常,因为 Integer 是 Number 的子类型 MyArrayList<String> l2; // 编译错误,因为 String 不是 Number 的子类型 error: type argument String is not within bounds of type-variable E MyArrayList<String> l2; ^ where E is a type-variable: E extends Number declared in class MyArrayList
了解: 没有指定类型边界 E,可以视为 E extends Object
4.3 复杂一点的示例
定义一个泛型类搜索树
public class BSTree<K extends Comparable<K>, V> { ... }
传入的 K 必须是 Comparable 的,并且是可以和另一个 K 类型做比较的,后边的 K 是对类型参数的使用了
🧶5. 类型擦除🧶
我们之前已经讲过,泛型是作用在编译期间的一种机制,实际上运行期间是没有这么多类的,那运行期间是什么类型呢?这里就是类型擦除在做的事情。
class MyArrayList<E> { // E 会被擦除为 Object } class MyArrayList<E extends Comparable<E>> { // E 被擦除为 Comprable }
总结: 即类型擦除主要看其类型边界而定
了解: 编译器在类型擦除阶段在做什么?
- 将类型变量用擦除后的类型替换,即 Object 或者 Comparable
- 加入必要的类型转换语句
- 加入必要的 bridge method 保证多态的正确性
👓6. 泛型类的使用-通配符(Wildcards)👓
? 用于在泛型的使用,即为通配符
6.1通配符解决什么问题
**通配符是用来解决泛型无法协变的问题的,**协变指的就是如果 Student 是 Person 的子类,那么 List 也应该是 List 的子类。但是泛型是不支持这样的父子类关系的。
1、泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围。
2、或者我们可以这么理解:泛型T就像是个变量,等着你将来传一个具体的类型,而通配符则是一种规定,规定你能传哪些参数。
class Alg3 { public static <T> void print1(ArrayList<T> 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<T> { 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<String> message = new Message() ; message.setMessage("庐山风景真美"); fun(message); } public static void fun(Message<String> temp){ System.out.println(temp.getMessage()); } }
以上程序会带来新的问题,如果现在泛型的类型设置的不是String,而是Integer.
public class TestDemo { public static void main(String[] args) { Message<Integer> message = new Message() ; message.setMessage(99); fun(message); // 出现错误,只能接收String } public static void fun(Message<String> temp){ System.out.println(temp.getMessage()); } }
我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符"?"来处理
范例:使用通配符
public class TestDemo { public static void main(String[] args) { Message<Integer> 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 通配符-上界
语法
只能调用传入参数的子类,不允许调用他们的父类
<? extends 上界> <? extends Number>//可以传入的实参类型是Number或者Number的子类
示例
// 可以传入类型实参是 Number 子类的任意类型的 MyArrayList 因为这里的? 继承了Number。 public static void printAll(MyArrayList<? extends Number> list) { ... } // 以下调用都是正确的 printAll(new MyArrayList<Integer>()); printAll(new MyArrayList<Double>()); printAll(new MyArrayList<Number>()); // 以下调用是编译错误的 printAll(new MyArrayList<String>()); printAll(new MyArrayList<Object>());
此时的list可以引用的子类对象很多,编译器无法确定你具体的类型的。 编译器为了安全起见,此时只允许你进行读取。
上述传入的是Number,调用Integer、Double和Number是可以的,但是调用String和Object是不可以的。
注意: 需要区分 泛型使用中的通配符上界 和 泛型定义中的类型上界
通配符的上界,不能进行写入数据,只能进行读取数据。
6.3 通配符-下界
语法
<? super 下界> <? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
示例
// 可以传入类型实参是 Integer 父类的任意类型的 MyArrayList public static void printAll(MyArrayList<? super Integer> list) { ... } // 以下调用都是正确的 printAll(new MyArrayList<Integer>()); printAll(new MyArrayList<Number>()); printAll(new MyArrayList<Object>()); // 以下调用是编译错误的 printAll(new MyArrayList<String>()); printAll(new MyArrayList<Double>());
假如我们引用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<E> { ... } // MyArrayList<Object> 不是 MyArrayList<Number> 的父类型 // MyArrayList<Number> 也不是 MyArrayList<Integer> 的父类型 // 需要使用通配符来确定父子类型 // MyArrayList<?> 是 MyArrayList<? extends Number> 的父类型 // MyArrayList<? extends Number> 是 MyArrayList<Integer> 的父类型
理论上来说:Object是所有类的父类
为什么上述代码的Object不是Number的父类呢,因为编译完成后我们的类型都被擦除了
🦺8. 泛型方法🦺
8.1 定义语法
方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }
8.2 示例
一般的泛型方法需要new对象,new完对象之后才可以使用我们的Alg方法
class Alg<T extends Comparable<T>> { 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<Integer> alg = new Alg<>(); Integer[] array = {1,2,3,4,7,5,6,9,100,97,123}; System.out.println(alg.findMax(array)); Alg<String> alg1 = new Alg<>(); String[] array1 = {"abc","xxhljq","hello"}; System.out.println(alg1.findMax(array1)); }
加入了static之后的泛型方法变为了静态的泛型方法,不需要new对象,直接就可以调用Alg2的方法。
class Alg2 { public static <T extends Comparable<T>>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)); 不写<Integer>我们的Alg2方法也会自己推导出array的类型是Integer System.out.println(Alg2.<Integer>findMax(array)); String[] array1 = {"abc","xxhljq","hello"}; //System.out.println(Alg2.findMax(array1)); 不写<String>我们的Alg2方法也会自己推导出array的类型是String System.out.println(Alg2.<String>findMax(array1)); } }
8.3 使用示例-可以类型推导
Integer[] a = { ... }; swap(a, 0, 9); String[] b = { ... }; swap(b, 0, 9);
8.4 使用示例-不使用类型推导
Integer[] a = { ... }; Util.<Integer>swap(a, 0, 9); String[] b = { ... }; Util.<String>swap(b, 0, 9);
👔9. 泛型的限制👔
泛型类型参数不支持基本数据类型
无法实例化泛型类型的对象
无法使用泛型类型声明静态的属性
无法使用 instanceof 判断带类型参数的泛型类型
无法创建泛型类数组
无法 create、catch、throw 一个泛型类异常(异常不支持泛型)
泛型类型不是形参一部分,无法重载
👕10. 完整定义一份泛型类支持的搜索树(不使用 Comparator)👕
import java.util.*; public class BSTree<K extends Comparable<K>, V> { private static class Entry<K, V> { private K key; private V value; private Entry<K, V> left = null; private Entry<K, V> 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<K, V> root = null; public V get(K key) { Entry<K, V> 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<K, V> parent = null; Entry<K, V> 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<K, V> 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<K, V> parent = null; Entry<K, V> 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<K, V> parent, Entry<K, V> 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<K, V> gParent = cur; Entry<K, V> 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<T> { void apply(T entry); } public static <K, V> void preOrderTraversal(Entry<K, V> node, Function<Entry<K, V>> func) { if (node != null) { func.apply(node); preOrderTraversal(node.left, func); preOrderTraversal(node.right, func); } } public static <K, V> void inOrderTraversal(Entry<K, V> node, Function<Entry<K, V>> 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<Integer, String> 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(); } }
感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘