Java 项目学习指南(一)

Java Spring Boot 全面学习指南 通过系统学习官方文档、经典书籍与实战课程,结合开源项目实践,能够快速掌握 Spring Boot 的核心能力。 阅读详情

原文:zh.annas-archive.org/md5/213b34f3b33252c99b2c0bd8aff6356f

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎来到 Java 编程的世界!Java 是世界上最多才多艺和最广泛使用的编程语言之一。它的平台独立性、面向对象的特点以及广泛的库支持使其成为开发各种应用程序的理想选择,从桌面到移动和企业解决方案。

无论你是渴望学习基础知识的初学者,还是寻求提升技能的经验丰富的开发者,这本书都是为了帮助你掌握 Java 编程语言而设计的全面指南。我们从基础知识开始:如何设置编辑器;原始数据类型;然后系统地进步到更高级的概念,如 lambda 表达式、流和并发。

这本书不仅仅是一本指南;它是你在掌握 Java 旅程上的伴侣。我们的目标是让这段旅程既愉快又有效。本书采用了一种动手实践的方法,将理论概念与大量的实践练习和综合项目相结合。无论你是自学成才者还是正规教育项目的一部分,动手练习和综合项目都将帮助你巩固知识,使学习体验既吸引人又实用。

实践出真知 在掌握任何编程语言中至关重要,我们在本书中对此深信不疑。在每个章节的结尾,你将有一些练习和一个项目,帮助你获得一些 Java 编程的经验。练习通常是较小的任务,而项目则稍微大一些。在所有这些中,你将有相当大的自由度来选择如何具体实现。为什么?因为这就是现实生活中的样子!我们将提供一些示例解决方案,但如果我们的解决方案与我们的不同,这并不意味着你的解决方案不好。如果你有疑问,可以询问一些 AI 助手,比如 ChatGPT,看看他们认为你的解决方案如何。还是不清楚?我们随时愿意帮助你!

好吧,回到练习和项目上来。我们希望为我们的练习和项目选择一个共同的主题。信不信由你,但我们的其中一位作者(提示:是那位女性)在写书的时候被微型巨型史前爬行动物复制品包围着。事实上,现在打字都很困难,因为这只巨大的电池供电的霸王龙试图摧毁我的笔记本电脑。

我不禁从中获得了不少灵感。所以,是的,所有的练习和项目都将围绕恐龙主题,基于我 5 岁儿子收集和生动的幻想游戏。 (我通常在 5 点到 6 点之间醒来。不是被闹钟叫醒的,当然也不是因为我加入了 5 点起床俱乐部。不,是有一个(最神奇)兴奋的孩子在告诉我关于恐龙的有趣事实。不妨把这种知识用起来!)

因此,这是你的背景:恭喜你,你被录用了!你现在正在为我们特殊的恐龙动物园:中生代伊甸园工作。这是一个史前荒野与现代舒适的独特结合,人类和恐龙在这里共存。人们可以在这里参观一天,或者在这里露营几周。

在中生代伊甸园,我们拥有丰富的恐龙物种,每种都有其独特的行为和生活方式,从庞大的雷龙到敏捷的迅猛龙,威严的霸王龙,以及许多其他恐龙。我们甚至有一个最先进的实验室,我们在这里继续发现和研究新的恐龙品种。

作为我们团队的一员,你的角色不仅是要照顾这些雄伟的生物并确保它们的健康,还要维护我们客人的安全和安保。我们的公园采用尖端技术和严格的协议,以确保为所有人提供一个安全的环境。

在练习和项目中,你将作为中生代伊甸园的一名员工承担各种软件开发任务,从编写喂食计划的软件到处理紧急警报的应用程序,确保公园运营顺利,最重要的是,为我们的游客创造难忘的体验。

这本书面向的对象

这本书适合想要开始编程之旅的初学者,以及正在转向 Java 的资深开发者。无论你是学生还是专业人士,内容都旨在满足不同受众的需求。

如果你对 Java 8 OCA Oracle 认证感兴趣,那么这本书非常有帮助,因为它通过深入内存来解释正在发生的事情,涵盖了众多重要的基本概念。两位作者都是 Oracle OCP 认证的,这并非巧合。

这本书涵盖的内容

第一章,Java 入门,首先讨论了 Java 的主要特性,如面向对象编程。还探讨了如何在各种操作系统上安装 Java,以及如何使用 IDE 或不使用 IDE 编写你的第一个 Java 程序。

第二章,变量和原始数据类型,解释了什么是变量,以及 Java 使用“强类型”(你必须声明变量的类型)。本章还涵盖了 Java 的原始数据类型,它们的字节大小(在讨论类型转换时需要了解),以及它们的范围。

第三章,运算符和类型转换,探讨了 Java 的运算符如何通过优先级和结合性协同工作。我们讨论了 Java 的各种运算符,并解释了在 Java 中进行类型转换时的宽化和窄化。

第四章,条件语句,侧重于作用域和条件语句。我们首先检查 Java 的块作用域的使用。然后解释了if语句的各种形式;并以switch语句和switch表达式结束本章。

第五章, 理解迭代,讨论了循环,包括while、do-while、for和增强型 for 循环。本章还探讨了break和continue语句。

第六章, 使用数组,描述了为什么需要数组。我们展示了如何声明和初始化各种原始类型的数组,包括使用简写语法。我们讨论了如何遍历数组,处理每个元素。也涵盖了多维数组;以及Arrays类。

第七章, 方法,讨论了方法的重要性以及方法定义和方法执行之间的区别。讨论了方法重载,并解释了可变参数格式。最后,解释了按值调用的重要概念。

第八章, 类、对象和枚举,是一个重要的面向对象编程章节,详细介绍了以下内容:类和对象之间的区别;this引用;访问修饰符;基本和高级封装;对象生命周期;instanceof关键字;枚举和记录。

第九章*,继承和多态*,解释了继承和多态。我们详细说明了重写意味着什么,并讨论了super、protected、abstract和final关键字。我们还探讨了sealed类和向上转型/向下转型。

第十章*,接口和抽象类*,涵盖了abstract类和接口。我们解释了static、default和private接口方法,以及sealed接口。

第十一章,处理异常,解释了异常及其目的。我们解释了检查型异常和非检查型异常之间的区别。我们深入探讨了抛出异常以及如何创建自己的自定义异常。讨论了重要的捕获或声明原则;以及try-catch、try-catch-finally和try-with-resources块。

第十二章, Java 核心 API,介绍了 API 中的重要类/接口,例如Scanner。我们比较和对比了String和StringBuilder,并讨论了如何创建自定义不可变类型。我们举例说明了List接口及其流行的实现ArrayList。最后,我们探讨了日期 API。

第十三章, 泛型和集合,讨论了集合框架及其接口List、Set、Map和Queue。我们检查了每个接口的几个实现和基本操作。我们解释了使用Comparable和Comparator接口进行排序。最后,我们检查了泛型和基本哈希概念。

第十四章,Lambda 表达式,解释了 lambda 表达式是什么以及它们与函数式接口的关系。检查了 API 中的几个函数式接口。最后,概述了方法引用及其在理解中的作用。

第十五章,流:基础,是我们关于流的第一个章节。在本章中,我们讨论了流管道是什么以及流惰性的含义。我们展示了创建有限和无限流的不同方法。我们检查了启动流过程的终端操作,包括如 collect() 这样的聚合操作,这对于从流中提取信息非常有用。

第十六章,流:高级概念,首先检查了中间操作,如 filter()、map() 和 sorted()。我们探讨了原始流,然后讨论了如何将一个流映射到另一个流,无论类型如何。解释了 Optional 类型,并以并行流的讨论结束。

第十七章,并发,首先解释了并发是什么。我们检查了线程的使用,并讨论了并发访问的问题。讨论了解决这些问题的机制;即:原子类、synchronized 块和 Lock 接口。接下来探讨了并发集合和 ExecutorService。我们以讨论线程问题,如数据竞争、死锁和活锁结束。

为了充分利用这本书

尽管大部分代码可以在更早的 Java 版本上运行,但我们建议安装或升级到 Java 21 以避免版本相关的编译错误。

如果您目前系统上没有任何东西,以下设置将非常理想:

  • JDK 21 或更高版本(Oracle 的 JDK 或 OpenJDK)

  • IntelliJ IDEA(社区版已经足够好)或 Eclipse 或 Netbeans

本书涵盖的软件/硬件操作系统要求
Java 21+Windows, macOS, 或 Linux

查看 第一章 了解如何在各种操作系统上安装 Java 和 IDE 的说明。

如果您使用的是本书的数字版,我们建议您亲自输入代码或从本书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。

免责声明

为了出版者和作者的本意,本书中包含的一些图形展示了大屏幕示例,其中文本内容与图形示例不相关。我们鼓励读者下载购买中包含的数字副本,以满足放大和可访问的内容需求。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Learn-Java-with-Projects。如果代码有更新,它将在 GitHub 仓库中更新。

我们还有其他来自我们丰富的书籍和视频目录的代码包可供在github.com/PacktPublishing/找到。查看它们吧!

代码实战

本书的相关代码实战视频可在bit.ly/3GdtYeC查看。

下载彩色图像

我们还提供了一份包含本书中使用的截图和图表彩色图像的 PDF 文件。您可以从这里下载:packt.link/gbp/9781837637188。

我们使用的约定

本书使用了多种文本约定。

文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“因此,main()方法现在已将控制权交给simpleExample()方法,并且只有在simpleExample()退出之前,控制权不会返回到main()”。

代码块设置如下:

public class HelloWorld { public static void main(String[] args) { 
System.out.println("Hello world!"); 
} 
} 

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

//        int age = 25;        System.out.println(age);

任何命令行输入或输出都应如下编写:

Enter a number (negative number to exit) -->1
Enter a number (negative number to exit) -->
2

小贴士或重要注意事项

看起来是这样的。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及书名。

勘误:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在本书中发现错误,我们将不胜感激,如果您能向我们报告,请访问www.packtpub.com/support/errata并填写表格。

盗版:如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过 copyright@packt.com 与我们联系,并提供材料的链接。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。

分享您的想法

读完《通过项目学习 Java》后,我们很乐意听听您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。

您的评论对我们和科技社区都很重要,并将帮助我们确保我们提供高质量的内容。

下载本书的免费 PDF 副本

感谢您购买本书!

您喜欢在路上阅读,但无法携带您的印刷书籍到处走吗?

您的电子书购买是否与您选择的设备不兼容?

别担心,现在每购买一本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。

在任何地方、任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。

优惠远不止这些,您还可以获得独家折扣、时事通讯和每日免费内容的每日电子邮件访问权限

按照以下简单步骤获取优惠:

  1. 扫描二维码或访问以下链接

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_QR_Free_PDF.jpg

packt.link/free-ebook/9781837637188

  1. 提交您的购买证明

  2. 就这样!我们将直接将您的免费 PDF 和其他优惠发送到您的电子邮件

第一部分:Java 基础

在这部分,我们将首先探讨 Java 的特点以及如何使用 Java 和 IDE 进行设置。我们将检查变量和 Java 的八个原始数据类型。随后,我们将讨论 Java 的运算符和类型转换。然后,我们将继续探讨 Java 的条件语句和循环结构。之后,我们将查看如何使用数组,最后以方法结束。

本节包含以下章节:

  • 第一章,Java 入门

  • 第二章,变量和原始数据类型

  • 第三章,运算符和类型转换

  • 第四章,条件语句

  • 第五章,理解迭代

  • 第六章,与数组一起工作

  • 第七章,方法

第一章:Java 入门

欢迎来到 Java 的精彩世界!Java 是一种非常流行的编程语言。它是一种多用途、强大且流行的编程语言,被全球数百万开发者使用,以创建各种应用程序。是的,它确实是多用途的,因为它可以用来创建各种应用程序,从 Web 和移动应用,到游戏开发以及其他更多。

因此,您已经选择了一种(新)语言。我们将带您踏上(希望是)令人着迷的旅程,这将为您提供宝贵的技能,并在不断发展的技术领域中开辟新的机会。

我们在等待什么?在本章中,我们将涵盖以下主要主题:

  • Java 特性

  • 安装 Java

  • 编译和运行 Java 程序

  • 使用集成开发环境(IDE)进行工作

  • 使用 IDE 创建和运行程序

技术要求

在深入 Java 编程的神奇世界之前,让我们确保您拥有合适的硬件。如果您的硬件不符合这些要求,请不要担心;本章后面将讨论在线替代方案。如果您正在使用工作笔记本电脑,请确保您有下载权限。以下是要求的简要概述:

  • 操作系统:Java 可以在包括 Windows、macOS 和 Linux 在内的各种操作系统上运行。请确保您在计算机上安装了这些操作系统的最新版本。

  • Java 开发工具包(JDK):要编译和运行 Java 程序,您需要在您的系统上安装 JDK。JDK 包括Java 运行环境(JRE),其中包含运行 Java 应用程序所需的库和组件。我们将在稍后介绍如何安装它。

  • 系统资源:越多越好,但 Java 的要求并不高。它不需要高端硬件,但仍然建议拥有一个资源充足的系统,以便获得流畅的开发体验。以下是最小和推荐的系统要求:

    • 最低要求:

      • CPU:1 GHz 或更快的处理器

      • RAM:2 GB

      • 磁盘空间:1 GB(用于 JDK 安装和附加文件)

    • 推荐要求:

      • CPU:2 GHz 或更快的多核处理器

      • RAM:4 GB 或更多

      • 磁盘空间:2 GB 或更多(用于 JDK 安装、附加文件和项目)

请记住,这些要求可能会随着 JDK 和相关工具的未来更新而改变。我们已经将这些文件放在 GitHub 仓库中。您可以使用 Git 克隆项目,并以此方式将它们导入到您的计算机上。这里不涉及如何使用 Git 的说明,但建议您独立研究。您可以通过以下链接访问本书中使用的文件和示例:github.com/PacktPublishing/Learn-Java-with-Projects。

探索 Java 特性

Java 是由 James Gosling 在 20 世纪 90 年代中期在 Sun Microsystems 开发的。当 Java 被创造出来时,它最初被设计为一种用于消费电子产品的语言。它试图支持复杂的宿主架构,专注于可移植性,并支持安全网络。然而,Java 超出了自己的野心。它迅速成为一种多用途的语言,用于创建企业、Web 和移动应用程序。如今,Java 不再属于 Sun Microsystems。Oracle Corporation 在 2010 年收购了 Sun Microsystems。随着这次收购,Java 成为了 Oracle 软件生态系统的一个组成部分。

Java 在它被创造的时候非常独特。Java 的巨大成功可以归因于其一些核心特性。这些特性在当时是非常创新的,但现在在许多其他(竞争)语言中也能找到。其中一个核心特性是面向对象编程。OOP 允许我们以一种整洁的方式组织代码,这有助于代码的可重用性和可维护性。我们将通过查看面向对象的编程(OOP)来开始讨论这些核心特性。

Java 中的 OOP

不可否认,Java 最重要的特性是其对 OOP 的支持。如果你问任何 Java 开发者 Java 是什么,答案通常是它是一种 OOP 语言。

可以肯定地说,OOP 是一个关键特性。*这个 OOP 是什么东西?*你可能想知道。OOP 是一种编程范式。它将应用程序结构化,以模拟现实世界对象及其交互和行为。让我们回顾一下 OOP 的主要概念:

  • 对象:这可能是显而易见的,但在 OOP 中,对象是程序的主要构建块。对象是现实世界实体的表示,例如用户、电子邮件或银行账户。每个对象都有自己的属性(数据字段)和行为(方法)。

  • Car类可能定义了诸如颜色、制造商和型号等属性,以及启动、加速和制动等方法。

  • Car可以继承自Vehicle类。我们在这里不会详细介绍细节,但继承有助于更好地组织代码。代码更可重用,相关类的层次结构为我们使用类型打开了大门。

  • 封装:封装是给予一个类对其自身数据的控制。这是通过捆绑数据(属性)和操作这些数据的方法来实现的。属性只能通过这些特殊方法从外部访问。封装有助于保护对象的内部状态,并允许你控制对象的数据如何被访问或修改。如果你觉得这听起来仍然很棘手,不要担心,我们将在稍后更详细地讨论这个问题。

  • 多态和抽象:这些是 OOP 的两个关键概念,将在你准备好时进行解释。

使用 OOP

我可以想象现在这一切听起来可能非常抽象,但很快你就会自己创建类和实例化对象。面向对象编程有助于使代码更易于维护、结构更清晰、可重用。这些因素确实有助于在需要时能够对应用程序进行更改、解决问题和扩展。

面向对象编程(OOP)只是 Java 的一个关键特性。另一个关键特性是它是一种编译型语言。让我们确保你现在理解这个意思。

编译型语言

Java 是一种编译型编程语言,这意味着你编写的源代码必须在被解释之前转换为机器可读格式。这种机器可读格式称为字节码。这个过程与解释型语言不同,解释型语言是在运行时逐行读取、解释和执行的。当编译型语言运行时,计算机在运行时解释字节码。当我们准备编译自己的代码时,我们将在稍后深入了解编译过程。现在,让我们看看编译型语言的优点是什么。

Java 作为编译型语言的优点

首先编译代码需要额外的一步,并且一开始会花费一些时间,但会带来优势。首先,编译型语言的性能通常比解释型语言更好。这是因为字节码被优化以在目标平台上高效执行。

编译的另一个优点是在代码执行之前可以提前检测到语法错误和某些其他类型的错误。这使得开发者能够在部署应用程序之前识别和修复问题,从而降低运行时错误的可能性。

Java 代码通过编译器转换为字节码——一种二进制代码的形式。这种字节码是平台无关的。这意味着它允许 Java 应用程序在不同的操作系统上运行而无需修改。平台无关性实际上是我们接下来将要讨论的关键特性。

编写一次,到处运行

Java 的一次编写,到处运行(WORA)原则是另一个关键特性。这曾经使 Java 与其他许多编程语言区分开来,但现在,这已经相当普遍,许多竞争语言也实现了这一特性。这个原则确保 Java 代码可以在不同的平台上运行,而无需为每个平台提供不同的 Java 代码版本。这意味着 Java 程序不会绑定到任何特定的操作系统或硬件架构。

当你为每个平台有不同的代码版本时,这意味着你必须维护所有这些代码版本。假设你有一个 Linux、macOS 和 Windows 的代码库。当需要新功能或变更时,你需要将其添加到三个地方!你可以想象,当 Java 出现时,WORA 是一个颠覆性的变化。它导致你的应用程序的覆盖范围增加——任何可以运行 Java 应用程序的设备都可以运行你的应用程序。

理解 WORA 元素

WORA(一次编写,到处运行)原则得以实现得益于字节码和Java 虚拟机(JVM)。字节码是编译后的 Java 程序。编译器将 Java 代码转换成这种字节码,而这种字节码是平台无关的。它可以在任何能够运行字节码执行器的设备上运行。

这个字节码执行器被称为 JVM。每个平台(Windows、macOS、Linux 等)都有自己的 JVM 实现,它是专门为将该平台的字节码转换为本地机器代码而设计的。由于字节码在平台之间保持不变,JVM 处理操作系统和硬件架构之间的差异。WORA 原则在图 1.1中解释。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_1.jpg

图 1.1 – 图解 WORA 原则

你可以看到编译器创建了字节码,并且这个字节码可以被 JVM 捕获。JVM 是平台特定的,并且将其翻译成它所在的平台。JVM 为我们做了更多的事情,那就是自动内存管理。让我们接下来探讨这一点。

自动内存管理

另一个使 Java 变得伟大的关键特性是其自动内存管理,它简化了开发并防止了常见的内存相关错误。Java 为你处理内存分配和垃圾回收。开发者不需要手动管理内存。

现在,这已经成为规则而不是例外。大多数其他现代语言也有自动内存管理。然而,了解自动内存管理意味着什么很重要。内存的分配和释放是自动完成的。这实际上简化了代码。没有只关注内存分配和释放的样板代码。这也导致内存相关错误更少。

让我们确保你理解内存分配和释放的含义。

内存分配

在代码中,你创建变量。有时,这些变量不是简单的值,而是具有许多数据字段的复杂对象。当你创建一个对象时,这个对象需要存储在运行它的设备内存中。这被称为 内存分配。在 Java 中,当你创建一个对象时,设备内存会自动分配以存储对象的属性和相关数据。这与 C 和 C++ 等语言不同,在这些语言中,开发者必须手动分配和释放内存。Java 的自动内存分配简化了开发过程,并减少了内存泄漏或悬挂指针的可能性,这些可能会导致意外的行为或崩溃。它还使代码更易于阅读,因为你不需要处理任何分配或释放代码。

垃圾回收

当内存块不再被应用程序使用时,它需要被释放。Java 用于此的过程称为 垃圾回收。垃圾回收是识别和回收程序不再使用的内存的过程。在 Java 中,当一个对象不再可访问或不再需要时,垃圾回收器会自动释放该对象占用的内存。这个过程确保内存得到有效利用,并防止内存泄漏及其相关问题。

JVM 定期运行垃圾回收器以识别和清理不可达的对象。Java 的垃圾回收机制使用许多不同的复杂算法来确定何时对象不再需要。

现在我们已经介绍了基础知识,接下来让我们继续学习如何安装 Java。

安装 Java

在你开始编写和运行 Java 程序之前,你需要在计算机上设置 JDK。JDK 包含 Java 开发所需的必需工具和库,例如 Java 编译器、JRE 以及其他有助于开发的实用工具。

我们将指导你如何在 Windows、macOS 和 Linux 上安装 Java,并为你提供一些在没有这些系统之一的情况下的一些建议。但在开始安装 Java 之前,检查系统是否已安装 Java 是一个好主意。

检查系统是否已安装 Java

Java 可能已经预装,或者你可能之前已经安装过,但没有意识到。要检查 Java 是否已安装,请按照以下简单步骤操作。第一步取决于你的操作系统。

第一步 – 打开终端

对于 Windows,按 Windows 键,输入 cmd,然后按 Enter 打开 命令提示符。

对于 macOS,按 Command + Space 打开 终端,然后按 Enter 打开 终端。

对于 Linux,打开终端窗口。打开终端窗口的方法取决于你的 Linux 发行版(例如,在 Ubuntu 中,按 Ctrl + Alt + T)。

第二步 – 检查 Java 版本

在命令提示符或终端窗口中,输入以下命令并按 Enter:

java -version

步骤 3 – 解读响应

如果已安装 Java,您将看到显示的版本信息。如果没有安装,命令提示符将显示错误消息,指示 Java 不可识别或未找到。

