Java数据结构基础——泛型通配符

Posted YoLo♪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java数据结构基础——泛型通配符相关的知识,希望对你有一定的参考价值。

 作者:敲代码の流川枫

博客主页:流川枫的博客

专栏:和我一起学java

语录:Stay hungry stay foolish

工欲善其事必先利其器,给大家介绍一款超牛的斩获大厂offer利器——牛客网

点击免费注册和我一起刷题吧

文章目录

1. 泛型概念

泛型的引出

2. 泛型语法

3. 泛型的使用

类型推导(Type Inference)

裸类型(Raw Type)

4. 泛型的擦除机制

5. 泛型的上界

6. 泛型方法

7. 通配符

通配符上界

通配符下界


1. 泛型概念

泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化

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

泛型的引出

实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值

可以将数组定义为Object类,因为所有类默认继承于这个类

public class Test 
    public static void main(String[] args) 
        MyArray myArray = new MyArray();
        myArray.setVal(0,10);
        myArray.setVal(1, "s");//字符串也可以存放
        myArray.setVal(2,10.3);
        //String ret = myArray.getPos(1);//编译报错
        String ret = (String) myArray.getPos(1);
        System.out.println(ret);
    

class MyArray 
    public Object[] array = new Object[10];
    public Object getPos(int pos) 
        return this.array[pos];
    
    public void setVal(int pos,Object val) 
        this.array[pos] = val;
    

        //String ret = myArray.getPos(1);//编译报错
        String ret = (String) myArray.getPos(1);
        System.out.println(ret);

这里我们看到发生了向下转型,需要手动强制类型转换才可以,这样数据很多的时候就会很麻烦

使用Object数组的缺点有二,存放元素时可以存放任何类型的元素,再者,取出元素的时候需要手动强转

此时泛型就解决了这些问题,它将类型参数化了

2. 泛型语法

class 泛型类名称<类型形参列表> 
// 这里可以使用类型参数

class ClassName<T1, T2, ..., Tn> 

class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ 
// 这里可以使用类型参数

class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> 
// 可以只使用部分类型参数

注意:

1. 类名后的 <T> 代表占位符,表示当前类是一个泛型类

类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型

2. 不能new 泛型类型的数组

T[] ts = new T[5];//是不对的

3.<>里必须是类类型,不能是简单类型

我们将上述代码改写: 

class MyArray<T> 
    public T[] array = (T[]) new Object[10];
    public T getPos(int pos) 
        return this.array[pos];
    
    public void setVal(int pos,T val) 
        this.array[pos] = val;
    

public class Test 
    public static void main(String[] args) 
        MyArray<Integer> myArray = new MyArray<Integer>();
        myArray.setVal(0,10);
        myArray.setVal(1, "s");//报错
        myArray.setVal(2,10.3);//报错
        int a = myArray.getPos(0);//不用强转
        System.out.println(a);
        MyArray<String> myArray1 = new MyArray<String>();
        myArray1.setVal(0,"hello");
        myArray1.setVal(1,"world");
        String b = myArray1.getPos(0);//不用强转
        System.out.println(b);
    

泛型的主要目的:就是指定当前的容器,要持有什么类型的对象。让编译器去做检查。此时,就需要把类型作为参数传递。需要什么类型,就传入什么类型 

泛型存在的意义:

1.在存放类型的时候会进行类型的检查

2.取出元素的时候会自动强制类型转换

注意:泛型时在编译的时候的一种机制,在运行时是没有泛型的概念的

3. 泛型的使用

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

MyArray<Integer> list = new MyArray<Integer>();

类型推导(Type Inference)

public class Test 
    public static void main(String[] args) 

        MyArray<Integer> myArray = new MyArray<>();

        myArray.setVal(0,10);
        int a = myArray.getPos(0);//不用强转
        System.out.println(a);

        MyArray<String> myArray1 = new MyArray<>();

        myArray1.setVal(0,"hello");
        myArray1.setVal(1,"world");
        String b = myArray1.getPos(0);//不用强转
        System.out.println(b);
    

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

    MyArray<Integer> myArray = new MyArray<>();

    MyArray<String> myArray1 = new MyArray<>();

裸类型(Raw Type)

裸类型是一个泛型类型,但是没有实参,取值是还是要强转

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

4. 泛型的擦除机制

编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制

getPos中返回值为T,被替换为Object,setVal中返回值为空,即V,val参数类型被替换为Object

