Effective Java学习笔记第27、28条原生态类型和非受检警告

news2024/9/25 11:12:05

目录

什么是泛型

泛型与编译器

不要轻易使用原生态类型

可以通过通配符类型来替代原生态类型

几个适合原生态类型的场景

消除非受检的警告

什么是非受检警告

如果无法消除警告


本书27-33条主要介绍泛型。首先介绍什么是泛型,它的应用场景是什么。然后重点介绍27和28条对于泛型编程的两点意见。一是不要轻易使用原生态类型,二是尽量消除非受检警告。

什么是泛型

顾名思义“泛型”指的是“多种类型”,声明中具有一种或多种类型参数的类或者接口成为泛型类或者泛型接口,这两者统称为泛型。常见的泛型接口包括List<T>和Set<T>。

泛型与编译器

首先要明确一个概念,泛型的受用范围仅限于编译阶段,编译完成后通过类型擦除进入执行的程序是不带有泛型信息的,下面举一个例子:

//定义一个简单的泛型
public class Myclass<T> {
    private T t;

    public void setT(T t) {
        this.t = t;
    }

    public T getT() {
        return t;
    }
}

通过javac将Myclass类编译后,由于类型擦除的原因当你将其反编译后相关的泛型信息会自动被替换成对应的实际类型,以下是通过javap反编译后的内容:

public class Myclass<T> {
  public Myclass();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void setT(T);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #7                  // Field t:Ljava/lang/Object;
       5: return

  public T getT();
    Code:
       0: aload_0
       1: getfield      #7                  // Field t:Ljava/lang/Object;
       4: areturn
}

这里可以看到,泛型类中的两个泛型方法public void setT(T)和public T get()的输入和输出域原来的泛型T表示都被改换成了Ljava/lang/Object;(这里有的朋友会问为什么从还原的源码中仍然能够看到泛型,那是因为尽管类型擦除移除了泛型的具体类型信息,Java 编译器仍然会在字节码中添加一些额外的信息来支持运行时的类型安全性和泛型的正确使用。而反编译器会利用这些标注自动还原出原有的泛型信息)。

那么在编译阶段编译器会对于泛型做哪些操作来确保类型的安全性和正确性呢?

首先是类型检查一是类型的兼容性检查,编译器会检查泛型类型的使用是否符合类型兼容性规则。例如,确保泛型参数的使用与上下文中的类型相匹配。二是类型参数限制,如果泛型参数有类型界限(如 T extends Number),编译器会确保传递给泛型参数的实际类型符合这些界限。

其次是类型推断:编译器能够自动推断出泛型参数的具体类型,这使得开发者在某些情况下不必显式指定泛型参数的类型。比如对于泛型的初始化:

List<String> stringlist = new ArrayList<>();

这里ArrayList<>的初始化就不需要指定对应类型,编译器会自动推断。

类型擦除:编译器在编译阶段会去除泛型类型信息,将泛型类型转换为其对应的原始类型(例如,List<T> 变为 List)。

插入类型转换:为了确保类型安全,编译器会在适当的位置插入必要的类型转换语句。比如要在一个List<Object>中插入一个Integer,编译器就会讲Integer类型强制转换成Object。

了解了编译器对于泛型进行的各种操作,就可以很好的理解书中的27、28两条。

不要轻易使用原生态类型

原生态类型即隐去了泛型参数信息的类或者接口,比如List是List<String>的原生态类型。这里需要强调的是原生态类型是对应泛型的超类。

很好理解,使用原生态类型以后,类或者接口失去了泛型的属性,自然而然就会绕过编译器的各种类型检测功能,也就失去了泛型在安全性和描述性上所有的优势。也就是说如果程序错误插入了一个不同类型的元素,这个错误会一直保留到运行中当调用这个元素的方法出现问题的时候才会暴露问题;另一方面,编译器无法进行自动的类型转换,因此如果有相关的需求需要靠手动转换。

下面给一个使用了原生态类型的案例,这个原本计划只有Integer的List中混入了String类型,虽然编译器会给出Application.java uses unchecked or unsafe operations.的告警,但是并未显示任何实质性的警告,只有当程序运行中调用到“30”这个元素时才会真正显示错误;另一方面由于编译器未进行插入类型转换,因此对于每一个元素都要进行Integer的强制类型转换,否则系统默认会当作Object来处理。

public class Application {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(21);
        list.add(23);
        list.add("30");
        for(Iterator i = list.iterator(); i.hasNext();){
            System.out.println((Integer)i.next()+1);
        }

    }
}

//编译警告
(base) chapter5$ javac Application.java
Note: Application.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

//运行异常
(base) chapter5$ java Application
22
24
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')

可以通过通配符类型来替代原生态类型

我们已经知道了原生态类型是有缺陷的,那当需要表示无限制类型的泛型时要如果实现呢?答案是无限制的通配符类型。

通配符类型是泛型功能的扩展,泛型将类和接口的应用类型设置为参数,而通配符就是允许把这个参数设置为一个集合。