如果您发现系统上已经安装了 Java,请确保它是 21 版本或更高版本,以确保与现代 Java 功能的兼容性。如果是较旧版本或未安装,请按照以下章节中描述的针对您特定平台的过程进行安装。如果已安装较旧版本,您可能希望首先卸载它,以避免设置过于复杂。您可以使用操作系统的常见程序卸载方式来安装它。

在 图 1*.2* 和 图 1*.6* 中,您将看到安装 Java 时可以预期的输出示例。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_2.jpg

图 1.2 – 安装了 Java 19 的 macOS 终端输出

现在,让我们看看如何在每个操作系统上安装 Java。

在 Windows 上安装 Java

要在 Windows 操作系统上安装 Java,请按照以下步骤操作:

  1. 访问 www.oracle.com/java/technologies/downloads/ 上的 Oracle Java SE 下载 页面。此软件可以免费用于教育目的,但在生产中需要许可证。您可以考虑切换到 OpenJDK 以在生产环境中运行程序而不需要许可证:openjdk.org/install/。

  2. 选择适合您 Windows 操作系统的适当安装程序(例如,Windows x64 安装程序)。

  3. 通过点击文件链接下载安装程序。

  4. 运行下载的安装程序(.exe 文件)并按照屏幕上的说明完成安装。

  5. 要将 Java 添加到系统的 PATH 环境变量中,在 开始 菜单中搜索 环境变量 并选择 编辑系统环境变量。您应该看到一个类似于 图 1*.3* 的屏幕。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_3.jpg

图 1.3 – 系统属性窗口

  1. 在 系统属性 窗口中,点击 环境变量… 按钮。将弹出一个类似于 图 1*.4* 的屏幕。

  2. 在 系统变量 下,找到 Path 变量,选择它,然后点击 编辑。您可以在以下 图 1*.4* 中看到一个选择示例:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_4.jpg

图 1.4 – 环境变量窗口

  1. 点击 Java 安装目录下的 bin 文件夹(例如,C:\Program Files\Java\jdk-21\bin)。在 图 1*.5* 中,这一步已经完成。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_5.jpg

图 1.5 – 将 Java 路径添加到 Path 变量中

  1. 点击 确定 保存更改并关闭 环境变量 窗口。

  2. 通过打开命令提示符(如果已经打开,请重新打开)并输入以下内容来验证 Java 是否已安装:

    java -version
    
  3. 输出应该看起来像 图 1*.6* 中所示的那样。然而,你的版本应该是 21 或更高,以跟上这本书中所有的代码片段!https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_6.jpg

=

图 1.6 – 安装 Java 后检查 Java 版本后的命令提示符

在 macOS 上安装 Java

要在 macOS 操作系统上安装 Java,请按照以下步骤操作:

  1. 访问 www.oracle.com/java/technologies/javase-jdk16-downloads.html 上的 Oracle Java SE 下载 页面。

  2. 选择 macOS 安装程序(例如,macOS x64 安装程序)。

  3. 通过点击文件链接下载安装程序。

  4. 运行下载的安装程序(.dmg 文件)并按照屏幕上的说明完成安装。

  5. Java 应该自动添加到你的系统 PATH 环境变量中。为了验证安装,打开终端并运行以下命令:

    java -version
    
  6. 你应该能看到你刚刚安装的 Java 版本,类似于 图 1*.2*。

在 Linux 上安装 Java

在 Linux 上安装可能需要几步才能解释清楚。不同的 Linux 发行版需要不同的安装步骤。在这里,我们将看看如何在 Linux Ubuntu 系统上安装 Java:

  1. 打开 终端 并通过运行以下命令更新你的软件包仓库:

    sudo apt-get update
    
  2. 通过运行以下命令安装默认的 JDK 软件包:

    sudo apt install default-jdk
    
  3. 为了验证安装,运行 java -version 命令。你应该能看到你刚刚安装的 Java 版本。

  4. 如果你需要设置 JAVA_HOME 环境变量(你不需要通过这本书的工作方式来完成,但你需要为更复杂的 Java 项目做这个),你首先需要通过运行以下命令确定安装路径:

    sudo update-alternatives --config java
    
  5. 记下显示的路径(例如,/usr/lib/jvm/java-19-openjdk-amd64/bin/java)。

  6. 使用具有 root 权限的文本编辑器打开 /etc/environment 文件:

    sudo nano /etc/environment
    
  7. 在文件的末尾添加以下行,将路径替换为你在 步骤 4 中记录的路径(不包括 /``bin/java 部分):

    JAVA_HOME="/usr/lib/jvm/java-19-openjdk-amd64"
    
  8. 保存并关闭文件。然后,运行以下命令以应用更改:

    source /etc/environment
    

现在,Java 应该已经安装并配置在你的 Linux 操作系统上了。

在线运行 Java

如果你没有访问 macOS、Linux 或 Windows 计算机的权限,网上有一些解决方案。免费选项可能不是完美的,但例如,尝试在浏览器中运行 Java 的 w3schools 解决方案并不差。网上有很多这样的解决方案。

为了处理多个文件,可能有一些免费工具可用,但大多数都是付费的。我们目前推荐的一个免费工具是 replit.com。你可以在这里找到它:replit.com/languages/java。

你需要注册,但你可以免费使用多个文件并将它们保存在你的账户上。如果你例如只有平板电脑来跟随这本书,这是一个很好的替代方案。

另一个选择是使用 GitHub Codespaces:github.com/codespaces。他们有机会进入一个仓库(例如我们用于这本书的仓库)并直接尝试仓库中可用的示例,并调整它们来尝试新事物。

在导航完 Java 的安装后,现在是时候讨论编译和运行程序了。

编写我们的第一个程序

在深入编译和运行 Java 程序的过程之前,让我们使用基本的文本编辑器创建一个简单的 Java 程序。这将帮助你理解 Java 程序的结构以及如何编写和保存 Java 源代码文件。在这个例子中,我们将创建一个 “Hello world!”程序,用于演示编译和执行的过程。

Hello world

你可能已经听说过向控制台输出 "Hello world!"。编写这个程序将帮助你获得对 Java 语法的基本理解,并且它将帮助你熟悉编写、编译和运行 Java 代码的过程。

创建程序的步骤

好的,让我们开始编码。以下是步骤:

  1. 首先,在你的电脑上打开一个基本的文本编辑器。Windows 上的 记事本、macOS 上的 文本编辑器 或 Linux 上的 Gedit 都是合适的选择。

  2. 在你的文本编辑器中写下以下 Java 代码:

    public class HelloWorld {    public static void main(String[] args) {        System.out.println("Hello world!");    }}
    
  3. 将文件保存为 HelloWorld.java 到你选择的目录中。保存文件时不要忘记 .java 扩展名。这表示文件包含 Java 源代码。代码不应该在 .java 后面有 .txt。在 Windows 中有时会发生这种情况,所以请确保不要在文件类型下拉菜单中选择文本文件。

TextEdit – 文件扩展名问题

macOS 的较新版本与 TextEdit 有点问题。默认情况下,你不能将其保存为 Java 文件。为了启用此功能,你需要转到 格式 | 制作纯文本 并选择 UTF-8。

在此之后,你可以将其保存为 .java 文件。你可能仍然会遇到编码错误;问题在于编码,修复它可能需要很多努力,但可能会错过这个练习的目标。可能更好的是,从我们的 GitHub 仓库下载 HelloWorld.java 文件。

理解程序

让我们看看我们刚刚使用的代码。首先,请注意,这是 区分大小写的。这意味着当你查看代码时,如果你混淆了大小写,大多数事情可能不会像你预期的那样工作。

首先,我们创建了一个名为HelloWorld的类,其中包含一个main方法。当然,我们还会更详细地介绍类和方法。但类是 Java 应用程序的基本构建块,它可以包含方法。方法可以被执行以执行某些操作——操作是指执行语句。

main方法是一个特殊的方法。它是我们的 Java 程序的入口点,包含当程序运行时将被执行的代码。带有System.out.println("Hello world!");的行将Hello world!消息写入控制台。请注意,println代表打印行,所以它使用小写的L,而不是大写的i。

在保存了HelloWorld.java文件之后,我们现在可以继续到下一节,我们将学习如何使用命令行和 IDE 编译和运行 Java 程序。

编译和运行 Java 程序

现在我们已经编写了第一个程序,让我们讨论如何编译和运行它。我们将涵盖编译过程的基础、JVM 的作用以及如何使用命令行和 IDE 编译和运行 Java 代码。

理解编译过程

源代码是用 Java 编程语言编写的可读格式。至少,我们希望在这本书之后,这也是你的观点。在代码可以执行之前,它必须转换成计算机可以理解的格式。你已经知道 Java 是一种编译型语言,这个过程被称为编译。

在编译过程中,.java文件被转换成字节码(.class文件)。一旦生成了字节码,它就可以由 JVM 执行。我们已经了解到 JVM 是字节码执行器,并且每个平台都有自己的定制 JVM,这实现了 Java 的 WORA 特性。

使用命令行上的 javac 编译代码

要使用命令行编译 Java 程序,请按照以下步骤操作:

  1. 打开终端(Windows 上的命令提示符,macOS 或 Linux 上的终端)。

  2. 导航到包含你的 Java 源代码文件的目录(例如,你之前创建的HelloWorld.java文件的目录)。如果你不知道如何操作,可以使用cd命令,它代表更改目录。例如,如果我在一个名为documents的目录中,我想进入名为java programs的子文件夹,我会运行cd "java programs"命令。引号只在目录名中有空格时需要。本书的范围不涉及解释如何在任何平台上更改目录。互联网上有许多关于如何使用命令行在各个平台上导航文件夹结构的优秀解释。

  3. 一旦你进入了包含 Java 文件的文件夹,请输入以下命令来编译 Java 源代码:

    javac HelloWorld.java
    

    如果编译成功,将在同一目录下创建一个具有相同名称但.class扩展名的新文件(例如,HelloWorld.class)。这个文件包含可以被 JVM 执行的字节码。

让我们看看如何运行这段编译后的代码。

在命令行上使用 Java 运行编译后的代码

要运行编译后的 Java 程序,请按照以下步骤操作:

  1. 在终端中,确保你仍然位于包含.class文件的目录中。

  2. 输入以下命令以执行字节码:

    java HelloWorld
    

JVM 将加载并运行字节码,你应该能看到程序的输出。在这种情况下,输出如下:

Hello world!

我们可以在记事本中编写 Java 代码并在命令行中运行它,这确实很酷,但现代 Java 开发者的生活要舒适得多。让我们把 IDE 加入进来,亲自看看。

使用 IDE 进行工作

在文本编辑器中创建文件有点过时了。当然,你仍然可以这样操作——这实际上是一个成为杰出程序员的极好方法,但也是一个非常令人沮丧的方法。有一些工具可以帮助我们完成大量繁重的工作,并在编写代码时提供协助。这些工具被称为集成开发环境(IDE)。

什么是 IDE?

集成开发环境(IDE)是一个软件应用程序,它包含了编写、编译、运行和测试代码所需的一切。使用 IDE 可以使开发各种程序变得更加容易。不仅如此,调试和管理代码也更加方便。相对而言,你可以将 IDE 比作我写这本书时使用的 Microsoft Office Word。虽然我可以用记事本写,但使用 Word 提供了显著的优势。它可以帮助检查拼写错误,并允许我轻松添加和可视化布局,以及其他有用的功能。这个类比描绘了 IDE 不仅仅提供了一个编写代码的平台,还提供了一套工具来简化和增强你的编码体验。

选择 IDE

在 Java 开发中,有几种 IDE 可供选择,每种都有其独特的功能和能力。在本节中,我们将讨论选择 IDE 时需要考虑的因素,并帮助你设置一些流行的 Java IDE。在整个书中,我们将使用IntelliJ。其他同样出色的选择包括VS Code和Eclipse。

选择 IDE 时需要考虑的因素

大多数现代 IDE 都具有代码补全、调试、版本控制集成以及支持第三方工具和框架等功能。其中一些在这方面比其他 IDE 做得更好。在选择或切换 IDE 时,比较和对比你偏好的功能。

一些 IDE 需要更强大的系统来运行,而其他 IDE 则较轻量。例如,VS Code 相对轻量,而 IntelliJ 则相对重量级。此外,VS Code 可以用于多种语言,包括 Java。使用 IntelliJ 进行非 Java 开发的其他事情相对较少。选择一个在功能和性能之间提供平衡的 IDE,尤其是如果您系统资源有限的话。

当然,您偏好的 IDE 可能不适合您所使用的平台。请确保它适用于您的系统,并且稳定可靠。

最后,非常重要的一点是,考虑成本。一些 IDE 是免费的,而其他 IDE 则需要付费许可证。幸运的是,许多需要付费许可证的 IDE 都提供了非商业用途的免费版。因此,在选择 IDE 时,请确保考虑您的预算和所需的许可证。

在接下来的子节中,我们将向您介绍设置当前(目前)最常用的三个 Java 开发 IDE 的步骤:

  • IntelliJ

  • Eclipse

  • Visual Studio Code

注意

在本书的剩余部分,我们将使用 IntelliJ。

设置 IntelliJ

那么,让我们从它开始。IntelliJ IDEA 是由 JetBrains 开发的一款流行的 Java 集成开发环境(IDE)。它提供免费 社区版 和付费 专业版。它提供了一系列功能,包括智能代码补全、调试工具、版本控制集成以及支持各种 Java 框架。

下面是安装 IntelliJ 的步骤:

  1. 访问 IntelliJ IDEA 下载页面,请点击www.jetbrains.com/idea/download/。

  2. 选择您偏好的版本:免费的 社区版 或付费的 专业版。对于初学者来说,社区版已经非常优秀了。

  3. 下载适用于您操作系统的安装程序(Windows、macOS 或 Linux)。

  4. 运行安装程序,并按照说明完成安装。

  5. 启动 IntelliJ IDEA。如果您使用的是专业版,您可能需要输入您的 JetBrains 账户凭证或许可证密钥。

  6. 在 欢迎 界面中,您可以创建一个新的 项目、导入现有的 项目,或探索可用的教程和文档。

设置 Eclipse

Eclipse 是一个免费的开源 Java 集成开发环境(IDE),在 Java 社区中广泛使用。它已经存在很长时间了,并且许多公司仍在使用它。它提供了各种功能,就像 IntelliJ 一样。Eclipse 可以根据您的需求进行定制,但其界面可能不如其他 IDE 直观。

要设置 Eclipse,请按照以下步骤操作:

  1. 访问 Eclipse 下载页面,请点击www.eclipse.org/downloads/。

  2. 下载适用于您操作系统的 Eclipse 安装程序(Windows、macOS 或 Linux)。

  3. 运行安装程序,并从可用包列表中选择 Eclipse IDE for Java Developers。

  4. 选择安装文件夹,并按照说明完成安装。

  5. 启动 Eclipse 并选择工作区目录。你的项目和设置将存储在这里。

  6. 在 欢迎 界面中,您可以创建一个新的 Java 项目,导入现有的 项目,或探索可用的教程和文档。

设置 Visual Studio Code

Visual Studio Code,通常简称为 VS Code,是由微软开发的一个轻量级、免费且开源的代码编辑器。它因其支持广泛的编程语言而受到各种任务的欢迎。它是那些更喜欢更简约和快速性能环境的开发者的首选。可以通过扩展添加各种功能。

这里是安装 VS Code 并为其 Java 开发做准备的分步指南:

  1. 访问 Visual Studio Code 下载页面 https://code.visualstudio.com/download。

  2. 下载适用于您操作系统的安装程序(Windows、macOS 或 Linux)。

  3. 运行安装程序,按照屏幕上的说明完成安装。

  4. 启动 Visual Studio Code。

  5. 通过点击窗口左侧的 扩展 图标(四个方块)打开 扩展 视图。

  6. 在 扩展市场 中搜索 Java 扩展包 并点击 安装 按钮。此扩展包包括各种用于 Java 开发的扩展,例如 Red Hat 提供的 Java ™ 语言支持、Java 调试器 和 Java 的 Maven。

  7. 安装了 Java 扩展包 后,您现在可以创建或导入 Java 项目。如果它没有直接加载,您可能需要重新打开 VS Code。

现在您已经设置了一个 IDE,让我们用它来创建和运行一个程序。

使用 IDE 创建和运行程序

与使用纯文本编辑器相比,使用 IDE(如 IntelliJ)工作要容易得多。现在我们将指导您使用 IntelliJ 创建、运行和调试程序。我们将创建与使用文本编辑器时相同的程序。

在 IDE 中创建程序

当你使用 IDE 编写代码时,你会看到它不断地帮助你完成代码。大多数人认为这非常有帮助,我们希望您也会喜欢这个功能。

为了开始使用 IntelliJ,我们首先需要创建一个项目。以下是再次创建我们的 Hello World 程序的步骤:

  1. 启动 IntelliJ IDEA 并从 欢迎 界面点击 新建项目,或转到 文件 | 新建 | 项目。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_7.jpg

图 1.7 – IntelliJ 的初始屏幕

  1. 将项目命名为 HelloWorld。

  2. 选择Java作为语言,并确保已选择正确的项目 SDK。点击下一步。

  3. 不要勾选创建 Git 仓库复选框,也不要勾选添加示例 代码复选框。

  4. 点击 创建 以创建项目。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_8.jpg

图 1.8 – 创建新项目的向导

  1. 一旦创建了项目,展开 src 文件夹中的 src 文件夹,并选择 新建 | Java 类。如果其下有另一个文件夹,那么可能有一个包含 Java 文件夹的主文件夹。右键单击 Java 文件夹并选择 新建 | Java 类。如果名称不同,只需右键单击蓝色文件夹。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_9.jpg

图 1.9 – 创建新的 Java 类

  1. 将新类命名为 HelloWorld 并单击带有类定义的 .java 文件。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_10.jpg

图 1.10 – 调用类 “HelloWorld”

  1. 在 HelloWorld 类中编写我们的 main 方法:

    public class HelloWorld {    public static void main(String[] args) {        System.out.println("Hello world!");    }}
    

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_11.jpg

图 1.11 – HelloWorld.java 中的代码

既然我们已经编写了第一个程序,请确保它已保存。默认情况下,IntelliJ 会自动保存我们的文件。让我们看看我们是否也能运行这个程序。

运行程序

虽然我们创建程序时不得不采取一些额外步骤,首先需要创建一个项目。好消息是,运行程序更容易!以下是操作方法:

  1. 如果您还没有这样做,请通过按 Ctrl + S(Windows/Linux)或 Cmd + S(macOS)确保保存您的更改。默认情况下,自动保存已启用。

  2. 要运行程序,请右键单击 HelloWorld 类中的任何位置,并选择 Run 'HelloWorld.main()'。或者,您也可以单击主方法旁边的绿色三角形图标,并选择 Run 'HelloWorld.main()'。IntelliJ 将编译并运行程序。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_12.jpg

图 1.12 – 运行程序

  1. 验证程序输出 "Hello world!" 是否显示在屏幕底部的 运行工具 窗口中。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_13.jpg

图 1.13 – 程序的输出

已保存和未保存的文件

在大多数集成开发环境(IDE)中,你可以通过查看打开文件的标签来了解文件是否已保存。如果文件未保存,其旁边会有一个点或星号。如果已保存,则点会缺失。

程序调试

我们现在的程序相当简单,但我们可能想逐行逐步执行程序。我们可以通过调试程序来实现这一点。让我们给我们的文件添加一些额外的调试内容。这样我们可以看到如何检查变量、理解执行流程,以及这样找到我们代码中的缺陷:

  1. 使用以下代码更新 HelloWorld.java 文件:

    public class HelloWorld {    public static void main(String[] args) {        String greeting = "Hello, World!";        System.out.println(greeting);        int number = 5;        int doubled = doubleNumber(number);        System.out.println("The doubled number is: " +          doubled);    }    public static int doubleNumber(int input) {        return input * 2;    }}
    
  2. 在这个程序的更新版本中,我们添加了一个名为 doubleNumber 的新方法,它接受一个整数作为输入并返回其两倍。在 main 方法中,我们调用此方法并打印结果。如果您不完全理解这一点,请不要担心——我们只是想向您展示如何逐步执行您的代码。

  3. 通过按 Ctrl + S(Windows/Linux)或 Cmd + S(macOS)保存您的更改。

    现在,让我们调试更新后的程序。

  4. 通过在编辑器中行号旁边的空白区域单击来在你想暂停执行的行上设置断点。会出现一个红色圆点,表示断点。例如,在行int doubled = doubleNumber(number);上设置断点。例如,请参阅图 1.7。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_14.jpg

图 1.14 – 在第 7 行添加断点

  1. 通过在HelloWorld类上右键单击并选择Debug 'HelloWorld.main()'或点击main方法旁边的绿色播放图标并选择调试选项来启动调试器。IntelliJ 将编译并以调试模式启动程序。

  2. 当即将执行带有断点的行时,程序将暂停。在暂停期间,你可以使用位于屏幕底部的调试工具窗口。在这里,你可以查看程序的状态,包括局部变量和字段的值。例如,请参阅图 1.8。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_01_15.jpg

图 1.15 – IntelliJ 中的调试工具窗口。此截图的目的是显示布局,不需要考虑文本可读性。

  1. 使用调试工具窗口中的步骤控件来逐步执行代码(图 1.8中的蓝色箭头带有角度),进入被调用的方法(蓝色箭头向下),或继续执行(图 1.8中左侧的绿色箭头)。

通过遵循这些步骤,你可以使用 IntelliJ IDEA 调试 Java 程序并逐步执行代码以查看正在发生的事情。这将在理解你的代码中派上用场。这个过程在其他 Java IDE 中也将类似,尽管具体的步骤和界面元素可能会有所不同。

练习

这就是本章的所有理论!所以,卷起袖子,让我们开始你的中生代伊甸园的第一天。欢迎加入!中生代伊甸园是一个著名的动物园,那里的恐龙是通过高端基因操控技术被带到这里生活的。以下是一些练习,以测试你到目前为止的知识:

  1. 你的第一个任务涉及欢迎我们的客人。修改以下代码片段,使其输出"Welcome to" "Mesozoic Eden":

    public class Main {    public static void main(String[] args) {        System.out.println("Hello world");    }}
    
  2. 通过填写空白处完成以下程序,以便打印出你希望在 5 年后的中生代伊甸园拥有的名字和职位:

    public class Main {    public static void main(String[] args) {        __________________________;        String position = "Park Manager";        System.out.println("My name is " + name + "          and I want to be a " ______ " in Mesozoic            Eden.");    }}
    
  3. 我们收到了一些关于开放时间的问题。完成以下程序,以便打印出公园的开放和关闭时间:

    public class Main {    public static void main(String[] args) {        String openingHours = "08:00";        String closingHours = "20:00";    }}
    
  4. 创建一个名为dinosaur的 Java 项目。你可以通过在src/main/java文件夹上右键单击,选择“新建”并选择“包”来创建一个包。

  5. 修改练习 1 中的代码,使其打印出"Welcome, [YourName] to Mesozoic Eden!",其中[YourName]将被惊喜惊喜地替换为你的名字。加分项:尝试创建一个单独的 String 变量,如第二和第三练习所示。

  6. 一些游客报告说在霸王龙附近感到不安全。让我们通过在练习 5 的程序中添加另一个System.out.println来解决这一问题。它应在欢迎信息后打印短语"Mesozoic Eden is safe and secure."。

