8.0 泛型

通过之前的学习,读者可以了解到,把一个对象存入集合后,再次取出该对象时,该对象的编译类型就变成了Object类型(尽管其在运行时类型没有改变)。集合设计成这样,提高了它的通用性,但是也带来了一些类型不安全和繁琐的问题,例如,集合可以同时存储任何类型的对象,通常对取出之后的对象都需要强制类型转换,而且如果不知道实际参数类型的情况,也无法进行强制类型转换。为了解决这些问题,从JDK 5版本开始引入了泛型,本章将围绕泛型的相关内容进行讲解。

1. 泛型基础

1.1. 泛型的概念

泛型是在JDK 5中引入的一个新特性,其本质是参数化类型,也就是将具体的类型形参化,参数化的类型(可以称之为类型形参)在使用或者调用时传入具体的类型(类型实参),类似于调用方法时传入实参才确定方法形参的具体值。泛型的声明由一对尖括号和类型形参组成,类型形参定义在尖括号中间,定义类、接口和方法时使用泛型声明,定义出的类、接口和方法分别称为泛型类、泛型接口和泛型方法。

1.2. 泛型的定义

使用泛型编程,会在使用或者调用时传入具体的类型时才确定最终的数据类型,所以集合需要存储什么类型的数据,在创建集合时传入对应的类型即可。

定义泛型时类型形参由一对尖括号(<>)包含在中间,使用或者调用泛型时,需要将类型实参写在尖括号(<>)之间。

JDK 5之后的类库中很多重要的类和接口都引入了泛型,例如集合体系中的类和接口。下面分别演示未引入泛型和使用泛型编程的区别,体验泛型具体有什么好处。

(1)未引入泛型前

public class TestDemo {
    
    @Test
    public void test(){
        // 创建一个只保存Integer类型的List集合
        List intList = new ArrayList();
        intList.add(1);
        intList.add(2);
        //因为失误存放了Integer类型之外的字符串数据
        intList.add("3");
        for (int i = 0; i < intList.size(); i++) {
            /*因为List里面默认取出的全部Object对象,所以使用之前需要进行强
             * 制类型转换。集合内最后一个元素进行转换时候将出现类型转换异常
             * */
            Integer num=(Integer)intList.get(i);
        }
    }
}

(2)引入泛型后

public class TestDemo {
    @Test
    public void test(){
        // 创建一个只保存Integer类型的List集合
        List<Integer> intList = new ArrayList<Integer>();
        intList.add(1);
        intList.add(2);
        //下面代码将出现编译时异常
        intList.add("3");
        for (int i = 0; i < intList.size(); i++) {
            //下面的代码无需强制类型转换
            Integer num=intList.get(i);
        }
    }
}

1.3. 泛型的好处

使用泛型的好处如下:

  • 提高类型的安全性

使用泛型后,将类型的检查从运行期提前到编译期,编译期的类型检查,可以更早、更容易的找出因为类型限制而导致的类型转换异常,从而提高程序的可靠性。

  • 消除强制类型转换

使用泛型后,程序会记住当前的类型形参,从而无需对传入的实参值进行强制类型转换。使得代码更加清晰和筒洁,可读性更高。

  • 提高代码复用性

使用泛型后,可以更好的将程序中通用的代码提取出来,在使用时传入不同类型的参数,避免了多次编写相同功能的代码,以提高代码的复用性。

  • 拥有更高的运行效率

使用泛型之前,传入的实际参数值作为Object类型传递时,需要进行封箱和拆箱操作,会消耗程序的一定的开销。使用泛型后,类型形参中都需要使用引用数据类型,即传入的实际参数的类型都是对应引用数据类型,避免了封箱和拆箱操作,降低了程序运行的开销,提高了程序运行的效率。

2. 泛型类

2.1. 泛型类的语法格式

定义类时,在类名后加上尖括号包含类型形参,定义的这个类就是泛型类。创建泛型类的实例对象时传入不同的类型实参,从而可以动态生成无数个该泛型类的子类。在JDK类包中泛型类的最典型应用就是各种容器类,如ArrayList、HashMap等。定义泛型类的格式具体如下。

public class 类名<类型形参变量>{
    
}