public class WildcardDemo {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = new ArrayList<>();
        
        // 使用上界通配符
        printNumbers(intList);
        printNumbers(numList);
        
        // 使用下界通配符
        addElement(intList, 100); // intList 是 Number 的子类型
        addElement(numList, 100); // numList 本身就是 Number 类型
    }

    public static void printNumbers(List<? extends Number> list) {
        for (Number n : list) {
            System.out.println(n);
        }
    }

    public static <T extends Comparable<T>> void addElement(List<? super T> list, T element) {
        list.add(element);
    }
}

这里printNumbers方法的输入参数是泛型接口List的上界通配符类型,代表该方法可以输入的泛型的类型参数可以是Number的子类。

这里要注意对于无限制通配符类型<?>,不能将任何元素(除null以外)放入,因为编译器无法确定相关元素是否符合类型要求(对于迭代器来说泛型的类型要求是一个未知类型)。

几个适合原生态类型的场景

在类文字中使用原生态类型:类文字是表示类的 Class 对象的一种特殊文字。List.class是合法的,而List<?>.class不合法。

通过instanceof操作符判断类型:用参数化类型使用instanceof操作符是非法的,但是无限制性通配符和原生态类型都是合法的。

消除非受检的警告

什么是非受检警告

首先警告是编译器在编译过程中发现的“异常”,编译过程中会有警告和错误两种“异常”,其中警告是对于运行过程中可能存在问题的提醒,但不影响编译的正常进行,而一旦存在错误将会提前终止编译。下面总结一下编译器的“异常”和程序运行过程中的“异常”。

非受检警告如果被无视就有可能在程序运行中出现各种异常或者错误情况,所以这也是为什么非受检异常无必要消除的原因(另外的弃用警告和未使用变量警告的影响会小一些,但最好还是也注意消除一下)。

如果无法消除警告

如果无法消除,同时证明引起警告的代码是类型安全的,才可以使用一个@SupressWarnings(“unchecked”)注解来禁止这条警告。

@SupressWarnings(“unchecked”)注解可以用在任何粒度的级别中,从单独的局部变量声明到整个类都可以,但是请尽量在尽可能小的范围内使用,否则可能会掩盖重要的警告(永远不要直接在类上使用)。

注意每次使用该注解时,都要添加一条注释,说明这么做为什么是安全的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序项目-宠物商城项目uniapp源码和代码讲解

&#x1f939;‍♀️潜意识起点&#xff1a;个人主页 &#x1f399;座右铭&#xff1a;得之坦然&#xff0c;失之淡然。 &#x1f48e;擅长领域&#xff1a;大前端 是的&#xff0c;我需要您的&#xff1a; &#x1f9e1;点赞❤️关注&#x1f499;收藏&#x1f49b; 是我…

3D渲染优化:视锥体剔除算法

现在我们知道如何创建场景图并在场景中组织对象&#xff0c;我们将了解如何通过技术“视锥剔除”来限制 GPU 的使用。 这种技术很容易理解。你无需将所有信息发送到 GPU&#xff0c;而是对可见和不可见元素进行排序&#xff0c;并仅渲染可见元素。借助这种技术&#xff0c;你将…

Matlab|考虑充电负荷空间可调度特性的分布式电源与电动汽车充电站联合配置方法

目录 1 主要内容 目标函数 电动汽车负荷建模 算例系统图 程序亮点 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序复现博士文章《互动环境下分布式电源与电动汽车充电站的优化配置方法研究》第四章《考虑充电负荷空间可调度特性的分布式电源与电动汽车充电站联合…

Baklib:强大的知识库创建工具

Baklib&#xff1a;强大的知识库创建工具 在信息管理和知识分享的数字化时代&#xff0c;Baklib 作为一款功能强大的知识库创建工具&#xff0c;为用户提供了全面的解决方案。本文将介绍 Baklib 的基本信息、特点&#xff0c;以及如何快速部署和使用。 软件简介 Baklib 是一款…

第二十节、有限状态机和抽象类多态

一、抽象类 挂载到动画器上的就是继承抽象类代码 1、使用onenable周期函数启用 2、在每一个周期函数中对抽象类进行调用 3、隐藏公开的变量

[Python学习日记-4] Python中的变量

[Python学习日记-4] Python中的变量 简介 变量的运行原理 变量的使用规则 简介 在Python中&#xff0c;变量是一个具有名称的存储位置&#xff0c;用于存储数据。它们被用来在程序中引用和操作数据。变量在使用前需要先进行声明或赋值&#xff0c;Python是一种动态类型语言&…

Linux应用层开发(7):网络编程

互联网对人类社会产生的巨大变革&#xff0c;大家是有目共睹的&#xff0c;它几乎改变了人类生活的方方面面。互联网通信的本质是数字通信&#xff0c;任何数字通信都离不开通信协议的制定&#xff0c;通信设备只有按照约定的、统一的方式去封装和解析信息&#xff0c;才能实现…

043字符串相乘

题意 给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 注意&#xff1a;不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 1 < num1.length, num2.length < 200 难度 中…