项目

创建一个程序,模拟 Mesozoic Eden 入口处的标志。这个标志通过向控制台打印输出进行模拟。标志应显示欢迎信息、营业时间和简短的安全信息。

摘要

你已经完成了第一章!我们已经做了很多。我们首先探索了 Java 的关键特性,例如它的 OOP 方法、(曾经独特的)WORA 原则、它的编译性质以及超级有用的自动内存管理。这些特性使 Java 成为一个极其灵活且强大的语言——是不同编程任务(如 Web 开发、桌面应用、移动应用等等)的一个很好的选择!

接下来,我们向您介绍了在 Windows、macOS 和 Linux 等平台上安装 Java 的过程。我们还讨论了如何检查 Java 是否已经安装在你的系统上。在这一部分之后,你可以确信你已经拥有了启动 Java 编程冒险的所有必需工具。

在你设置好 Java 之后,我们解密了编译过程,并介绍了 JVM,它是 Java 生态系统的一个关键组件,使得 Java 代码具有可移植性。然后我们演示了如何使用javac和java命令行工具编译和运行 Java 代码。这些工具为使用 Java 程序的核心工作奠定了基础。

当然,使用命令行来做这个很好。但如今,我们更经常使用 IDE,我们只需按一个按钮就可以完成所有这些。因此,我们提到了使用 IDE 的一些优点和良好特性,例如代码补全、调试和项目管理。我们讨论了在选择 IDE 时需要权衡的因素,并提供了设置流行的 IDE(如 IntelliJ IDEA、Eclipse 和 VS Code)的指导。在这本书中,我们将使用 IntelliJ 作为示例。

在介绍了 IDE 的基础知识之后,我们深入探讨了使用 IDE 创建和运行 Java 程序的过程。我们解释了典型 Java 程序的结构,并逐步引导你创建、运行和调试你的第一个 Java 程序。

在此之后,你准备好进行第一个实战项目了。现在你在这里!一切准备就绪,准备在 Java 之旅上迈出下一步。这一步将是处理变量和基本数据类型。祝你好运!

第二章:变量和原始数据类型

在第一章中,我们介绍了编译器和 JVM。当我们编写第一个 Java 程序HelloWorld时,我们学习了如何从命令行使用它们。我们还介绍了IntelliJ,一个强大且友好的 IDE,并且也从那里运行了HelloWorld。

所有编程语言都需要变量并提供内置的原始数据类型。它们是甚至最简单程序操作的基础。在本章结束时,你将能够使用 Java 的原始数据类型声明变量。此外,你将了解不同原始数据类型之间的区别以及在特定情况下应使用哪些类型。

在本章中,我们将涵盖以下主要主题:

  • 理解和声明变量

  • 探索 Java 的原始数据类型

技术要求

本章的代码可以在 GitHub 上找到,网址为github.com/PacktPublishing/Learn-Java-with-Projects/tree/main/ch2。

理解和声明变量

如果你想要存储一个用于以后使用的值,你需要一个变量。因此,每种编程语言都通过变量提供这个功能。在本节中,我们将学习什么是变量以及如何声明它。你可以在代码中使用的特定变量的区域称为变量的作用域。这是一个非常重要的概念,将在第四章中详细讨论。

什么是变量?

变量是内存中的位置,具有一个名称(称为标识符)和一个类型。它们类似于带名称的鸽巢或邮政信箱(参见图 2.1)。变量的名称是必需的,这样我们才能引用变量并将其与其他变量区分开来。

变量的类型指定它可以存储/保留的值的类型。例如,变量是用来存储像 4 这样的整数还是像 2.98 这样的十进制数?对这个问题的回答决定了变量的类型。

声明变量

假设我们想要将数字 25 存储在一个变量中。我们将假设这个数字代表一个人的年龄,因此我们将使用age标识符来引用它。第一次引入变量被称为“声明”变量。

一个整数(正数或负数)是一个整数,Java 提供了一个专门用于整数的内置原始类型,称为int。我们将在下一节中更详细地讨论原始数据类型。在 Java 中声明变量时,我们必须指定变量的类型。这是因为 Java 被称为强类型语言,这意味着你必须立即在声明变量时指定它的类型。

让我们声明一个变量,给它一个类型,并初始化它:

int age;age = 25;

第一行将age声明为int,第二行将其赋值为25。

注意,行尾的分号(;)是分隔符,告诉编译器 Java 语句在哪里结束。等号(=)是赋值运算符,将在第三章中介绍。现在,只需明白25被“赋值给”age变量。

赋值运算符

Java 中的=符号与数学中的等于符号(=)不同。Java 使用==符号表示等于,这被称为等价。

我们可以将前面的两行代码写在一行中:

int age = 25;

图 2*.1*显示了这两个代码段的内存表示:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_02_01.jpg

图 2.1 – 命名为 age 的整型变量,其值为 25

如图 2**.1所示,age是变量的名称,25是存储在变量位置中的整数值。

变量命名

标识符只是你给正在编写的 Java 结构起的名字;例如,命名变量、方法、类等都需要标识符(名称)。

标识符

标识符由字母、数字、下划线和货币符号组成。标识符不能以数字开头,也不能包含空白(空格、制表符和换行符)。在以下示例中,逗号分隔了不同的标识符。

一些不寻常但有效的标识符示例有 a£€_23, _29, Z, thisIsAnExampleOfAVeryLongVariableName, €2_, 和 $``4 ;.

无效的标识符示例有9age和abc def;.

仔细命名你的变量。这有助于使代码更易读,从而减少错误并便于维护。驼峰式命名法在这方面非常流行。关于变量名,驼峰式命名法意味着所有第一个单词都是小写。此外,变量名中每个后续单词的第一个字母都是大写字母。以下是一个例子:

int ageOfCar;int numberOfChildren;

在这个代码段中,我们有两个整数变量,它们的名称/标识符遵循驼峰式命名法。

访问变量

要访问变量的值,只需输入变量的名称。当我们输入 Java 中的变量名称时,编译器将首先确保存在具有该名称的变量。假设存在,JVM 将在运行时返回该变量的内部值。因此,以下代码将在屏幕上输出25:

int age = 25;System.out.println(age);

第一行声明了age变量并将其初始化为25。第二行访问变量的位置并输出其值。

System.out.println()

System.out.println()在屏幕上显示括号()内的任何内容。

访问未声明的变量

如前所述,Java 是一种强类型语言。这意味着你必须在声明变量时立即指定其类型。如果编译器遇到一个变量而不知道其类型,它将生成错误。例如,考虑以下代码行:

age = 25;

假设没有其他代码声明 age,编译器将生成一个错误,指出 cannot resolve symbol 'age'。这是因为编译器正在寻找与 age 关联的 类型,但它找不到它(因为我们没有指定类型)。

这里有一个稍微不同的例子。在这个例子中,我们试图将 age 变量输出到屏幕上:

        int length = 25;        System.out.println(age);

在这个例子中,我们声明了一个名为 length 的变量,因此没有声明 age。当我们尝试在 System.out.println() 中访问 age 变量时,编译器会寻找 age。编译器找不到 age 并生成一个错误,指出 cannot resolve symbol 'age'。实际上,编译器所说的是我们尝试使用编译器找不到的名为 age 的变量。这是因为我们甚至没有声明该变量,更不用说指定其类型了。

注释

注释非常有用,因为它们帮助我们解释代码中的内容。

// 是单行注释。当编译器看到 // 时,它会忽略该行剩余的部分。

./* some text */ 是多行注释。在 /* 开头和 */ 结尾之间的任何内容都被忽略。这种格式可以节省在每个行首插入 //。

这里有一些例子:

int age; // from here to the rest of the line is ignored

// this whole line is ignored

/* all

of

these lines

are

ignored */

由于 Java 作为一种强类型语言,要求所有变量都有数据类型,我们将现在讨论 Java 对原始数据类型的支持。

理解 Java 的原始数据类型

Java 提供了八个内置数据类型。内置意味着这些数据类型与语言一起提供。这些原始数据类型是本节的主题。

Java 的原始数据类型

所有的原始数据类型都只使用小写字母命名;例如,int 和 double。当我们稍后创建自己的数据类型,即类、记录和接口时,我们将遵循不同的命名约定。例如,我们可能有一个名为 Person 或 Cat 的类。这只是一个广泛采用的编码约定,编译器不会区分命名约定。然而,很容易识别任何原始数据类型,因为它们总是只使用小写字母。在我们讨论原始数据类型本身之前,有一些重要的事项需要说明。

数字原始数据类型是有符号的

在 Java 中,所有数字原始数据类型都表示为一系列位。此外,它们也是有符号的。最高有效位(最左边的位)用于表示符号;1 表示负数,0 表示正数。

整数字面量

文字值是在键盘上键入的值(与计算值相对)。整数文字可以用不同的数制表示:十进制(基数 10)、十六进制(基数 16)、八进制(基数 8)和二进制(基数 2)。然而,十进制是使用最广泛的表示方式并不令人惊讶。为了信息目的,以下所有声明都表示十进制数字 10:

int a = 10; // 十进制,默认

int b = 0b1010; // 二进制,以 0b 或 0B 开头

int c = 012; // 八进制,以 0 开头

int d = 0xa; // 十六进制,以 0x 或 0X 开头

符号位影响范围

符号位的存在意味着 byte 的范围是 -27 到 27-1(-128 到 +127,包括 +127)。正数范围内的 -1 是为了允许在 Java 中 0 被视为正数的事实。在任何范围内都没有一个更少的正数。例如,对于 byte,你有 128 个负数(-1 到 -128)和 128 个正数(0 到 +127),结果有 256 种表示(2⁸)。为了通过一个简单的例子来强调这一点,-1 到 -8 是 8 个数字,0 到 7(包括 0)也是 8 个数字。

讨论了这些观点后,让我们来看看各种原始类型。表 2.1 列出了八个原始数据类型、它们的字节大小和它们的范围(所有这些都是包括在内的):

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_02_Table_01.jpg

表 2.1 – Java 的原始类型

下面是从前面表格中的一些有趣的观点:

  • byte、short、char、int 和 long 被称为 整型 类型,因为它们具有整数值(整数,正数或负数)。例如,-8、17 和 625 都是整数。

  • char 用于字符 – 例如 a,‘b’,‘?’ 和 ‘+’。注意字符被单引号包围。在代码中,char c = 'a'; 表示变量 c 代表字母 a。由于计算机最终将所有字符(在键盘上)作为内部数字(二进制)存储,我们需要一个编码系统来将字符映射到数字,反之亦然。Java 使用 Unicode 编码标准,它确保每个字符都有一个唯一的数字,无论平台、语言、脚本等。这就是为什么 char 使用 2 个字节而不是 1 个。实际上,从计算机的角度来看,char c = 'a'; 与 char c = 97; 是相同的,其中 97 是 Unicode 中 a 的十进制值。显然,我们作为人类更喜欢字母表示法。

  • short 和 char 都需要 2 个字节,但范围不同。注意 short 可以表示负数,而 char 不能。相比之下,char 可以存储如 65,000 这样的数字,而 short 不能。

  • float 和 double 用于浮点数 – 换句话说,有小数位的数字,例如 23.78 和 -98.453。这些浮点数可以使用科学记数法 – 例如,130000.0 可以表示为 double d1=1.3e+5;,而 0.13 可以表示为 double d2=1.3e-1;。

各种类型的表示

从上一个调用中扩展,我们可以使用以下数制表示整数字面量:

  • 十进制:基数为 10;数字 0…9。这是默认值。

  • 十六进制:基数为 16;数字 0…9 和字母 a…f(或 A…F)。在字面量前加上 0x 或 0X 以指示这是一个十六进制字面量。

  • 二进制:基数为 2;数字 0…1。在字面量前加上 0b 或 0B 以指示这是一个二进制字面量。

这里有一些示例代码片段,使用不同的数制初始化 int 变量为 30。首先使用十进制,然后是十六进制,最后是二进制:

// decimalint dec = 30;
// hexadecimal = 16 + 14
int hex = 0x1E;
// binary = 16 + 8 + 4 + 2
int bin = 0b11110;

虽然有几种初始化 int 的方法,但使用十进制是最常见的。

默认情况下,字面量数字,如 22,被视为 int。如果你想让 22 被视为 long(而不是 int),必须在字面量后加上大写或小写 L。以下是一个示例:

int x  = 10;long n = 10L;

根据 表 2.1,使用 long 而不是 int,可以访问更大和更小的数字。为了表示 long,建议使用大写 L 而不是小写 l,因为小写 l 与数字 1(one)相似。

浮点数的行为类似。默认情况下,十进制数字是 double。要将任何十进制数字视为 float(而不是 double),必须在字面量后加上大写或小写 F。假设范围不是问题,那么使用 float 而不是 double 的一个原因是为了节省内存(因为 float 需要 4 个字节,而 double 需要 8 个字节)。以下是一个示例:

double d = 10.34;float f  = 10.34F;

char 类型的变量用单引号括起来的字面量初始化。以下是一个示例:

char c = 'a';

boolean 类型的变量只能存储 true 或 false。这些 boolean 字面量仅使用小写,因为它们是 Java 中的保留字,Java 是区分大小写的:

boolean b1 = true;boolean b2 = false;

这部分关于 Java 原始类型系统的内容到此结束,我们考察了各种类型、它们的尺寸/范围以及一些代码片段。

现在,让我们将变量和原始类型理论付诸实践!但在那之前,这里有一些作弊代码可以帮助你完成练习。

屏幕输出

如我们所知,System.out.println() 输出括号内的内容。为了进行练习,我们想要在此基础上进行扩展。首先,这里有一些代码:

String name = "James";    // line 1int age = 23; // line 2
double salary = 50_000.0; // line 3
String out = "Details: "  +  name  +  ", "  +  age  +  ", "  +  salary;//line 4
System.out.println(out);  // line 5

第 1 行声明了一个字符串字面量 "James" 并用它初始化了 name 变量。字符串字面量是一系列字符(包括数字),用双引号括起来。我们将在 第十二章 中详细讨论 String 类。

第 2 行和第 3 行应该没问题。我们声明了一个名为 age 的 int 类型和一个名为 salary 的 double 类型,并使用字面量初始化它们。第 3 行中使用的下划线使我们能够使大数字更容易阅读。

第 4 行构建要输出的字符串,即 out。我们想要输出变量的值,以及一些有用的文本来解释输出。Java 从左到右构建字符串。

在这里,+ 不是常规的数学加法。我们将在 第三章 中详细讨论这个问题,但暂时明白,当你有一个字符串变量或字面量位于 + 的左边或右边时,操作就变成了字符串连接(而不是数学加法)。

这个 append 属性与加法有一个共同点,即 + 的两边必须是同一类型。由于本例中并非所有变量都是字符串变量(或字面量),Java 在后台需要做一些工作(将它们都转换为同一类型)。Java 将数值变量的值复制到新的字符串位置,以便在构建字符串时使用。例如,内存中某个位置已经创建了一个字符串字面量“23”(除了 int 类型的 age 位置)。对于 double 类型的 salary 变量也是如此。现在,Java 准备构建字符串并将其分配给 out(第 4 行)。

在后台,Java 执行以下操作:

"Details: " + "James" => "Details: James""Details: James" + ", " => "Details: James, "
"Details: James, " + "23" => "Details: James, 23"
"Details: James, 23" + ", " => "Details: James, 23,
"Details: James, 23, " + "50000.0" => "Details: James, 23,
  50000.0"

因此,"Details: James, 23, 50000.0" 用于初始化 out,这是执行第 5 行时屏幕上显示的内容。

练习

在我们可爱的恐龙公园里,一切都很顺利。然而,我们确实需要做一些行政工作:

  1. 我们需要跟踪公园中的恐龙。在主方法中声明变量来表示一只恐龙的品种、身高、长度和体重。给这些变量赋值并打印它们。

  2. 现在,我们想要做与练习 1 类似的事情,并打印恐龙的年龄、姓名以及它是否是肉食性动物。这需要在主方法中完成。给这些变量赋值并打印它们。

  3. 我们的公园运营得很好!但有时会变得有点拥挤。消防部门建议我们引入在任何给定时间内允许的最大游客数量。声明一个变量来表示公园每天允许的最大游客数量。你可以为该变量选择一个合理的值。然后,在句子“Mesozoic Eden 允许的最大人数为 [x] 人。”中打印它。

  4. 我们的团队是 Mesozoic Eden 不可或缺的一部分。让我们为一名员工创建一个档案。声明变量来表示 Mesozoic Eden 员工的姓名和年龄。赋值并打印它们。

  5. 我们想知道在任何时候我们有多少只恐龙。声明一个变量来表示公园中的恐龙数量。给它赋值并打印它。

  6. 安全是我们的首要任务。我们维护一个安全评级量表以确保我们的标准。声明一个变量来表示公园的安全评级,范围从 1 到 10。给它赋值并打印它。

  7. 现在,让我们在一个语句中汇总一些恐龙信息。创建一个程序,使用字符串连接来打印出恐龙的名字、年龄和食性(值为carnivore或herbivore的字符串)。

  8. 每种恐龙物种都有一个独特的名字。为了快速参考系统,我们使用恐龙物种的首字母。声明一个代表恐龙物种首字母的字符变量,赋值并打印它。

项目 – 恐龙档案生成器

作为你在中生代伊甸园责任的一部分,你被要求创建一个包含公园内所有恐龙的详尽数据库。目前,你只需要完成第一步:档案生成器。这些档案不仅有助于跟踪我们的史前居民,还为科学研究、医疗保健、饮食管理和游客互动提供了必要的数据。

在这个项目中,我们将专注于开发一个可以模拟单个恐龙档案的程序。

档案应包括以下特征:

  • 名字

  • 年龄

  • 物种

  • 食性(肉食性或草食性)

  • 重量

每个特征都应该作为程序中的一个变量来存储。这是你发挥创意和思考你想要描述的恐龙类型的机会。是高耸的霸王龙,还是友好的甲龙?也许它是一只敏捷、可怕的迅猛龙,或者是一头强大的三角龙?

一旦你声明并给这些变量赋值,程序应该打印出恐龙的完整档案。输出可以是类似这样的内容:“遇见[名字],一只[年龄]岁的[物种]。作为一种[食性],它拥有健壮的体重[重量]千克。”

摘要

在本章中,我们了解到变量只是一个带有名称和值的内存位置。为了利用变量,我们必须知道如何声明和访问它们。

声明变量时,我们指定变量的名称和类型——例如,int countOfTitles=5;。这一行代码声明了一个名为countOfTitles的int变量,其值为5。使用驼峰命名法正确命名它们,对于使代码更易于阅读和维护大有裨益。要访问变量,我们只需指定变量的名称——例如,System.out.println(countOfTitles);。

由于 Java 是一种强类型语言,我们在声明变量时必须指定其类型。Java 为我们提供了八个内置的基本数据类型供我们使用。它们由于使用小写字母而易于识别。在上面的代码行中,int是countOfTitles变量的基本数据类型。我们看到了原始数据类型的大小(以字节为单位),这决定了它们的值域。所有数值类型都是有符号的,最高位用于表示符号。char类型是无符号的,大小为 2 字节,这样 Java 就可以支持世界上任何语言的任何字符。通过使用代码片段,我们看到了不同类型变量在实际中的应用。

现在我们已经知道了如何声明和使用变量,接下来让我们继续学习那些能够让我们组合变量的运算符。

第三章:运算符和类型转换

在第二章中,我们了解到变量只是命名的小鸽笼,并包含值。这些值是变化的,Java 相应地提供了八个原始数据类型。这些原始类型包括整数(byte、char、short、int和long)、小数(float和double)以及布尔字面量(boolean)。

我们还学习了如何声明一个变量。由于 Java 是一种强类型语言,这意味着你必须在声明变量时立即为其指定一个数据类型。这正是原始数据类型非常有用的地方。

现在我们已经知道了如何声明变量,让我们用它们做一些有趣的事情。到本章结束时,你将能够使用 Java 的各种运算符组合变量。此外,你将理解 Java 类型转换,包括它是什么,以及何时以及为什么发生。

在本章中,我们将涵盖以下主要内容:

  • 学习 Java 运算符的协作方式

  • 理解 Java 的运算符

  • 解释 Java 类型转换

技术要求

本章的代码可以在 GitHub 上找到:github.com/PacktPublishing/Learn-Java-with-Projects/tree/main/ch3。

学习 Java 运算符的协作方式

Java 为我们提供了大量的运算符来使用。根据定义,如果我们有一个表达式 3 + 4,那么 + 是 运算符,而 3 和 4 是 操作数。由于 + 有 两个 操作数,它被称为 二元 运算符。

在我们讨论运算符本身之前,我们必须首先讨论与 Java 运算符相关的两个重要特性,即优先级顺序和结合性。

优先级顺序

优先级顺序指定了操作数如何与运算符组合。当你在复杂表达式中共享操作数时,这一点变得很重要。在下面的代码段中,我们有一个表达式 2 + 3 * 4,其中 * 代表乘法,+ 代表加法:

int a = 2 + 3 * 4;System.out.println(a);

在前面的代码中,3 同时被 2 和 4 使用。因此,问题出现了,我们是将 3 与 2 组合在一起,表达式为 (2 + 3) * 4,得到 20;还是将 3 与 4 组合在一起,表达式为 2 + (3 * 4),得到 14?这就是优先级顺序发挥作用的地方。由于 * 的优先级高于 +,3 与 4 组合在一起,因此表达式计算为 2 + (3 * 4)。请注意,计算顺序仍然是左到右;只是 3 与 4 组合在一起,而不是与 2 组合在一起。

表达式中的括号

注意,括号可以改变运算符的默认优先级顺序。正如我们所见,默认的优先级顺序,其中 * 的优先级高于 +,意味着 2 + 3 * 4 的结果是 14。这等同于 2 + (3 * 4)。

然而,(2 + 3) * 4 的结果是 20。在这种情况下,括号将 3 与 2 组合在一起,因此表达式计算为 5 * 4 = 20。

这就引出了一个问题,如果你正在评估一个包含具有相同优先级的运算符的表达式,这时结合性就适用了。