上述语法格式中,类名<类型形参变量>是一个整体的数据类型,通常称为泛型类型;类型形参变量,没有特定的意义,可以是任意一个字母,但是为了提高可读性,建议使用有意义的字母。一般情况下使用较多的字母及意义如下所示。

  • E:表示Element(元素),常用在java Collection里使用,如 List<E>,Iterator<E>,Set<E>。

  • K,V:表示Key,Value(Map的键值对)。

  • N:表示Number(数字)。

  • T:表示Type(类型),如String,Integer等。

2.2. 泛型类的定义与创建

定义:定义泛型类时,类的构造方法名称还是类的名称,类型形参变量可以用于属性的类型、方法的返回值类型和方法的参数类型。

创建:创建泛型类的对象时,不强制要求传入类型实参,如果传入类型实参,类型形参会根据传入的类型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入类型实参的话,在泛型类中使用类型形参的方法或成员变量定义的类型可以为任何的类型。

(1)定义泛型类Goods,声明私有变量info,定义构造方法,与getter/setter方法。

/**
 * 定义泛型类Goods
 * @param <T>
 */
public class Goods<T> {
    // 类型形参变量作用于属性的类型
    private T info ;
    //无参构造方法
    public Goods(){

    }
    // 类型形参变量作用于构造方法的参数类型
    public Goods(T info) {
        this.info = info;
    }
    // 类型形参变量作用于方法的参数类型
    public void setInfo(T info){
        this.info = info ;
    }
    // 类型形参变量作用于方法的返回值类型
    public T getInfo(){
        return this.info ;
    }
}

(2)定义测试类,创建Goods对象,分别调用setInfo()方法和getInfo()方法。

public class TestDemo{
    @Test
    public void test(){
        Goods goods = new Goods();
        goods.setInfo("电脑");
        System.out.println(goods.getInfo() + ":" + goods.getInfo().getClass());
        goods.setInfo(200);
        System.out.println(goods.getInfo() + ":" + goods.getInfo().getClass());
    }
    @Test
    public void test2(){
        Goods<String> goods = new Goods<>();
        goods.setInfo("电脑");
        System.out.println(goods.getInfo() + ":" + goods.getInfo().getClass());
    }
    @Test
    public void test3(){
        Goods<Integer> goods= new Goods<>();
        goods.setInfo(200);
        System.out.println(goods.getInfo() + ":" + goods.getInfo().getClass());
    }
}

2.3. 泛型类的练习

定义一个泛型类Point<T>,其中包含x和y两个类型为T的成员,定义类的带参构造方法,为x和y定义setter和getter,定义show方法输出坐标。

编写测试方法,创建Point<Integer>对象和Point<Double>对象。

  • Point<T>类

public class Point<T> {
    //泛型成员
    private T x;
    private T y;
    //构造方法
    public Point(T x,T y){
        this.x = x;
        this.y = y;
    }

    public T getX() {
        return x;
    }
    public void setX(T x) {
        this.x = x;
    }
    public T getY() {
        return y;
    }
    public void setY(T y) {
        this.y = y;
    }
    //输出坐标
    public void show(){
        System.out.println("x坐标是:" + x + ",y坐标是:" + y);
    }
}
  • 测试类

public class Test {
    public static void main(String[] args) {
        //Integer型
        Point<Integer> p1 = new Point(1,2);
        p1.show();
        //Double型
        Point<Double> p2 = new Point(1.1,2.2);
        p2.show();
    }
}

3. 泛型接口

3.1. 泛型接口的语法格式

定义泛型接口和定义泛型类的语法格式类似,在接口名称后面加上尖括号包含类型形参即可。集合相关的接口中很多接口也都是泛型接口,如Collection、List等。定义泛型接口的基本语法格式如下所示:

public interface 接口名称<类型形参变量>{
    
}

3.2. 泛型接口的应用

泛型接口可以有两种类方式实现,第一种是使用非泛型类实现泛型接口,第二种是使用泛型类实现泛型接口。

(1)使用非泛型类实现泛型接口

当使用非泛型类实现接口时,需要明确接口的泛型类型,也就是需要将类型实参传入到接口中。此时实现类重写接口中使用泛型的地方,都需要将类型形参替换成传入的类型实参,这样可以直接使用泛型接口的类型实参,具体代码如下所示。

  • 定义一个泛型接口

