锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. Java数据结构基础——泛型、通配符

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

0
  • 软件开发
  • 发布于 2024-09-19
  • 0 次阅读
黄健
黄健

作者:敲代码の流川枫

博客主页:流川枫的博客

专栏:和我一起学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数组的缺点有二,存放元素时可以存放任何类型的元素,再者,取出元素的时候需要手动强转

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

  1. 泛型语法

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

注意:

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

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

  1. 不能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.取出元素的时候会自动强制类型转换

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

  1. 泛型的使用

泛型类<类型实参> 变量名; // 定义一个泛型类引用
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 保留的机制

  1. 泛型的擦除机制

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

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

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

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

  1. 泛型的上界

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

语法

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> {
}
  1. 泛型方法

静态的泛型方法 需要在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;
    }
}

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

非静态

  1. 通配符

看一个例子:

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类型的参数,而传入的是Message类型的参数

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

 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的父类类型
” 本期的分享就到这里了, 记得给博主一个三连哈,你的支持是我创作的最大动力!

原文链接: https://blog.csdn.net/chenchenchencl/article/details/126865282

标签: #JAVA 991 #软件开发 1171
相关文章

万字:支付“核心系统”详解 2024-11-02 15:33

专栏作者:隐墨星辰 \| 主编:陈天宇宙 这篇文章也尝试化繁为简,探寻支付系统的本质,讲清楚在线支付系统最核心的一些概念和设计理念。 虽然支付行业已经过了风头最劲的时光,但跨境支付仍然在蓬勃发展,每年依然有很多新人进入这个行业,这篇文章尝试为这些刚入行的新人提供一点帮助。 文章只介绍一些支付行业十几

资深支付架构师视角:实战从问题定义到代码落地的完整套路 2024-11-02 15:33

前言 今天从一个实际案例入手,介绍站在架构师的角度,如何识别并定义问题,提炼需求,技术方案选型,再到详细设计,最后利用AI的能力协助写出核心的代码,验证与调优。 解决问题存在一定的模式,也可以称之为框架,总结出自己的思考和解题框架,以后再碰到同类型的问题就可以如庖丁解牛一样容易。 很多年前,我写代码

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

设计模式第16讲——迭代器模式(Iterator) 2024-10-08 11:24

一、什么是迭代器模式 迭代器模式是一种行为型设计模式,它提供了一种统一的方式来访问集合对象中的元素,而不是暴露集合内部的表示方式。简单地说,就是将遍历集合的责任封装到一个单独的对象中,我们可以按照特定的方式访问集合中的元素。 二、角色组成 抽象迭代器(Iterator):定义了遍历聚合对象所需的方法

vue2路由和vue3路由区别及原理 2024-10-08 11:24

一、Vue2 与 Vue3 路由的区别 1. 创建路由实例方式的不同 Vue 2 中,通过 Vue.use() 注册路由插件,并通过 new VueRouter() 来创建路由实例。 import Vue from 'vue';import VueRouter from 'vue-router';i

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.