结合性

当一个表达式包含两个具有相同优先级的运算符时,运算符的结合性决定了运算符和操作数的分组。例如,在以下代码段中,我们正在评估一个涉及两个除法(它们具有相同的优先级)的简单表达式:

int x =  72 / 6 / 3;

由于除法运算符从左到右结合,6 将与 72 结合,而不是与 3 结合。因此,表达式等同于 (72 / 6) / 3,其结果为 12 / 3 = 4。括号也可以用来改变默认的结合顺序。例如,以下代码:

int x =  72 / (6 / 3);

在这种情况下,6 现在将与 3 结合,表达式计算结果为 72 / 2 = 36。

表 3.1 概述了优先级和结合性规则:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_Table_01.jpg

表 3.1 – 优先级和结合性规则顺序

注意,表 3.1 是简化的,因为它只提到了常用的运算符。例如,无符号右移运算符 >>> 被省略,因为它很少使用。另外,请注意,instanceof 运算符将在 第八章 中讨论。

有趣的是,赋值运算符,即 =,在优先级表中位于底部。这意味着无论赋值右侧的表达式是什么,赋值总是最后进行的。这是有道理的。此外,虽然大多数运算符的结合性是从左到右的,但赋值运算符的结合性是从右到左的。这在下述代码段中得到了演示:

boolean b1 = false;boolean b2;
boolean b3;
b3 = b2 = b1;
System.out.println(b1);
System.out.println(b2);
System.out.println(b3);

上述代码段输出了 false 三次。关键行是 b3 = b2 = b1;。由于赋值运算符的结合性是从右到左的,所以 b1 中的值,即 false,被赋值给 b2;然后,b2 中的值,现在是 false,被赋值给 b3。

现在我们已经了解了这些属性,让我们来检查运算符本身。

理解 Java 的运算符

运算符可以分为以下几类:

  • 一元运算符

  • 算术运算符

  • 关系运算符

  • 逻辑运算符

  • 三元运算符

  • 复合赋值运算符

我们现在将依次讨论每一类。

一元运算符

一元运算符只有一个操作数,因此称为一元。让我们来检查它们。

前缀和后缀一元运算符

++ 和 -- 表示这些运算符,它们分别增加和减少 1。如果运算符出现在变量之前,它被称为前缀,而如果运算符出现在变量之后,它被称为后缀。例如,++x 是前缀递增,而 y– 是后缀递减。

根据 ++ 或 -- 是出现在变量之前还是之后,在某些情况下可能会影响整个表达式的结果。这最好通过代码示例来解释,如 图 3*.1* 所示:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_1.jpg

图 3.1 – 前缀和后缀递增和递减运算符

在图 3.1 中,第 25 行我们可以看到x被初始化为3。在第 26 行,x增加 1 变为4。第 26 行是一个简单语句,因此,无论是前缀还是后缀表示法,都没有关系。第 27 行输出x的值,以显示此时它的值是4。

第 28 行是事情变得有趣的地方。第 28 行的后缀表示法对屏幕输出有实际影响。因为它是在System.out.println命令中的后缀表示法,所以输出的是x的当前值,然后x增加 1。所以,屏幕上的输出是4,然后x增加到5。第 29 行演示了x是5。

在第 31 行,变量y被初始化为4。在第 32 行,y减去 1 变为3。再次强调,由于第 32 行是一个简单语句,前缀或后缀表示法没有区别。第 33 行输出y的值,以显示此时它的值是3。

第 34 行的前缀表示法对屏幕输出没有实际影响。因为它是在System.out.println命令中的前缀表示法,所以先递减y的值,然后输出到屏幕的值匹配(两者都是2)。最后,第 35 行演示了y的当前值是2。

一元加减运算符

现在我们已经讨论了前缀和后缀运算符,让我们来讨论其他一元运算符。图 3.2 中的代码将有所帮助:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_2.jpg

图 3.2 – 其他一元运算符

在图 3.2 中,第 37 行使用一元加号+将x初始化为6。在这里,+是默认的,因为不带符号的数字被认为是正数。第 38 行使用一元减号-将y初始化为x的相反数。第 39 和第 40 行演示了x和y分别是6和-6。

类型转换运算符

在图 3.2 中,第 42 行使用了类型转换运算符。我们将在本章后面更详细地讨论类型转换。现在,3.45是一个double字面量(8 字节),不能存储在大小为 4 字节的int变量z中。编译器会检测到这一点并生成错误。为了绕过这个错误,我们可以使用类型转换,其形式为(cast type)。这种转换使我们能够覆盖编译器错误。在这种情况下,我们将3.45转换为一个int变量,这意味着我们失去了小数位。因此,我们将3存储在z中,如第 43 行的输出所示。

逻辑非运算符

在图 3.2 中,第 45 行声明了一个布尔变量b并将其初始化为true。在第 46 行,我们通过使用逻辑非运算符输出b的相反值。请注意,我们并没有改变b的值,也就是说,b的值仍然是true。这可以通过第 47 行的输出得到证明。

现在,让我们来检查算术运算符。

算术运算符

有五种算术运算符,我们将在下面逐一考察。

加法/减法运算符

如同数学一样,+ 运算符代表加法,- 运算符代表减法。两者都是二元运算符;换句话说,有两个操作数,一个在运算符的左侧,一个在右侧。以下代码示例展示了这一点:

int res = 6 + 4 - 2;System.out.println(res); // 8

在此代码段中,res 被分配了 6 + 4 – 2 的值,即 8。

乘法/除法运算符

* 运算符代表乘法,/ 运算符代表除法。两者都是二元运算符。请注意,整数除法会截断。以下代码段展示了这一点:

System.out.println(10/3); // 3

此代码段输出 3,因为整数除法会截断。我们正在将一个整数 10 除以另一个整数 3,余数被简单地丢弃。

余数运算符

% 运算符用于计算余数(余数)。以下代码示例展示了余数运算符的作用:

int mod1 = 10 % 3;System.out.println(mod1); // 1
int mod2 = 0 % 3;
System.out.println(mod2); // 0

第一行将 mod1 初始化为 10 除以 3 的余数,即 1。换句话说,3 可以整除 10 三次,剩下 1。因此,1 被分配给 mod1。

mod2 的初始化很有趣:3 不能整除 0,没有剩余。因此,0 被分配给 mod2。

算术运算符的优先级

根据 表 3.1,*、/ 和 % 的优先级高于 + 和 – 运算符,赋值运算符的优先级最低。图 3*.3* 展示了这如何影响代码中表达式的计算:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_3.jpg

图 3.3 – 算术运算符优先级

第 61 行展示了 * 的优先级高于 +,因为表达式的计算结果是 3 + (2 * 4) = 3 + 8 = 11。

第 63 行展示了括号会改变分组。现在,共享值 2 与 3 分组(而不是第 61 行的情况,那时是 4)。表达式现在计算结果为 5 * 4 = 20。

第 65 行展示了 + 和 - 也是从左到右结合的。表达式的计算结果为

10 - 2 = 8。

第 67 行展示了 *、/ 和 % 也从左到右结合。表达式计算结果为 2 * 6 % 10,这又转换为 12 % 10,结果是 2。

涉及 int 变量或更小类型的数学运算结果为 int

有趣的是,任何涉及 int 类型或更小类型的数学运算都会得到 int 类型。以下代码段展示了这一点:

byte b1=2, b2=3;

byte b3 = b1 + b2; // 编译器错误

byte b4 = (byte)(b1 + b2);// 正确

第一行声明了两个字节,即 b1 和 b2。请注意,尽管 2 和 3 是整数文字,但编译器知道这些值在 byte 的范围(-128 到 +127)内,因此允许这些声明。

然而,下一行是一个问题。编译器有一个规则,即所有涉及 int 类型或更小类型的数学运算的结果都是 int。因此,即使两个字节之和 5 在 byte 范围内,编译器也会报错,说“从 int 转换到 byte 可能会丢失数据”。

最后一行通过在赋值之前将加法的结果(int 类型)强制转换为 byte 来解决这个问题。这意味着 int 类型的额外 3 个字节(无法放入 byte 中)将被简单地丢弃。因此,b1 + b2 的和从 int 转换为 byte,结果 byte 被赋值给 b4。在后面的章节中会更详细地讨论强制转换。

我们将通过考察 + 在不同上下文中的用法来结束对算术运算符的讨论。

字符串连接

如我们所见,Java 使用 + 进行数学加法。然而,这仅在两个操作数都是数字时才会发生。例如,3 + 4 的结果是 7,因为两个操作数 3 和 4 都是数字。

然而,如果任一操作数(或两个都是)是字符串,Java 会执行 String 连接。字符串字面量用双引号括起来——例如,"abc"、"123"、"Sean" 和 "Maaike" 都是 String 字面量。所以,为了清楚地了解何时执行何种操作,让我们看看一些示例:

  • 3 + 4 是数学加法。因此,结果是 7。

  • “3” + 4 是一个字符串连接操作,因为 + 的左边有一个字符串。结果是字符串 “34。”

  • 3 + “4” 是一个字符串连接操作,因为 + 的右边有一个字符串。再次,结果是字符串 “34。”

  • “3” + “4” 是一个字符串连接操作,因为 + 的两边都有字符串。结果也是字符串 “34。”

那么,在字符串连接操作中到底发生了什么?Java 当操作数类型不同时无法执行任何数学运算。让我们通过一段示例代码来分析:

String s = "3" + 4;System.out.println(s); // "34"

首先要注意的是,第一行代码之所以能编译,是因为 "3" + 4 的结果是字符串字面量。当 Java 遇到 + 的左边/右边/两边都有字符串时,它会执行字符串连接(追加)。本质上,由于 + 是从左到右结合的,Java 会将 + 右边的字符串追加到 + 左边的字符串的末尾。

在这个例子中,Java 看到了字符串字面量 "3" 和 + 操作符,并意识到它必须执行字符串连接。为此,在内存中,它创建了一个 4 的字符串版本——换句话说,"4"。整数字面量 4 没有被修改。因此,在底层创建了一个新的变量——它是一个 String 变量,其值为 "4"。现在表达式是 "3" + "4"。由于 + 的两边现在都是相同类型的操作数(都是字符串),Java 可以执行连接。新的字符串是 "3" + "4" 的结果,即 "34"。这就是被赋值给 s 的内容。第二行通过输出 s 的 "34" 来演示这一点。

在 图 3*.4* 中,提供了一个更复杂的示例:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_4.jpg

图 3.4 – 字符串连接操作

在第 79 行,由于两个操作数 a 和 b 都是整数,Java 将 res 初始化为 5(3 和 2 的和)。

第 82 行的评估方式如下:3 + "abc" = "3" + "abc" = "3abc"。换句话说,Java 认识到由于 + 的右侧存在 "abc",它必须执行字符串连接。因此,在内存的某个地方,创建了一个包含 a 值的字符串版本。换句话说,创建了一个值为 "3" 的变量。请注意,a 仍然是一个值为 3 的 int 类型。现在,Java 可以继续执行,因为两个操作数都是同一类型(字符串):"3" + "abc" 结果为 "3abc"。

第 83 行演示了字符串在 + 的哪一侧并不重要。此外,字符串是字面量还是字符串变量也不重要。第 83 行的表达式评估方式如下:"abc" + 3 = "abc" + "3" = "abc3"。这就是 s2 的初始化值。第 84 行输出 s1 和 s2 的值,它们之间有一个空格。请注意,System.out.println 期望一个字符串。第 84 行的字符串输出构造方式如下:"3abc" + " " = "3abc " + "abc3" = "3abc abc3"。

第 86 和 87 行需要特别说明。第 86 行的问题在于输出字符串的构造方式如下:"Output is "+ 3 = "Output is " + "3" = "Output is 3" + 2 = "Output is 3" + "2" = "Output is 32"。这不是我们想要的结果。

第 87 行通过使用括号来确保 a + b 被分组,从而纠正了这个问题。因此,字符串的构造方式如下:"Output is "+ 5 = "Output is "+ "5" = "Output is 5"。

这样就完成了算术运算符的介绍。接下来,我们将研究关系运算符。

关系运算符

Java 有六个关系运算符,它们都返回 boolean 类型的 true 或 false 值。具体如下:

  • == 是等价运算符

  • != 是不等价运算符

  • > 是大于运算符

  • >= 是大于或等于运算符

  • < 是小于运算符

  • <= 是小于或等于运算符

图 3*.5* 展示了关系运算符在代码中的实际应用:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_5.jpg

图 3.5 – 代码中的关系运算符

第 89 行声明了两个 int 类型的变量,分别是 x 和 y,并将它们分别初始化为 3 和 4。第 90 行使用 Java 的等价运算符 == 来检查 x 和 y 是否等价。由于它们不等价,第 90 行输出 false。第 91 行检查相反的情况。由于 x 不等价于 y,第 91 行输出 true。

第 92 行输出 x 是否大于 y。当然,这是 false,因为 3 不大于 4。同样,第 93 行输出 x 是否大于或等于 y。这同样是 false。

第 94 行输出 x 是否小于 y。这是 true,因为 3 小于 4。第 95 行输出 x 是否小于或等于 y。这同样是 true。