public interface Student<T> {
    public abstract void show(T t);
}
  • 定义泛型接口的实现类,在泛型接口后指定类型实参以明确接口的泛型类型。

public class StudentImpl implements Student<String>{
    @Override
    public void show(String s) {
        System.out.println(s);
    }
}
  • 定义测试类,创建Student对象时,传入的类型实参必须是String类型,否则编译异常。

public class TestDemo {

    @Test
    public void test(){
        Student<String> stu = new StudentImpl();
        stu.show("你好,我是张三");
    }

}

(2)使用泛型类实现泛型接口

当使用泛型类实现泛型接口时,需要将泛型的声明加在实现类中,并且泛型类和泛型接口使用的都是同一个类型形参变量,否则会出现编译异常。具体代码如下所示。

  • 定义一个泛型接口

public interface Student<T> {
    public abstract void show(T t);
}
  • 定义泛型接口的实现类,使用泛型类实现泛型接口

public class StudentImpl<T> implements Student<T> {

    @Override
    public void show(T t) {
        System.out.println(t);
    }
}
  • 定义测试类,创建Student对象时,传入不同的类型实参,并分别调用show()方法进行输出验证。

public class TestDemo {

    @Test
    public void test(){
        Student<String> stu1 = new StudentImpl<>();
        stu1.show("你好,我是张三");
        Student<Integer> stu2 = new StudentImpl<>();
        stu2.show(20);
    }

}

4. 泛型方法

4.1. 泛型方法的语法格式

泛型方法是将类型形参的声明放在修饰符和返回类型之间的方法。在Java程序中,定义泛型方法常用的格式如下所示:

public [static] [final] <类型形参> 返回值类型 方法名 (形式参数列表){
     
}

定义泛型方法注意事项如下所示:

  • 访问权限修饰符(包括private、public、protected)、static和 final都必须写在类型形参列表的前面。

  • 返回值类型必须写在类型形参列表的后面。

  • 泛型方法可以在泛型类中,也可以在普通类中。

  • 泛型类中的任何方法本质上都是泛型方法,所以在实际使用中很少会在泛型类中再用上面的形式来定义泛型方法。

  • 类型形参可以用在方法体中修饰局部变量,也可以修饰方法的返回值。

  • 泛型方法可以是实例方法(没有用static修饰,也叫非静态方法)也可以是静态方法。

4.2. 泛型方法的应用

如果泛型方法是实例方法,则需要使用对象名进行调用;如果泛型方法是静态方法,可以使用类名进行调用,泛型方法的两种使用方式。

  • 方式一

对象名|类名.<类型实参> 方法名(类型实参列表);
  • 方式二

对象名|类名.方法名(类型实参列表);

两种调用泛型方法的差别在于,方法名之前是否显式地指定了类型实参。调用时是否需要显式地指定了类型实参,要根据泛型方法的声明形式,以及调用时编译器能否从实际参数表中获得足够的类型信息决定,如果编译器能够根据实际参数推断出参数类型,就可以不指定类型实参,反之则需要指定类型实参。

4.3. 泛型方法的练习

下面通过一个案例,演示泛型方法的定义与使用,具体代码如下所示。

(1)定义Student类,在类中定义一个静态泛型方法和一个普通泛型方法。

public class Student {
    // 静态泛型方法
    public static <T> void show(T t) {
        System.out.println(t + ":" + t.getClass());
    }
    // 普通泛型方法
    public <T> void study(T t) {
        System.out.println(t + ":" + t.getClass());
    }
}

(2)定义测试方法,调用方法测试结果。

public class TestDemo {
    @Test
    public void test(){
        //静态方法
        Student.show("你好,我是张三");            // 使用方式一调用静态的泛型方法
        Student.<String>show("你好,我是张三");    // 使用方式二调用静态的泛型方法

        //普通方法
        Student stu = new Student();
        stu.study("好好学习");          // 使用方式一调用普通的泛型方法
        stu.<String>study("好好学习");  // 使用方式二调用普通的泛型方法
    }
}

从运行结果可以得出,泛型方法可以在非泛型类中定义,并且在调用泛型方法的时候确定泛型的具体类型 。上述结果中虽然使用方式一和方式二的输出结果一致,但是方式一隐式的传入类型实参,不能直观的查看到调用的方法是泛型方法,不利于代码的阅读和维护,通常建议使用第二种方式调用泛型方法。

