0


Java泛型

Java泛型

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

🧳1. 泛型类的定义🧳

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

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

1.1 语法

class 泛型类名称<类型形参列表>{// 这里可以使用类型参数}classClassName<T1, T2,...,Tn>{}
class 泛型类名称<类型形参列表>extends 继承类/* 这里可以使用类型参数 */{// 这里可以使用类型参数}classClassName<T1, T2,...,Tn>extendsParentClass<T1>{// 可以只使用部分类型参数}

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

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

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

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

1.2 简单示例

定义一个泛型类顺序表

// 演示泛型目的,没有做错误处理和扩容publicclassMyArrayList<E>{privateE[] array;privateint size;publicMyArrayList(){// 泛型类型无法直接创建数组,具体的见下面的注意事项
        array =(E[])newObject[16];
        size =0;}// 尾插publicvoidadd(E e){
        array[size++]= e;}// 尾删publicEremove(){E element = array[size -1];
        array[size -1]=null;// 将容器置空,保证对象被正确释放
        size--;return element;}}

1.3 加入静态内部类的示例

定义一个泛型类链表

publicclassMyLinkedList<E>{publicstaticclassNode<E>{privateE value;privateNode<E> next;privateNode(E e){
            value = e;
            next =null;}}privateNode<E> head;privateint size;publicMyLinkedList(){
        head =null;
        size =0;}// 头插publicvoidpushFront(E e){Node<E> node =newNode<>(e);
        node.next = head;
        head = node;
        size++;}// 尾插publicvoidpushBack(E e){if(size ==0){pushFront(e);return;}Node<E> cur = head;while(cur.next !=null){
            cur = cur.next;}
        cur.next =newNode<E>(e);
        size++;}}

1.4 加入继承或实现的示例

定义一个泛型类顺序表