关系运算符及其布尔返回值在以后将非常有用,尤其是当我们查看第四章(B19793_04.xhtml#_idTextAnchor087)中的条件语句时。

隐式提升

虽然 Java 的运算符不需要操作数必须是完全相同的类型,但操作数必须是兼容的。考虑以下代码片段:

System.out.println(3 + 4.0); // 7.0

System.out.println(4 == 4.0); // true

第一行尝试将一个int类型的3变量加到一个double类型的变量上。Java 意识到这两个类型并不相同。然而,Java 可以找出一个安全的解决方案而不打扰我们。这就是隐式提升的作用。int类型需要 4 个字节的存储空间,而double类型需要 8 个字节的存储空间。在后台,内存的某个地方,Java 声明了一个临时的double变量,并将int 3提升为double 3.0,然后将3.0存储在这个临时位置。现在,Java 可以将3.0加到4.0上(因为两者都是double类型),得到的结果是7.0。

第二行比较int 4和double 4.0。发生同样的过程。Java 隐式地将4提升为4.0(在新的临时位置),然后比较4.0和4.0。这导致输出true。

现在,我们将注意力转向逻辑运算符。

逻辑运算符

逻辑运算符使我们能够通过组合子表达式来构建复杂的boolean表达式。这些运算符如下:

  • &&是逻辑与

  • ||是逻辑或

  • &是位与

  • |是位或

  • ^是位异或(XOR)

我们将依次通过代码示例来检查这些运算符,但在这样做之前,回顾一下表 3.2中的真值表是有益的,如下所示:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_Table_02.jpg

表 3.2 – 布尔真值表

在表 3.2中,前两列,P和Q,代表两个表达式,其中T表示真,F表示假。例如,逻辑与列(P && Q列)表示整体表达式P && Q的结果,这取决于P和Q的值。所以,如果P为真且Q为T,那么P && Q也为真。

在这个表格的指导下,我们依次检查运算符。

逻辑与(&&)

逻辑与运算符表示两个布尔操作数都必须为真,整个表达式才为真。这由表 3.2中的P && Q列表示。

注意,这个操作符被称为短路操作符。例如,在一个表达式 P && Q 中,如果 P 评估为假,那么 && 将不会评估表达式 Q,因为整体表达式将评估为假。这是因为 F && F 是假,F && T 也是假。实际上,Java 知道一旦在 && 表达式的左侧表达式 P 为假,整体表达式必须是假。因此,没有必要评估右侧的表达式 Q,所以它短路了。这最好用一个代码示例来解释:

boolean b1 = false, b2 = true;boolean res = b1 && (b2=false); // F &&
System.out.println(b1 + " " + b2 + " " + res);// false true
  false

第一行初始化了两个布尔变量,b1 和 b2,分别设置为 false 和 true。第二行是重要的一行。请注意,在 b2=false 子表达式中需要括号来使代码能够编译(否则,你会得到一个语法错误)。因此,当我们为 b1 插入 false 时,表达式计算为 F && (b2=false)。由于评估顺序是从左到右,这将导致 && 短路,因为无论表达式中剩下什么,整体表达式都无法评估为真。这意味着 (b2=false) 子表达式不会被执行。

最后一行输出了变量的值。输出分别是 false、true 和 false,对应于 b1、b2 和 res。关键的是,b2 是 true,这证明了 && 短路了。

逻辑或(||)

逻辑或表示,只要有一个或两个布尔操作数是真,整体表达式就是真。这由 表 3.2 中的 P || Q 列表示。

这个操作符也是一个短路操作符。例如,在一个表达式 P || Q 中,如果 P 评估为真,那么 || 将不会评估表达式 Q,因为整体表达式将评估为真。这是因为 T || F 是真,T || T 也是真。实际上,Java 知道一旦在 || 表达式的左侧表达式 P 为真,整体表达式必须是真。因此,没有必要评估右侧的表达式 Q,所以它短路了。再次用一个代码示例来帮助说明:

boolean b1=false, b2=true;boolean res = b2 || (b1=true);  // T ||
System.out.println(b1 + " "+ b2 + " "+res);// false true
  true

第一行初始化了两个布尔变量,b1 和 b2,分别设置为 false 和 true。第二行是重要的一行。再次请注意,在 b1=true 子表达式中需要括号来使代码能够编译。因此,当我们为 b2 插入 true 时,表达式计算为 T || (b1=true)。由于评估顺序是从左到右,这将导致 || 短路,因为无论表达式中剩下什么,整体表达式都无法评估为假。

最后一行输出了变量的值。输出分别是 false、true 和 true,对应于 b1、b2 和 res。关键的是,b1 是 false,这证明了 || 短路了。

评估顺序与优先级

这个主题经常引起混淆,最好通过一些示例代码来解释。让我们从一个看似简单的例子开始:

int x=2, y=3, z=4;

int res = x + y * z; // x + (y * z)

System.out.println(res); // 14

由于 * 的优先级高于 +,公共元素 y 与 z 而不是 x 组合在一起。因此,整个表达式是 x + (y * z) = 2 + 12 = 14。

这里要注意的重要一点是评估顺序是从左到右的,并且由于评估顺序高于优先级,x 在 (y * z) 子表达式之前被评估。虽然在这个例子中这没有区别,但让我们看看一个有区别的例子:

boolean a=false, b=false, c=false;

// a || (b && c)

// 下行计算结果为 T ||

boolean bool = (a = true) || (b = true) && (c = true);

System.out.print(a + ", " + b + ", " + c); // true, false, false

由于 && 的优先级高于 ||,表达式计算结果为 (a = true) || ( (b = true) && (c = true) )。

换句话说,常见的子表达式 (b = true) 与 (c = true) 而不是 (a = true) 组合在一起。现在来谈谈关键点:评估顺序高于优先级。因此,(a = true) 首先被评估,结果为 T || ((b = true) && (c = true))。

由于 || 是短路运算符,表达式右侧的其余部分(|| 右侧)为 true、false、false,分别对应 a、b 和 c。这里要注意的关键点是 b 和 c 仍然是 false!

既然我们已经讨论了逻辑运算符,我们将继续讨论位运算符。

位运算符

虽然一些位运算符看起来与逻辑运算符非常相似,但它们的操作方式却截然不同。主要区别在于位运算符可以与布尔和整型(byte、short、int、long 和 char)操作数一起工作。此外,位运算符不会短路。

让我们先检查布尔位运算符。

位与(&)

比较位与(&)与逻辑与(&&),区别在于位与不会短路。这由 表 3.2 中的 P & Q 列表示。如果我们使用逻辑与的示例代码,但将其更改为使用位与运算符,你将看到输出结果的不同:

boolean b1 = false, b2 = true;boolean res = b1 & (b2=false); // F & F
System.out.println(b1 + " " + b2 + " " + res);// false
  false false

在这种情况下,(b2=false) 子表达式被执行,因为 & 没有短路。因此,我们得到 false & false,结果是 false。因此,所有变量的输出都是 false。

位或(|)

比较位或(|)与逻辑或(||),区别在于位或不会短路。这由 表 3.2 中的 P | Q 列表示。如果我们使用逻辑或的示例代码,但将其更改为使用位或运算符,你将看到输出结果的不同:

boolean b1=false, b2=true;boolean res = b2 | (b1=true);  // T | T
System.out.println(b1 + " "+ b2 + " "+res);// true true
  true

在这种情况下,(b1=true) 子表达式被执行,因为 | 没有短路。所以,我们有:true | true,结果是 true。因此,所有变量的输出都是 true。

按位异或 (^)

这是一个非短路运算符。按位异或运算符,用 ^ 表示,当且仅当其中一个操作数是 true 但 不是 两个都为 true 时,结果为 true。这由 表 3.2 中的 P ^ Q 列表示。让我们通过代码来看一些示例:

boolean b1 = (5 > 1)  ^ (10 < 20);   // T ^ T == Fboolean b2 = (5 > 10) ^ (10 < 20);   // F ^ T == T
boolean b3 = (5 > 1)  ^ (10 < 2);    // T ^ F == T
boolean b4 = (5 > 10) ^ (10 < 2);    // F ^ F == F
// false true true false
System.out.println(b1 + " " + b2 + " " + b3 + " " + b4);

boolean 变量 b1 被初始化为 false,因为两个子表达式——(5 > 1) 和 (10 < 20)——都是 true。同样,b4 也被初始化为 false,因为 (5 > 10) 和 (10 < 2) 都是 false。

然而,b2 是 true,因为尽管 (5 > 10) 是 false,(10 < 20) 是 true,且 F ^ T 是 true。同样,b3 是 true,因为 (5 > 1) 是 true,(10 < 2) 是 F,且 T ^ F 是 true。

现在我们已经检查了按位运算符与 boolean 操作数一起使用的情况,我们现在将简要地检查当操作数是整数时,这些运算符是如何工作的。

按位运算符(整数操作数)

虽然不常用,但我们为了完整性而包括它们。一个代码示例在这里很有用:

byte b1 = 6 & 8;       // both bits must be 1byte b2 = 7 | 9;       // one or the other or both
byte b3 = 5 ^ 4;       // one or the other but not both
System.out.println(b1 + ", " + b2 + ", "+b3); // 0, 15, 1

当操作数是整数(而不是布尔值)时,位模式在评估结果时变得重要。对于 & 运算符,结果中的该位必须两个位都为 1 才能是 1:

6 & 8 (in binary) = 0110 & 1000 = 0000 = 0

对于 | 运算符,结果中的该位必须为 1,至少有一个位或两个位必须为 1:

7 | 9 (in binary) = 0111 | 1001 = 1111 = 15

对于 ^ 运算符,结果中的该位必须至少有一个位为 1,但不能两个都为 1:

5 ^ 4 (in binary) = 0101 ^ 0100 = 0001 = 1

这样就完成了按位运算符。现在,让我们来介绍三元运算符。

三元运算符

如其名所示,三元运算符是一个接受三个操作数的运算符。三元运算符用于评估布尔表达式,并根据结果相应地给变量赋值。换句话说,由于布尔表达式只评估为 true 或 false,三元运算符的目标是决定将哪个值赋给变量。

语法形式如下:

variable = boolean expression ? value to assign if true :value to assign if false

让我们来看一个示例:

int x = 4;String s = x % 2 == 0 ? " is an even number" : " is an odd
  number";
System.out.println(x + s); // 4 is an even number

在这个例子中,要评估的布尔表达式是 x % 2==0,由于 x = 4,它评估为 true。因此,是偶数 被分配给字符串 s 并输出。如果 x 是 5,那么布尔表达式将是 false,因此,是奇数 将被分配给 s 并输出。

我们将要检查的最后一批运算符是复合赋值运算符。

复合赋值运算符

这些运算符作为更冗长的表达式的简写而存在。例如,假设 x 和 y 都是整数,x = x + y 可以写成 x += y。所有数学运算符都有相应的复合赋值运算符:

  • += 示例:x += y 等同于 x = x + y

  • -= 示例:x -= y 等同于 x = x - y

  • *= 示例:x *= y 等同于 x = x * y

  • /= 示例:x /= y 等同于 x = x / y

  • %= 示例:x %= y 等同于 x = x % y

的确,对于位运算符也有复合赋值运算符——例如,x &= 3 等同于 x = x & 3,但它们很少使用,所以我们只是提一下它们的存在。

有一些细微之处需要注意。如前所述,任何涉及 int 类型或更小类型的数学运算都会得到 int 类型。这可能会导致需要类型转换才能使代码编译。对于复合赋值运算符,类型转换是内置的,因此不需要显式转换。以下代码为例:

byte b1 = 3, b2 = 4;//  b1 = b1 + b2;         // compiler error
b1 = (byte)(b1 + b2);     // ok
b1 += b2;         // ok, no cast required

第一行初始化了两个字节,b1 和 b2,分别设置为 3 和 4。第二行被注释掉,因为它会生成编译器错误。b1 和 b2 的相加结果是一个 int 类型,不能直接赋值给 byte 变量,除非你将其从 int 类型转换为 byte 类型。这就是第三行所做的工作——使用类型转换(byte)来覆盖编译器错误。我们很快就会介绍类型转换,但到目前为止,只需意识到,使用类型转换,你正在覆盖编译器错误,实际上是在说“我知道我在做什么,继续。”

最后一行很有趣,因为在幕后,它与第三行相同。换句话说,编译器将 b1 += b2 转换为 b1 = (byte) (b1 + b2)。

另一个需要注意的细微之处是,复合赋值运算符右侧的内容将被分组,无论其优先级如何。以下是一个例子。考虑以下内容:

int x = 2;x *= 2 + 5;                   // x = x * (2 + 5) = 2 * 7 = 14
System.out.println(x); // 14

我们知道 * 的优先级高于 +,且评估顺序是从左到右。话虽如此,*= 右侧的内容被编译器通过将 2 + 5 括起来(在幕后)进行分组。因此,表达式变为 2 * (2 + 5) = 2 * 7 = 14。为了进一步说明这一点,如果编译器没有插入括号,表达式将被评估为 9。换句话说,由于运算符优先级,表达式将被评估为 (2 * 2) + 5 = 4 + 5 = 9。然而,正如我们所看到的,这不是情况。

让我们看看另一个更复杂的例子:

int k=1;k += (k=4) * (k+2);
System.out.println(k); // 25

在这个例子中,右侧再次被括号包围:

k += (right hand side) where the right hand side is (k=4) *  (k+2)

将 += 转换为其较长的形式,我们得到以下输出:

k = k + (right hand side)

评估顺序是从左到右,所以插入当前 k 的值,即 1,结果如下:

k = 1 + (right hand side)

现在,通过插入右侧的表达式,我们得到以下结果:

k = 1 + ( (k=4) * (k+2) )

由于评估顺序是从左到右,我们在加上 2 之前将 k 改为 4:

k = 1 + ( 4 * 6 )k = 1 + 24
k = 25

这就结束了我们对 Java 运算符的处理。现在,让我们来探讨 Java 类型转换,这是我们已经在本章中提到过的主题。

解释 Java 类型转换

为了正确地讨论类型转换,我们需要解释 Java 原始数据类型的类型提升和类型缩小。考虑到这一点,记住原始数据类型的大小(以字节为单位)是有帮助的。表 3.3 表示了这些信息:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_Table_03.jpg

表 3.3 – Java 的原始数据类型大小

前面的表格展示了 Java 各种原始数据类型的大小(以字节为单位)。这将在我们讨论类型提升和类型缩小时有所帮助。

类型提升

类型提升是自动完成的;换句话说,不需要类型转换。由于提升是在后台完成的,因此类型提升也被称为 隐式提升。考虑到 表 3.3,类型提升的规则如下:

byte → short/char → int → long → float → double

根据表 3.3 中的大小,大多数这些规则应该是有意义的。例如,byte 可以自动适应 short,因为 1 个字节可以自动适应 2 个字节。唯一有趣的一个是 long → float,这是从 8 个字节到 4 个字节的类型提升。这是可能的,因为尽管 long 需要 8 个字节,而 float 只需要 4 个字节,但它们的范围不同 – 即 float 类型可以容纳任何 long 值,但反之则不行。这在下述代码片段中显示:

System.out.println("Float: " + Float.MAX_VALUE);// Float:  3.4028235E38
System.out.println("Float: " + Float.MIN_VALUE);// Float:
  1.4E-45
System.out.println(Long.MAX_VALUE);  //   9223372036854775807
System.out.println(Long.MIN_VALUE);   //  -9223372036854775808

注意用于浮点数的科学记数法 E。float 占用的空间更少,但由于其表示方式,它可以容纳比 long 更大和更小的数字。

科学记数法

科学记数法是一种表示十进制数字的简写方式,可以用于表示非常大和/或非常小的数字。以下是一些示例:

double d1 = .``00000000123;

double d2 = 1.23e-9;

System.out.println(d1==d2); // true

double d3 = 120_000_000;

double d4 = 1.2e+8;

System.out.println(d3==d4); // true

由于这两个比较都返回 true,这意味着 d1 是 d2 的内部表示。同样,d3 和 d4 也是等效的。

让我们通过代码来检查类型提升。图 3*.6* 展示了这一点:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_6.jpg

图 3.6 – 隐式类型提升示例

第 14 行是一个常规的 char 赋值 – 换句话说,没有类型提升。注意字符(由 char 表示)只是简单的数字(0…65,535)。为了表示一个字符,我们将字符放在单引号内。相比之下,String(字符序列)用双引号表示。因此,"a" 是一个 String,而 'a' 是一个字符。

第 15 行是从char(2 字节)到int(4 字节)的扩展。第 16 行是从int到float的扩展。尽管int和float都需要 4 字节,如前所述,与long一样,float有更大的范围,所以这里没有问题。第 17 行是从float到double的扩展。最后,第 18 行是从long到float的扩展。注意,任何地方都没有编译器错误,并且在这些赋值中不需要使用转换运算符。

现在,让我们讨论需要转换的窄化。

窄化

转换运算符是一个括号内的类型 – 例如,(int)和(byte)都是转换运算符,分别将值转换为int和byte。考虑到表 3.3,以下图,图 3*.7*,展示了需要转换的赋值:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_7.jpg

图 3.7 – 转换示例

在前面的图中,第 23 行试图将3.3,一个double类型(8 字节),赋值给一个int类型(4 字节)。没有转换,这将是一个编译器错误。有了转换,你将覆盖编译器错误。因此,在第 23 行,我们将3.3转换为int,并将这个int赋值给i变量。因此,赋值完成后,i的值为3。

第 24 行是将int类型的233转换为byte变量b。这个字面值超出了byte的范围(-128 到+127),因此需要转换。第 25 行是将double类型的3.5转换为float。记住,默认情况下,十进制数是double;要将其视为float而不是double,必须后缀f或F。例如,3.3f是float。

第 26 行的输出是i为 3,b为-23,f为 3.5。注意,在输出中,float变量没有f。

我们是如何得到-23的,将在以下说明中解释。

字节溢出

记住,byte的范围是-128(10000000)到+127(01111111)。最左边的位是符号位,其中 1 表示负数,0 表示正数。

在前面的例子中,我们做了以下操作:

byte b = (byte) 233;

233(一个整数)的值对于byte来说太大,但b是如何被赋予-23 的值的呢?将233映射为int类型会得到以下位模式:

11101001 = 1 + 8 + 32 + 64 + 128 = 233 (int)

注意,由于int是 4 字节,233的位模式是 00000000000000000000000011101001。将该位模式映射为byte(高阶 3 字节被截断)会得到以下输出:

11101001 = 1 + 8 + 32 + 64 + (-128) = -23 (byte)

记住,最左边的位是符号位。这就是为什么计算中包含-128的原因。它是-(2⁷) = -128。

我们将通过查看一些需要/不需要转换的非常规示例来结束本节。

是要转换还是要不转换,这是一个问题

有一些情况下,由于编译器在后台应用规则,不需要进行类型转换。让我们通过代码示例来检查这些情况。图 3.8展示了代码:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_03_8.jpg

图 3.8 – 不总是需要类型转换的情况

第 32 行声明并初始化了一个名为c的char变量,将其赋值为int类型的12。记住,char变量本质上是很小的正数。尽管我们将一个int类型的值(4 字节)赋给了一个char变量(2 字节),但由于字面值在char的范围内(0 到 65,535),编译器允许这样做。如果字面值超出了char的范围,编译器将生成错误——这就是第 33 行发生的情况。

第 34 行声明并初始化了一个名为s的short变量,将其赋值为int类型的12。同样,尽管short只能存储 2 字节,但编译器意识到它可以存储字面值12,并允许这样做。

注意,从编译器的角度来看,将字面值赋给变量与将变量赋给变量是不同的。例如,第 32 和 37 行是相当不同的。当我们讨论图中的下一几行时,这一点将变得明显。

第 35 到 38 行展示了尽管char和short都需要 2 字节,但它们的范围不同:char(0 到 65,535)和short(-32,768 到+32,767)。这意味着short变量可以存储负值,例如-15,而char变量则不能。相反,char变量可以存储如 65,000 这样的值,但short变量则不能。因此,正如第 35 和 37 行所展示的,你不能直接将char变量赋给short变量,反之亦然。在这两种情况下,你需要进行类型转换。第 36 和 38 行展示了这一点。

编译时常量

然而,第 40 到 42 行展示了绕过我们刚才概述的类型转换要求的方法。如果你将变量声明为编译时常量(并且假设值在范围内),编译器将允许变量到变量的赋值。第 40 行使用final关键字声明了一个编译时常量。我们将在第九章中详细讨论final,但在这个上下文中,它意味着c1将始终具有12的值。对于c1来说,值是固定的(或常量),这是在编译时完成的。如果你尝试更改c1的值,你将得到编译器错误。现在编译器知道c1将始终具有12作为其值,编译器可以应用与字面值相同的规则;换句话说,值是否在范围内?这就是为什么第 42 行不会生成编译器错误。

这就结束了我们对运算符的讨论。现在,让我们应用它们!

练习

侏罗纪乐园做得很好。恐龙很健康,客人也很高兴。现在你有一些新技能,让我们继续进行稍微复杂一些的任务!

  1. 饲养员们希望能够跟踪恐龙的体重。你的任务是编写一个程序,计算两只恐龙的平均体重。这将帮助我们的营养师团队规划正确的食物分量。

  2. 适当的营养对于我们恐龙的健康至关重要。饲养员们希望有一个大致的指导,了解应该给恐龙喂食多少食物。编写一个程序,根据恐龙的体重来确定所需的食物量。你可以根据恐龙每单位体重的食物需求量来计算。

  3. 对于我们的公园,我们需要有一个闰年检查器。在我们对科学准确性的承诺中,使用取模运算符来确定当前年份是否是闰年。我们想确保我们的以日历为主题的展览总是最新的。

  4. 创建一个程序,检查公园的最大容量是否已达到。程序只需在“最大容量达到:”之后打印出 true 或 false。这对于维护安全标准和确保良好的游客体验至关重要。

  5. 有时游客想要比较恐龙的年龄。我们理解——这可能在教育目的上很有趣。编写一个程序,计算两只恐龙之间的年龄差异。

  6. 在中生代伊甸园,我们有一个非常强的以安全为第一的政策。编写一个程序,检查公园的安全评级是否高于某个阈值。保持良好的安全评级是我们的首要任务。

项目 - 恐龙餐食规划器

作为中生代伊甸园的饲养员,关键任务包括为我们心爱的恐龙规划餐食。虽然我们还没有使用条件语句和循环,但我们仍然可以计算一些基本需求!

开发一个简单的程序,帮助饲养员为不同的恐龙规划餐食分量。该程序应使用恐龙的体重来计算每餐需要吃多少食物。

如果你需要更多指导,以下是你可以这样做的方法:

  • 为恐龙的体重和它每天需要摄入的体重比例声明变量。例如,如果一个恐龙每天需要摄入其体重的 5%,而它重 2,000 公斤,那么它就需要摄入 100 公斤的食物。

  • 现在,假设你每天给恐龙喂食两次。声明一个变量来表示喂食次数,并计算每次喂食需要提供多少食物。在这个例子中,每次喂食将是 50 公斤。

  • 以有意义的方式打印出结果——例如,“我们的 2,000 公斤恐龙每天需要吃 100 公斤食物,这意味着我们每次喂食需要提供 50 公斤。”

摘要

在这一章中,我们学习了 Java 运算符的工作原理以及它们是如何协作的。此外,我们还学习了如何在 Java 中进行类型转换。

最初,我们讨论了与运算符相关的两个重要属性:优先级和结合性。我们了解到优先级决定了常见项是如何分组的。当运算符具有相同的优先级顺序时,结合性就会发挥作用。

然后,我们检查了运算符本身。我们首先查看一元运算符,它们有一个操作数,例如前缀/后缀增量/减量运算符++和--。

然后,我们转向算术运算符:+、-、*、/和%。我们指出,整数除法会截断。此外,我们还讨论了涉及int类型或更小类型的任何数学运算都会导致结果为int。最后,我们详细讨论了当其中一个或两个操作数是字符串时+运算符的工作方式。在这些情况下,执行字符串连接操作。

接下来,我们讨论了关系运算符。这些运算符的结果始终是布尔值,当我们在第四章中构建条件语句时将使用它们。

由于 Java 无法执行不同类型之间的操作,因此尽可能进行隐式提升。这就是 Java 在内存中的某个位置将较小的类型提升为较大的类型的方式。这是 Java 在操作中无意识地继续下去的方式。

然后,我们讨论了逻辑运算符:&&、||、&、|和^。为了帮助理解,我们展示了真值表。逻辑&&和逻辑||运算符都是短路运算符。理解这一点很重要,因为求值的顺序优于优先级。

位运算符,位与运算符(&)和位或运算符(|),与&&和||类似,但不同之处在于,与&&和||不同,&和|永远不会短路,并且也可以与整型操作数一起工作。

三元运算符有三个操作数。它评估一个布尔表达式,并根据布尔表达式是true还是false将两个值之一赋给一个变量。

关于运算符,我们最后讨论的是复合赋值运算符,每个数学运算符都有一个。

在我们讨论类型转换时,我们涵盖了宽化和狭义转换。宽化是在后台进行的,通常被称为隐式提升。这里没有风险,因为被提升的类型可以轻松地适应目标类型。

狭义转换需要使用类型转换。这是因为,既然你正在从一个需要更多存储空间的类型转换到一个需要较少存储空间的类型,那么可能会丢失数据。

现在我们知道了如何使用运算符,在下一章中,我们将转向条件语句,其中经常使用运算符。

第四章:条件语句

在 第三章 中,我们学习了 Java 运算符。我们讨论了运算符的两个重要属性,即优先级和结合性。优先级有助于分组共享的操作数。当优先级级别相同时,结合性用于分组。

我们讨论了一元运算符——前缀和后缀增量/减量、类型转换和逻辑非。我们还涵盖了二元运算符——算术、关系、逻辑、位运算和复合赋值。我们学习了当操作数(或两者)为字符串时 + 符号的行为。我们讨论了逻辑与 (&&) 和逻辑或 (||) 以及它们的短路特性。最后,我们介绍了具有三个操作数的三元运算符。

我们还学习了 Java 的类型转换。这可以隐式地进行,称为隐式提升或宽化。另一种选择是显式转换,称为窄化。在窄化时,我们必须将类型转换为目标类型,以消除编译器错误。最后,我们讨论了编译时常量,由于它们的值永远不会改变,因此使编译器能够应用不同的规则。

现在我们已经了解了运算符,让我们用它们做一些有趣的事情。到本章结束时,你将能够使用 Java 的运算符来创建条件语句。条件语句使我们能够做出决策。此外,你将理解 Java 中的一个基本概念,即作用域。

本章我们将涵盖以下主要内容:

  • 理解作用域

  • 探索 if 语句

  • 掌握 switch 语句和表达式

技术要求

本章的代码可以在 GitHub 上找到,链接为 github.com/PacktPublishing/Learn-Java-with-Projects/tree/main/ch4。

理解作用域

在编程中,作用域定义了变量在程序中的可用性。这通常被称为变量的可见性。换句话说,变量在代码中的“可见”位置在哪里。Java 使用块作用域。为了解释 Java 的作用域,我们首先必须了解什么是块。

什么是块?

大括号分隔代码块。换句话说,一个块从开括号 { 开始,以闭括号 } 结束。注意,大括号面对面,如 {}。一个变量从其声明的地方开始,到该块的闭括号 },都是可见并可用的。图 4*.1* 展示了一个代码示例,以帮助解释:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_1.jpg

图 4.1 – Java 中的块作用域

在前面的图中,我们在第 5 行声明了一个 int 类型的变量 x 并将其初始化为 1。当前代码块是包围在 {} 中的 Java 语句组。因此,x 变量的代码块从第 4 行开始,即开括号所在的位置,到第 15 行结束,即闭括号所在的位置。因此,x 的作用域从声明它的第 5 行开始,到当前作用域的闭括号第 15 行。当我们第 6 行引用 x 时,没有问题,因为 x 在作用域内。

在第 8 行,我们使用 { 开始一个新的代码块/作用域。尽管有些不寻常,因为第 8 行之前没有代码,但第 8 行到第 12 行定义了一个有效的代码块。注意,外部作用域中的变量可以在内部(嵌套)作用域中可见。这可以在第 11 行看到,在内部作用域中,我们引用了在外部作用域中声明的变量,即 x,没有任何问题。

然而,情况并非总是如此;在内部作用域中定义的变量在外部作用域中不可见。y 变量的作用域从其声明处(内部作用域的第 9 行)到第 12 行(该作用域的闭括号)。因此,我们在第 14 行得到编译错误,因为在外部作用域中我们引用了 y 变量。

缩进

缩进确实有助于识别代码块和作用域。我们使用的风格是在行的末尾开始代码块。例如,在 图 4.1 中,注意第 3 行的开括号 {。该代码块的闭括号 }(以及作用域)在第 16 行。从缩进的角度来看,闭括号 } 直接位于第 3 行的 public 关键字下方。更具体地说,闭括号直接位于 public 中的 ‘p’ 下方。虽然这对编译不是必需的,但它确实使代码更容易阅读和维护——作用域从第 3 行开始,要找到作用域的结束,只需扫描程序以找到匹配的闭括号 },该括号与 public(第 3 行)对齐。

第 4 行也定义了一个代码块和作用域。第 15 行包含该作用域的匹配闭括号 }——注意闭括号与关键字 public(第 4 行)对齐。幸运的是,编辑器在保持代码正确缩进方面非常有帮助。

总结来说,代码块是用 {} 定义的。由于 Java 使用代码块作用域,代码块定义了变量可以使用的范围。变量在嵌套作用域中是可见的,但反之则不然。

现在我们已经了解了 Java 中的作用域,让我们来检查 Java 中的条件逻辑。我们将从 if 语句开始。

探索 if 语句

如其名所示,条件语句基于条件的评估。这个条件的结果是 true 或 false——换句话说,是一个布尔值。图 4.2 介绍了整体 if 语句的语法:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_2.jpg

图 4.2 – if 语句的语法

前一个图中的方括号 [] 表示某项是可选的。例如,else if 语句和 else 语句都是可选的。if 语句本身是必需的。三个省略号 ... 表示你可以有任意数量的 else if 语句(或者一个都没有)。

现在我们有了整体语法,让我们将其分解成更小的部分。

if 语句本身

如前所述,if 语句评估一个 boolean 表达式。这个 boolean 表达式被括号包围。如果 if 子句之后只有一个语句,则界定代码块的括号是可选的。然而,始终显式声明一个代码块被认为是良好的实践。图 4*.3* 展示了两种风格:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_3.jpg

图 4.3 – 简单的 if 语句

首先,让我们解释一下代码。注意缩进,这是由代码编辑器自动提供的。这使得更容易看到受各种 if 语句控制的语句。第 9 行和第 11 行都展示了简单的 if 语句,它们只控制一个语句。第 9 行控制第 10 行。这意味着如果第 9 行是真的,则第 10 行将执行。如果第 9 行是假的,则跳过第 10 行。同样,如果第 11 行是真的:第 12 行将执行;如果第 11 行是假的,则跳过第 12 行。

然而,如果你希望在 if 语句为真时执行两个或更多语句,则需要一个代码块。这由第 13 行到第 16 行的代码块演示。如果第 13 行的布尔表达式评估为真,则第 14 行和第 15 行的两个语句都将执行。这是因为它们在一个代码块中。

关于运行程序,当 x 初始化为 5,y 为 4 时,当第 9 行执行,它为真(因为 5 > 4)。因此,第 10 行执行,因此 图 4*.3* 的输出是 5 > 4。第 11 行和第 13 行都执行了,但它们都评估为假,所以屏幕上没有其他输出。

假设在第 8 行,我们将变量初始化如下:

int x=4, y=5;

现在,if(x > y) 为假且第 10 行未执行;if(x < y) 为真且第 12 行将 4 < 5 输出到屏幕上;if(x == y) 也为假,因此第 14 行和第 15 行未执行。

最后,让我们通过以下方式更改第 8 行,使变量相等:

int x=4, y=4;

