编程语言历史:聊聊Java最失败的设计:“==”

news2024/9/29 15:35:19

聊聊Java最失败的设计:==

Java以其强大的生态系统、丰富的库支持以及跨平台的能力而闻名。但是Java有许多设计在今天看来已经不合时宜,其中最失败的设计非操作符==莫属。我们今天一起探讨==操作符的来龙去脉,失败在哪,为什么会失败的问题。

首先,我们先了解==操作符的基本概念。在Java中,==操作符主要用于比较两个变量或表达式是否相等。对于基本数据类型(如int、char等),它直接比较值是否相同。当涉及到对象时,==操作符比较的是两个对象的引用是否指向内存中的同一个位置,而不是这两个对象的内容是否相等。

字符串比较中的陷阱

一个典型的例子就是字符串的比较。假设你有两个字符串,如果和其他语言一样,习惯性地使用==来判断它们是否相等。但这种做法往往会带来错误的结果。比如下面这段代码:

String a = "hello";
String b = "hello";
String c = new String("hello");

System.out.println(a == b); // 输出 true
System.out.println(a == c); // 输出 false

即使abc都包含了相同的字符序列“hello”,但ab是通过字符串字面量定义的,因此它们共享同一个内存位置,所以a == b返回true。而c是通过new关键字创建的新对象,虽然它的内容与ab相同,但由于它位于不同的内存位置,因此a == c返回false

正确的做法应该是使用equals()方法来比较字符串的内容:

System.out.println(a.equals(c)); // 输出 true

性能优化和==结合后的坑

当两个Integer对象的值在-128到127之间时,Java为了节省内存会重用这些对象,所以在这个范围内,使用==来比较也会返回true。但是,当数值超出这个范围时,即使数值相同,由于每个对象都是独立创建的,==比较也会返回false

Integer a = 100; // 自动装箱
Integer b = 100;
System.out.println(a == b); // true, 因为在-128至127之间的值会被重用

Integer c = 1000; // 自动装箱
Integer d = 1000;
System.out.println(c == d); // false, 每个对象都在堆上单独创建

在这个例子中,ab都表示数字100,但它们是否相等取决于它们的值是否在-128到127之间。如果是的话,a == b会返回true,因为Java对这个区间内的整数进行了缓存。然而,如果值超出了这个范围,即使它们的值相同,==也会返回false

最佳实践

为了避免这些问题,我们一般遵循的一些最佳实践包括:

  1. 使用equals()方法:当你想比较两个对象的内容是否相等时,应该使用equals()方法,而不是==
  2. 注意自动装箱的细节:对于包装类对象,要特别留意自动装箱的机制,特别是在进行对象引用比较的时候。
  3. 保持意图明确:编写代码时要尽量清晰地表达你的意图,比如使用Objects.equals()方法来处理可能为null的对象,使代码更安全、更易读。
  4. 编写单元测试:编写单元测试以验证逻辑,确保在各种情况下都能正确地比较对象。

JVM中其他语言纷纷改进了==的语义

Kotlin:智能的==比较

Kotlin是近年来非常受欢迎的一种现代编程语言,它直接运行在JVM上,并且可以与Java无缝集成。Kotlin的设计者们意识到了==操作符在Java中的局限性,并对其进行了改进。在Kotlin中,==操作符默认是比较对象的内容,而不是引用。因此,当你使用==来比较两个字符串或其他对象时,它实际上调用的是equals()方法来进行比较,而不是简单的引用比较。而Kotlin给予===严格相等的含义,这个设计与Java的==相似,但又不完全一样。

例如,在Kotlin中:

val a: String = "hello"
val b: String = "hello"
val c = "hello"

println(a == b) // 输出 true
println(a == c) // 输出 true

这里的ab是通过不同的方式初始化的,但是它们的内容是一样的,因此a == b返回true。同样,ac也是内容相同的不同对象,所以a == c同样返回true

Groovy:灵活的==比较

Groovy是另一种在JVM上运行的动态语言,它也提供了一个更加灵活的==操作符实现。在Groovy中,默认情况下==操作符的行为类似于Java,但是Groovy允许用户通过元编程技术来自定义对象的比较行为。或者覆盖对象的equals()方法,因为当使用==比较对象时,Groovy会调用equals()方法来确定两个对象是否相等。如果需要改变默认的行为,可以在类中覆盖equals()方法。
这使得开发者可以根据自己的需求来定制==操作符的行为,使其更加符合业务逻辑的需求。

Scala:多重比较机制

Scala是一种兼具面向对象和函数式编程特性的语言,它在JVM上运行并且提供了多种比较机制。Scala中,默认情况下==操作符用于比较对象的内容,与Kotlin类似。此外,Scala还提供了eq方法来专门比较对象的引用。

例如:

val a: String = "hello"
val b: String = "hello"
val c = new String("hello")

println(a == b) // 输出 true
println(a == c) // 输出 false
println(a eq b) // 输出 true
println(a eq c) // 输出 false

在这里,==用于比较内容,而eq用于比较引用,这样可以更加清晰地区分两种不同的比较方式。

当时为什么Java当时要这样设计?

Java作为一种面向对象的编程语言,自诞生之初就旨在提供一种统一的、跨平台的编程模型。在Java的设计初期,很多决策都是基于当时的技术环境和对未来发展的预期。对于==操作符的设计,其初衷是为了提供一种快速且直观的方式来比较对象的引用。这种设计背后有几个重要的考虑因素:

受C/C++影响

Java的设计受到C/C++的影响较大,这两种语言中的==操作符都用来比较值或引用,而不是内容。继承这一设计可以让熟悉C/C++的开发者更容易过渡到Java。这也是Java这样设计的直接原因
但C++与Java完全不同,C++开发者经常需要处理引用和值的区别,而且C++有强大的运算符重载能力,没有自动装拆箱,很好地规避了这个设计带来的缺陷。

性能和观念影响

在Java语言最初设计的那个年代,计算资源相对有限,性能优化尤为重要。直接比较对象引用的速度远远快于比较对象的内容。这是Java设计者鼓励开发者多用==来提高性能。而将比较内容的运算符设计为===

明确语义一致性

Java的设计者希望为程序员提供明确的语义边界。如果==操作符默认比较对象的内容,那么程序员需要时刻警惕哪些地方需要比较引用,哪些地方需要比较内容。这种混淆会导致更多的错误。因此,Java选择将==固定为引用比较,并推荐使用equals()方法来比较内容,这种区分有助于减少歧义。

Java==设计回顾

虽然Java在==操作符的设计上有其历史背景和技术考量,但这一设计在某些情况下显得不再那么理想。现代编程更注重避免误用,代码更符合人的直觉,因此Java的这个设计原则已经不符合现代要求了。

总结

通过观察JVM上的其他语言对==操作符的改进,我们可以看到,许多现代语言倾向于让==默认比较对象的内容,而不是引用。这样的设计更加符合开发者的直觉,减少了因误解==操作符而引入的bug。同时,这些语言也提供了额外的方法来比较对象的引用,使得开发者可以在需要的时候进行精确控制。

总结来说,==操作符的设计在Java中确实存在不合理之处。因此,在学习和使用Java时,深刻理解其设计的缘由和缺陷,可以有效地帮助我们在实践中规避许多问题,解答许多疑惑,进而更好地掌握Java并写出更加健壮和易于维护的代码。

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

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

相关文章

本地IO与远程IO:揭秘工业自动化中的两大关键角色

在工业自动化领域,IO(Input/Output,输入/输出)模块扮演着至关重要的角色。它们作为连接控制系统与现场设备的桥梁,负责数据的采集与指令的执行。然而,随着技术的不断进步,IO模块也分为本地IO和远…

【百度文心智能体】想开发爆款智能体?来看看黑神话旅游指南 智能体开发流程大揭秘

🎬 博主:鸽芷咕 ⛺️生活的理想,就是为了理想的生活! 前言 2022年到2023年整年度随着 ChatGPT的爆火,“AI 人工智能 智能助手”概念开始引起各行各业的广泛关注,一些曾经存在于科幻片中的智能助手,到如今也…

探索SpringBoot:学科竞赛管理项目开发

2 相关技术简介 2.1Java技术 Java是一种非常常用的编程语言,在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中,Java的身影无处不在,并且拥有旺盛的生命力。Java的跨平台能力十分强大,只需一次编译,任…

从零预训练一个tiny-llama#Datawhale组队学习Task2

完整的教程请参考:datawhalechina/tiny-universe: 《大模型白盒子构建指南》:一个全手搓的Tiny-Universe (github.com) 这是Task2的学习任务 目录 Qwen-blog Tokenizer(分词器) Embedding(嵌入) RMS …

Wed前端--HTML基础

目录 一、开发工具 二、HTML文档结构 2.1头部head 2.1.1title标记 2.1.2元信息meta标记 具体实例 ​编辑 一、开发工具 最基础的开发工具是:HBuilder 二、HTML文档结构 HTML文档由头部head和主体body组成 头部head标记中可以定义标题样式,头部信…

VR视频怎样进行加密和一机一码的使用?--加密(一)

在视频加密领域,我们常见接触的就是在普通设备上使用的加密视频,如电脑、手机、平板等。Vr的发展和兴起给人们带来最真实的体验感受,不仅在游戏行业应用较广,在一些影院或者元宇宙文旅、展厅等视频场景也备受青睐。 随着VR视频场景…

django的URL配置