publicinterfaceMyList<E>{// 尾插voidadd(E e);// 尾删Eremove();}
publicclassMyArrayList<E>implementsMyList<E>{// TODO: 未完成}

1.5泛型的上界

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

语法

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

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

示例

publicclassMyArray<EextendsNumber>{...}

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

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

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

不指定边界就默认为Object

1.5.1复杂举例

publicclassMyArray<EextendsComparable<E>>{...}

E必须是实现了Comparable接口的

1.6泛型的下界

泛型没有下界!!!

🌂2. 泛型类的使用🌂

2.1 语法

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

2.2 示例

MyArrayList<String> list =newMyArrayList<String>();

2.3 类型推导(Type Inference)

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

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

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

3.1 说明

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

MyArrayList

就是一个裸类型

MyArrayList list =newMyArrayList();

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

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

3.2 未检查错误

MyArrayList<String> list =newMyArrayList();// 自己永远不要这么用

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

Note:Example.java usesunchecked or unsafe operations.
Note:Recompilewith-Xlint:unchecked for details.

可以使用

@SuppressWarnings

注解进行警告压制

@SuppressWarnings("unchecked")

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

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

4.1 语法

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

4.2 示例

publicclassMyArrayList<EextendsNumber>{...}

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

MyArrayList<Integer> l1;// 正常,因为 Integer 是 Number 的子类型MyArrayList<String> l2;// 编译错误,因为 String 不是 Number 的子类型

error: type argument String is not within bounds of type-variable EMyArrayList<String> l2;^
where E is a type-variable:EextendsNumber declared in classMyArrayList

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

4.3 复杂一点的示例

定义一个泛型类搜索树

publicclassBSTree<KextendsComparable<K>,V>{...}

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

🧶5. 类型擦除🧶

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

classMyArrayList<E>{// E 会被擦除为 Object}classMyArrayList<EextendsComparable<E>>{// E 被擦除为 Comprable}

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

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

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

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

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

6.1通配符解决什么问题

通配符是用来解决泛型无法协变的问题的,协变指的就是如果

Student

Person

的子类,那么

List

也应该是

List

的子类。但是泛型是不支持这样的父子类关系的。

1、泛型

T

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

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

classAlg3{publicstatic<T>voidprint1(ArrayList<T> list){for(T x: list){System.out.println(x);}}publicstaticvoidprint2(ArrayList<?> list){for(Object x: list){System.out.println(x);}}}

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

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

示例

packagewww.bit.java.test;classMessage<T>{privateT message ;publicTgetMessage(){return message;}publicvoidsetMessage(T message){this.message = message;}}publicclassTestDemo{publicstaticvoidmain(String[] args){Message<String> message =newMessage();
        message.setMessage("庐山风景真美");fun(message);}publicstaticvoidfun(Message<String> temp){System.out.println(temp.getMessage());}}

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

publicclassTestDemo{publicstaticvoidmain(String[] args){Message<Integer> message =newMessage();
        message.setMessage(99);fun(message);// 出现错误,只能接收String}publicstaticvoidfun(Message<String> temp){System.out.println(temp.getMessage());}}

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

范例:使用通配符

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

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

? extends 类:设置泛型上限

? super 类:设置泛型下限

6.2 通配符-上界

语法

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

<?extends 上界><?extendsNumber>//可以传入的实参类型是Number或者Number的子类

示例

// 可以传入类型实参是 Number 子类的任意类型的 MyArrayList 因为这里的? 继承了Number。publicstaticvoidprintAll(MyArrayList<?extendsNumber> list){...}// 以下调用都是正确的printAll(newMyArrayList<Integer>());printAll(newMyArrayList<Double>());printAll(newMyArrayList<Number>());// 以下调用是编译错误的printAll(newMyArrayList<String>());printAll(newMyArrayList<Object>());

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

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

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

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

6.3 通配符-下界

语法

<?super 下界><?superInteger>//代表 可以传入的实参的类型是Integer或者Integer的父类类型

示例

// 可以传入类型实参是 Integer 父类的任意类型的 MyArrayListpublicstaticvoidprintAll(MyArrayList<?superInteger> list){...}// 以下调用都是正确的printAll(newMyArrayList<Integer>());printAll(newMyArrayList<Number>());printAll(newMyArrayList<Object>());// 以下调用是编译错误的printAll(newMyArrayList<String>());printAll(newMyArrayList<Double>());

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

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

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

为什么

Person person = arrayList1.get(0);

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

注:Student是Person的子类

不可以取出arrayList的元素?

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

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

publicclassMyArrayList<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方法

classAlg<TextendsComparable<T>>{publicTfindMax(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;}}publicclassTestDemo{publicstaticvoidmain(String[] args){Alg<Integer> alg =newAlg<>();Integer[] array ={1,2,3,4,7,5,6,9,100,97,123};System.out.println(alg.findMax(array));Alg<String> alg1 =newAlg<>();String[] array1 ={"abc","xxhljq","hello"};System.out.println(alg1.findMax(array1));}


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

classAlg2{publicstatic<TextendsComparable<T>>TfindMax(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;}}publicclassTestDemo{publicstaticvoidmain(String[] args){Integer[] array ={1,2,3,4,7,5,6,9,100,97,123};//System.out.println(Alg2.findMax(array));  不写<Integer>我们的Alg2方法也会自己推导出array的类型是IntegerSystem.out.println(Alg2.<Integer>findMax(array));String[] array1 ={"abc","xxhljq","hello"};//System.out.println(Alg2.findMax(array1));  不写<String>我们的Alg2方法也会自己推导出array的类型是StringSystem.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. 泛型的限制👔

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

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

importjava.util.*;publicclassBSTree<KextendsComparable<K>,V>{privatestaticclassEntry<K,V>{privateK key;privateV value;privateEntry<K,V> left =null;privateEntry<K,V> right =null;privateEntry(K key,V value){this.key = key;this.value = value;}@OverridepublicStringtoString(){returnString.format("{%s=%s}", key, value);}}privateEntry<K,V> root =null;publicVget(K key){Entry<K,V> cur = root;while(cur !=null){int r = key.compareTo(cur.key);if(r ==0){return cur.value;}elseif(r <0){
                cur = cur.left;}else{
                cur = cur.right;}}returnnull;}publicVput(K key,V value){if(root ==null){
            root =newEntry<>(key, value);returnnull;}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;}elseif(r <0){
                parent = cur;
                cur = cur.left;}else{
                parent = cur;
                cur = cur.right;}}Entry<K,V> entry =newEntry<>(key, value);if(key.compareTo(parent.key)<0){
            parent.left = entry;}else{
            parent.right = entry;}returnnull;}publicVremove(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;}elseif(r <0){
                parent = cur;
                cur = cur.left;}else{
                parent = cur;
                cur = cur.right;}}returnnull;}privatevoidremoveEntry(Entry<K,V> parent,Entry<K,V> cur){if(cur.left ==null){if(cur == root){
                root = cur.right;}elseif(cur == parent.left){
                parent.left = cur.right;}else{
                parent.right = cur.right;}}elseif(cur.right ==null){if(cur == root){
                root = cur.left;}elseif(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;}}}publicstaticinterfaceFunction<T>{voidapply(T entry);}publicstatic<K,V>voidpreOrderTraversal(Entry<K,V> node,Function<Entry<K,V>>
            func){if(node !=null){
            func.apply(node);preOrderTraversal(node.left, func);preOrderTraversal(node.right, func);}}publicstatic<K,V>voidinOrderTraversal(Entry<K,V> node,Function<Entry<K,V>>
            func){if(node !=null){inOrderTraversal(node.left, func);
            func.apply(node);inOrderTraversal(node.right, func);}}publicvoidprint(){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();}publicstaticvoidmain(String[] args){BSTree<Integer,String> tree =newBSTree<>();int count =0;Random random =newRandom(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();}}


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

标签: java 数据结构

本文转载自: https://blog.csdn.net/xinhang10/article/details/130513086
版权归原作者 晓星航 所有, 如有侵权,请联系我们删除。

“Java泛型”的评论:

还没有评论