Python Dash 一个可以玩转AI的可视化利器

很多人提到Tableau、Power BI等老牌可视化工具&#xff0c;这些工具确实引领了可视化的风潮&#xff0c;有开疆拓土之功。 但这次我要提名一个有黑马潜质的可视化工具-Dash&#xff0c;在某些地方比Tableau、PowerBI更胜一筹。 Dash是一个基于web的Python工具包&#xff0c;所…

[io]进程间通信 -信号函数 —信号处理过程

sighandler_t signal(int signum, sighandler_t handler); 功能&#xff1a; 信号处理函数 参数&#xff1a; signum&#xff1a;要处理的信号 handler&#xff1a;信号处理方式 SIG_IGN&#xff1a;忽略信号 SIG_DFL&#xff1a;执行默认操作 handler&#xff1a;捕捉信 …

mysql数据存储问题

目录 MySQL数据存储基础 MySQL数据存放位置 InnoDB存储引擎介绍 Mermaid图表&#xff1a;InnoDB存储引擎数据文件结构 表空间结构详解 组成要素 组织方式 页内组织 性能影响 Mermaid图表&#xff1a;表空间的层次化结构和页内组织 InnoDB行格式详解 行格式类型 Co…

Linux 操作系统:基于环形队列的生产者消费者模型

Linux 操作系统&#xff1a;基于环形队列的生产者消费者模型 一、前言二、大致框架二、P操作、V操作三、生产者生产数据四、生产者获取数据五、代码测试六、所有代码 一、前言 环形队列采用数组模拟&#xff0c;用模运算来模拟环状特性。和基于阻塞队列的生产者消费者模型不同的…

WPF篇(11)-ToolTip控件(提示工具)+Popup弹出窗口

ToolTip控件 ToolTip控件继承于ContentControl&#xff0c;它不能有逻辑或视觉父级&#xff0c;意思是说它不能以控件的形式实例化&#xff0c;它必须依附于某个控件。因为它的功能被设计成提示信息&#xff0c;当鼠标移动到某个控件上方时&#xff0c;悬停一会儿&#xff0c;…

【云存储】SDS软件定义存储,数据存储的类型与技术方案(块/文件/对象,Ceph、RBD等)

【云存储】SDS软件定义存储&#xff0c;数据存储的类型与技术方案&#xff08;块/文件/对象&#xff0c;Ceph、RBD等&#xff09; 文章目录 1、分布式存储架构&#xff08;软件定义存储SDS&#xff0c;超融合基础架构HCI&#xff09;2、存储类型&#xff08;块存储&#xff0c;…

SQL面试题练习 —— 用户行为路径分析

目录 1 题目2 建表语句3 题解 题目来源&#xff1a;拼多多。 1 题目 有一张用户行为日志表 ods_usr_log, 包含用户id&#xff08;user_id&#xff09;和页面id&#xff08;page_id&#xff09;以及进入页面时间&#xff08;in_ts&#xff09; 问题&#xff1a;统计每天进入A页…

【SpringMVC】SpringMVC实现文件上传和下载

目录 1.文件上传 2.文件下载 1.文件上传 大概的图如下所示&#xff1a; 客户端&#xff1a; 文件上传就是把客户端的文件上传到服务端进行保存。在文件上传时文件和其他请求参数是在 请求体中进行传递。所以不支持 GET 类型请求。实现文件上传&#xff0c;需要提供一个上传的…

状态压缩动态规划——状压dp

状压dp&#xff1a;意思是将状态进行压缩&#xff0c;从而更容易地写出状态转移方程 通常做法&#xff1a;将每个状态&#xff08;一个集合&#xff09;用二进制表示&#xff0c;每个位的1就代表着这个编号的元素存在&#xff0c;0就代表着这个编号的元素不存在&#xff0c;如…

【Python】练习题附带答案

1、使用for循环实现输出9*9乘法表 代码&#xff1a; 2、写代码实现累乘计算器。 示例&#xff1a;用户输入&#xff1a;5*9*87输出答案&#xff1a;3915 代码&#xff1a; 3、写代码实现&#xff0c;循环提示用户输入的内容&#xff08;Q/q终止循环&#xff09;&#xff0c;…

黑马Java零基础视频教程精华部分_18_Arrays各种方法

系列文章目录 文章目录 系列文章目录Arrays简介Arrays各种方法toString代码示例binarySearch代码示例copyOf代码示例copyOfRange和fill代码示例sort代码示例 Arrays简介 操作数组的工具类。 Arrays各种方法 toString代码示例 int[]arr{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //to…

单片机IO灌入5V电压导致其他IO电压测量到大于供电电压问题

最近用GD32F103RCT6做项目&#xff0c;用了3个485收发器&#xff0c;都是直接接在单片机IO上的。 485收发器是5V供电的&#xff0c;这个时候就出现5V电平和3.3V电平兼容的问题了。 一开始只用了PA10、PC11这两个串口&#xff0c;他俩是兼容5V的&#xff0c;从手册可以看出IO最…