5. 类型通配符

类型通配符使用一个问号(?)表示,类型通配符可以匹配任何类型的类型实参。

下面使用一个案例演示类型通配符的使用。

(1)定义泛型类Student,声明私有变量info,定义有参构造方法和getter方法。

/**
 * 定义泛型类Student
 * @param <T>
 */
public class Student<T> {
    private T info;

    public Student(T info) {
        this.info = info;
    }
    public T getInfo() {
        return info;
    }
}

(2)定义测试方法,创建Student对象,分别传入String类型和Integer类型的类型实参,进行测试。

public class TestDemo {
    @Test
    public void test(){
        // 创建student对象,传入String类型的类型实参
        Student<?> student = new Student<String>("张三");
        System.out.println( student.getInfo()+":"+student.getInfo().getClass());
        // 创建student对象,传入Integer类型的类型实参
        student =new Student<Integer>(20);
        System.out.println( student.getInfo()+":"+student.getInfo().getClass());
    }
}
  • 不使用通配符的情况一

如果创建Student对象时,不使用类型通配符,而是使用指定的类型实参,会出现编译异常,具体如下图所示。

  • 不使用通配符的情况二

使用Object代替类型通配符?接收所有的类型,也会出现编译异常,具体如下图所示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/195390.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL(免密登录)

简介: MySQL免密登录是一种允许用户在没有输入密码的情况下直接登录到MySQL服务器的配置。这通常是通过在登录时跳过密码验证来实现的。 1、修改MySQL的配置文件 使用vi /etc/my.cnf&#xff0c;添加到【mysqld】后面 skip-grant-tables #配置项告诉mysql跳过权限验证&#…

OpenCV快速入门【完结】:总目录——初窥计算机视觉

文章目录 前言目录1. OpenCV快速入门&#xff1a;初探2. OpenCV快速入门&#xff1a;像素操作和图像变换3. OpenCV快速入门&#xff1a;绘制图形、图像金字塔和感兴趣区域4. OpenCV快速入门&#xff1a;图像滤波与边缘检测5. OpenCV快速入门&#xff1a;图像形态学操作6. OpenC…

【计算机组成原理】存储系统

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理计算机组成原理中 存储系统的知识点和值得注意的地方 &#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以…

Docker可视化工具Portainer(轻量)或者Docker容器监控之 CAdvisor+InfluxDB+Granfana(重量)

Docker轻量级可视化工具Portainer 是什么 Portainer 是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 安装 官网 https://www.portainer.io/ https://docs.portainer.io/v/ce-2.9/start/instal…

【密码学】【安全多方计算】浅析隐私求交PSI

文章目录 隐私求交的定义隐私求交方案介绍1. 基于DH的PSI方案2. 基于OT的PSI方案3.基于OPRF的PSI方案 总结 隐私求交的定义 隐私集合求交使得持有数据参与方通过计算得到集合的交集数据&#xff0c;而不泄露任何交集以外的数据信息。 隐私求交方案介绍 1. 基于DH的PSI方案 …

如何通过内网穿透实现公网远程ssh连接kali系统

文章目录 1. 启动kali ssh 服务2. kali 安装cpolar 内网穿透3. 配置kali ssh公网地址4. 远程连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 简单几步通过[cpolar 内网穿透](cpolar官网-安全的内网穿透工具 | 无需公网ip | 远程访问 | 搭建网站)软件实现ssh远程连接kali 1…

docker安装Sentinel

文章目录 引言I Sentinel安装1.1 运行容器1.2 DOCKERFILE 参考1.3 pom 依赖1.4 .yml配置(整合springboot)II 资源保护2.1 Feign整合Sentinel2.2 CommonExceptionAdvice:限流异常处理类引言 I Sentinel安装 Sentinel 分为两个部分: 核心库(Java 客户端)不依赖任何框架/库,能…

了解静态测试?

静态测试是一种软件测试方法&#xff0c;它主要通过分析软件或代码的静态属性来检查潜在的问题和缺陷&#xff0c;而无需实际执行程序。这种测试方法侧重于检查源代码和其他软件文档&#xff0c;以发现错误并提高软件质量。 为什么要做静态测试&#xff1f; 提前发现和修复错…

ESP32-Web-Server编程-CSS 基础 2