现在,if(x > y) 为假,所以第 10 行未执行;if(x < y) 也为假,所以第 12 行未执行;然而,if(x == y) 为真,所以第 14 行将字符串 s 构建为 “4 == 4”,第 15 行将其输出。

注意缩进,这是由代码编辑器自动提供的。这使得更容易看到受各种 if 语句控制的语句。

else if 语句

在 图 4*.3* 中,没有代码来处理 if 表达式评估为假的情况。这就是 else if 语句发挥作用的地方。图 4*.4* 展示了代码中的 else if:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_4.jpg

图 4.4 – else if语句

由于x是 4 而y是 5(第 19 行),第 20 行的if表达式评估为假,因此控制跳转到第 22 行,那里第一个else if被评估。由于这评估为真,第 23 行被执行。现在,不会评估其他分支。换句话说,在执行第 23 行之后的下一行代码是第 27 行。请注意,根据良好的编码实践,每个分支都应编码为一个块,即使每个块中只有一个语句。

如果x被初始化为5,那么第 20 行和第 22 行都会评估为假。第 24 行将是真,因此第 25 行将被执行。

对于if和else if语句不匹配的情况,我们可以使用else语句。现在让我们来讨论这个问题。

else语句

图 4.4中的代码评估了比较x和y时所有可能的情况。x可能大于、小于或等于y。这种逻辑非常适合引入else语句。else语句是一个通配符。正如图 4.2中的[]所示,else子句是可选的。如果存在,它必须在任何if和/或else if子句之后编码。图 4.5是使用else子句重构的图 4.4,除了图 4.5中的x和y的值现在相同。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_5.jpg

图 4.5 – else语句

在图 4.5中,由于x和y都是4,第 31 行和第 33 行评估为false。然而,第 35 行没有条件,因为它只是一个else语句(而不是else if)。这意味着从第 35 行开始的代码块将自动执行,第 36 行将被执行。

关于if else语句,了解可能出现的微妙问题很重要,这个问题被称为“悬挂的else”问题。

悬挂的else

考虑以下未缩进的代码,它没有使用代码块:

boolean flag=false;                // line 1if (flag)                          // line 2
if (flag)                          // line 3
System .out.println("True True");  // line 4
else                               // line 5
System.out.println("True False");  // line 6

这段代码有两个if语句但只有一个else语句。else与哪个if匹配?规则是:当else语句寻找与if匹配时,它将与它向上回溯代码时遇到的最近的未匹配if匹配。

依照这个规则,else语句与第二个if(第 3 行)匹配,因为那个if语句尚未匹配。这意味着第 2 行的if语句仍然未匹配。当使用适当的缩进编写代码时,代码会更加清晰:

if (flag)                    // line 2    if (flag)                // line 3
        System.out.println("True True");     // line 4
    else                    // line 5
        System.out.println("True False");    // line 6

这一点通过输出得到证实。如果flag为真,输出将是"True True";然而,如果flag为假,屏幕上则没有任何输出。有趣的是,现在无法到达第 6 行(因为布尔变量只有两个值:true和false)。

使用代码块可以使代码更容易理解,如下所示:

if (flag) {    if (flag) {
        System.out.println("True True");
    }
    else {
        System.out.println("True False");
    }
}

这就是为什么即使对于一条语句,使用代码块也是非常有帮助的。在整个书中,我们将使用适当的缩进和代码块来帮助清晰和易于理解。

现在,让我们来看一个更复杂的例子。这个例子(以及随后的其他例子)使用了 Java 应用程序编程接口(API)中预定义的Scanner类。API 是一套预定义的类型(例如类),可供我们使用。随着我们在书中继续前进,我们将涵盖这些主题,但可以说 API 非常有用,因为它为我们提供了预定义和经过良好测试的代码。

Scanner类位于java.util包中。因此,我们需要简要讨论这两个包和Scanner类。

包

包是一组相关的类型,例如类,我们可以使用。方便的是,许多已经为我们准备好了,可以在 API 中使用。

要访问这些类型,我们需要将它们“导入”到我们的代码中。为此,Java 提供了import关键字。任何import语句都放在文件的顶部。我们可以使用*通配符导入整个包;例如:import java.util.*;。我们也可以通过在import语句中命名它来显式地导入特定类型;例如:import java.util.Scanner;。

当你在类型前加上它的包名,例如java.util.Scanner,这被称为“完全限定名”。我们可以省略import语句,每次简单地使用其完全限定名来引用Scanner;换句话说,在提到Scanner的地方,将其替换为java.util.Scanner。然而,一般来说,导入类型并使用其非限定名是首选的。

有一个包是自动对我们可用的(已导入),那就是java.lang包。例如,String类位于java.lang中,这就是为什么我们永远不需要导入任何东西就能访问String类。

Scanner 类

了解这一点是有帮助的,即虽然Scanner是一个多功能的类,但就我们的目的而言,我们只需使用Scanner来使我们能够从用户那里检索键盘输入。考虑到这一点,请注意,在示例中使用的System.in指的是已经打开并准备好提供输入数据的标准输入流。通常,这对应于键盘。因此,System.in非常适合通过键盘从用户那里获取输入数据。Scanner类提供了各种解析/解释键盘输入的方法。例如,当用户在键盘上输入一个数字时,nextInt()方法会以int原生的形式提供这个数字给我们。我们将在示例中使用这些方法。

嵌套 if 语句

现在我们已经讨论了包和Scanner,在图 4.6和图 4.7中,我们讨论了一个更复杂的if-else示例,该示例处理来自用户的输入(通过键盘)。这两个图都与一个示例相关。图 4.6侧重于声明常量以使代码更易读。此外,图 4.6还侧重于声明和使用Scanner。另一方面,图 4.7*侧重于随后的if-else结构。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_6.jpg

图 4.6 – 使用 Scanner 从键盘获取输入

第 11 到 14 行使用final关键字定义常量。这意味着它们的值不能改变。使用大写标识符(单词之间用下划线分隔)作为常量的命名习惯是好的。这将使图 4.7*中的代码更易读;换句话说,我们不会比较month与1(在这个上下文中表示一月),而是将month与JAN进行比较,这样读起来更好。为了简洁起见,我们每行声明了三个常量,但你也可以轻松地每行声明一个。

在图 4.6*的第 16 行创建我们的Scanner对象引用,sc。本质上,我们创建了一个引用,即sc,它指向使用new关键字创建的Scanner对象。正如之前所述,System.in意味着sc正在查看键盘。这个引用是我们用来与Scanner交互的,就像遥控器用来与电视交互一样。

第 17 行提示用户在键盘上输入一个月份(1..12)。这实际上非常重要,因为没有提示,光标就会闪烁,用户会想知道他们应该输入什么。第 18 行是Scanner真正发挥作用的地方。在这里,我们使用nextInt()方法获取一个数字。目前,只需知道当我们调用sc.nextInt()时,Java 不会返回到我们的代码中,直到用户输入了一些内容并按下了回车键。目前,我们将做出(方便的)假设,它是一个整数。我们将返回的int原始值存储在我们的int原始值month中。现在,在我们的代码中,我们可以使用用户输入的内容。图 4.7*展示了这一过程。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_7.jpg

图 4.7 – 一个复杂的 if 语句

注意,前面图像中显示的代码是图 4.6的延续。在第 20 行,我们声明了一个int变量,即numDays,并将其初始化为0。第 21 到 22 行是if语句的开始。使用boolean逻辑或运算符,if语句检查month值是否与图 4.6中定义的任何常量匹配。在后台,使用常量值,所以实际上,if语句如下:

if(month == 1 || month == 3 || month == 5 || month == 7 ||   month == 8 || month == 10 || month == 12)

注意,每次都必须指定month变量。换句话说,if(month == JAN || MAR || MAY || JUL || AUG || OCT || DEC)将无法编译。

假设用户输入1(代表一月),month变为1,因此第 21 到 22 行评估为真,第 23 行将numDays设置为31。如果用户输入3、5、7、8、10和12,分别代表三月、五月、七月、八月、十月和十二月,逻辑相同。

如果用户输入4(代表四月),第 21 到 22 行评估为假,第 24 行的else if语句将被评估。第 24 行评估为真,第 25 行将numdays设置为30。如果用户输入6、9和11,分别代表六月、九月和十一月,逻辑相同。

现在,让我们处理用户输入的2,代表二月。第 21 到 22 行的if条件和第 24 行的else if条件都评估为假。第 26 行评估为真。现在,我们需要回顾闰年的逻辑。当然,二月每年都有 28 天(所有月份都是这样!),但在闰年时会有额外的一天。判断是否为闰年的逻辑如下:

  • year是 400 的倍数 => 闰年

  • year是 4 的倍数且year不是 100 的倍数 => 闰年

以下都是闰年:2000 年(满足情况 A),2012 年和 2016 年(都满足情况 B)。

由于闰年算法依赖于年份,我们首先需要从用户那里获取年份。第 27 到 28 行完成了这个任务。然后,我们遇到了从第 30 到 34 行的嵌套if语句,它根据前面概述的逻辑确定用户输入的年份是否为闰年。第 30 行实现了 A 和 B 两种情况下的逻辑。判断year是否是 400 的倍数是通过(year % 400 == 0)实现的。year是 4 的倍数且不是 100 的倍数的条件是通过

(year % 4 == 0 && !( year % 100 == 0))。通过在它们之间使用逻辑或运算符,使得任一条件满足闰年计算。假设年份为 2000,第 30 行将为真,numDays将被设置为29。假设年份为 1900,第 30 行的if语句为假,由于第 32 行没有条件(它只是一个else语句),因此执行第 33 行,将numDays设置为28。

无效的month值,例如25或-3,会导致第 35 行的else分支被执行。结果,第 36 行将输出错误消息到屏幕。

第 38 到 40 行在numDays从其初始值0改变时输出天数。第 38 行的if语句防止在用户输入无效的month值时在屏幕上显示消息"Number of days is: 0"。

这就结束了我们对if语句的处理。现在,让我们来检查两个switch语句和表达式,在某些情况下,它们可能是一个更优雅的选择。

掌握switch语句和表达式

复杂的 if 语句,带有许多 else if 分支和一个 else 分支可能会很冗长。在许多情况下,switch 结构可以更加简洁和优雅。让我们从 switch 语句开始。

switch 语句

首先,让我们来检查 switch 语句的语法。图 4.8 介绍了语法。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_8.jpg

图 4.8 – switch 语句语法

switch 语句评估一个表达式。从 Java 21 开始,表达式可以是一个整型原始值(不包括 long)或任何引用类型。这意味着我们可以对 byte、char、short 或 int 类型的原始变量进行 switch,也可以对类类型、枚举类型、记录类型和数组类型进行 switch。现在 case 标签可以包括一个 null 标签。Java 21 还引入了 switch 的模式匹配。当涉及到这些主题时,我们将展示另一个 switch 示例来演示这个功能(第九章)。在此之前,我们将专注于更传统的 switch。

包装类型

对于每种原始类型,都有一个相应的类,称为“包装类型”:byte(由 Byte 包装),short (Short),int (Integer),long (Long),float (Float),double (Double),boolean (Boolean),和 char (Character)。它们之所以被称为包装类型,是因为它们代表封装原始值的对象。由于它们是类类型,因此有可用的一些有用方法。例如,int val = Integer.parseInt("22"); 将 String "22" 转换为数字 22,存储在 val 中,我们可以在其中执行算术运算。

刚刚评估的表达式与 case 标签进行比较。case 标签与 switch 表达式具有相同类型的编译时常量。如果与 case 标签匹配,则执行相关的代码块(注意:在 case 或 default 块中不需要花括号)。要退出 case 块,请确保插入一个 break 语句。break 语句退出 switch 块。然而,break 语句是可选的。如果您省略了 break 语句,即使没有匹配,代码也会 跌入 到下一个 case 标签(或 default)。

default 关键字用于指定在没有任何 case 标签匹配时执行的代码块。通常,它被编码在 switch 块的末尾,但这不是强制性的。实际上,default 可以出现在 switch 块的任何位置,具有类似的语义(然而,这并不是一个好的编程实践)。

图 4.9 展示了一个示例。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_9.jpg

图 4.9 – 在 String 上进行 switch 的示例

使用 Scanner 类,第 18 到 20 行会询问并从用户那里检索一项运动。请注意,在这种情况下使用的 Scanner 方法是 next(),它返回一个 String 类型(与原始类型相反)。

第 21 行到第 31 行展示了 switch 块。请注意,第 22 行和第 25 行的 case 标签都是 String 编译时常量。如果用户输入 "Soccer",则第 22 行的 case 标签匹配,并且第 23 行和第 24 行都将执行。有趣的是,尽管有两个语句要执行,但这里不需要花括号。这是 switch 块的一个特性。由于第 22 行匹配,第 23 行将执行,并且 "I play soccer" 将被显示在屏幕上。第 24 行的 break 语句确保退出 switch 块,并且不执行 "Rugby" 部分。

如果用户输入 "Rugby",则第 25 行匹配,并且 "I play Rugby" 将被显示在屏幕上。同样,这次在第 27 行的 break 语句确保退出 switch 块,并且不执行 default 部分。

如果用户输入 "Tennis",则第 22 行和第 25 行的 case 标签都不会匹配。这时 default 部分就派上用场了。当没有任何 case 标签匹配时,将执行 default 部分。通常,default 部分被编码在 switch 块的末尾,并且传统上插入 break 语句以确保完整性。

case 标签是区分大小写的

注意,case 标签是区分大小写的。换句话说,在 图 4*.9* 中,如果用户输入 "soccer",则第 22 行的 case 标签不会匹配。自然地,"Rugby" 的 case 标签也不会匹配。因此,将执行 default 部分,并且 "Unknown sport" 将被显示(打印)在屏幕上。

让我们来看另一个例子。图 4*.10* 是一个基于整数的 switch 语句。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_10.jpg

图 4.10 – 整数开关示例

在前面的图中,第 37 行声明了一个编译时常量 two,并将其初始化为整数文字 2。第 38 行通过切换名为 number 的 int 变量开始 switch 块,该变量在第 36 行根据用户输入声明并初始化。现在 switch 块中的所有 case 标签都必须是整数——无论是第 39 到 43 行和第 47 行的文本值,还是第 46 行的编译时常量。请注意,如果 two 变量不是 final 的,将生成编译时错误(因为它不再是常量)。

在这个例子中,我们有多个标签一起,例如,第 39-43 行。这段代码可以读作 如果数字是 1 或 3 或 5 或 7 或 9,则执行以下操作。例如,如果用户输入 1,第 39 行的 case 标签匹配。这被称为 switch 语句的 入口点。由于第 39 行没有 break 语句,代码会 穿透 到第 40 行,尽管第 40 行有一个 case 标签用于 3。同样,第 40 行没有 break 语句,代码会 穿透 到第 41 行。实际上,代码会从入口点一直执行到遇到 break 语句(或 switch 语句本身的末尾)。这种 穿透 行为使得 如果是 1 或 3 或 5 或 7 或 9 类型的逻辑能够工作。如果没有这种 穿透 行为,我们就必须为每个 case 标签复制第 44 至 45 行!第 44 行使用 String 追加输出输入的数字是奇数,通过将 "is odd" 追加到数字上——例如,"7 is odd"。第 45 行是确保我们退出 switch 块的 break 语句。

第 46 行只是为了演示编译时常量对 case 标签有效。第 47 行显示,如果需要,case 标签可以按水平方式组织。记住,缩进和间距的使用只是为了提高人类可读性——编译器只看到一长串字符。因此,第 46 至 47 行与数字 2、4、6、8 和 10 匹配。同样,使用 穿透 逻辑来保持代码简洁。第 48 至 49 行输出该数字是偶数,并退出 switch 块。

第 50 行是 default 部分,用于处理 1..10 范围之外的任何数字。第 51 行输出输入的数字超出范围,第 52 行是 break 语句。虽然在这里 break 语句不是严格必需的(因为 default 在 switch 语句的底部),但包括它是良好的实践。

让我们用 switch 语句重写 图 4.7 中的代码,而不是复杂的 if-else 语句。请注意,图 4.6 仍然适用于声明 Scanner 和使用的常量;这就是为什么我们将其分开(这样我们就可以同时使用 if 和 switch 代码)。图 4.11 表示使用 switch 语句重构的 图 4.7。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_11.jpg

图 4.11 – 使用 switch 语句重构 if 语句

在前面的图中,我们可以看到 switch 语句从第 23 行开始。第一组 case 标签在第 24 行,第二组在第 27 行。二月,作为特例,有一个单独的 case 标签(第 30 行)。最后,default 标签在第 40 行。就我个人而言,由于没有像 图 4.7(第 21 至 22 行和第 24 行)中所需的多个逻辑 OR 表达式,我认为在这个例子中使用 switch 更为可取。

现在我们已经介绍了有效的 switch 语句,让我们在 图 4.12 中检查一些可能导致编译器错误的情况。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_12.jpg

图 4.12 – 一些 switch 编译器错误

在前面的图中,我们正在根据 byte 变量 b 进行切换。回想一下,有效的 byte 范围是 -128 到 +127。第 60 行演示了最小和最大值是正常的。第 63 行显示,由于 128 超出了我们的 byte 类型 b 的范围,超出 switch 变量范围的 case 标签会导致编译器错误。第 64 行原本是好的,直到第 65 行使用了相同的 case 标签——不允许 case 标签重复。

我们将通过讨论 switch 表达式来结束对 switch 的讨论。

switch 表达式

与所有表达式一样,switch 表达式计算出一个单一值,因此使我们能够返回值。到目前为止的所有 switch 示例都一直是 switch 语句,它们不返回任何内容。请注意,在 switch 表达式中不允许使用 break 语句。

另一方面,switch 表达式 会返回一些内容——无论是隐式地还是显式地(使用 yield)。我们很快就会解释 yield,但请注意,yield 不能在 switch 语句中使用(因为它们不返回任何内容)。此外,switch 语句可以 穿透,而 switch 表达式则不能。这些差异在 表 4.1 中得到了封装。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_Table_01.jpg

表 4.1 – switch 语句与 switch 表达式的比较

让我们看看一些示例代码来展示这些差异。图 4.13 是传统的 switch 语句。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_13.jpg

图 4.13 – 一个传统的 switch 语句

在前面的图中,我们正在根据 String 变量 name 进行切换。由于它被初始化为 "Jane",第 19 行为真,第 23 行将 nLetters 设置为 4(“Jane” 中的字母数量)。第 24 行的 break 语句确保没有穿透到第 27 行。第 39 行将 4 输出到屏幕上。

注意,代码相当冗长,需要正确使用 break 语句来防止穿透。此外,这些 break 语句编写起来很繁琐,很容易忘记。图 4.14 表示使用 switch 表达式编写的 图 4.13。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_14.jpg

图 4.14 – switch 表达式

前面的图示显示了新的case标签,其中标签以逗号分隔,并且一个箭头符号将标签与表达式(或代码块)分开。任何地方都不需要break语句,因为没有需要担心的穿透行为。由于name仍然是"Jane"(来自图 4.13,第 17 行),第 42 行被执行,将4初始化/返回到nLetters中。因此,第 50 行输出4。请注意,switch表达式中的case标签列表必须是详尽的。在几乎所有情况下,这意味着需要一个default子句。在这个例子中,default子句执行一个代码块(第 45-48 行),其中将错误输出到屏幕上,并使用yield关键字,将nLetters初始化为(错误值)-1。

我们可以通过直接将表达式值返回到System.out.println()语句来省略对nLetters变量的需求。图 4.15展示了这一点。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_15.jpg

图 4.15 – 一个直接返回到 System.out.println()的 switch 表达式

在前面的图示中,没有变量用于存储switch表达式的结果。这使得代码更加简洁。switch表达式的结果直接返回到System.out.println()语句中。同样,4被输出到屏幕上。

yield 关键字

图 4.14和图 4.15是简单的switch表达式,其中(大部分情况下),箭头符号右侧是返回的值。然而,如果你不仅想返回一个值,还想执行一个代码块,那么yield就派上用场了。图 4.16展示了在switch表达式中使用yield。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_16.jpg

图 4.16 – 使用 yield 的 switch 表达式

前面的图示强调了这样一个事实,如果你需要在switch表达式中执行多个语句,你必须提供一个代码块。这通过第 61-64 行、65-68 行、69-72 行和 73-76 行的花括号来展示。要从代码块返回一个表达式结果,我们使用yield。这就是第 63、67、71 和 75 行所做的工作,分别返回4、5、6和-1。由于name仍然是"Jane",第 63 行从switch表达式中返回4,将nLetters初始化为4。因此,第 78 行输出4。

为了增加灵活性,你可以在一定程度上混合语法。换句话说,在switch表达式中可以使用常规的case标签,在switch语句中可以使用新的case标签语法。然而,如前所述,break只出现在switch语句中,而yield(如果需要)只出现在switch表达式中。图 4.17是对图 4.13中冗长的switch语句的重构,其中使用了新的case标签。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_17.jpg

图 4.17 – 使用新case标签和箭头标记的switch语句

在前面的图中,标签是逗号分隔的,并且存在箭头标记。和之前一样,在箭头标记的右侧,我们有nLetters的初始化,即名字中的字母数。然而,与图 4.13不同,因为我们使用了箭头标记,所以不需要break语句。然而请注意,根据default子句(第 86-89 行),代码块需要大括号。

我们也可以使用常规的case标签与switch表达式一起使用。这如图 4.18 所示,它是图 4.16 的重构版本。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_04_18.jpg

图 4.18 – 使用旧式case标签的switch表达式

在前面的图中,使用了旧式的case标签。这意味着关键字case必须在每个标签之前。然而,代码块的括号可以省略。由于这是一个switch表达式,当找到匹配时,会有多个语句需要执行,因此我们需要使用yield来返回表达式结果。由于name变量在整个示例中从未从"Jane"改变,因此在第 95 行进行匹配,导致第 99 行将"There are 4 letters in: Jane"输出到屏幕。第 100 行的yield返回4,因此nLetters被初始化为4。最后,第 114 行将4输出到屏幕。

这完成了我们对switch表达式以及一般switch语句的处理。现在,我们将所学应用到实践中。

练习