1 django如何处理一个请求 首先Django要使用根URLconf模块,通过setting.py配置文件的ROOT_URLCONF来设置。 加载该模块后并查找变量 urlpatterns。这是一个Python的django.conf.urls.url()实例列表。 Django按顺序运行每个URL模式,并在匹配所请求的…

Java项目实战II基于Java+Spring Boot+MySQL的智能物流管理系统(文档+源码+数据库)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者 一、前言 随着电子商务的蓬勃发展,物流行业迎来了前所未有的挑战与机遇。传统物流管理方式在应对海…

Acwing 快速幂

1.快速幂 作用:可以快速求出 a k m o d p a^k mod p akmodp的值,时间复杂度是 O ( l o g k ) . O( log k). O(logk). 核心思路:反复平方法 ①预处理出: a 2 0 m o d p 、 a 2 1 m o d p 、 a 2 2 m o d p 、 … 、 a 2 log ⁡ 2…

IOT-research虚拟机的中文语言设置

首先在setting(设置)中找到Region & Langguage 在Input Source中添加Chinese ubuntu 卡在waiting for unattended-upgr to exit的解决 sudo rm /var/cache/apt/archives/lock sudo rm /var/lib/dpkg/lock sudo rm /var/lib/dpkg/lock-frontend …

数据库管理-第245期 主流国产数据库RAC架构概览(20240929)

数据库管理245期 2024-09-29 数据库管理-第245期 主流国产数据库RAC架构概览(20240929)1 DMDSC2 KingBaseES RAC3 PolarDB4 Cantian5 HaloDB DLB/Data Sharding总结 数据库管理-第245期 主流国产数据库RAC架构概览(20240929) 作者…

HDFS不会自动退出安全模式问题

问题说明 Hadoop集群启动之后,HDFS进入了安全模式,并且不会自动退出,提示信息如下 Safe mode is ON. The reported blocks 1223 needs additional 3 blocks to reach the threshold 0.9990 of total blocks 1228. The minimum number of …

探索基于知识图谱和 ChatGPT 结合制造服务推荐前沿

0.概述 论文地址:https://arxiv.org/abs/2404.06571 本研究探讨了制造系统集成商如何构建知识图谱来识别新的制造合作伙伴,并通过供应链多样化来降低风险。它提出了一种使用制造服务知识图谱(MSKG)提高 ChatGPT 响应准确性和完整…

[Python学习日记-32] Python 中的函数的返回值与作用域

[Python学习日记-32] Python 中的函数的返回值与作用域 简介 返回值 作用域 简介 在函数的介绍中我们提到了函数的返回值,当时只是做了简单的介绍,下面我们将会进行详细的介绍和演示,同时也会讲一下 Python 中的作用域,作用域分…

fmql之Linux中断

中断 下半部机制 软中断 softirq_action tasklet 工作队列 设备树 fmql: 代码 目的 使能key对应GPIO的中断,中断服务函数为使用定时器延时15ms;定时器处理函数为检测key的状态 设备树修改 fmql不用把system.dtb放到SD卡。修改设备树后要在…

【RocketMQ】初识

基础概念 Message(消息):Message 是 RocketMQ 传输的基本单元,包含了具体的业务数据以及一些元数据(如消息 ID、主题、标签、发送时间等)。消息可以是文本、二进制数据或其他任何序列化后的对象形式。Topi…

MDIO Frame介绍

在MII管理界面上传输的框架应具有表22-10所示的框架结构。位传输顺序从左到右。 IDLE (IDLE condition) MDIO上的空闲条件是高阻抗状态。所有三个状态驱动器都应被禁用,而PHY的上拉电阻器将把MDIO线拉到一个逻辑线上。 PRE (preamble) 32位前导码,都是 1 ST (start of frame…

基于Springmvc的网上书城的设计与实现

文未可获取一份本项目的java源码和数据库参考 选题意义: 网上书城是以当前商务的网络化、快速化实际需求为背景,实现图书购买的方便、快捷、送货上门等服务为前提综合信息服务系统的设计;实现通过Internet互联网对图书购买的相关信息进行发…

jvm专题 之 内存模型

文章目录 前言一个java对象的运行过程jvm内存分布程序的基本运行程序对象什么是对象对象的创建一、类加载检查二、对象内存分配三、初始化零值四、设置对象头五、执行初始化方法 对象的访问定位 对象与类的关系由类创建对象的顺序 对象的创建 前言 一个程序需要运行&#xff0…

编程语言图书创作要注意的事情有哪些?

编程语言图书的创作是一项复杂且具有挑战性的任务,需要作者深入理解技术、清晰表达,并考虑读者的学习体验。一本优秀的编程书籍不仅能够教授技术知识,更能引导读者逐步深入,激发他们的思考和实际应用能力。以下将详细探讨编程语言…