先根据你制定了类型进行检查和转换,在编译的时候把T都擦除成了Object

因此,Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息

5. 泛型的上界

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

语法

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

示例

public class MyArray<E extends Number> 
        //...

只接受 Number 的子类型作为 E 的类型,实参没有指定类型边界 E,可以视为 E extends Object

public class Test 
    public static void main(String[] args) 
        MyArray<Integer> myArray = new MyArray<>();
        MyArray<Float> myArray1 = new MyArray<>();
        MyArray<Double> myArray2 = new MyArray<>();
    

class MyArray<E extends Number> 

6. 泛型方法

静态的泛型方法 需要在static后用<>声明泛型类型参数

class Util 
    public static <E> void swap(E[] array, int i, int j) 
        E t = array[i];
        array[i] = array[j];
        array[j] = t;
    

<E>是泛型的形参,void是返回值,可以直接通过类名.来调用,不用new 

 非静态

7. 通配符

看一个例子:

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("hello");
    fun(message);
    Message<Integer> message1 = new Message() ;
    message1.setMessage(10);
    fun(message1);//报错
    
    public static void fun(Message<String> temp)
        System.out.println(temp.getMessage());
    

报错原因是fun方法只能接收Message<String>类型的参数,而传入的是Message<Integer>类型的参数

接下来我们体会通配符的作用

 public static void fun(Message<?> 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 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
class Food 


class Fruit extends Food 


class Apple extends Fruit 


class Banana extends Fruit 


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<Apple> message = new Message<>() ;
        message.setMessage(new Apple());
        fun(message);
        Message<Banana> message2 = new Message<>() ;
        message2.setMessage(new Banana());
        fun(message2);
    
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(Message<? extends Fruit> temp)
    //temp.setMessage(new Banana()); //无法修改!
    //temp.setMessage(new Apple()); //无法修改!
        System.out.println(temp.getMessage());
    

此时无法在fun函数中对temp进行添加元素,因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定。所以添加会报错,但是可以获取元素

public static void fun(Message<? extends Fruit> temp)
    //temp.setMessage(new Banana()); //无法修改!
    //temp.setMessage(new Apple()); //无法修改!
    Fruit b = temp.getMessage();
    System.out.println(b);

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

通配符下界

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

看一个例子

class Food 

class Fruit extends Food 

class Apple extends Fruit 

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<Fruit> message = new Message<>() ;
        message.setMessage(new Fruit());
        fun(message);

        Message<Food> message2 = new Message<>() ;
        message2.setMessage(new Food());
        fun(message2);

        Message<Apple> message1 = new Message<>();
        fun(message1);//报错
    

    //temp 接收Fruit及其子类的一个Message
    public static void fun(Message<? super Fruit> temp)

        // 此时可以修改 添加的是Fruit 或者Fruit的子类
        temp.setMessage(new Apple());//这个是Fruit的子类
        temp.setMessage(new Fruit());//这个是Fruit的本身

        //Fruit fruit = temp.getMessage(); 不能接收,这里无法确定是哪个父类

        System.out.println(temp.getMessage());//只能直接输出
    

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

因为无法确定是哪个父类

说明可以传入的实参的类型必须是Fruit或者Fruit的父类类型

“ 本期的分享就到这里了, 记得给博主一个三连哈,你的支持是我创作的最大动力!

大数据必学Java基础(五十五):泛型深入了解

文章目录

泛型深入了解

一、引入

1、什么是泛型(Generic)

2、没有泛型的时候使用集合

3、JDK1.5以后开始使用泛型,集合中使用泛型

4、泛型总结

二、自定义泛型结构

1、泛型类,泛型接口

2、泛型方法

3、泛型参数存在继承关系的情况

4、通配符

5、使用通配符后的细节

6、泛型受限


泛型深入了解

一、引入

1、什么是泛型(Generic)

泛型就相当于标签

形式:<>  

集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,

JDK1.5之 后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。

Collection<E>, List<E>, ArrayList<E> 这个<E>就是类型参数,即泛型。

2、没有泛型的时候使用集合

package com.lanson.test01;

import java.util.ArrayList;

/**
 * @author : lanson
 */
public class Test01 
    //这是main方法,程序的入口
    public static void main(String[] args) 
        //创建一个ArrayList集合,向这个集合中存入学生的成绩:
        ArrayList al = new ArrayList();
        al.add(98);
        al.add(18);
        al.add(39);
        al.add(60);
        al.add(83);
        al.add("lili");

        //对集合遍历查看:
        for(Object obj:al)
            System.out.println(obj);
        
    

如果不使用泛型的话,有缺点

一般我们在使用的时候基本上往集合中存入的都是相同类型的数据 --》便于管理,所以现在什么引用数据类型都可以存入集合,不方便!

3、JDK1.5以后开始使用泛型,集合中使用泛型

package com.lanson.test01;

import java.util.ArrayList;

/**
 * @author : lanson
 */
public class Test01 
    //这是main方法,程序的入口
    public static void main(String[] args) 
        //创建一个ArrayList集合,向这个集合中存入学生的成绩:
        //加入泛型的优点:在编译时期就会对类型进行检查,不是泛型对应的类型就不可以添加入这个集合。
        ArrayList<Integer> al = new ArrayList<Integer>();
        al.add(98);
        al.add(18);
        al.add(39);
        al.add(60);
        al.add(83);
        /*al.add("lili");
        al.add(9.8);*/

        //对集合遍历查看:
        /*for(Object obj:al)
            System.out.println(obj);
        */
        for(Integer i:al)
            System.out.println(i);
        
    

4、泛型总结

1、JDK1.5以后泛型实际就是 一个<>引起来的 参数类型,这个参数类型具体在使用的时候才会确定具体的类型。

 

2、使用了泛型以后,可以确定集合中存放数据的类型,在编译时期就可以检查出来。

3、使用泛型你可能觉得麻烦,实际使用了泛型才会简单,后续的遍历等操作简单。

4、泛型的类型:都是引用数据类型,不能是基本数据类型。

5、ArrayList<Integer> al = new ArrayList<Integer>();在JDK1.7以后可以写为:

ArrayList<Integer> al = new ArrayList<>();  --<>  ---钻石运算符 

二、自定义泛型结构

1、泛型类,泛型接口

1.1、泛型类的定义和实例化

package com.lanson.test02;

/**
 * @author : lanson
 * GenericTes就是一个普通的类
 * GenericTest<E> 就是一个泛型类
 * <>里面就是一个参数类型,但是这个类型是什么呢?这个类型现在是不确定的,相当于一个占位
 * 但是现在确定的是这个类型一定是一个引用数据类型,而不是基本数据类型
 */
public class GenericTest<E> 
    int age;
    String name;
    E sex;

    public void a(E n)

    
    public void b(E[] m)

    


class Test
    //这是main方法,程序的入口
    public static void main(String[] args) 
        //GenericTest进行实例化:
        //(1)实例化的时候不指定泛型:如果实例化的时候不明确的指定类的泛型,那么认为此泛型为Object类型
        GenericTest gt1 = new GenericTest();
        gt1.a("abc");
        gt1.a(17);
        gt1.a(9.8);
        gt1.b(new String[]"a","b","c");

        //(2)实例化的时候指定泛型:---》推荐方式
        GenericTest<String> gt2 = new GenericTest<>();
        gt2.sex = "男";
        gt2.a("abc");
        gt2.b(new String[]"a","b","c");
        
    

1.2、继承情况

1.2.1、父类指定泛型

class SubGenericTest extends GenericTest<Integer>



class Demo
    //这是main方法,程序的入口
    public static void main(String[] args) 
        //指定父类泛型,那么子类就不需要再指定泛型了,可以直接使用
        SubGenericTest sgt = new SubGenericTest();
        sgt.a(19);
    

1.2.2、父类不指定泛型

如果父类不指定泛型,那么子类也会变成一个泛型类,那这个E的类型可以在创建子类对象的时候确定:

class SubGenericTest2<E> extends GenericTest<E>



class Demo2
    //这是main方法,程序的入口
    public static void main(String[] args) 
        SubGenericTest2<String> s = new  SubGenericTest2<>();
        s.a("abc");
        s.sex = "女";
    

1.3、应用场合

1.4、细节

1.4.1、泛型类可以定义多个参数类型

1.4.2、泛型类的构造器的写法

 

1.4.3、不同的泛型的引用类型不可以相互赋值

 

1.4.4、泛型如果不指定,那么就会被擦除,反应对应的类型为Object类型

 

1.4.5、反省类中的静态方法不能使用类的泛型

1.4.6、不能直接使用E[]的创建

 

2、泛型方法

package com.lanson.test04;

/**
 * @author : lanson
 * 1.什么是泛型方法:
 * 不是带泛型的方法就是泛型方法
 * 泛型方法有要求:这个方法的泛型的参数类型要和当前的类的泛型无关
 * 换个角度:
 * 泛型方法对应的那个泛型参数类型 和  当前所在的这个类 是否是泛型类,泛型是啥  无关
 * 2.泛型方法定义的时候,前面要加上<T>
 *     原因:如果不加的话,会把T当做一种数据类型,然而代码中没有T类型那么就会报错
 * 3.T的类型是在调用方法的时候确定的
 * 4.泛型方法可否是静态方法?可以是静态方法
 */
public class TestGeneric<E> 
    //不是泛型方法 (不能是静态方法)
    public static void a(E e)

    
    //是泛型方法
    public static <T>  void b(T t)

    


class Demo
    //这是main方法,程序的入口
    public static void main(String[] args) 
        TestGeneric<String> tg = new TestGeneric<>();
        tg.a("abc");
        tg.b("abc");
        tg.b(19);
        tg.b(true);
    

3、泛型参数存在继承关系的情况

4、通配符

4.1、在没有通配符的时候

下面的a方法,相当于方法的重复定义,报错

public class Test 
    /*public void a(List<Object> list)

    
    public void a(List<String> list)

    
    public void a(List<Integer> list)

    */

 

4.2、引入通配符

public class Demo 
    //这是main方法,程序的入口
    public static void main(String[] args) 
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<Integer> list3 = new ArrayList<>();

        List<?> list = null;
        list = list1;
        list = list2;
        list = list3;

    

发现: A 和 B是子类父类的关系,G<A>和G<B>不存在子类父类关系,是并列的

加入通配符?后,G<?>就变成了 G<A>和G<B>的父类

4.3、使用通配符

package com.lanson.test06;

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

/**
 * @author : lanson
 */
public class Test 
    /*public void a(List<Object> list)

    
    public void a(List<String> list)

    
    public void a(List<Integer> list)

    */
    public void a(List<?> list)
        //内部遍历的时候用Object即可,不用?
        for(Object a:list)
            System.out.println(a);
        
    


class T
    //这是main方法,程序的入口
    public static void main(String[] args) 
        Test t = new Test();
        t.a(new ArrayList<Integer>());
        t.a(new ArrayList<String>());
        t.a(new ArrayList<Object>());
    

4.4、查看API中应用位置

 

5、使用通配符后的细节

public class Test 
    public void a(List<?> list)
        //1.遍历:
        for(Object a:list)
            System.out.println(a);
        
        //2.数据的写入操作 :
        //list.add("abc");-->出错,不能随意的添加数据
        list.add(null);

        //3.数据的读取操作:
        Object s = list.get(0);
    


class T
    //这是main方法,程序的入口
    public static void main(String[] args) 
        Test t = new Test();
        t.a(new ArrayList<Integer>());
        t.a(new ArrayList<String>());
        t.a(new ArrayList<Object>());
    

6、泛型受限

package com.lanson.test07;

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

/**
 * @author : lanson
 */
public class Test 
    //这是main方法,程序的入口
    public static void main(String[] args) 
        //a,b,c三个集合是并列的关系:
        List<Object> a = new ArrayList<>();
        List<Person> b = new ArrayList<>();
        List<Student> c = new ArrayList<>();
        /*开始使用泛型受限:泛型的上限
        List<? extends Person>:
        就相当于:
        List<? extends Person>是List<Person>的父类,是List<Person的子类>的父类
         */
        List<? extends Person> list1 = null;
        /*list1 = a;
        list1 = b;
        list1 = c;*/
        /*开始使用泛型受限:泛型的下限
        List<? super Person>
        就相当于:
        List<? super Person>是List<Person>的父类,是List<Person的父类>的父类
         */
        List<? super Person> list2 = null;
        list2 = a;
        list2 = b;
        list3 = c;
    

  • 📢博客主页:https://lansonli.blog.csdn.net
  • 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
  • 📢本文由 Lansonli 原创,首发于 CSDN博客🙉
  • 📢停下休息的时候不要忘了别人还在奔跑,希望大家抓紧时间学习,全力奔赴更美好的生活✨

以上是关于Java数据结构基础——泛型通配符的主要内容,如果未能解决你的问题,请参考以下文章

尚硅谷_Java零基础教程(泛型generics)-- 学习笔记

# Day11-Java基础

二刷java基础第十六天——泛型和类型通配符

java-基础-泛型

Java 基础知识点 笔记总结

Java 基础知识点 笔记总结