我们最终拥有了做出决策的编码能力。中生代伊甸园将因此受益匪浅。让我们展示我们新获得的本领,怎么样?

  1. 我们需要确定恐龙是肉食性还是草食性。编写一个if语句,根据布尔变量打印恐龙是肉食性还是草食性。这些信息对于饲养和护理指南至关重要。

  2. 不同的物种需要不同的护理策略,并表现出独特的行为特征。编写一个switch语句,根据恐龙的物种打印恐龙的描述。这将有助于教育员工和公园游客。

  3. 有些恐龙比其他恐龙更难处理。编写一个if语句,检查是否有足够的经验来处理某种类型的恐龙。这确保了我们的恐龙和员工的安全。

  4. 我们正在与美丽但危险的生物打交道。所以,安全第一。编写一个程序,如果公园的安全评级低于某个阈值,则打印警告信息。我们必须始终保持警惕,以防止可能伤害我们的员工、游客或恐龙的问题。

  5. 适当的住所对于恐龙的幸福至关重要。编写一个switch语句,根据恐龙的大小(XS、S、M、L 或 XL)将其分配到特定的围栏。

  6. 适当的营养对于维持我们恐龙的健康至关重要。编写一个if语句,根据恐龙的体重确定其每天所需的喂食次数。

  7. 正确地委派任务是保持运营顺利的重要因素。创建一个程序,使用switch语句根据员工的职位分配不同的职责。

  8. 公园不是全天 24 小时对日间游客开放。编写一个if语句,根据时间检查公园是否对日间游客开放。他们从上午 10 点到下午 7 点对日间游客开放。这有助于管理游客的期望和员工的时间表。

项目 – 任务分配系统

中生代伊甸园的经理需要一个系统化的方法来管理团队并确保所有任务都高效完成。

设计一个简单的程序,根据员工的角色(例如,喂食、清洁、安全和导游)将任务分配给中生代伊甸园的员工。该程序应根据时间、员工的角色和其他因素(如公园的安全评级)来决定任务。

这个程序不仅可以帮助简化运营,还可以确保我们员工、游客以及最重要的是恐龙的安全和满意度!

摘要

在本章中,我们首先解释了 Java 使用块作用域。一个块由{}界定。一个变量从声明点可见到该块的结束}。由于块(以及因此作用域)可以嵌套,这意味着在块中定义的变量对任何内部/嵌套块都是可见的。然而,反之则不成立。在内部块中声明的变量在外部块中不可见。

条件语句使我们能够做出决定,并基于条件的评估结果为真或假。if语句允许评估多个分支。一旦某个分支评估为真并执行,则不会评估其他分支。if语句可以单独编码,而不需要任何else if或else子句。else if和else子句是可选的。然而,如果存在else子句,它必须是最后一个子句。我们看到了一个复杂的if示例如何导致代码冗长。

我们简要讨论了包和Scanner类。Scanner类位于java.util包中,对于从用户那里获取键盘输入非常有用。

我们还讨论了switch语句和switch表达式。一个表达式可以返回一个值,但一个语句不能。我们看到了switch语句如何使复杂的if语句更加简洁和优雅。你switch上的表达式(通常是变量)可以是原始类型byte、char、short或int;或者是一个引用类型。case标签必须是编译时常量,并且必须在switch变量的范围内。switch语句有一个穿透特性,这使得多个case标签可以使用相同的代码段而不需要重复。然而,这种穿透行为需要break语句来退出switch语句。这需要小心,因为break语句很容易忘记。

switch表达式可以返回一个值。它们没有穿透逻辑,所以不需要break语句。这使得代码更加简洁且错误更少。如果你想在switch表达式中执行一个代码块,请使用yield来返回值。

switch语句不支持yield(因为它们不返回任何内容),而switch表达式不支持break(因为它们必须返回某些内容)。然而,两种case标签,即旧式的case X:和新式的case A, B, C ->,都可以与switch语句或switch表达式一起使用。

现在我们知道了如何做出决策,我们将在下一章中继续讨论迭代,我们将检查 Java 结构,这些结构使我们能够重复执行语句。

第五章:理解迭代

在 第四章 中,我们学习了 Java 中的作用域和条件语句。作用域决定了标识符的可见性——换句话说,你可以在哪里使用它们。Java 使用块作用域,由花括号 {} 定义。作用域可以嵌套,但不能反过来。

我们讨论了 if 语句的变体。这些语句中的每一个都会评估一个布尔条件,结果为真或假。如果为真,则执行该分支,不评估其他分支。如果为假,则评估下一个分支。除非存在 else 子句,否则可能根本不会执行任何分支。

对于复杂的 if 语句,Java 支持更优雅的 switch 结构。我们考察了 switch 语句及其 穿透 行为,以及 break 语句的使用。此外,我们还讨论了 switch 表达式,其中可以返回一个值,以及它们使用 yield 的方式。

现在我们已经了解了条件逻辑,让我们来考察迭代(循环)。循环结构使我们能够重复执行语句和/或代码块有限次数,直到布尔条件为真或直到数组/集合中还有更多条目。到本章结束时,你将能够使用 Java 的循环结构。

在本章中,我们将涵盖以下主要主题:

  • while 循环

  • do-while 循环

  • for 循环

  • 增强的 for (for-each) 循环

  • break 和 continue 语句

技术要求

本章的代码可以在 GitHub 上找到,地址为 github.com/PacktPublishing/Learn-Java-with-Projects/tree/main/ch5。

while 循环

任何编程语言的一个重要特性是能够重复执行一个动作。这被称为“循环”。我们可能想要有限次数地重复一段代码,或者直到满足某个条件;例如,用户输入一个值表示循环应该终止。在大多数情况下,可以使用布尔表达式来确定循环是否继续。

while 循环是这样一种循环结构。只要布尔表达式为真,它就会重复执行一个语句或代码块。一旦布尔表达式为假,循环就会退出,然后执行 while 循环之后的下一个语句。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_1.jpg

图 5.1 – while 循环语法

在前面的图中,我们假设了一段代码块,因此有花括号 {}。当然,你可以省略花括号 {},循环将只重复执行一个语句(以分号结束)。有趣的是,由于布尔表达式可能一开始就是假的,while 循环可能根本不会执行。更正式地说,while 循环执行 零 次或多次。让我们看看一些例子。图 5.2 展示了一个简单的 while 循环:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_2.jpg

图 5.2 – 一个简单的 while 循环

在前一个图中的第 9 行,局部变量x被初始化为1。第 11 行评估布尔表达式x <= 3。由于x是1,布尔表达式为 true,循环执行。第 12 行输出"Loop: 1",第 13 行将x增加到2。第 14 行的}符号被到达,循环条件(第 11 行)会自动重新检查是否仍然成立。由于x现在是2且2 <= 3为 true,条件为 true,我们重新进入循环。第 12 行输出"Loop: 2",第 13 行将x增加到3。在第 14 行达到代码块的末尾,再次,循环继续表达式被重新评估。由于表达式3 <= 3为 true,循环再次执行。第 12 行输出"Loop: 3",第 13 行将x增加到4。再次,达到代码块的末尾并重新评估循环继续表达式。由于x现在是4且4 <= 3为 false,循环退出。这由第 15 行输出"Final x value is: 4"所示。

注意,如果在第 9 行,x被初始化为11(而不是1),那么第 11 行的初始布尔表达式将被评估为 false,循环将根本不会执行。

当你不知道循环将迭代多少次时,while循环非常有用。例如,循环继续表达式可能基于用户输入。图 5.3就是这样一种循环。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_3.jpg

图 5.3 – 基于用户输入结束的 while 循环

在前一个图中,展示了一个求和算法,该算法用于累加用户输入的正数序列。循环将一直进行,累加用户输入的数字,直到输入一个负数。这个负数自然地,不是总和的一部分。让我们更详细地讨论一下。

第 19 行声明了一个局部int变量sum并将其初始化为0。第 20 行声明了一个局部布尔变量keepGoing并将其设置为 true。第 21 行的布尔表达式评估为 true(由于第 20 行),因此循环块执行。第 22 行声明了我们的Scanner引用sc指向键盘。第 23 行提示用户输入一个数字,同时告知用户任何负数将终止循环。第 24 行使用Scanner方法nextInt()从用户那里获取一个整数(整数)。这个数字被存储在局部变量n中。第 25 行检查是否输入了一个负数。如果是这样,第 26 行将keepGoing标志设置为 false,这样循环就不会再次执行。如果用户输入了一个非负整数,那么输入的数字n将被添加到运行总和sum中。

让我们通过一个例子来了解一下。我们将添加以下数字:1、2和3,总共是 6。这是屏幕输出的样子:

Enter a number (negative number to exit) -->1
Enter a number (negative number to exit) -->
2
Enter a number (negative number to exit) -->
3
Enter a number (negative number to exit) -->
-1
Sum of numbers is: 6

让我们来检查代码中正在发生的事情。循环(第 21 行)被进入,因为第 20 行将keepGoing布尔值设置为 true。然后我们被提示输入第一个数字(第 23 行)。我们输入1,导致第 24 行将n初始化为1。由于n是1,第 25 行的if语句为假,执行else块(第 27-29 行);将sum设置为1(0 + 1)。

达到循环块末尾(第 30 行),循环继续表达式自动重新评估(第 21 行)。由于keepGoing仍然为 true,循环继续。我们被提示输入第二个数字;我们输入2,sum变为3(1 + 2)。

再次达到循环块末尾,并且由于keepGoing仍然为 true,循环继续。我们被提示输入下一个数字;我们输入3,sum变为6(3 + 3)。

再次,达到循环块末尾,并且由于keepGoing仍然为 true,循环继续。我们被提示输入下一个数字。这次我们输入-1。由于n现在是负数,第 25 行的if语句为真,keepGoing被设置为 false(第 26 行)。现在,当下一次评估循环继续表达式时,由于keepGoing为 false,循环退出。

最后,第 31 行输出"数字之和是:6"。

现在我们已经介绍了while循环,让我们来考察它的近亲,do-while循环。

do-while 循环

正如我们在while循环中看到的那样,布尔循环继续表达式位于循环的开始。尽管与while循环相似,但do-while循环在一点上有所不同:在do-while循环中,循环继续表达式位于循环的末尾。因此,do-while循环至少执行一次。更正式地说,do-while循环执行一次或多次。

图 5.4展示了do-while循环的语法。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_4.jpg

图 5.4 – do-while 循环语法

如前图所示,循环继续表达式位于循环的末尾,在第一次循环迭代之后。注意在)之后有一个分号,;。

图 5.5展示了图 5.2的do-while版本。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_5.jpg

图 5.5 – 基于用户输入结束的 do-while 循环

在前面的图中,与图 5.2相比,唯一的区别是第 21 行和第 30 行。在第 21 行,我们简单地进入循环,因为在while循环中,没有条件阻止我们这样做。第 30 行检查是否可以重新进入循环。其余的代码相同,执行也相同。

虽然(请原谅这个双关语)给出的两个示例在结果上没有实质性的区别,但让我们考察一个使用while循环而不是do-while循环更可取的情况。

while 与 do-while

如前所述,do-while循环至少执行一次,而while循环可能根本不执行。这在某些情况下非常有用。让我们看看这样一个例子。图 5.6展示了一个检查一个人是否达到法定年龄购买酒精(在爱尔兰是 18 岁)的while循环。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_6.jpg

图 5.6 – 防止未成年购买酒精的while循环

在前面的图中,第 49 行声明了Scanner并将其指向键盘,这样我们就可以获取用户输入。第 50 行提示用户输入他们的年龄。第 51 行接收用户输入并将其存储在局部变量age中。第 52 行很重要。该条件防止循环使用无效的age执行。循环本身很简单,只是输出包含age的消息,这样我们就可以验证循环是否正确执行。

第 57 到 58 行非常重要,因为它们使我们能够提示并从用户那里获取一个新的age。代码故意覆盖了age变量。如果我们不这样做,那么age将保持为用户首次输入的值,我们将有一个无限循环。因此,第一个年龄是在进入while循环之前输入的,而其他每个年龄都是在循环的末尾输入的。这是while循环中的常见模式。第 52 行的条件防止任何小于 18 岁的age输入进入循环。

这是图 5.6中代码的第一次运行:

Please enter your age -- >21
As you are 21 years of age, you can purchase alcohol.
Please enter your age -- >
12

前两行:提示和用户输入,在while循环之前,由于 21 岁大于等于 18 岁,我们进入了循环。消息As you are 21 years of age, you can purchase alcohol.是完全正确的。最后两行:重复提示和用户输入,来自循环的底部。我们输入了12,这导致while循环终止。

如果在提示输入第一个年龄时我们输入12,以下是将显示的输出:

Please enter your age -- >12
Process finished with exit code 0

重要的是,关于购买酒精的消息没有出现。

现在,让我们看看do-while版本。图 5.7展示了图 5.6中while循环的do-while版本。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_7.jpg

图 5.7 – 防止未成年购买酒精的do-while循环

为了使代码尽可能相似,第 49 到 51 行保持不变。第 52 和 59 行是唯一有所更改的地方。现在条件位于循环的末尾,循环迭代一次之后。当我们从12岁开始时,这会产生影响,如输出所示:

Please enter your age -- >12
As you are 12 years of age, you can purchase alcohol.
Please enter your age -- >
12
Process finished with exit code 0

输出的第三行存在问题。显然,12 岁太小,不能购买酒精,但do-while循环需要if语句来保护其代码,而while循环则自动提供这种保护。因此,在这个例子中,使用while循环而不是do-while循环具有实质性的优势。

现在我们已经介绍了while循环和do-while循环,让我们现在讨论for循环。

for 循环

for循环有两种风格:传统的for循环和增强的for循环。增强的for循环也被称为for-each循环,它专门设计用来与数组集合一起工作。我们将首先检查传统的for循环。

传统 for 循环

这种类型的for循环在你事先知道要执行多少次迭代时非常有用。其语法在图 5**.8中有详细说明。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_8.jpg

图 5.8 – 传统的 for 循环

上一个图中的代码块是可选的。我们可以简单地控制一个语句,例如System.out.println("Looping");,并省略{}。for头是()内的部分。它由三个部分组成,由分号分隔:

  • i、j、k等等。

  • while循环和传统的for循环可以互换使用。

  • 增量/减量部分:这是你增加/减少循环控制变量(在初始化部分声明)的地方,以便循环终止。

我们必须理解循环执行的顺序。换句话说,哪个部分被执行,何时执行。图 5*.9*,展示了一个简单的for循环,将有助于这一点。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_9.jpg

图 5.9 – 一个简单的传统 for 循环

在这个图中,代码执行的顺序用数字顺序表示如下:

  1. i被声明并初始化为1。

  2. 布尔表达式:评估布尔表达式以确定是否可以执行循环。因为 1 <= 3,所以可以进入循环。

  3. 显示到屏幕上。

  4. i从1增加到2,然后执行跳转到布尔表达式。

  5. 评估布尔表达式:因为 2 <= 3,所以循环被执行。

  6. 将2显示到屏幕上。

  7. 从 2 增加到 3 增加 i:然后跳转到布尔表达式。

  8. 评估布尔表达式:因为 3 <= 3,所以循环被执行。

  9. 将3显示到屏幕上。

  10. 从 3 增加到 4 增加 i:然后跳转到布尔表达式。

  11. 评估布尔表达式:因为 4 不是<= 3,所以循环退出。

总结来说,初始化部分仅在循环开始时执行一次。布尔表达式被评估,并且假设它是真的,那么循环体将被执行,随后是增量/减量部分。布尔表达式再次被评估,并且再次假设它是真的,那么循环体将被执行,随后是增量/减量部分。这种循环体执行后跟增量/减量部分的重复执行会一直持续到布尔表达式失败,循环退出。

图 5*.10*展示了一个从3递减到1的for循环,每次递减 1:

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_10.jpg

图 5.10 – 一个按降序操作的简单 for 循环

在前面的图中,我们将 i 初始化为 3 并检查布尔表达式。由于 3 >= 1,我们进入循环并输出 3。然后我们将 i 减少到 2 并再次检查布尔表达式。由于 2 >= 1,我们输出 2 并将 i 减少到 1。由于布尔表达式仍然为真,我们输出 1 并将 i 减少到 0。此时,由于 i 为 0,布尔表达式为假,循环终止。

图 5*.11* 展示了一些代码示例,使我们能够进一步讨论这个循环结构。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_11.jpg

图 5.11 – 附加的传统 for 循环

在前面图中的第一个循环(第 20-22 行)中,需要注意的重要事情是 ; 符号,它位于 for 标头的 ) 符号之后。这个循环控制一个空语句!尽管缩进可能暗示了其他情况,但随后的代码块与循环没有任何关系,因此输出中只出现一次 "Looping"。实际上,循环迭代了三次,每次都没有做任何事情。第 21 行周围的代码块不基于任何条件,并且只执行一次(像正常一样)。

在第二个循环(第 24-26 行)中,循环控制变量 i 从 10 开始,每次增加 10,直到达到 60,此时循环终止。每个有效的 i 值都会输出到屏幕上——换句话说,10、20、30、40 和 50。请注意,第 27 行无法编译,因为前面循环中声明的每个 i 变量只在其各自的循环范围内有效。例如,第 20 行声明的 i 变量仅在直到第 22 行可用;同样,第 24 行声明的 i 变量仅在直到第 26 行可用。注意:显然,为了使代码编译和运行,第 27 行必须被注释掉。

最后一个循环(第 29-31 行)显示我们可以声明多个循环控制变量并在整个循环中使用它们。在这个循环中,我们声明了 i 和 j 并将它们都初始化为 0。布尔表达式为真,因为 i < 1 和 j < 1 都为真(true && true == true)。因此,我们执行循环并输出 0 和 0。然后 i 和 j 都增加到 1。循环条件失败,循环终止。

虽然 for 循环将在 第六章 中详细讨论,但由于 for 循环与数组如此自然地匹配,我们在此也插入了一些示例。让我们首先检查传统的 for 循环如何用于处理数组。

处理数组

任何for循环都适用于遍历数组。数组简单地说是一个分配了内存并赋予标识符名称的区域,以便于引用。数组由元素组成,这些元素按连续的内存位置组织 – 换句话说,数组元素在内存中紧挨着。这使得使用循环处理数组变得容易。

数组中的每个元素都通过索引来访问。关键的是,数组索引从0开始,每次递增 1。因此,最后一个有效的索引是数组大小减一。例如,大小为5的数组有有效的索引0、1、2、3和4。图 5.12是一个处理数组的循环。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_12.jpg

图 5.12 – 使用传统 for 循环处理数组

在这个图中,第 33 行声明了一个包含值1、2和3的int数组,分别位于索引0、1和2。数组的长度,可以通过length属性访问,是3。for循环(第 34-35 行)处理数组,逐个输出每个位置。因此,当i为0时,ia[0]将1输出到屏幕上;当i为1时,ia[1]输出2,当i为2时,ia[2]输出3。

现在我们已经介绍了传统的for循环,让我们来考察增强型for循环。

增强型 for 循环

如前所述,增强型for循环,也称为for-each循环,非常适合处理数组以及/或者集合。我们将在第十三章中详细讨论集合。目前,只需想象一个集合是一个列表的项目。增强型for循环允许你逐个元素遍历列表。增强型for循环的语法在图 5.13中概述。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_13.jpg

图 5.13 – 增强型 for 循环语法

在前面的图中,我们可以看到一个变量的声明。变量的类型与数组/集合的类型相匹配。例如,如果数组是一个String数组,那么String就是变量的数据类型。变量名当然由我们决定。再次强调,代码块是可选的。

让我们通过一个例子来进一步解释。图 5.14是图 5.12中展示的传统for循环的增强型for循环版本。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_14.jpg

图 5.14 – 使用增强型 for 循环处理数组

在这个图中,第 38 行的内容如下:对于数组(ia)中的每个 int n。因此,在第一次迭代中,n是1;在第二次迭代中,n是2;在最后一次迭代中,n是3。在增强的for循环中,我们不必自己跟踪循环控制变量。虽然这很有用,但请注意,你被限制从数组的开始处开始,逐个元素前进,直到达到末尾。使用传统的for循环,这些限制都不适用。

然而,使用传统的for循环,如果你错误地编码了增量/减量部分,你可能会陷入无限循环。在增强的for版本中这是不可能的。

嵌套循环

当然,循环可以嵌套。换句话说,循环可以编码在其他循环内。图 5.15展示了这样一个例子。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_15.jpg

图 5.15 – 嵌套循环

此程序的输出以图 5.16展示。在前面的图中,我们表示一个int值数组,即data,为一个直方图(表示为一行星号)。数组在 12 行声明。第 14 行输出一行文本,以便程序输出更容易解释。输出有三个列:当前数组索引、该索引处data数组中的值,以及直方图。注意,输出是制表符分隔的。这是通过使用\t转义序列实现的。

转义序列

转义序列是前面带有反斜杠的字符。例如,\t是一个有效的转义序列。当编译器看到\时,它会向前查看下一个字符,并检查这两个字符是否组成一个有效的转义序列。常见的转义序列如下:

\t: 在文本此点插入制表符

\b: 在文本此点插入退格符

\n: 在文本此点插入换行符

\": 在文本此点插入双引号

\\: 在文本此点插入反斜杠

在某些情况下,它们非常有用。例如,如果我们想将文本我的名字是“Alan”(包括双引号)输出到屏幕上,我们会说:

System.out.println("My name is \"Alan\"");

如果我们没有在Alan中的A之前转义双引号(换句话说,如果我们尝试System.out.println("My name is "Alan");),那么Alan前的双引号将与字符串开头的第一个"匹配。这将导致编译器错误,错误出现在Alan中的A。

通过在Alan中的A之前跳过双引号,编译器不再将那个双引号视为字符串结束的双引号,而是将"插入到要输出的字符串中。同样,在Alan中的n之后的双引号也是如此——它也被跳过,因此被视为字符串结束的双引号并插入到要输出的字符串中。然而,在)之前的双引号没有被跳过,它被用来匹配字符串的开头双引号,即(之后的那个。

外层循环(第 15-21 行)遍历data数组。由于数组有 4 个元素,有效的索引是0、1、2和3。这些是第 15 行声明的i循环控制变量将表示的值。第 16 行输出了两列:当前数组索引和data数组中该索引的值。例如,当i为0时,data[0]是9,所以输出"0\t9\t";当i为1时,data[1]是3,所以输出"1\t3\t",依此类推。

内层循环(第 17-19 行)输出实际的直方图,作为一排水平的星号。内层循环控制变量j从1到data[i]的值。例如,如果i是0,data[i]是9;因此,j从1到9,每次输出一个星号。请注意,使用的是print()方法而不是println()——这是因为println()会自动将你带到下一行,而print()则不会。由于我们希望星号水平输出,print()正是我们需要的。当我们输出一排星号后,我们执行System.out.println()(第 20 行),这会将我们带到下一行。

图 5.16表示了图 5.15中的代码输出。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_16.jpg

图 5.16 – 图 5.15 中的代码输出

在这个图中,你可以看到第一列是数组索引。第二列是data数组在该索引处的值,第三列是基于第二列的星号直方图。例如,当i为2时,data[2]是5,我们输出一个包含5个星号的直方图。

现在我们已经了解了循环,我们将继续讨论两个与循环特别相关的关键字,即break和continue。

break 和 continue 语句

break和continue语句都可以用在循环中,但它们的语义非常不同。在提供的代码示例中,我们将使用嵌套循环来对比带标签的版本和不带标签的版本。我们将从break语句开始。

break 语句

我们已经在switch语句中遇到了break。当在循环中使用时,循环会立即退出。图 5.17展示了嵌套的for循环,其中内层循环有一个break。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_17.jpg

图 5.17 – 展示循环中的 break

在这个图中,外循环由i控制,从1开始循环到3,每次增加 1。内循环由j控制,从1开始循环到5,每次增加 1。

第 16 行的if语句在j为3时变为真。此时,执行第 17 行的break语句。没有标签的break语句退出最近的包围循环。换句话说,第 17 行的break指的是第 15 行(由j控制)的循环。由于两个循环的结束}之间没有代码(第 20 行和第 21 行),当在这个程序中执行break时,接下来执行的代码是外循环的}(第 21 行)。自动地,外循环的下一个迭代i++(第 14 行)开始。实际上,输出中永远不会出现j值为3或更高的值,因为当j为3时,我们跳出内循环并从i的下一个值开始。输出反映了这一点:

i, j1, 1
1, 2
2, 1
2, 2
3, 1
3, 2

没有任何break语句,换句话说,如果我们注释掉第 16 到 18 行,输出将如下(注意j的值从 1 到 5):

i, j1, 1
1, 2
1, 3
1, 4
1, 5
2, 1
2, 2
2, 3
2, 4
2, 5
3, 1
3, 2
3, 3
3, 4
3, 5

在我们讨论带标签的break之前,我们将简要讨论标签本身。

标签

标签是一个区分大小写的标识符,后跟一个冒号,紧接在要标识的循环之前。例如,以下代码为i控制的循环定义了一个有效的标签OUTER:

OUTER:for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 5; j++) {

现在,让我们看看带标签的break本身。

带标签的断点

使用标签的break语句退出由该标签标识的循环。带标签的break语句必须在该循环的范围内。换句话说,你不能将break到代码中的其他地方,与当前作用域完全不相关。图 5*.18与图 5**.17*中的代码密切相关,但这次使用了标签和带标签的break。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_18.jpg

图 5.18 – 带标签的断点

在前面的图中,我们在第 26 行将外循环标记为OUTERLOOP。是的,想出这个标识符花了一些时间!请注意,在标签和循环之间有任何代码都是编译错误。这就是为什么第 25 行在标签之前的原因。

循环控制变量i和j的行为与之前相同;i从1开始,每次增加 1,直到3;在i的每个步骤中,j从1开始,每次增加 1,直到5。然而,这次当内循环中的j为3时,我们不是跳出内循环,而是跳出外循环。在执行带标签的break(第 30 行)之后,不再有i的迭代,接下来执行的是第 35 行的System.out.println("here")。因此,输出如下:

i, j1, 1
1, 2
here

如所示,一旦j达到3,外循环就会退出,并输出here。

现在,让我们看看continue语句。

continue 语句

continue语句只能出现在循环内部。当执行时,continue表示“跳转到循环的下一个迭代”。当前迭代中剩余的任何其他语句都将被跳过。还有一个带标签的版本。我们将首先检查无标签版本。图 5*.19*展示了continue的一个示例。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_19.jpg

图 5.19 – continue示例

在前面的图中,嵌套循环与之前相同 – 外部循环从1迭代到3;在其内部,内部循环从1迭代到5。这次,当j为3时,我们执行continue。这意味着我们会跳到循环的末尾,接下来要执行的语句是j++。这意味着第 38 行将被跳过,值为 3 的j将永远不会被输出。输出展示了这一点:

i, j1, 1
1, 2
1, 4
1, 5
2, 1
2, 2
2, 4
2, 5
3, 1
3, 2
3, 4
3, 5

如所示,值为3的j永远不会被输出。现在,让我们检查带标签的continue。

带标签的continue

使用标签的continue语句会继续执行由该标签标识的循环的下一个迭代。所有其他语句都将被跳过。与带标签的break语句一样,带标签的continue语句必须位于标识的循环的作用域内。图 5*.20与图 5**.19*中的代码密切相关,但这次使用了标签和带标签的continue语句。

https://github.com/OpenDocCN/freelearn-java-zh/raw/master/docs/lrn-java-proj/img/B19793_05_20.jpg

图 5.20 – 带标签的continue示例

在这个图中,第 29 行将OUTERLOOP标签赋予从第 30 行开始的循环。现在,当j为3且执行continue OUTERLOOP时,接下来要执行的代码行是i++。因此,每次j达到3时,我们都会从i的下一个值开始。所以,没有大于2的j值被输出,如输出所示:

i, j1, 1
1, 2
2, 1
2, 2
3, 1
3, 2

这就完成了我们对各种循环结构和与它们一起使用的break和continue语句的解释。现在让我们将这些知识付诸实践,以巩固这些概念。

练习

现在我们能够迭代了,是时候做一些与之前章节类似但迭代多个值的任务了!

在实现这些时要有创意,并在需要的地方添加上下文。就像往常一样,没有唯一正确的答案:

  1. 我们所有的恐龙都是独一无二的。好吧,我们克隆了它们的 DNA,但仍然如此。假设它们有独特的个性。这就是为什么我们所有恐龙的 ID 都是独一无二的:它们被称为dino1、dino2、dino3等等。编写一个for循环,打印出公园前 100 只恐龙的 ID。

  2. 我们的一些恐龙食量很大!编写一个do-while循环,持续给恐龙喂食,直到它不再饿。

  3. 我们都喜欢等待公园开门的刺激感。使用while循环打印出公园开门时间的倒计时。

  4. 为了规划目的,知道特定围栏中所有恐龙的总重量是至关重要的。编写一个for循环来计算这个值。

  5. 在旺季,售票可能会变得非常繁忙。编写一个while循环来模拟公园的售票过程,直到票售罄。

  6. 安全是我们最优先考虑的事项。使用do-while循环来模拟一个安全检查过程,直到所有安全措施都得到满足。

项目 – 恐龙餐食计划

恐龙不是容易饲养的动物。这是一项非常高级的宠物饲养。正确的营养管理很困难,但对于它们的健康和福祉至关重要。因此,你需要创建一个系统来管理我们各种恐龙居民的喂食时间表。

该项目的首要目标是创建一个程序,计算每种恐龙的餐食分量和喂食时间。由于我们还没有介绍数组,我们现在将专注于单个恐龙。

下面是如何做到这一点:

  1. 首先声明一个变量来保存当前时间;假设它是一个整数,从0(午夜)到23(一天中的最后一个小时)。

  2. 为每种恐龙定义不同的喂食时间变量。例如,霸王龙可以在早上 8 点、下午 2 点以及晚上 8 点进食,而长颈龙可以在早上 7 点、上午 11 点、下午 3 点以及晚上 7 点进食。

  3. 接下来,建立一个条件语句(例如if-else块)来检查是否是每种动物的喂食时间,比较当前时间与它们的喂食时间。

  4. 现在,让我们定义恐龙的喂食分量。我们可以假设每种动物需要的食物量不同,这取决于它们的体型。例如,霸王龙每餐需要 100 公斤的食物,而长颈龙每餐需要 250 公斤的食物。

  5. 类似地,使用if-else块,检查你正在处理哪种动物,并相应地分配食物分量。

  6. 最后,打印结果。例如,"现在是 8:00 - 给重达 100kg 的霸王龙喂食时间"。

  7. 将所有上述信息包裹在一个从0到23的循环中,模拟一天中的 24 小时。

代表公园里饥饿的恐龙们:非常感谢您将您的 Java 技能付诸实践!

概述

在本章中,我们讨论了 Java 如何实现迭代(循环)。我们从while循环开始,由于条件在循环的开始处,它将执行零次或多次。相比之下,do-while循环的条件在循环的末尾,它将执行一次或多次。while和do-while循环在你不知道循环将迭代多少次时非常有用。

相比之下,当你知道循环需要执行的频率时,传统的for循环非常有用。传统的for循环的头部由三部分组成:初始化部分、布尔表达式和增量/减量部分。因此,我们可以迭代一个离散的次数。这使得传统的for循环非常适合处理数组。

增强的for(for-each)循环在处理数组(和集合)方面更加合适,前提是你不关心当前循环的迭代索引。它简洁、简明且易于编写,是一个更优雅的for循环。

实际上,如果你需要循环特定次数,请使用传统的for循环。如果你需要从开始到结束处理数组/集合,而不关心循环索引,请使用增强的for版本。

当然,所有循环都可以嵌套,我们查看了一个这样的例子。我们定义标签为一个区分大小写的标识符,后跟一个冒号,该冒号紧接在循环之前。

嵌套循环和标签为我们讨论break和continue关键字做好了准备。break也可以在switch语句中使用,而continue只能用于循环内部。两者都有带标签和不带标签的版本。关于break,不带标签的版本会退出当前循环,而带标签的版本会退出指定的循环。至于continue,不带标签的版本会继续当前循环的下一个迭代,而带标签的版本会继续指定循环的下一个迭代。

这样,我们就完成了对迭代的讨论。在本章中,我们简要提到了数组。接下来,我们将进入下一章,第六章,我们将详细讲解数组。

大数据学习指南从入门到精通 利用框架的力量,看懂游戏规则,才是入行的前提大多数人不懂,不会,不做,才是你的机会,你得行动,不能畏首畏尾选择才是拉差距关键,风向,比你流的汗水重要一万倍,逆风划船要累死人的为什么选择学习大数据开发,不选择Java开发?借棋弈做比喻,智商高的不要选择五子琪,要选择围棋,它能长久地吸引你。不都是在棋盘上一颗黑子一颗白子地下吗?因为围棋更复杂,能够掌握如此复杂的技艺、产生稳定输出的棋手、让我们更佩服。选择学习大数据开发也如此,能让你的职业生涯走得更远,少走弯路。 阅读详情

相关推荐

7、Java编程学习指南与项目实践

本文是一篇Java编程学习指南与项目实践教程,涵盖开发环境搭建、工具安装、Java命令行工具使用等内容,并通过一个机器人老鼠控制的完整项目,详细讲解了从需求分析、设计到测试和优化的全过程。适合初学者掌握Java编程核心技能,并通过实际项目提升开发能力。

nut55的博客 45

从零到一:SpringBoot3+Java17实战项目12306的深度部署与学习指南

SpringBoot3实战项目12306是一个面向大学春招、秋招和应届生的分布式高仿铁路购票系统,采用SpringBoot3+Java17+SpringCloud Alibaba+Vue3等现代技术栈,帮助开发者深度掌握分布式系统设计的核心技巧,特别适合对高并发、分布式系统感兴趣的同学学习。 ## 🎯 项目核心价值与学习意义 ### 实战价值:从理论到实践的桥梁 12306项目不仅仅是一个

gitblog_00738的博客 278

吃透Chisel语言.02.Chisel+VS Code+Ubuntu/WSL2开发环境搭建与使用

既然是学Chisel语言,那肯定得先把环境配置好,磨刀不误砍柴工嘛。而且有个测试环境的话,在后期的学习过程中自己去试运行代码段可以加深学习效果。这里推荐使用Ubuntu或者基于WSL2(Windows Subsystem Linux 2)的Ubuntu,本人使用的是WSL2上的Ubuntu 20.04.3 LTS,在使用体验上与Ubuntu几乎没有区别,且能同时使用Windows提供的诸多便利。...

github-3rr0r的博客 8448

Carson带你学Android:请收好这一份全面&详细的Android学习指南

以自身的经验 & 所见所闻,旨在告诉大家,针对Android,需要学习什么知识、怎么学习 & 怎么应用到实际项目中,希望你们会喜欢。

Carson带你学Android 1万+

Qoder CN 完全指南:阿里云AI智能体的全景拆解

阿里云旗下AI编程助手"通义灵码"于2026年5月20日升级为QoderCN,转型为覆盖软件开发与办公场景的AI智能体产品矩阵。该平台具备四大核心能力:代码补全、智能问答、文件编辑和智能体模式,支持GLM、DeepSeek等多模型切换。特色功能包括Quest2.0任务委派、专家团协作和记忆感知,满足金融、政务等行业的合规需求。产品形态涵盖IDE插件、CLI工具、办公助手和数字员工,形成"全家桶"式解决方案。相比Cursor等海外产品,QoderCN更注重全链路覆盖,个

几十年资深高级架构师,技术专家的技术笔记。专注C、Java、Python、微服务、云原生、AI工程化及高并发系统分布式架构实战,分享源码剖析、性能调优与架构治理经验。 938

python画弗里德曼(Friedman) 检验和秩和检验(Nemenyi)检验图

python画弗里德曼(Friedman) 检验和秩和检验(Nemenyi)检验图 画好后效果图 实现代码如下 import matplotlib.pyplot as plt #算法平均排名 _alg_=[1.405,2.643,2.786,2.167] y=[1,2,3,4]#alg1,alg2,alg3,alg4 通过Nemenyi测试计算得到的临界值CD CD=1.024 h_CD=CD/2 plt.figure(figsize=(10,6)) plt.scatter(_alg_,y,s=10

大模型天花板 4464

6月份Github上最热门的Java开源项目!

来源:开源最前线 1、halo,这是一个轻快,简洁,功能强大,使用Java开发的博客系统。 项目地址:https://github.com/halo-dev/halo Star 6139 2、jeecg-boot 项目地址:https://github.com/zhangdaiscott/jeecg-boot Star 7573 这是一款基于代码生成器的JAVA快速开发平台!提高U...

JEECG官方博客 1万+

matlab电子科技大学数学实验期末模拟35道(1)

【代码】matlab电子科技大学数学实验期末模拟35道。

c_wrj_的博客 2044

从Java到Go:Java工程师的Golang学习指南

作为一名Java工程师,你可能已经习惯了Java的面向对象编程、强大的生态系统以及丰富的工具链。然而,随着Go语言(Golang)在云计算、微服务和并发编程领域的崛起,学习Go语言成为了一个非常有价值的选择。本文将通过对比Java和Go语言的方式,帮助你快速掌握Go语言的核心概念,并通过详细的案例讲解,帮助你理解两者之间的异同。 Go 对比: Go 对比: Go 对比: Go 对比:Java通过类和接口实现多线程编程,或者使用包中的高级并发工具。 Go Go语言通过实现并发,是轻量级的线程,由Go运

SunnyRivers 1751

基于BEV(鸟瞰图)的3D检测:PointPillars与CenterPoint

基于BEV(鸟瞰图)的3D检测:PointPillars与CenterPoint​ ,人工智能,计算机视觉,大模型,AI,本文围绕基于BEV的3D检测,解析了PointPillars与CenterPoint两种算法。PointPillars用点柱特征编码网络处理点云,通过2D卷积网络提取特征,检测头为解耦头;CenterPoint将三维物体表为点,分两阶段检测,先找中心点及属性,再细化预测。两者都可借特定骨干网生成BEV特征图且基于BEV检测,但检测头设计和物体表示方式不同。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 27万+

新系统申请软件著作权详细操作说明

今年八月底版权中心系统升级,新系统相对好用一些,至少不用再拿IE访问了,但是多了一项实名认证。下面介绍新系统的操作流程。 目录注册、登录和实名认证注册登录实名认证软件登记申请申请表填写鉴别材料身份证明文件权利归属证明文件其他材料提交材料变更登记 注册、登录和实名认证 首先访问版权中心系统:https://register.ccopyright.com.cn/login.html,如果老系统有账号直接登录即可,没有的话需要注册。 注册 点击注册按钮进入注册。 根据实际情况选择个人或单位,每个个人或单位只能在

Programming Talk 1万+

Java Web开发学习指南:从入门到实战

Java Web的概念Java Web是基于Java语言开发的Web应用程序。它通过Java技术实现动态网页的生成和数据交互。应用场景企业级Web应用开发,如电子商务网站、在线办公系统、企业资源管理系统等。Java Web技术在互联网、金融、教育等多个领域都有广泛应用。Java Web与Java SE、Java EE的关系:是Java的基础,提供了核心库和运行环境,是Java Web开发的基础。

4096

微信小程序 - 实现嵌套H5页面唤起微信支付的实现方案+完整流程源码,微信小程序webview(H5页面)调用微信小程序支付,嵌套h5页面怎么实现小程序支付(Vue2/3移动端H5网页、公众号网页等)

h5嵌入微信小程序微信支付,H5网页嵌入到微信小程序内如何支付,微信小程序webview组件交互,内联h5页面并网页实现微信支付,微信H5页面实现微信小程序支付,微信小程序嵌套webview实现小程序微信支付,小程序中内嵌H5网页支付怎么办,用网页的支付还是小程序的支付?支付没反应,支付报错怎么办,微信小程序内嵌h5,h5调用小程序支付功能解决方案,H5网页cors跨域,真机测试无法唤起支付,无法唤起支付,uniapp微信小程序,h5小程序调用微信支付,微信小程序内嵌H5页面,如何实现微信支付功能,微信小程

🏆 前端领域优质创作者 1万+

Java设计模式学习指南

Java设计模式学习指南 本指南旨在帮助您深入了解位于 https://github.com/JeffLi1993/java-designpattern-learning.git 的开源项目。我们将依次探讨该项目的目录结构、启动文件以及配置文件,以便于您快速上手并运用其中的设计模式。 1. 项目目录结构及介绍 该项目遵循了清晰的Java项目结构,便于理解和维护: java-designpatter...

gitblog_00402的博客 744

基于Simulink的双向DC-DC变换器在储能系统中的应用仿真

摘要:本文详细介绍了基于Simulink的双向DC-DC变换器在光伏储能系统中的仿真实现。主要内容包括:1)采用非隔离型双向Buck-Boost拓扑作为储能系统枢纽;2)针对750V直流微网中的20kWh锂电池接口场景进行建模;3)详细阐述双闭环控制器设计(电压外环+电流内环)、工作模式判断逻辑等关键步骤;4)通过仿真验证了系统在功率阶跃和模式切换下的性能。文章提供了完整的建模流程和控制方法,为储能系统接口设计提供了实用参考。

amy_mhd的博客 179

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

零基础Java趣味学习指南

零基础Java学习指南摘要 Java适合零基础入门,6-12个月可掌握基础能力,需逻辑思维和持续实践(300小时以上)。

m0_73203093的博客 620

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

视频教程-Java学习指南 Swing项目实战篇-Java

Java学习指南 Swing项目实战篇 邵发,清华大学毕业,从业软件开发十余...

weixin_33392602的博客 625

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Java 开发者 Linux 学习指南

本文详细介绍了Java开发者为何必须掌握Linux,并提供了从Linux基础到Java应用部署的全面指南。文章首先强调了Linux在Java应用部署中的重要性,随后深入讲解了Linux的核心概念、常用命令、Java环境安装与配置、开发与构建工具、JVM性能优化与监控、应用部署与容器化、日志分析与自动化运维、安全配置与最佳实践等内容。通过实战案例,展示了如何在Linux上部署Spring Boot应用。最后,文章总结了学习资源,鼓励开发者通过持续学习和实践,提升在Linux与Java领域的专业技能。

m0_71744084的博客 3397

Java从入门到精通:超详细学习指南(2025版)

通过本指南的系统学习,配合每日4-6小时的实践编码,可在6个月内达到中级Java工程师水平。建议每完成一个章节后进行知识复盘,并参与开源项目贡献以提升实战能力。

夜雨的博客 2064

超硬核Java学习路线图+学习资源+实战项目汇总,看完以后不用再问我怎么学Java了!

之前写过很多次关于Java学习指南、Java技术路线图的文章。但是总还是有小伙伴来问我,Java怎么学,项目怎么做,资源怎么找,真是让人头秃。 于是这次黄小斜决定来一波狠的,把所有这些内容都整理起来,做成一份非常硬核的Java学习指南+路线图,一篇文章搞定Java学习,360度无死角(可能)如果你们喜欢这类硬核的文章,以后我也会继续这么硬核下去的!不要忘了分享噢。 以下就是本文的主要内容,万字长文,你可悠着点看,拒绝到收藏夹吃灰! 一、Java学习路线图 1 计算机基础 2Java编程...

Java技术江湖 7665

视频教程-Java学习指南16 项目应用篇-Java

Java学习指南16 项目应用篇 邵发,清华大学毕业,从业软件开发十余年,自...

weixin_33389360的博客 261

2019年 Github 上最热门的 Java 开源项目

10 月份 GitHub 上最热门的Java开源项目排行已经出炉啦,在本月的名单中,实战项目类居多,当然也有像JavaGuide这样学习指南类项目,下面就是本月上榜的10个开源项目: 1 Java https://github.com/TheAlgorithms/JavaStar 18468 该项目用Java实现的所有算法,对算法感兴趣的伙伴们不要错过了。 ...

记事本 1947

Java多线程学习指南

Java 多线程学习指南

感谢阅读 1558

终极Java学习指南:从入门到精通的Java-Tutorial项目全解析

Java-Tutorial项目是一份由资深开发者十年积累打造的全面系统的Java学习资源,涵盖JavaEE、中间件、工具框架等多个领域,为新手和普通用户提供了清晰易懂的学习路径和实践指导。 [![Java学习之路](https://raw.gitcode.com/gh_mirrors/ja/java-tutorial/raw/9ef8de317919aa6692236a00e3f4e3702db

gitblog_00182的博客 929

Java高效学习指南

摘要:本文提供系统化的Java学习指南,包含三个阶段的学习路径:基础(1-2月)、进阶(2-3月)和实战阶段。推荐高效学习方法如实践驱动(附多线程代码示例)、工具链配置和精选资源(书籍、在线课程、文档)。指出新手常见误区(如跳过基础直接学框架)并提供解决方案。建议从初级到高级的实战项目路线,强调持续提升需关注JDK更新和工程能力培养。推荐每天2小时有效编码,结合项目实践和官方文档学习。

lumutong的博客 329

Java工程师学习指南(初级篇)

Java工程师学习指南 初级篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好。原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我之前写的文章都是站在Java后端的全局上进行思考和总结的,忽略了很多小白们的感受,而很多朋友都需要更加基础,更加详细的学习路线。 所以,今天我们重新开一个新的专题,分别按照四篇文章讲述Java的学习路线(分别...

Java技术江湖 1万+
上一篇: Java Lambda 学习指南(二)
下一篇: Java 项目学习指南(二)
绝不原创的飞龙
绝不原创的飞龙 优质创作者: 人工智能、数据科学与机器学习技术领域 优质创作者: 人工智能、数据科学与机器学习技术领域
博客等级 码龄15年 4万+粉丝 2万+原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值