ESP32-Web-Server编程-CSS 基础 2 概述 如上节所述&#xff0c;可以使用外部 CSS 文件来修饰指定的 HTML 文件。 外部引用 - 使用外部 CSS 文件。 当样式需要被应用到很多页面的时候&#xff0c;外部样式表将是理想的选择。使用外部样式表&#xff0c;就可以通过更改一个文件…

Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/134561660 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

Vue 实现低代码开发平台,没想到这么好用!

前言 在众多开发技术中&#xff0c;Vue 组件化开发技术以其卓越的灵活性和高效性备受瞩目。 低代码平台相信不少人知道它的存在&#xff0c;而且现在大部分公司都在开发自己的低代码平台&#xff0c;首先我们来看看低代码平台可视化界面&#xff1a; 官网&#xff1a;https:/…

MySQL中自增id用完怎么办?

MySQL中自增id用完怎么办&#xff1f; MySQL里有很多自增的id&#xff0c;每个自增id都是定义了初始值&#xff0c;然后不停地往上加步长。虽然自然数是没有上限的&#xff0c;但是在计算机里&#xff0c;只要定义了表示这个数的字节长度&#xff0c;那它就有上限。比如&#…

第二十章,多线程

创建线程 有两种方式&#xff0c;分别为继承Java.lang.Thread类与实现Java.lang.Runnable接口 继承Thread类 Thread常用的两个构造方法语法 public Thread&#xff08;&#xff09;&#xff1b; public Thread&#xff08;String threadName&#xff09;&#xff1b; 继承…

新生儿腺体肥大:原因、科普和注意事项

引言&#xff1a; 新生儿的健康问题常常让父母感到焦虑&#xff0c;其中之一就是腺体肥大。了解腺体肥大的原因、科普相关知识&#xff0c;并采取一些建议的注意事项&#xff0c;对于保障新生儿的健康非常重要。本文将深入解析新生儿腺体肥大的原因、提供相关科普知识&#xf…

可移动框 弹窗 可拖拽的组件

电脑端: <template><divv-if"show"ref"infoBox"mousedown.stop"mouseDownHandler"class"info-box":style"styleObject"><slot></slot></div> </template> <script> export defa…

自媒体爆文采集工具,几个免费的网站让你每日爆文增加

随着自媒体的蓬勃发展&#xff0c;许多人憧憬着在这个领域获得成功和流行。然而&#xff0c;随着寒冷的冬天的降临&#xff0c;媒体从业者的日常生活并没有变得更加美好。在竞争激烈的环境中&#xff0c;为了生存&#xff0c;他们必须发布引人注目的内容&#xff0c;然而&#…

4个Python实战项目,让你瞬间读懂Python!

前言 Python 是一种极具可读性和通用性的编程语言。Python 这个名字的灵感来自于英国喜剧团体 Monty Python&#xff0c;它的开发团队有一个重要的基础目标&#xff0c;就是使语言使用起来很有趣。Python 易于设置&#xff0c;并且是用相对直接的风格来编写&#xff0c;对错误…

从裸机启动开始运行一个C++程序(十五)

前序文章请看&#xff1a; 从裸机启动开始运行一个C程序&#xff08;十四&#xff09; 从裸机启动开始运行一个C程序&#xff08;十三&#xff09; 从裸机启动开始运行一个C程序&#xff08;十二&#xff09; 从裸机启动开始运行一个C程序&#xff08;十一&#xff09; 从裸机启…

lightdb-ignore_row_on_dupkey_index

LightDB 支持 ignore_row_on_dupkey_index hint LightDB 从23.4 开始支持oracle的 ignore_row_on_dupkey_index hint&#xff0c; 这个hint是用来忽略唯一键冲突的。类似与mysql的 insert ignore。 语法如下&#xff1a; 在LightDB中ignore_row_on_dupkey_index的效果等同于o…

大坝安全监测的内容及作用

大坝安全监测是指对大坝水雨情沉降、倾斜、渗压以及大坝形状特征有效地进行监测&#xff0c;及时发现潜在的安全隐患和异常情况&#xff0c;以便大坝管理人员能够做出科学决策&#xff0c;以确保大坝安全稳定运行。 大坝安全监测的主要内容 1.表面位移监测&#xff1a;监测大坝…