Java17 零基础入门手册(一)
一、Java 及其历史简介
根据谷歌搜索,在 2020 年底,据报道有 9492 家公司在他们的技术栈中使用 Java,包括谷歌和我,本书的作者,在本书写作时工作的公司。即使过了 25 年,Java 仍然是最有影响力的编程语言之一。这一切都始于 1990 年,当时一家引领计算机行业革命的美国公司决定召集其最优秀的工程师设计和开发一种产品,让他们成为新兴互联网世界的重要参与者。这些工程师中有詹姆斯·亚瑟·高斯林,他是一位加拿大计算机科学家,被公认为 Java 编程语言之父。这需要五年的设计、编程和一次重命名(因为商标问题,从 Oak 改为 Java),但最终在 1996 年 1 月, 1 Java 1.0 发布,适用于 Linux、Solaris、Mac 和 Windows。
阅读技术书籍的一般倾向是完全跳过介绍性章节。但在这种情况下,我认为这将是一个错误。在写这本书之前,我对 Java 的历史并不感兴趣。我知道詹姆斯·高斯林是创造者,甲骨文买下了太阳,差不多就是这样。我从来不太关心语言是如何发展的,灵感来自哪里,或者一个版本与另一个版本有什么不同。我从 1.5 版本开始学习 Java,我把语言中的很多东西都当成了理所当然。所以当我被分配到一个运行在 Java 1.4 上的项目时,我非常困惑,因为我不知道为什么我写的部分代码不能编译。尽管 IT 行业发展非常迅速,但总会有一个客户端拥有遗留应用。了解每个 Java 版本的特性是一个优势,因为您知道执行迁移时的问题。
当我开始为这本书做研究时,我被迷住了。Java 的历史很有趣,因为这是一个难以置信的成长故事,一个技术成功的故事,也是一个管理上的自我冲突几乎杀死创造它的公司的例子。目前,Java 是软件开发中最常用的技术,而诞生它的公司已经不存在了,这简直是自相矛盾。
本章描述了 Java 的每个版本,跟踪了语言和 Java 虚拟机的发展。
这本书是给谁的
大多数面向初学者的 Java 书籍都是从典型的 Hello World 开始的!清单 1-1 中描述的示例。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
Listing 1-1The Most Common Java Beginner Code Sample
这段代码在执行时会打印出 *Hello World!*在控制台中。但是如果你已经买了这本书,它假定你想用 Java 开发真正的应用,并且在申请 Java 开发人员的职位时有真正的机会。如果这是你想要的,如果这是你,一个有充分利用这种语言的能力的智慧和愿望的初学者,那么这本书就是给你的。这就是为什么我会给你们足够的信任,让你们用一个更复杂的例子来开始这本书。
Java 是一种基于英语的语法可读的语言。因此,如果你有逻辑思维并且对英语有一点了解,你应该很清楚清单 1-2 中的代码在不执行它的情况下做了什么。
package com.apress.ch.one.hw;
import java.util.List;
public class Example01 {
public static void main(String[] args) {
List<String> items = List.of("1", "a", "2", "a", "3", "a");
items.forEach(item -> {
if (item.equals("a")) {
System.out.println("A");
} else {
System.out.println("Not A");
}
});
}
}
Listing 1-2The Java Beginner Code Sample a Smart Beginner Deserves
在此代码示例中,声明了一个文本值列表;然后遍历列表,当一个文本等于“A”时,在控制台中打印字母“A”;否则,将打印“非 A”。
如果你是一个绝对的编程初学者,这本书是为你准备的,特别是因为这本书附带的源代码利用了编程中常用的算法和设计模式。所以,如果你的计划是进入编程领域,学习一门高级编程语言,读一读这本书,运行例子,写你自己的代码,你应该会有一个好的开端。
如果你已经了解 Java,你也可以使用这本书,因为它涵盖了 Java 版本 17(早期访问程序或 EAP 2 版本)的语法和内部细节,你肯定会发现一些你不知道的东西。
这本书的结构
您目前正在阅读的这一章是介绍性的,涵盖了 Java 历史的一小部分,向您展示了该语言是如何发展的,并提供了对其未来的一瞥。此外,还将介绍执行 Java 应用的机制,以便您为第章 2 做好准备。下一章将向你展示如何建立你的开发环境,并向你介绍第一个简单的应用。
从第章 3 开始,将涵盖语言的基础部分:包、模块、类、接口、注释对象、操作符、数据类型、记录、语句、流、lambda 表达式等等。
从第章 8 开始,涵盖了与外部数据源的交互:读取和写入文件、序列化/反序列化对象、测试和创建用户界面。
章节 12 完全致力于 Java 9 中引入的发布-订阅框架和反应式编程。
第十三章第十五章将垃圾收集器盖上。
****本书列表中使用的所有资源,以及一些因为本书必须保持合理大小而未能使用的资源,都是名为java-17-for-absolute-beginners的项目的一部分。这个项目由模块组成(因此它是一个多模块项目),这些模块相互链接,并且必须由一个叫做 Maven 的东西来管理。Maven 是我们开发人员称为构建工具的东西,它提供了构建包含大量源代码的项目的能力。构建项目意味着将编写的代码转换成可以执行的东西。我选择为我写的书使用多模块项目,因为它更容易构建,还因为公共元素可以组合在一起,保持项目的配置简单且不重复。此外,通过将所有的源代码组织在一个多模块项目中,您可以尽快获得关于源代码是否工作的反馈,并且您可以联系作者并要求他们更新它们。我知道拥有一个构建工具会带来一定程度的复杂性,但是它让您有机会适应一个非常类似于您将作为一名员工工作的开发环境。
约定
这本书使用了许多格式约定,应该会使它更容易阅读。为此,本书使用了以下约定:
-
段落中的代码或概念名称如下:
java.util.List -
代码列表如下所示:
-
控制台输出中的日志将如下所示:
public static void main(String[] args) {
System.out.println("Hello World!");
}
-
{xx}是占位符;xx值是一个伪值,给出了应该在命令或语句中使用的真实值的提示。 -
出现在你要特别注意的段落前面。提示和警告也有类似的图标。
-
斜体字体用于幽默的隐喻和表达。
-
粗体字体用于章节参考和重要术语。
01:24:07.809 [main] INFO c.a.Application - Starting Application
01:24:07.814 [main] DEBUG c.a.p.c.Application - Running in debug mode
至于我的写作风格,我喜欢用与同事和朋友进行技术对话的方式来写书:通篇散布笑话,给出生产实例,并与非编程情况进行类比。因为编程不过是模拟现实世界的另一种方式。
当 Java 被太阳微系统公司拥有时
Java 的第一个稳定版本发布于 1996 年。在那之前,有一个名为 Green Team 的小团队在开发一个名为 Oak 的原型语言,该语言通过一个工作演示介绍给了世界——一个名为 Star7 的交互式手持家庭娱乐控制器。动画触摸屏用户界面的明星是一个名为杜克的卡通人物,由团队的图形艺术家之一乔·帕朗创作。多年来,杜克(图 1-1 )已经成为 Java 技术官方吉祥物,每一届 JavaOne 大会(由 Oracle 每年举办一次)都有自己的杜克吉祥物个性。
图 1-1
公爵,Java 官方吉祥物(图片来源:https:// oracle。com)
绿色团队通过互联网向世界发布 Java,因为这是创造广泛采用的最快方式。你可以想象,每当有人下载它时,他们会高兴得跳起来,因为这意味着人们对它感兴趣。软件开源还有一些其他的优势,比如贡献和反馈是由来自世界各地的许多人提供的。因此,对于 Java 来说,这是最好的决定,因为它塑造了今天许多开发人员使用的语言。即使过了 25 年,Java 仍然是最常用的三种编程语言之一。
绿色团队成立于 1982 年,为一家名为太阳微系统的美国公司工作。它通过销售计算机、计算机零件和软件引导了计算机革命。他们最伟大的成就之一是 Java 编程语言。在图 1-2 中可以看到公司 logo3从 Java 诞生那一年开始使用,直到 2010 年被甲骨文收购。
图 1-2
太阳微系统公司的标志(图片来源:en . Wikipedia . org/wiki/Sun _ Microsystems)
很难找到关于 Java 第一版的信息,但是见证了它诞生的专注的开发人员,当 Web 还很小并且充满静态页面时,确实创建了博客并与世界分享他们的经验。对于 Java 来说,显示与用户交互的动态内容的小程序很容易就能大放异彩。但是因为开发团队想得更大,Java 不仅仅是一种 Web 编程语言。在尝试让小程序在任何浏览器上运行的过程中,该团队找到了一个常见问题的解决方案:可移植性。
现在的开发人员在开发应该在任何操作系统上运行的软件时面临着许多令人头痛的问题。随着移动革命的到来,事情变得非常棘手。在图 1-3 中,你可以看到一张被认为是第一个 Java 徽标的抽象画。
图 1-3
第一个 Java logo,1996–2003(图片来源: oracle. com/ )
Java 1.0 在第一届 JavaOne 大会上发布,有 6000 多名与会者。Java 最初是一种名为 Oak 的语言。这种语言非常类似于 C++,是为手持设备和机顶盒设计的。它演变成了 Java 的第一个版本,为开发人员提供了一些 C++所没有的优势:
-
安全性:在 Java 中,当意外超过数组的大小时,没有读取假数据的危险。
-
自动内存管理:Java 开发人员不必检查是否有足够的内存分配给一个对象,然后显式地取消分配;这些操作由垃圾收集器自动处理。这也意味着指针不是必需的。
-
简单性:没有指针、联合、模板和结构。Java 中的大多数东西都可以被声明为一个类。此外,通过修改继承模型和不允许多重类继承,避免了使用多重继承时的混淆。
-
支持多线程执行 : Java 从一开始就是为了支持多线程软件的开发而设计的。
-
可移植性:最广为人知的 Java 格言之一是一次编写,随处运行 (WORA)。这是由 Java 虚拟机实现的。
所有这些使得 Java 对开发人员很有吸引力,到 1997 年 Java 1.1 发布时,世界上已经有大约 400,000 名 Java 开发人员。那年 JavaOne 大会有 10,000 名与会者。通往伟大的道路已经确定。在进一步分析每个 Java 版本之前,让我们澄清一些事情。
Java 是如何移植的?
我几次提到 Java 是可移植的,Java 程序可以在任何操作系统上运行。是时候解释这是如何可能的了。让我们从一张简单的图开始,如图 1-4 中的那张。
图 1-4
在多个平台上运行 Java 程序
Java 是我们所说的高级编程语言,它允许开发者编写独立于特定类型计算机的程序。高级语言更容易读、写和维护。但他们的代码必须由编译器翻译或解释成机器语言(人类无法阅读,因为它是由数字组成的)才能执行,因为这是计算机理解的唯一语言。
在图 1-4 中,注意在操作系统之上,需要一个 JVM 来执行一个 Java 程序。JVM 代表 Java 虚拟机,它是一种抽象的计算机器,使计算机能够运行 Java 程序。它是一个独立于平台的执行环境,将 Java 代码转换成机器语言并执行。
那么 Java 和其他高级语言有什么区别呢?其他高级语言将源代码直接编译成机器码,这些机器码是为在特定的微处理器体系结构或操作系统上运行而设计的,如 Windows 或 UNIX。JVM 所做的是模仿 Java 处理器,使 Java 程序有可能被解释为任何处理器上的一系列动作或操作系统调用,而不管操作系统是什么。当然,编译步骤使 Java 比纯编译语言(如 C++)慢,但它的优势过去是,现在仍然是美丽的。此外,Java 不是 JVM 语言家族的唯一成员。Groovy、Scala、Kotlin 和 Clojure 都是运行在 JVM 上的非常流行的编程语言。
因为提到了 Java 编译器,我们不得不回到 Java 1.1,即使在新版本发布时,它仍被广泛使用。它附带了一个改进的抽象窗口工具包(AWT)图形 API(用于构建小程序的组件集合)、内部类、数据库连接类(JDBC 模型)、远程调用类(RMI)、一个名为 JIT 5 编译器(用于JustInTime)的微软平台专用编译器、对国际化的支持以及 Unicode。Java 被广泛接受的另一个原因是,在 Java 发布后不久,微软就对它进行了许可,并开始使用它创建应用。反馈有助于 Java 的进一步发展,因此 Java 1.1 在当时的所有浏览器上都得到支持,这也是它被如此广泛部署的原因。
本书导言中使用的许多术语现在可能对你来说是陌生的,但随着你阅读本书,更多的信息被引入,这些词将开始变得更有意义。现在,只要记住每一个新的 Java 版本都比前一个版本有更多的东西,在那个时候,每一个新的组件都是新奇的。
那么,在实际执行之前,开发人员编写的 Java 代码到底发生了什么呢?该过程如图 1-5 所示。
图 1-5
从 Java 代码到机器代码
Java 代码被编译并转换成字节码,然后由 JVM 在底层操作系统上解释和执行。
Java 是一种经过编译和解释的通用编程语言,它具有许多特性,非常适合 web。
既然我们已经介绍了 Java 代码是如何执行的,让我们再回顾一些历史。
太阳微系统公司的 Java 版本
Sun Microsystems 发布的第一个稳定的 Java 版本可以从网站上下载,名为 JDK,,当时的版本是 1.0.2。JDK 是 ??、??、发展和科技的首字母缩写。这是用于开发 Java 应用和小程序的软件开发环境。它包括JavaRuntimeEn environment(JRE)、解释器(loader)、编译器、归档器、文档生成器以及 Java 开发所需的其他工具。我们将在关于在你的计算机上安装 JDK 的章节中深入探讨这个问题。
从 1998 年发布的 1.2 版本开始,Java 版本被赋予了代码名称。6Java 1.2 版本的代号是游乐场。这是一个大规模的发布,这是人们开始谈论 Java 2 平台的时刻。从这个版本开始,J2SE 5.0 之前的版本都被重新命名,J2SE取代了 JDK,因为 Java 平台现在由三部分组成:
-
J2SE (Java 2 平台,标准版),后来成为 JSE,一个为桌面和服务器环境开发和部署可移植代码的计算平台。
-
J2EE (Java 2 平台,企业版),后来成为 JEE,一组扩展 Java SE 的规范,用于分布式计算和 web 服务等企业特性。
-
J2ME (Java 2 平台,微型版),后来成为 JME,一个为嵌入式和移动设备开发和部署可移植代码的计算平台。
在这个版本中,JIT 编译器成为 Sun Microsystem 的 JVM 的一部分(这基本上意味着将代码转换为可执行代码成为一种更快的操作,并且生成的可执行代码得到了优化),Swing 图形 API 作为 AWT 的一种奇特的替代方法被引入(引入了创建奇特的桌面应用的新组件),并且引入了 Java Collections 框架(用于处理数据集)。
J2SE 1.3 于 2000 年发布,代号为 Kestrel (可能是指新引入的 Java 声音类)。这个版本还包含了 Java XML APIs。
J2SE 1.4 于 2002 年发布,代号为梅林。这是 Java 社区过程成员第一年参与决定这个版本应该包含哪些特性,因此这个版本相当一致。这是在 Java 社区过程下开发的 Java 平台的第一个版本,名为 JSR 59。 7 以下特性值得一提:
-
对 IPv6 的支持:基本上,现在可以使用网络协议 IPv6 编写运行在网络上的应用。
-
非阻塞 IO : IO 是 input-output 的缩写,指的是读写数据——一种非常慢的操作。使 IO 不阻塞意味着优化这些操作,以提高运行应用的速度。
-
Logging API :需要将执行的操作报告给一个文件或资源,在失败的情况下可以读取该文件或资源,以确定原因并找到解决方案。这个过程被称为日志记录,显然只有在这个版本中引入了支持这个操作的组件。
-
图像处理 API :组件开发者可以用这个用 Java 代码来操作图像。
Java 的咖啡杯标志在 2003 年的 JavaOne 大会上首次出现(在 1.4 和 5.0 版本之间)。在图 1-6 中可以看到。 8
图 1-6
Java 官方 logo 2003-2006(图片来源: oracle。com
J2SE 5.0 于 2004 年发布,代号为老虎。最初,它遵循典型的版本控制,并被命名为 1.5,但因为这是一个具有大量新功能的主要版本,证明了 J2SE 在成熟度、稳定性、可扩展性和安全性方面的重大改进,所以该版本被标记为 5.0,并以这种方式向公众发布,即使内部仍使用 1.5。对于这个版本和接下来的两个版本,我们认为 1.x = x.0。让我们列出这些特性,因为它们中的大多数都包含在本书中:
-
泛型为集合提供了编译时(静态)类型安全支持,并消除了对大多数类型转换的需要(这意味着在特定上下文中使用的类型是在应用运行时决定的,我们在第 章 5 中有一整节关于这一点)。
-
注释,也称为元数据,用于标记类和方法,以允许支持元数据的实用程序处理它们(这意味着一个组件被标记为另一个组件可以识别并对其执行特定操作的东西)。
-
自动装箱/拆箱是指原语类型和匹配对象类型(包装器)之间的自动转换,在章节5 中也有涉及。
-
枚举使用
enum关键字定义静态的最终有序值集;章节 章节章节章节章节。 -
Varargs 为支持一种类型的任意数量参数的方法提供了一种简写方式。方法的最后一个参数是使用类型名后跟三个点(例如,
String...)来声明的,这意味着可以提供该类型的任意数量的参数并将其放入数组中;章节 第三章 。 -
为每个循环增强:也用于迭代集合和数组,在章节 5 中也有介绍。
-
改进多线程 Java 程序的语义,在第章 第七章 中介绍。
-
静态进口也涵盖在章 4 中。
-
对 RMI 的改进(书中未涉及)、Swing ( 章 10 )、并发实用程序(章 7 )、以及
Scanner类(**章11)**的介绍。
Java 5 是苹果 Mac OS X 10.4 的第一个可用版本,也是苹果 Mac OS X 10.5 的默认版本。到 2015 年为止,这个版本发布了很多更新 9 ,以修复与安全和性能相关的问题。这是一个错误百出的版本,这是可以理解的,因为很多特性都是在两年内开发出来的。
2006 年, Java SE 6 稍微延迟发布,代号野马。是的,这又是一次重命名,是的,又一次在相当短的时间内实现了大量的特性。之后需要大量的更新来解决现存的问题。这是 Sun Microsystems 发布的最后一个主要 Java 版本,因为 Oracle 在 2010 年 1 月收购了该公司。下面列出了此版本中最重要的功能。
-
核心平台的显著性能提升(应用运行速度更快,执行时需要的内存或 CPU 更少)。
-
改进的 web 服务支持(开发 web 应用所需的优化组件)。
-
JDBC 4.0(使用数据库开发应用所需的优化组件)。
-
Java 编译器 API(您可以从代码中调用用于编译代码的组件)。
-
许多 GUI 改进,例如在 API 中集成了
SwingWorker,表格排序和过滤,以及真正的 Swing 双缓冲(消除了灰色区域效应);总的来说,改进了用于创建桌面应用界面的组件。
在(Java 术语)之后不久,2008 年 12 月, JavaFX 1.0 SDK 发布。JavaFX 适合为任何平台创建图形用户界面。最初的版本是一种脚本语言。直到 2008 年,在 Java 中有两种创建用户界面的方法:
-
使用 AWT (抽象窗口工具包)组件,这些组件由特定于底层操作系统的本机对等组件呈现和控制;这就是 AWT 组件也被称为重量级组件的原因。
-
使用 Swing 组件,之所以称之为轻量级,是因为它们不需要在操作系统的窗口工具包中分配本机资源。Swing API 是 AWT 的补充扩展。
对于第一个版本,JavaFX 是否真的有前途,是否会取代 Swing,从来都不清楚。Sun 内部的管理混乱也无助于为这个项目确定一条清晰的道路。
甲骨文接管
尽管 Sun Microsystems 赢得了对微软的诉讼,他们同意支付 2000 万美元,因为没有完全实现 Java 1.1 标准,但在 2008 年,该公司的状况非常糟糕,以至于与 IBM 和惠普的合并谈判开始了。2009 年,甲骨文和 Sun 宣布他们就价格达成一致:甲骨文将以每股 9.50 美元的现金收购 Sun,这相当于 56 亿美元的报价。影响是巨大的。很多工程师辞职了,包括 Java 之父詹姆斯·高斯林,这让很多开发者质疑 Java 平台的未来。
Java 7
Java SE 7,代号 Dolphin ,是甲骨文在 2011 年发布的第一个 Java 版本。它是 Oracle 工程师和全球 Java 社区成员广泛合作的结果,如 OpenJDK 社区和 Java 社区进程(JCP)。它包含了很多变化,但比开发人员预期的要少很多。考虑到两次发布之间的长时间间隔,期望值相当高。Project Lambda,它应该允许在 Java 中使用 Lambda 表达式(这在某些情况下会导致相当大的语法简化),Jigsaw(使 JVM 和 Java 应用模块化;中有一段 章 3 关于他们)被撤掉了。两者都在未来版本中发布。
以下是 Java 7 中最显著的特性:
-
JVM 通过新的 invoke 动态字节码支持动态语言(基本上,Java 代码可以使用用 Python、Ruby、Perl、Javascript 和 Groovy 等非 Java 语言实现的代码)。
-
压缩的 64 位指针(JVM 的内部优化,因此消耗的内存更少)
-
在项目下分组的小型语言更改硬币:
-
switch 语句中的字符串(包含在章节7T5)
-
try-statement 中的自动资源管理(包含在章节 5 中)
-
泛型的改进类型推理—菱形<>操作符(在章节 5 中介绍)
-
-
二进制整数文字:整数可以直接表示为二进制数,使用形式为 0b(或 0B)后跟一个或多个二进制数字(0 或 1)(在章 5 中介绍)。
- 多个异常处理改进(在章 5 中涉及)
-
并发性改进
-
新的 I/O 库(添加了新的类来从文件中读取/写入数据,在章 8 中介绍)
-
Timsort引入算法来排序对象的集合和数组,而不是合并排序,因为它具有更好的性能。更好的性能通常意味着减少消耗的资源:内存和/或 CPU,或者减少执行所需的时间。
在几乎没有原始开发团队参与的情况下继续开发一个项目一定是一项非常艰难的工作。这是显而易见的,因为随后有 161 个更新;他们中的大多数人需要修复安全问题和漏洞。
JavaFX 2.0 随 Java 7 一起发布。这证实了 JavaFX 项目与 Oracle 的合作前景。作为一个主要的变化,JavaFX 不再是一个脚本语言,而成为一个 Java API。这意味着 Java 语言语法知识足以开始用它构建用户图形界面。JavaFX 开始超越 Swing,因为它的硬件加速图形引擎 Prism ?? 在渲染方面做得更好。
从 Java 7 开始,OpenJDK 诞生了,它是 Java SE 平台版的开源参考实现。这是 Java 开发人员社区为提供一个不受 Oracle 许可的 JDK 版本所做的努力,因为人们认为 Oracle 将为 JDK 引入更严格的许可以从中获利。
Java 8
Java SE 8,代号 Spider ,于 2014 年发布,包含了最初打算成为 Java 7 一部分的功能。迟到总比不到好,对吧?历经三年的发展,Java 8 包含了以下关键特性:
-
语言语法变化
-
lambda 表达式的语言级支持(函数式编程特性)
-
对接口中默认方法的支持(在章节 4 中涉及)
-
新的日期和时间 API(包含在章节 5 中)
-
使用 streams 进行并行处理的新方法(在第章 第八章 中介绍)
-
-
改进了与 JavaScript 的集成(Nashorn 项目)。JavaScript 是一种 web 脚本语言,在开发社区中很受欢迎,所以用 Java 提供对它的支持可能会为 Oracle 赢得一些新的支持者。
-
垃圾收集过程的改进
从 Java 8 开始,代号被取消,以避免任何商标法纠纷;取而代之的是,采用了一种易于区分主要版本、次要版本和安全更新版本的语义版本。 10 版本号匹配以下模式:$MAJOR.$MINOR.$SECURITY。
当在终端中执行java -version时(如果您安装了 Java 8),您会看到类似于清单 1-3 中的日志。
$ java -version
java version "1.8.0_162"
JavaTM SE Runtime Environment build 1.8.0_162-b12
Java HotSpotTM 64-Bit Server VM build 25.162-b12, mixed mode
Listing 1-3Java 8 Log for Execution of java -version
在此日志中,版本号具有以下含义:
-
1 表示主版本号,对于包含新版本的 Java SE 平台规范中指定的重要新功能的主发行版,主版本号会增加。
-
8 代表次要版本号,次要更新版本会增加,可能包含兼容的错误修复、标准 API 的修订和其他小功能。
-
0 表示包含关键修补程序(包括提高安全性所必需的修补程序)的安全更新版本的安全级别。当SECURITY 不会重置为零,这让用户知道这个版本更安全。
-
162 是内部版本号。
-
b12 表示附加的构建信息。
这种版本控制风格在 Java 应用中很常见,所以采用这种版本控制风格是为了与一般的行业实践保持一致。
Java 9
Java SE 9 于 2017 年 9 月发布。期待已久的拼图项目终于来了。Java 平台终于模块化了。
对于 Java 世界来说,这是一个巨大的变化;这不是语法上的变化,也不是什么新功能。这是平台设计的改变。我认识的一些有经验的开发人员从 Java 诞生的第一年就开始使用它,他们很难适应。它应该修复 Java 已经存在多年的一些严重问题(在章 3 中有所涉及)。您很幸运,因为作为初学者,您是从零开始的,所以您不需要改变开发应用的方式。
除了引入 Java 模块之外,以下是最重要的特性:
-
Java Shell 工具,一个交互式命令行界面,用于用 Java 编写的评估声明、语句和表达式(在第章 第三章 中介绍)
-
相当多的安全更新
-
private方法现在在接口中得到支持(在章节 4 中涉及) -
强化
try-with-resources:最终变量现在可以作为资源使用(在章节 5 中有所涉及) -
“_”从合法标识符名称集中删除(包含在章 4 中)
-
对垃圾优先(G1)垃圾收集器的增强;这就变成了默认的垃圾收集器(在章13T5)中有介绍)
-
在内部,使用了一种新的更紧凑的字符串表示法(在第章 5 中介绍)
-
并发更新(与并行执行相关,在章节 5 中提到过)
-
集合的工厂方法(包含在章 5 中)
-
更新图像处理 API 优化用于编写处理图像的代码的组件
Java 9 遵循与 Java 8 相同的版本控制方案,有一点小的变化。JDK 名字中包含的 Java 版本号最终成为版本方案中的$MAJOR号。因此,如果您安装了 Java 9,当在终端中执行java -version时,您会看到类似于清单 1-4 中的日志。
$ java -version
java version "9.0.4"
JavaTM SE Runtime Environment build 9.0.4+11
Java HotSpotTM 64-Bit Server VM build 9.0.4+11, mixed mode
Listing 1-4Java 9 Log for Execution of java -version
Java 10
Java SE 10(又名 Java 18.3)于 2018 年 3 月 20 日发布。Oracle 改变了 Java 发布风格,每六个月发布一个新版本。Java 10 还使用了 Oracle 建立的新版本约定:版本号跟随月格式。这种版本控制风格是为了让开发人员和最终用户更容易计算出一个版本的年龄,这样他们就可以判断是否要升级到一个具有最新安全补丁和附加特性的新版本。
以下是 Java 10 的一些特性。 12
-
一个局部变量类型推理来增强语言,将类型推理扩展到局部变量(这是最令人期待的特性,在第章 5 中有所介绍)
-
针对垃圾收集的更多优化(在第章 第十三章 中介绍)
-
应用类数据共享,通过跨进程共享公共类元数据来减少内存占用(这是一个高级特性,本书不会涉及)
-
更多并发更新(与并行执行相关,在章节5中提到)
-
备用内存设备上的堆分配(JVM 运行 Java 程序所需的内存—称为堆内存—可以在备用内存设备上分配,因此堆也可以在易失性和非易失性 ram 之间划分。更多关于 Java 应用使用的内存可以在章节 5 中阅读。)
安装 JDK 10 后,在终端中运行 java -version 会显示一个类似于清单 1-5 中的日志。
$ java -version
java version "10" 2018-03-20
JavaTM SE Runtime Environment 18.3 build 10+46
Java HotSpotTM 64-Bit Server VM 18.3 build 10+46, mixed mode
Listing 1-5Java 10 Log for Execution of java -version
Java 11
2018 年 9 月 25 日发布的 Java SE 11(又名 Java 18.9), 13 ,包含以下特性:
-
删除用于构建企业 Java 应用和 Corba(用于远程调用的非常老的技术,允许您的应用与安装在不同计算机上的应用通信)模块的 JEE 高级组件。
-
lambda 参数的局部变量语法允许在声明隐式类型 lambda 表达式的形式参数时使用 var 关键字。
-
Epsilon,一个低开销的垃圾收集器(一个 no-GC,所以基本上你可以在没有 GC 的情况下运行一个应用),基本上对垃圾收集进行了更多的优化(在章 13 中有所涉及)。
-
更多的并发更新(与并行执行相关,在章节5 中提到)。
-
Nashorn JavaScript 脚本引擎和 API 被标记为不推荐使用,以便在未来的版本中删除它们。ECMAScript 语言结构发展得相当快,所以 Nashorn 变得难以维护。
除了这些变化之外,还推测应该引入一个新的版本变化,因为$YEAR.$MONTH格式与开发人员不太合拍。(为什么这么多版本命名变化?这真的这么重要吗?很明显,是的。)提出的版本控制变化类似于 Java 9 中引入的版本控制变化。 14
当安装 JDK 11 时,在终端中运行 java -version 会显示一个类似于清单 1-6 中的日志。
$ java -version
java version "11.0.3" 2019-04-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.3+12-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.3+12-LTS, mixed mode)
Listing 1-6Java 11 Log for Execution of java -version
JDK 11 是一个长期支持版本,计划提供几年的支持。这就是版本名中的 LTS 的意思。
随着 JDK 11 的发布,甲骨文宣布他们将开始对 Java SE 8 许可证收费,因此试图降低软件成本的小企业开始寻找替代方案。AdoptOpenJDK 从一组完全开源的构建脚本和基础设施中为多个平台提供预构建的 OpenJDK 二进制文件。
OpenJDK 与 OracleJDK 具有相同的代码,这取决于您使用的提供者。
另一个优势是,虽然甲骨文,JDK 不能修改,以适应业务应用的需要;OpenJDK 可以修改,因为它是在 GNU 通用公共许可证下授权的,这是相当宽松的。
此外,如果钱不是问题,亚马逊的 Coretto、Azul Zulu 和 GraalVM 都是以某种方式优化的替代 JDK。
Java 12
Java SE 12,152019 年 3 月 29 日发布,包含以下重要特性:
-
一个名为 Shenandoah 的新的实验性垃圾收集器(GC)算法减少了 GC 暂停时间。
-
修改了
switch语句的语法,允许它也作为表达式使用。它还消除了对break语句的需要(在章节 7 中有所涉及)。 -
JVM 常量 API,对关键类文件和运行时工件的名义描述进行建模。这个 API 对操作类和方法的工具很有帮助。
-
对 G1 垃圾收集器的小改进(包含在第章 第十三章 )。
-
改进 JDK 构建过程的 CDS 档案。
-
大约 100 个微基准 16 被添加到 JDK 源中。
安装 JDK 12 后,在终端中运行 java -version 会显示一个类似于清单 1-7 中的日志。
$ java -version
java version "12.0.2" 2019-07-16
Java(TM) SE Runtime Environment (build 12.0.2+10)
Java HotSpot(TM) 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing)
Listing 1-7Java 12 Log for Execution of java -version
JDK 12 是甲骨文 2017 年 9 月随 JDK 9 推出的为期六个月的发布节奏的一部分。JDK 12 是一个支持期限很短的功能版本。这个版本已经发布了两个补丁。
Java 13
2019 年 9 月 17 日发布的 Java SE 13, 17 ,包含了一些重要的特性,数百个较小的增强,以及数千个 bug 修复。这个版本最重要的特点是:
-
动态 CSD 存档(JDK 12 中增加的对 CDS 存档支持的改进)
-
z 垃圾收集器增强功能(包含在章13T5)
-
传统套接字 API 的新实现
-
对
switch表情的更多改进(包含在章节 7 ) -
对文本块的支持(在章 5 中介绍)
安装 JDK 13 后,在终端中运行 java -version 会显示一个类似于清单 1-8 中的日志。
$ java -version
java version "13.0.2" 2020-01-14
Java(TM) SE Runtime Environment (build 13.0.2+8)
Java HotSpot(TM) 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)
Listing 1-8Java 13 Log for Execution of java -version
JDK 13 是一个功能版本,支持期限也很短。这个版本已经发布了两个补丁。
Java 14
Java SE 14, 18 于 2020 年 3 月 17 日发布,包含了一个重要特性、增强功能和错误修复的大列表。这个版本最重要的特点是:
-
instanceof运算符的模式匹配(包含在章节 7 中) -
JFR 事件流 API,用于收集 Java 应用和 JVM 运行时的分析和诊断数据
-
G1 垃圾收集器的更多改进(包含在第章 第十三章 )
-
CMS(并发标记清除)垃圾收集器已被删除。
-
支持 macOS 的 Z 垃圾收集器(在第章 第十三章 中介绍)
-
Records的引入是为了提供一个简洁的语法来声明类,这些类是浅不可变数据的透明持有者(在章节 5 中讨论) -
外部内存访问 API 支持 Java 程序安全有效地访问 Java 堆外的外部内存
-
对
NullPointerException类的改进,提供了更精确的细节,以便于识别变量null -
引入
jpackage工具是为了提供对本地打包格式的支持,给最终用户一种自然的安装体验
安装 JDK 14 时,在终端中运行 java -version 会显示一个类似于清单 1-9 中的日志。
$ java -version
java version "14.0.2" 2020-07-14
Java(TM) SE Runtime Environment (build 14.0.2+12-46)
Java HotSpot(TM) 64-Bit Server VM (build 14.0.2+12-46, mixed mode, sharing)
Listing 1-9Java 14 Log for Execution of java -version
即使这个版本包含了很多新特性,但其中大部分仅在预览模式下可用,或者被认为处于incubation阶段,这使得这个版本不稳定,不能作为长期支持的候选。
Java 15
2020 年 9 月 15 日发布的 Java SE 15, 19 ,包含了对之前版本中添加的项目的相当大的改进。这个版本最显著的特点是:
-
删除 Nashorn JavaScript 引擎
-
添加密封和隐藏类(包含在章节4T5)
-
加密签名现在支持爱德华兹曲线数字签名算法(EdDSA)
-
对传统 DatagramSocket API 的更多增强
-
偏向锁定被禁用和废弃,这导致多线程应用的性能提高
安装 JDK 15 后,在终端中运行 java -version 会显示一个类似于清单 1-10 中的日志。
$ java -version
java version "15" 2020-09-15
Java(TM) SE Runtime Environment (build 15+36-1562)
Java HotSpot(TM) 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)
Listing 1-10Java 15 Log for Execution of java -version
JDK 15 只是一个短期版本,在 JDK 16 于 2021 年 3 月推出之前,Oracle 标准服务将提供六个月的支持。
Java 16
2021 年 3 月 16 日发布的 Java SE 16, 20 ,是标准 Java set 追随 JDK 15 的版本的参考实现。这意味着在 JDK 15 不稳定的一切在 JDK 16 预计会更稳定。除此之外,这个版本最显著的特点是:
-
引入矢量 API,以表达矢量计算,在受支持的 CPU 架构上编译成最佳矢量硬件指令,从而实现优于同等标量计算的性能
-
默认情况下 JDK 内部构件的强封装(涵盖在章 3 )
-
引入外部链接器 API 是为了提供对本机代码的静态类型的纯 Java 访问
-
引入弹性元空间,促进将未使用的热点类元数据(即元空间)内存更迅速地返回给操作系统
-
增加了对 C++ 14 语言特性的支持
当安装了 JDK 16 后,在终端中运行java -version会显示一个类似于清单 1-11 中的日志。
$ java -version
openjdk version "16-ea" 2021-03-16
OpenJDK Runtime Environment (build 16-ea+30-2130)
OpenJDK 64-Bit Server VM (build 16-ea+30-2130, mixed mode, sharing)
Listing 1-11Java 16 Log for Execution of java -version
JDK 16 只是一个短期版本,在 JDK 17 于 2021 年 9 月推出之前,Oracle 标准服务将提供六个月的支持。在写这一章的时候,JDK 16 只能通过早期访问计划获得,这就是为什么“ea”字符串出现在版本名称中。
Java 17
JDK17、21接下来的长期支持发布,将由甲骨文支持八年。它于 2021 年 9 月 14 日发布,按照甲骨文针对 Java SE 版本的六个月发布节奏。
在写这一章的时候,JDK 17 只能通过早期访问程序获得,这就是为什么“ea”字符串出现在版本名称中;意思是提前进入。它很难使用,因为还没有任何编辑器或其他构建工具支持它。特性列表也不完整,仍然欢迎 Java 社区对缺陷修复和特性提出建议。
到这本书发行的时候,Java 17 已经稳定并可以使用了。这本书将全面涵盖这个版本的所有重要的稳定功能。不包括预览功能,因为它们对该项目的稳定性有风险。
-
JDK 16 版中引入的 Vector API 的性能和实现改进
-
对密封类和接口的改进
-
开关表达式的模式匹配介绍(特征预览)
-
macOS 的特定改进
-
伪随机数生成器的增强:引入了伪随机数生成器(PRNG)的新接口和实现,包括可跳转的 prng 和一类新的可拆分 PRNG 算法(LXM)
-
封装 JDK 内部构件的改进
-
弃用 Applet API(准备在 JDK 18 中移除)
-
反对安全管理器(准备在 JDK 18 中删除)
-
外部函数和内存 API 合并了两个先前孵化的 API:外部内存访问 API 和外部链接器 API,允许开发人员调用本地库和处理本地数据,而没有 JNI 的风险
JDK 17 的特性列表集中在 JVM 内部,以提高性能和废弃旧的 API。
当安装 JDK 17 时,在终端中运行 java -version 会显示一个类似于清单 1-12 中的日志。
openjdk version "17" 2021-09-14
OpenJDK Runtime Environment (build 17+35-2724)
OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)
Listing 1-12Java 17 Log for Execution of java -version
细节到此为止。如果你想知道更多关于前 25 年的信息,你可以很容易地在网上找到。 22
先决条件
在结束本章之前,公平地说,要学习 Java,你需要几样东西:
-
了解操作系统,如 Windows、Linux 或 macOS。
-
如何完善你的搜索标准,因为与你的操作系统相关的信息在书中没有涉及;如果你有问题,你必须自己解决。
-
互联网连接。
如果您已经了解 Java,并且出于好奇或为了模块章节而购买了这本书,那么了解像 Maven 或 Gradle 这样的构建工具是很有帮助的,因为源代码被组织在一个多模块项目中,可以用一个简单的命令完全构建。我选择使用构建工具是因为在这个时代,没有构建工具学习 Java 是没有意义的;你申请的任何一家公司肯定都会用到它。
除了我列出的先决条件,您还需要安装一个 JDK 和一个 Java 编辑器。这在章2中有所涉及。你不需要知道数学、算法或设计模式(尽管在你读完这本书后你可能会知道一些)。
话虽如此,我们还是深入探讨一下吧。
摘要
Java 已经统治这个行业超过 25 年了。它并不总是处于最常用的开发技术的顶端,但也从未离开过前五名。即使有了 Node.js 这样的服务器端 JavaScript 智能框架,繁重的工作还是留给了 Java。像 Scala 和 Kotlin 这样的新兴编程语言运行在 JVM 上,所以为了竞争,Java 编程语言可能会经历严重的蜕变,但它仍然会在这里。
版本 9 中引入的模块化功能为在更小的设备上安装 Java 应用打开了大门,因为要运行 Java 应用,我们不再需要整个运行时—只需要它的核心加上构建应用所用的模块。
此外,有许多应用是用 Java 编写的,尤其是在金融领域,所以 Java 仍然会存在,因为遗留的原因,并且因为将这些应用移植到另一种技术是一项不可能完成的任务。然而,这些应用中的大多数都停留在 JDK 8 上,因为它们很复杂,并且有许多需要升级的依赖项,而这并不总是可能的。
Java 可能会存活下来,并在未来的 10 到 15 年内处于领先地位。它是一项非常成熟的技术,有一个巨大的社区围绕着它,这确实有所帮助。非常容易学习和开发人员友好使它仍然是大多数公司的首选。因此,此时您可能会得出结论,学习 Java 和购买这本书是一项不错的投资。
这一章有很多参考资料。它们是有趣的读物,但是它们不是理解本书内容的强制性要求。其余章节也是如此。
Footnotes 1参考: https://en.wikipedia.org/wiki/Java_(software_platform) 。
2
早期访问程序
3
这个标志背后的故事可以在 2021 年 10 月 15 日访问的【标题】https://goodlogo.com/extended.info/sunmicrosystems-logo-2385中阅读。您还可以阅读更多关于 Sun Microsystems 的信息。
4
这种语言是由詹姆斯·高斯林以他家门前的橡树命名的。
5
jvatIntime
6
所有中间版本的代码名称都列在甲骨文的“JDK 版本”、“??”、“??”、“??”中,访问日期为 2021 年 10 月 15 日。
7
如果您想查看 Java 规范请求的内容和列表,请参见 Java 社区流程, http://www.jcp.org/en/jsr/detail?id=59 ,访问于 2021 年 10 月 15 日。
8
Java 语言最初被命名为 Oak。因为版权问题改名为 Java。你会发现一些关于新名字的理论。有一种说法是,JAVA 的名字实际上是绿色团队成员名字首字母的集合:詹姆斯·高斯林、亚瑟·范·霍夫和安迪·贝希托尔斯海姆,这个标志的灵感来自他们对咖啡的热爱。
9
让我们称它们实际上是:修补程序。
10
打开 JDK,《JEP 223:新版本-字符串方案》, http://openjdk.java.net/jeps/223 ,访问时间 2021 年 10 月 15 日。
11
开放 JDK 描述的约定,“JEP 322:基于时间的版本控制”, http://openjdk.java.net/jeps/322 ,2021 年 10 月 15 日访问。
12
完整的列表可在 2021 年 10 月 15 日访问的开放 JDK、“JDK 10、 http://openjdk.java.net/projects/jdk/10 中找到,包含 API 和内部更改的详细列表的发行说明可在 2021 年 10 月 15 日访问的甲骨文、“JDK 10 发行说明、https://www.oracle.com/java/technologies/javase/10-relnote-issues.html 中找到。
13
完整的功能列表在开放的 JDK,“JDK 11”, http://openjdk.java.net/projects/jdk/11/ ,2021 年 10 月 15 日访问。
14
如果你有兴趣,你可以在开放 JDK 阅读它的详细规范,“基于时间的版本控制”
15
完整的功能列表在开放的 JDK,“JDK 12”, http://openjdk.java.net/projects/jdk/12/ ,2021 年 10 月 15 日访问。
16
基于 Java 微基准线束,打开 JDK,“代码工具:jmh”, https://openjdk.java.net/projects/code-tools/jmh/ ,访问时间 2021 年 10 月 15 日。
17
完整的功能列表在开放的 JDK,“JDK 13”, http://openjdk.java.net/projects/jdk/13/ ,2021 年 10 月 15 日访问。
18
完整的功能列表位于开放 JDK,“JDK 14”,Open JDK . Java . net/projects/JDK/14/,于 2021 年 10 月 15 日访问。
19
完整的功能列表在开放的 JDK,“JDK 15”, http://openjdk.java.net/projects/jdk/15/ ,2021 年 10 月 15 日访问。
20
完整的功能列表在开放的 JDK,“JDK 16”, http://openjdk.java.net/projects/jdk/16/ ,2021 年 10 月 15 日访问。
21
完整的功能列表在 JDK上。java。net 、【JDK 17 通用-发售】、、https://jdk.java.net/17、,2021 年 10 月 15 日访问。
22
如果你认为有必要,可以从这里开始阅读:免费 Java 指南,《Java 编程语言的历史》, https://www.freejavaguide.com/history.html ,2021 年 10 月 15 日访问。
****
二、准备您的开发环境
要开始学习 Java,你的电脑需要设置成 Java 开发机。因此,要求如下:
-
你的电脑对 Java 的支持是强制性的*。*
-
集成开发环境,也称为 IDE,它基本上是一个用来编写代码的应用。IDE 在编写代码、编译代码和执行代码时为您提供帮助。
-
本书推荐的 IDE 是 IntelliJ IDEA 。你可以去他们的网站获取免费的社区版;就本书的目的而言,这就够了。
-
或者可以选择 Java 开发最流行的免费 IDE:Eclipse。
-
或者你可以试试 NetBeans ,1 这是大多数初学者的默认选择,因为在版本 8 之前,它是与 JDK 捆绑在一起的。是从 Java 9 的 JDK 里拿出来的,现在可以从这里下载:
https://netbeans.org/。
-
-
Maven 是一个构建工具,用于组织项目,轻松处理依赖关系,并使您在大型多模块项目中的工作更容易。(这是强制性的,因为本书中的项目是用 Maven 设置组织和构建的。)
-
Git 是一个版本控制系统,你可以用它来获得这本书的源代码,你可以用它来做实验并创建你自己的版本。它是可选的,因为 GitHub 是本章源代码的宿主,支持直接下载它们作为存档。 1
要编写和执行 Java 程序/应用,你只需要安装好JavaDdevelopmentKit(JDK)。没有什么能阻止你在记事本中编写 Java 代码,如果这是你想要的。我在这里列出的所有其他工具只是为了让您的工作更容易,并让您熟悉真正的开发工作。
如果你为所有用户安装这些应用,你可能需要管理权限。对于 Windows 10,你甚至可能需要一个特殊的程序来授予你的用户管理权限,以便你可以安装必要的工具。这本书提供了如何安装所有东西的说明——假设你的用户有必要的权限。如果你需要更多的信息,互联网可以帮助你。
如果看起来很多,不要气馁;本章包含如何安装和验证每个工具是否正常工作的说明。让我们从确保你的电脑支持 Java 开始。
安装 Java
现在你有了电脑,你迫不及待地开始编写 Java 应用。但是首先你需要给自己弄一个 JDK 并安装它。为此,你需要一个互联网连接。打开浏览器,进入 https://developer.oracle.com/java 。菜单应该有一个下载部分。展开它,选择 Java SE,如图 2-1 所示。
图 2-1
浏览 Oracle 站点以找到所需的 JDK
在 Oracle 网站上,您可以找到最新的稳定 Java 版本。单击所需版本下的下载链接。您应该会被重定向到一个类似于图 2-2 中的下载页面。
图 2-2
您可以在 Oracle 页面下载所需的 JDK
JDK 可用于一些操作系统。你应该下载和你的相匹配的。为了写这本书和写源代码,我用的是 macOS 电脑,这意味着我会用 *下载 JDK。dmg 分机。
您需要接受许可协议,才能使用 Java 进行开发。好奇的可以看一下,但基本上它告诉你,只要不修改它原来的组件,就允许你使用 Java。它还告诉你,你要对你如何使用它负责,所以如果你用它来编写或执行邪恶的应用,你要独自承担法律等方面的责任。
如果你想得到一个尚未正式发布的 JDK 早期版本,这是你必须去的页面: http://openjdk.java.net/projects/jdk/ 。在写这一章的时候,在那个页面上,在版本下,版本 16 和 17 被列为开发中的*,一个早期访问(不稳定)的 JDK 17 可供下载。*
*这本书将涵盖 Java 语法和 17 版本的细节。在撰写本章时,该版本还有八个月的时间,因此一些图片和细节可能会过时(例如,Oracle 可能会更改其网站的主题)。从一个版本到另一个版本,有一些相同的细节。JDK 不再被称为 JDK 的可能性很小。这些将不会被审查和改变,因为唯一不同的是版本号。由于本书计划在 Java 17 发布后发布,建议下载该版本的 JDK,以确保源代码的完全兼容性。
下载完 JDK 后,下一步是安装它。只需双击它,然后单击下一步,直到完成。这将适用于 Windows 和 macOS。JDK 安装在特定的位置。
在 Windows 中,这是C:\Program Files\Java\jdk-17。
在 macOS 中这是/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home。
在 Linux 系统上,根据发行版的不同,JDK 的安装位置也会有所不同。我的首选方法是从 Oracle 站点获取包含 JDK 全部内容的*.tar.gz,将其解压缩,并复制到一个特定的位置。另外,我在 Linux 上的首选位置是/home/iuliana.cosmina/tools/jdk-17.jdk。
使用 PPA(储存库;也称为软件包管理器)安装程序会自动将 JDK 文件放在它们在 Linux 上应该放的位置,并在发布新版本时使用 Linux(全局)更新程序自动更新它们。但是如果你熟练地使用 Linux,你现在可能已经发现可以跳过这一节了。
在 Linux 或 Unix 系统上简化事情的另一种方法是使用 SDKMAN。从这里得到: https://sdkman.io/ 。
如果你去那个地方,你可以检查 JDK 的内容。在图 2-3 中,左边是 JDK 17 的内容,右边是 JDK 8 的内容。
图 2-3
8 版和 17 版内容对比
我选择进行这种比较是因为从 Java 9 开始,JDK 的内容以不同的方式组织。在 Java 8 之前,JDK 包含一个名为jre的目录,其中包含 JDK 使用的 Java 运行时环境(JRE)。对于只对运行 Java 应用感兴趣的人来说,JRE 可以单独下载。
lib目录包含开发工具所需的 Java 库和支持文件。
从 Java 9 开始,JRE 不再被隔离在自己的目录中。从版本 11 开始,Java 变得完全模块化。这意味着可以使用运行应用所需的特定模块来创建定制的 JRE 发行版。这意味着从 Java 11 开始,Oracle 站点上没有 JRE 可供下载。
关于 JDK,您需要知道的最重要的事情是,bin目录包含编译、执行和审计 Java 代码所必需的可执行文件和命令行启动器。其他目录是jmods目录和include目录,前者包含编译后的模块定义,后者包含 C 语言头文件,支持使用 Java 本地接口(JNI)和 Java 虚拟机(JVM)调试接口进行本地代码编程。
JAVA_HOME 环境变量
JDK 中最重要的目录是bin目录,因为该目录必须添加到系统的路径中。这允许您从任何地方调用 Java 可执行文件。这允许其他应用也调用它们,而不需要额外的配置步骤。大多数用于处理 2 Java 代码的 ide 都是用 Java 编写的,它们需要知道 JDK 安装在哪里才能运行。这是通过声明一个名为 JAVA_HOME 的环境变量来实现的,该变量指向 JDK 目录的位置。要使 Java 可执行文件可以从系统中的任何位置调用,必须将 bin 目录添加到系统路径中。接下来的三个部分解释了如何在三种最常见的操作系统上实现这一点。
Windows 上的 JAVA_HOME
要在 Windows 系统上声明 JAVA_HOME 环境变量,需要打开设置系统变量的对话框。在 Windows 系统上,点击开始按钮。在菜单中,有一个搜索框。在更近的版本中,水平工具栏上有一个搜索框;你也可以用这个。在那里输入单词环境(单词的前三个字母就足够了)。该选项应该可以点击。在 Windows 10 上,这些步骤如图 2-4 所示。
图 2-4
用于配置环境变量的 Windows 菜单项
点击该菜单项后,应会打开如图 2-5 所示的窗口。
图 2-5
在 Windows 上设置环境变量的第一个对话框
点击环境变量按钮(有硬边的那个)。另一个对话窗口打开,分为两部分:用户变量和系统变量。你对系统变量感兴趣,因为那是我们声明 JAVA_HOME 的地方。只需点击新建按钮,就会出现一个小的对话窗口,里面有两个文本字段;一个要求您输入变量名,在本例中为JAVA_HOME,另一个要求您输入路径,在本例中为 JDK 路径。第二个窗口和变量信息弹出对话框窗口如图 2-6 所示。
图 2-6
在 Windows 10 上将 JAVA_HOME 声明为系统变量
在定义了JAVA_HOME变量之后,您需要将可执行文件添加到系统路径中。这可以通过编辑Path变量来完成。只需从系统变量列表中选择并点击编辑按钮。从 Windows 10 开始,Path变量的每一部分都显示在不同的行上,所以你可以添加一个新行,在上面添加%JAVA_HOME%\bin。这种语法很实用,因为它从JAVA_HOME变量包含的任何位置获取 bin 目录的位置。对话窗口如图 2-7 所示。
图 2-7
在 Windows 10 上,将 JDK 可执行文件目录声明为系统路径变量的一部分
在旧的 Windows 系统上,Path的内容显示在一个文本字段中。这意味着您必须在变量文本字段中添加%JAVA_HOME%\bin表达式,并使用分号(;).
无论你用的是哪种 Windows 系统,你都可以通过打开命令提示符并执行set命令来检查你的设置是否正确。这将列出所有系统变量及其值。JAVA_HOME和Path应该有期望的值。对于本节提出的设置,当执行set时,输出如图 2-8 所示。
图 2-8
使用“set”命令列出的 Windows 10 系统变量
如果您执行了前面的命令并看到了预期的输出,现在您可以通过在命令提示符窗口中执行java -version来测试您的 Java 安装,它会打印出预期的结果,类似于清单 2-1 的内容。
$ java -version
openjdk version "17-ea" 2021-09-14
OpenJDK Runtime Environment (build 17-ea+3-125)
OpenJDK 64-Bit Server VM (build 17-ea+3-125, mixed mode, sharing)
Listing 2-1Java 17 Log for Execution of java -version
macOS 上的 JAVA_HOME
JDK 安装的位置是/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home。你的JAVA_HOME should指向这个位置。要为当前用户执行此操作,您可以执行以下操作:
-
在
/Users/{your.user}目录中, 3 创建一个名为.bash_profile的文件,如果它还不存在的话。 -
在该文件中,编写以下内容:
export JAVA_HOME=$(/usr/libexec/java_home -v17)
export PATH=$JAVA_HOME/bin:$PATH
如果您使用不同的 shell,只需在它自己的配置文件中添加相同的两行。
在 macOS 上,可以同时安装多个 Java 版本。您可以通过调用/usr/libexec/java_home命令并给出您感兴趣的 Java 版本作为参数来获取所需版本的 JDK 位置,从而设置系统上当前使用的版本。执行命令的结果存储为JAVA_HOME变量的值。
在我的系统上,我安装了 JDK 8 到 17。我可以通过执行/usr/libexec/java_home命令并提供每个版本作为参数来检查每个 JDK 的位置。清单 2-2 中描述了版本 8 和 17 的命令和输出。
$ /usr/libexec/java_home -v17
/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
$ /usr/libexec/java_home -v1.8
/Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home
Listing 2-2Java 8 and 17 Locations Obtaind By Calling /usr/libexec/java_home
使用 SDKMAN 可以避免手动安装 Java 和声明
JAVA_HOME环境变量。
行 export PATH=$JAVA_HOME/bin:$PATH将 bin 目录的内容从 JDK 位置添加到系统补丁中。这意味着我可以打开一个终端并在它下面执行任何 Java 可执行文件。例如,我可以通过执行java –version来验证为我的用户设置的默认 Java 版本是否是期望的版本。
根据作为参数给出的版本,将返回不同的 JDK 位置。如果您想测试JAVA_HOME的值,那么echo命令可以帮助您。清单 2-3 描述了echo和java –version命令的输出。
$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
$ java -version
openjdk version "17-ea" 2021-09-14
OpenJDK Runtime Environment (build 17-ea+3-125)
OpenJDK 64-Bit Server VM (build 17-ea+3-125, mixed mode, sharing)
Listing 2-3echo and java –version commands to check JAVA_HOME value and the Java version installed
Linux 上的 JAVA_HOME
如果你熟练地使用 Linux,你要么使用 PPA,要么使用 SDKMAN,所以你可以跳过这一节。但是,如果您喜欢控制 JDK 的位置并定义自己的环境变量,请继续阅读。
Linux 系统是类似 Unix 的操作系统。这和 macOS 类似,都是基于 Unix 的。根据您的 Linux 发行版,安装 Java 可以通过特定的包管理器来完成,或者直接从 Oracle 官方网站下载 JDK * . tar . gz 档案文件。
如果使用包管理器安装 Java,必要的可执行文件通常会在安装时自动放在系统路径中。这就是为什么在本书中,我们只讨论手动完成所有事情的情况,并选择只为当前用户在某个位置安装 Java,比如/home/{your.user}/tools/jdk-17.jdk,因为讨论包管理器不是本书的目的。4
从 Oracle 站点下载 JDK 归档文件并在/home/{your.user}/tools/jdk-17.jdk解包后,您需要在您的用户主目录中创建一个名为.bashrc或.bash_profile的文件。在某些 Linux 发行版上,这些文件可能已经存在,您只需要编辑它们。将以下内容添加到行中:
export JAVA_HOME=/home/{your.user}/tools/jdk-17.jdk
export PATH=$JAVA_HOME/bin:$PATH
如你所见,语法类似于 macOS。为了检查 JDK 和 Java 版本的位置,使用了 macOS 部分中提到的相同命令。
安装 Maven
这本书第一版的资料来源被组织在一个多模块项目中。根据读者的要求,本书的源代码被组织在一个 Maven 多模块项目中。
本书附带的源代码被组织成小项目,可以使用 Apache Maven 编译和执行。您可以下载并在其官方页面上了解更多信息: https://maven.apache.org 。Apache Maven 是一个软件项目管理和理解工具。之所以选择它作为本书的构建工具,是因为它易于安装(XML 现在几乎无处不在),也是因为它与 Java 的长期关系。学习构建工具是很实用的,因为对于中型和大型项目来说,它们是必备的,因为它们方便了依赖项的声明、下载和升级。
安装 Maven 相当容易。只需下载它,在某个地方解包,并声明M2_HOME环境变量。如何做到这一点的说明是官方网站的一部分,或者你可以使用 SDKMAN。
一旦安装了 Maven,就可以通过打开终端(或 Windows 上的命令提示符)并执行mvn -version来检查它是否安装成功,以及它是否使用了您期望的 JDK 版本。输出应该与清单 2-4 中的输出非常相似。
$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /Users/iulianacosmina/.sdkman/candidates/maven/current
Java version: 17-ea, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
Default locale: en_GB, platform encoding: UTF-8
OS name: "mac os x", version: "10.16", arch: "x86_64", family: "mac"
Listing 2-4Output of Command mvn -version on macOS
如果你想从事 Java 开发,熟悉构建工具是一个很有价值的优势。大多数使用 Java 的公司都有大型项目,这些项目被组织在相互依赖的模块中,没有构建工具就无法管理这些模块。Apache Maven 长期以来一直是 Java 事实上的构建工具,所以您可能想熟悉一下它。
安装 Git
这是一个可选的部分,但是作为一个开发人员,熟悉版本控制系统是很重要的,所以在这里。要在你的系统上安装 Git,只需进入官方页面 https://git-scm.com/downloads 下载安装程序。打开安装程序,点击下一个,直到完成。这适用于 Windows 和 macOS。 5 没错,就是这个容易;你不需要做任何其他事情。在 Linux 上,可以使用 PPA 来完成。
以防万一,这里有一页是关于如何为所有操作系统安装 Git 的说明: https://gist.github.com/derhuerst/1b15ff4652a867391f03
要测试 Git 是否成功安装在您的系统上,请打开一个终端(Windows 中的命令提示符,以及您在 macOS 和 Linux 上安装的任何类型的终端)并运行git --version来查看打印的结果。它应该是您刚刚安装的 Git 的版本。预期输出应类似于清单 2-5 。
$ git --version
git version 2.20.1
Listing 2-5Output of Command git –version to Verify Git Installation
现在已经安装了 Git,您可以通过在终端中克隆官方的 Git 存储库或者直接从 IDE 中获得这本书的源代码。
安装 Java IDE
基于我十多年的经验,我推荐的编辑器是 IntelliJ IDEA。它由一家名为 JetBrains 的公司生产。你可以从他们的官方网站 https://www.jetbrains.com/idea/download/ 下载这个 IDE。终极版 30 天后过期;除此之外,还需要付费许可证。还有一个社区版,可以在没有许可证的情况下使用。对于便于学习 Java 的简单项目,这个版本就足够了。
下载 IntelliJ IDEA 归档文件后,双击它进行安装。之后,启动它并首先配置您的插件。点击插件菜单项,在窗口的右侧会出现一个插件列表。在已安装选项卡上的插件列表是您可能想要检查的(如图 2-9 所示)。
图 2-9
IntelliJ IDEA Community Edition 配置插件窗口
在 IntelliJ IDEA 中,不同风格的 Java 项目所需的插件是默认启用的。您可以修改该列表并禁用不需要的列表。这将减少 IntelliJ IDEA 运行所需的内存量。
默认情况下,Maven 插件是启用的;Git 插件也是如此。这意味着您的 IDE 适合立即使用。这意味着你需要得到这本书的来源。有三种方法可以获得这本书的来源:
-
直接从 GitHub 下载压缩包。
-
使用以下命令,使用终端(或 Windows 中的 Git Bash Shell)克隆存储库:
-
使用 IntelliJ IDEA 克隆项目。
$ git clone https://github.com/Apress/java-17-for-absolute-beginners.git
当使用存储库的 HTTPS URL 时,从命令行或 IntelliJ IDEA 克隆不需要 GitHub 用户。图 2-10 显示了为这本书克隆 GitHub 项目的两个必要步骤。
图 2-10
IntelliJ IDEA 社区版克隆自 VCS windows
第一次打开 IntelliJ IDEA 时,选择项目菜单项,然后点击克隆自 VCS 按钮。将出现一个新的对话窗口,您可以在其中插入存储库 URL 和源文件的复制位置。点击克隆按钮后,项目将被复制,IntelliJ IDEA 将打开它,并指出它使用了 Maven。
如果您使用命令行克隆了项目,您可以使用打开按钮并选择克隆操作创建的目录,在 IntelliJ IDEA 中导入它。
IntelliJ IDEA 有自己的内部 Maven 包。如果你想告诉 IntelliJ IDEA 使用你的本地安装,只需打开首选项菜单项,转到构建、执行、部署 ➤ 构建工具 ➤ Maven 部分,选择外部 Maven 安装目录。
这就是了。从下一章开始,将会出现一些代码片段,所以继续构建这个项目。这可以通过在 IntelliJ IDEA Maven 视图中双击maven install阶段来执行,如图 2-11 所示。
图 2-11
IntelliJ IDEA 专家视图
期望在编辑器的底部打开一个窗口,描述构建进度,如果源代码也没问题,这个过程应该以打印消息BUILD SUCCESS结束。
如果在 IntelliJ ideA 中构建失败,并且您想要识别问题,第一步是在 IDE 之外运行。您可以通过执行
mvn clean install在终端(或 Windows 上的命令提示符)中运行构建。如果构建在终端中通过,那么源代码和您的设置是正确的,编辑器配置肯定有问题。
Maven 构建遵循特定的生命周期,将 Java 项目从源代码转换成可以在应用服务器上执行或部署的东西。各个阶段以特定的顺序执行。使用mvn {phase}命令运行一个特定的阶段会执行许多名为 goals 的步骤,每个步骤负责一个特定的任务。
之前推荐的mvn clean install命令执行一个clean阶段,删除之前生成的字节码文件,然后执行一个install阶段,将 Java 文件编译成字节码,执行测试,如果有的话,将它们打包到 Java 档案(*。jar 文件)并将它们复制到本地 Maven 存储库中。如果你想了解更多关于 Maven 的内容,只需查看官方网站: https://maven.apache.org ,但对于本书的范围来说,一切都变得非常容易,正如在章 3 中所解释的那样。
摘要
如果任何说明对你来说不清楚(或者我错过了什么),不要犹豫使用万维网寻找答案。本章介绍的所有软件技术都有全面的官方网站和渴望提供帮助的庞大开发者社区的支持。在最坏的情况下,当你一无所获时,你可以在 Apress GitHub 官方知识库上为这本书创建一个问题,或者给我发邮件。如果需要的话,我会尽力支持你。
但是我想你会没事的。Java 几乎不是火箭科学。 6
Footnotes 1如今,任何一家受人尊敬的软件公司都使用版本控制系统,所以在申请软件开发职位时,熟悉 Git 是一个重要的优势。
2
包括编写代码、分析代码、编译代码和执行代码等操作。
3
用您的实际系统用户名替换{your.user}。
4
无论如何,Linux 用户并不真正需要这个部分。
5
对于 macOS,你也可以使用自制软件( https://brew.sh )。
6
直到 Java 9 才出现,但这本书应该会让初学者更容易理解。
*
三、开始干活
本章涵盖了 Java 语言的基本构件和术语。虽然它可以被认为是另一个介绍性的章节,但它是相当重要的。前一章为您提供了一个为编写 Java 代码而配置的完整开发环境。是时候利用它了。本章包括以下主题:
-
核心语法部分
-
使用 JShell
-
Java 基础构件:包、模块和类
-
用 IntelliJ IDEA 创建 Java 项目
-
Java 代码的编译和执行
-
将 Java 应用打包到可执行的 jar 中
-
使用 Maven
核心语法部分
编写 Java 代码很容易,但是在这之前,有几个基本的语法规则是必要的。让我们分析本书开头的代码示例,现在在清单 3-1 中描述。
package com.apress.ch.one.hw;
import java.util.List;
public class Example01 {
public static void main(String[] args) {
List<String> items = List.of("1", "a", "2", "a", "3", "a");
items.forEach(item -> {
if (item.equals("a")) {
System.out.println("A");
} else {
System.out.println("Not A");
}
});
}
}
Listing 3-1The Java Beginner Code Sample a Smart Beginner Deserves
下一个列表解释了具有相同用途的每一行或一组行:
-
;(分号)用来标记一个语句或声明的结束。 -
package com.apress.ch.one.hw;是一个包声明。您可以将此语句视为该类在文件中声明的地址。 -
import java.util.List;是一个导入语句。JDK 提供了许多在编写代码时使用的类。这些类也组织在包中,当您想要使用其中一个时,您必须指定要使用的类及其包,因为两个类可能有相同的名称,但在不同的包中声明。当编译器编译你的代码时,它需要准确地知道需要哪个类。 -
public class Example01是类声明语句。它包含一个访问器(public)、类型(class)和类名(Example01)。一个类有一个用花括号括起来的主体。 -
{ ...}(花括号)用于将语句组合成代码块。块不需要以;结束。代码块可以代表一个类的主体、一个方法,或者只是一些必须组合在一起的语句。 -
public static void main(String[] args)是一个方法声明语句。它包含一个访问器(public)、一个保留关键字(static)(将在后面解释)、方法名(main)和一个声明参数的部分((String[] args))。 -
List<String> items = List.of("1", "a", "2", "a", "3", "a");是声明类型为List<String>的名为items的变量并将该语句返回的值赋给它的语句:List.of("1", "a", "2", "a", "3", "a")。 -
items.forEach(...)是包含对items变量的函数调用的语句,用于遍历列表变量中的所有值。 -
item -> { ...}是一个λ表达式。它声明了要为列表中的每一项执行的代码块。 -
if (<condition>) { ...} else { ...}是决定性的陈述。正在执行的代码块是通过评估条件来决定的。 -
System.out.println(<text>);是用来打印传递给它的参数的语句。
在书中开始详细解释前面列表中的所有内容还为时过早,但编写 Java 代码时最重要的规则是,除了包声明和导入语句,所有代码都必须在一个块内。此外,如果一个语句不是多行的,它必须以“;”结尾,否则代码将无法编译。
在开始编写更详细的 Java 类之前,最好先编写简单的 Java 语句,并习惯语法。从 Java 9 开始,这可以通过使用 JShell 来实现,JShell 是一个用于学习 Java 编程语言和构建 Java 代码原型的交互式工具。因此,不用在类中编写代码、编译代码并执行字节码,您可以直接使用 JShell 来执行语句。
使用 JShell
JShell 加入得相当晚,因为 Python 和 Node 等脚本语言在几年前就引入了类似的实用程序,Scala、Clojure 和 Groovy 等 JVM 语言紧随其后。但迟做总比不做好。
JShell 是一个读取-求值-打印循环(REPL ),它在输入声明、语句和表达式时对它们进行求值,然后立即显示结果。快速尝试新的想法和技术是切实可行的,不需要完整的开发环境,也不需要执行代码的完整上下文。
JShell 是 JDK 的标准组件。要启动的可执行文件位于 JDK 安装目录下的bin目录中。这意味着你所要做的就是打开一个终端(Windows 中的命令提示符)并键入jshell。如果将bin目录的内容添加到系统路径中,您应该会在您的系统上看到包含 JDK 版本的欢迎消息。此外,你的终端的根目录变为jshell>,让你知道你现在正在使用jshell。
在清单 3-2 中,jshell 通过调用jshell -v以详细模式启动,这使得能够为所有执行的语句提供详细的反馈,直到会话结束。
$ jshell -v
| Welcome to JShell -- Version 17-ea
| For an introduction type: /help intro
jshell>
Listing 3-2Output of Command jshell -v
如果您在阅读本书时正在执行命令,请继续并输入/help查看所有可用动作和命令的列表。假设你不是,列表 3-3 描述了预期的输出。
jshell> /help
| Type a Java language expression, statement, or declaration.
| Or type one of the following commands:
| /list [<name or id>|-all|-start]
| list the source you have typed
| /edit <name or id>
| edit a source entry
| /drop <name or id>
| delete a source entry
| /save [-all|-history|-start] <file>
| Save snippet source to a file
...
| /exit [<integer-expression-snippet>]
| exit the jshell tool
...
Listing 3-3Output of Command /help in jshell
在 Java 中,值被分配给名为变量的字符组。(在章节 4 中有更多关于如何选择和使用它们的内容)。)要开始使用 JShell,我们将声明一个名为six的变量,并将值 6 赋给它*(我知道,聪明吧?)*。清单 3-4 中描述了语句和jshell日志。
jshell> int six = 6;
six ==> 6
| created variable six : int
Listing 3-4Declaring a Variable Using jshell
如您所见,日志消息很清楚,它告诉我们我们的命令执行成功,并且创建了一个名为six的类型为int的变量。six == > 6 让我们知道值 6 被赋给了我们刚刚创建的变量。
您可以创建任意数量的变量,并执行数学运算、字符串连接和任何需要快速执行的操作。只要 JShell 会话没有关闭,变量就存在并且可以使用。清单 3-5 描述了用 JShell 执行的一些语句及其结果。
jshell> int six = 6
six ==> 6
| modified variable six : int
| update overwrote variable six : int
jshell> six = six + 1
six ==> 7
| assigned to six : int
jshell> six +1
$14 ==> 8
| created scratch variable $14 : int
jshell> System.out.println("Current val: " + six)
Current val: 7
Listing 3-5jshell Various Statements and Outputs
前面的代码清单中描述的$1 4 == > 8 显示了值 8 被赋给一个名为$14的变量。这个变量是由 jshell 创建的。当语句的结果没有赋给开发人员命名的变量时,jshell 会生成一个临时变量,其名称由$(美元)字符和一个表示该变量内部索引的数字组成。文档中没有明确说明,但是根据我在使用 jshell 时的观察,索引值似乎是导致创建它的语句的编号。
Java 代码最重要的组成部分之一是类。类是模拟现实世界对象和事件的代码片段。类包含两种类型的成员:建模状态,即类变量,也称为字段或属性,以及建模行为,称为方法。
JDK 提供了许多类,这些类对创建大多数应用所需的基本组件进行建模。在下一章中会更详细地介绍这些类。即使有些概念现在看起来很陌生,只要耐心等待,让它积累;它们以后会变得更有意义。
最重要的 JDK 类之一是java.lang.String,它用来表示文本对象。这个类提供了一组丰富的方法来操作一个String变量的值。清单 3-6 描述了在String类型的声明变量上调用的一些方法。
jshell> String lyric = "twice as much ain't twice as good"
lyric ==> "twice as much ain't twice as good"
| created variable lyric : String
jshell> lyric.toUpperCase()
$18 ==> "TWICE AS MUCH AIN'T TWICE AS GOOD"
| created scratch variable $18 : String
jshell> lyric.length()
$20 ==> 33
| created scratch variable $20 : int
Listing 3-6jshell Method Calling Examples with String Variable
使用 JDK 类型的变量在jshell中编写 Java 代码的任务可能看起来很复杂,因为您不知道要调用什么方法,对吗?jshell非常有用,因为它会告诉您方法何时不存在。当试图调用一个方法时,您可以按下<Tab>键,显示可用方法列表。这被称为代码完成,智能 Java 编辑器也提供这种功能。
在清单 3-7 中,你可以看到当你试图调用一个不存在的方法时jshell打印出的错误信息,以及如何显示和过滤某个类型可用的方法。
jshell> lyric.toupper()
| Error:
| cannot find symbol
| symbol: method toupper()
| lyric.toupper()
| ^-----------^
jshell> lyric.to # <Tab>
toCharArray() toLowerCase( toString() toUpperCase(
jshell> lyric. # <Tab>
charAt( chars() codePointAt(
codePointBefore( codePointCount( codePoints()
...
Listing 3-7More jshell Method Calling Examples with String Variable
JShell 很明显地告诉我们,toupper()方法对于String类是未知的。
当列出可能的方法时,以(结尾的方法不需要参数。以单个左括号结尾的方法不带或带多个参数,并且有多种形式。要查看这些表单,只需在变量上编写方法,然后再次按下<Tab>。清单 3-8 描述了indexOf方法的多种形式。
jshell> lyric.indexOf( # <Tab>
$1 $14 $18 $19 $2 $20 $5 $9 lyric six
Signatures:
int String.indexOf(int ch)
int String.indexOf(int ch, int fromIndex)
int String.indexOf(String str)
int String.indexOf(String str, int fromIndex)
<press tab again to see documentation>
Listing 3-8jshell Listing All the Forms of the indexOf Method in the String Class
在第lyric.indexOf(行之后,jshell 列出了会话期间创建的变量,让您可以轻松选择现有的参数。
你可以在 Java 项目中写任何东西,你也可以在jshell中写。这样做的好处是,你可以把你的程序分成一系列的语句,立即执行它们来检查结果,并根据需要进行调整。还有其他事情jshell可以为你做,最重要的是这本书的一部分。
您在 JShell 会话中声明的所有变量都通过执行/vars命令列出。清单 3-9 描述了本章会话中声明的变量。
jshell> /vars
| int $1 = 5
| int $2 = 42
| int $5 = 8
| int $9 = 8
| int six = 7
| int $14 = 8
| String lyric = "twice as much ain't twice as good"
| String $18 = "TWICE AS MUCH AIN'T TWICE AS GOOD"
| int $19 = 9
| int $20 = 33
Listing 3-9jshell> /vars Output Sample for a Small Coding Session
如果想保存 JShell 会话中的所有输入,可以通过执行/save {filename}.java. 1 来实现
假设所有语句都是有效的 Java 语句,那么可以使用/open {filename}.java命令将结果文件中的语句执行到一个新的 JShell 会话中。
如果您有兴趣尝试它提供的每一个命令和每一个特性,Oracle 官方网站上有一个 JShell 完整用户指南 2 。
Java 基础构件
这是对 Java 平台的一贯介绍。要自信地编写代码,您需要了解幕后发生了什么,构建模块是什么,以及您必须配置/编写它们的顺序。如果你愿意,你可以完全跳过下一节,但同样的,一些新司机在自信地抓住方向盘之前需要了解一点发动机的工作原理,一些人如果了解一点机械原理,在编程时可能会感到更加自信和可控。所以我想确保每个阅读这本书的人都有一个合适的开始。
要编写 Java 应用,开发人员必须熟悉 Java 程序的 Java 构件。你可以这样想:如果你想制造一辆汽车,你必须知道轮子是什么,它们放在哪里,对吗?这就是我在本书中试图为 Java 实现的目标:解释所有的组件及其用途。
这个生态系统的核心是类。Java 中还有其他的对象类型 3 ,但是类是最重要的,因为它们代表组成应用的对象的模板。一个类主要包含字段和方法。创建对象时,字段的值定义对象的状态,方法描述其行为。
Java 对象是现实世界对象的模型。因此,如果我们选择用 Java 建模汽车,我们将选择定义描述汽车的字段:制造商、型号名称、生产年份、颜色和速度。我们的汽车类的方法描述了汽车做什么。汽车主要做两件事:加速和刹车,所以任何方法都应该描述与这两件事相关的动作。
包装
当您编写 Java 代码时,您是在编写描述现实世界项目的状态和行为的代码。代码必须组织在类和其他类型中,这些类和类型一起用于构建应用。所有类型都在扩展名为.java的文件中描述。对象类型被组织在包中。
包是类型的逻辑集合:有些类型在包外是可见的,有些是不可见的,这取决于它们的作用域。
为了理解包裹的工作方式,想象一个包含其他盒子的盒子。那些盒子可能被其他盒子填满,或者它们可能被一些不是盒子的东西填满。为了这个例子,让我们假设这些物品是乐高积木。这个类比很管用,因为 Java 类型可以像乐高积木一样组合。
包名必须是唯一的,并且它们的名字应该遵循一定的模板。该模板通常由从事该项目的公司定义。良好的实践表明,为了确保唯一性和意义,您通常以组织的 Internet 域名逆序开始名称,然后添加各种分组标准。
在这个项目中,包名遵循这里描述的模板:com.apress.bgn.[<star>]+。该模板以一个出版社的反向域名( www. apress.com )开始,然后添加一个标识该书的术语( bgn 是初学者的快捷方式),最后,<star>替换源(通常)匹配的包的编号。
考虑到之前介绍的盒子和乐高类比,com包就是包含apress盒子的大盒子。它也可以包含其他乐高玩具,但是在这个例子中没有。
apress框代表com.apress包,包含bgn框。
bgn代表com.apress.bgn包装盒,包含特定于每个章节的盒子,包含其他盒子和/或乐高积木。乐高积木是 Java 文件,包含 Java 代码。图 3-1 展示了这些盒子和乐高积木以及它们嵌套的方式。
图 3-1
Java 包,源代码表示为嵌套的盒子和乐高积木
在您的计算机上,包是目录的层次结构。每个目录包含其他目录和/或 Java 文件。这完全取决于你的组织能力。这种组织很重要,因为任何 Java 对象类型都可以使用包名和它自己的名称来唯一地标识。
如果我们要在一个名为HelloWorld.java的文件中编写一个名为HelloWorld的类,并将这个文件放在包com.apress.bgn.one中,在 Java 项目中com.apress.bgn.one.HelloWorld头韵是作为这个类的唯一标识符的完整类名。您可以将包名视为该类的地址。
从 Java 5 开始,在每个包中可以创建一个名为package-info.java的文件,其中包含包声明、包注释、包注释和 Javadoc 注释。注释被导出到该项目的开发文档中,也称为 Javadoc 。章节 9 讲述了如何使用 Maven 生成项目 Javadoc。package-info.java必须位于包中的最后一个目录下。所以如果我们定义一个com.apress.bgn.one包,Java 项目的整体结构和内容看起来如图 3-2 所示。 4
图 3-2
Java 包内容
package-info.java的内容可能类似于清单 3-10 的内容。
/**
* Contains classes used for reading information from various sources.
* @author iuliana.cosmina
* @version 1.0-SNAPSHOT
*/
package com.apress.bgn.one;
Listing 3-10package-info.java Contents
将包含类型定义的扩展名为.java的文件编译成扩展名为.class的文件,这些文件按照相同的包结构组织,并打包成一个或多个JARs(JavaArchives)。 5 对于上一个例子,如果我们在编译和链接之后解包 JAR 结果,你会看到如图 3-3 所示的内容。
图 3-3
样品罐的内容物
package-info.java文件也被编译,即使它只包含关于包的信息,没有行为或类型。
package-info.java文件不是强制性的;没有它们也可以定义包。它们主要用于文档目的。
一个包的内容可以跨越多个 jar,这意味着如果您的项目中有多个子项目,那么您可以在多个包含不同类的包中使用相同的包名。图 3-4 描述了这种情况的符号表示。
图 3-4
跨越多个 jar 的包内容示例
库是一个 jar6的集合,包含用于实现特定功能的类。例如,JUnit 是一个非常著名的 Java 框架,它提供了多个类来帮助编写 Java 单元测试。
一个中等复杂的 Java 应用引用一个或多个库。要运行应用,它的所有依赖项(所有 jar)都必须在类路径中。这是什么意思?这意味着为了运行 Java 应用,需要 JDK、依赖项(外部 jar)和应用 jar。图 3-5 非常清楚地描述了这一点。
图 3-5
应用类路径
这里,我们假设应用运行在编写它的同一个环境中,因此使用 JDK 来运行应用。在 JDK 11 之前,任何 Java 应用都可以使用 JRE 运行。但是从版本 11 开始,Java 变得完全模块化。这意味着定制的“JRE”发行版只能从运行应用所需的模块中创建。间接地,这意味着最终的 JRE 将包含最少数量的 JDK 编译类。
组成应用类路径的 jar(显然)并不总是相互独立的。21 年来,这种组织方式已经足够了,但是在复杂的应用中,由于以下原因导致了许多复杂情况:
-
分散在多个罐子里的包裹。(还记得图 3-4 ?)这可能会导致代码重复和循环依赖。
-
jar 之间的可传递依赖关系有时会导致同一类的不同版本出现在类路径中。这可能会导致不可预测的应用行为。
-
缺少传递依赖和可访问性问题。这可能会导致应用崩溃。
所有这些问题都被归为一类:罐子地狱。 7 这个问题在 Java 9 中通过引入另一个层次来组织包得到了解决:模块。或者至少这是他们的意图。然而,业界一直不愿意采用 Java 模块。在写这一章的时候,大多数 Java 生产应用不仅停留在 Java 8 上,而且开发人员也像躲避瘟疫一样躲避模块。
然而,在介绍模块之前,应该提到访问修饰符。Java 类型和它们的成员在包中被声明为具有一定的访问权限,在开始编码之前,理解这一点非常重要。
访问修饰符
当在 Java 中声明一个类型时——现在让我们坚持使用class——因为这是到目前为止唯一提到的类型,您可以使用访问修饰符来配置它的作用域。
访问修饰符可以用来指定对类的访问,在这种情况下,我们说它们在top-level使用。
它们也可以用来指定对类成员的访问,在这种情况下,它们用在member-level. 8
在top-level只能使用两个访问修饰符:public和 none。
声明为public的top-level类必须在同名的 Java 文件中定义。清单 3-11 描述了一个名为Base的类,它定义在位于包com.apress.bgn.zero中的名为Base.java的文件中。
package com.apress.bgn.zero;
// top-level access modifier
public class Base {
// code omitted
}
Listing 3-11Base Class
这个类的内容暂时不描述,用...代替,以免你失去焦点。公共类对应用中任何地方的所有类都是可见的。不同包中的不同类可以创建这种类型的对象,如清单 3-12 所示:
package com.apress.bgn.three;
import com.apress.bgn.zero.Base;
public class Main {
public static void main(String... args) {
// creating an object of type Base
Base base = new Base();
}
}
Listing 3-12Creating an Object Using the Base Class
线Base base = new Base();是创建对象的地方。new关键字代表一个类的名为instantiation的操作,这意味着一个对象是基于代表Base类的代码所描述的规范而创建的。
一个类就是一个模板。使用该模板创建对象,称为
instances。
现在,让这种肯定深入人心:公共类对任何地方的所有类都是可见的。
当没有提到显式的访问修饰符时,就说这个类被声明为默认的或者是包私有的。我知道有两种方式来谈论缺少访问修饰符似乎令人困惑,但是由于您可能会阅读其他涉及这种情况的书籍或博客帖子,因此最好在这里列出所有的可能性。
这意味着如果一个类没有访问修饰符,那么这个类只能被同一个包中定义的类用来创建对象。它的范围仅限于定义它的包。没有访问修饰符的类可以在任何 Java 文件中定义:同名的文件,或者紧挨着给文件命名的类。
当在同一个文件中声明了多个类时,公共类必须与定义它的文件同名,因此这就是命名文件的类。
为了测试这一点,让我们在前面介绍的Base.java文件中添加一个名为HiddenBase的类,如清单 3-13 所示。
package com.apress.bgn.zero;
public class Base {
// code omitted
}
class HiddenBase {
// you cannot see me
}
Listing 3-13Class with No Access Modifier
注意,Base类是在com.apress.bgn.zero包中声明的。如果我们试图在包com.apress.bgn.three中声明的类中创建一个HiddenBase类型的对象,IDE 将通过读取文本并拒绝提供任何代码完成来警告我们。更有甚者,会打开一个列出当前文件问题的标签,并带有一个非常明显的错误信息,如图 3-6 所示。
图 3-6
没有访问器修饰符错误的 Java 类
现在,接受这个肯定,并让它深入人心:一个没有访问修饰符的类对同一个包中的所有类(和其他类型)都是可见的。
在一个类中,定义了类成员:字段和方法。 9 在member-level处,除了前面提到的两个修改器之外,还可以应用另外两个修改器:private和protected。在member-level处,访问修饰符具有以下效果:
-
public与顶级相同;可以从任何地方访问该成员。 -
private表示该成员只能在声明了的类中访问。 -
protected表示该成员只能在声明包含它的类的包中访问,或者由另一个包中该类的任何子类访问。 -
none表示该成员只能从其自己的包中访问。
这看起来很复杂,但是一旦你开始写代码,你就会习惯了。在 Oracle 官方文档页面上,甚至还有一个成员可见性的表格,如表 3-1 所示。 10
表 3-1
成员级访问器范围
|修饰语
|
班级
|
包裹
|
亚纲
|
世界
| | --- | --- | --- | --- | --- | | 公众的 | 是 | 是 | 是 | 是 | | 保护 | 是 | 是 | 是 | 不 | | 无(默认/包专用) | 是 | 是 | 不 | 不 | | 私人的 | 是 | 不 | 不 | 不 |
为了全面了解该表如何应用到代码中,清单 3-14 中的类非常有用。
package com.apress.bgn.three.same;
public class PropProvider {
public int publicProp;
protected int protectedProp;
/* default */ int defaultProp;
private int privateProp;
public PropProvider(){
privateProp = 0;
}
}
Listing 3-14PropProvider
a Java class with Members Decorated with Various Accessors
类PropProvider声明了四个字段/属性,每个都有不同的访问修饰符。字段privateProp只能在该类的主体中修改。这意味着该类的所有其他成员都可以读取并更改该属性的值。
在本书的这一点上,只有方法被认为是其他类型的成员。
但是类可以在另一个类的主体中声明。这样的类被称为嵌套类,可以访问包装在它周围的类的所有成员,包括私有成员。图 3-7 描述了修改后的PropProvider类,增加了一个额外的方法,名为printPrivate。该方法读取私有字段的值并打印出来。还声明了一个名为LocalPropRequester的嵌套类,私有字段在这个类中被修改(第 56 行)。
图 3-7
表 3-1 ,Java 代码中的列类访问器
图 3-7 是在 IntelliJ IDEA 中查看 Java 代码的截图。如果任何字段不可访问,则显示为红色。
表 3-1 中的第二列,即Package列,包含了与类PropProvider在同一个包中声明的类可访问的字段。图 3-8 描述了一个名为PropRequester的类试图修改类PropProvider中的所有字段。请注意,私有字段显示为鲜红色。这意味着该字段是不可访问的,IntelliJ IDEA 对此非常清楚。
图 3-8
表 3-1 ,Java 代码中的列包访问器
表 3-1 中的第三列,即Subclass列,包含了类别PropProvider的子类可访问的字段。一个subclass从一个被称为它的superclass的类中继承状态和行为。子类是使用extends关键字和超类名创建的。图 3-9 描绘了一个名为SubClassedProvider class 的类试图修改从PropProvider继承的所有字段。请注意,私有字段和没有访问器的字段以鲜红色显示。这意味着这些字段是不可访问的,IntelliJ IDEA 对此表现得非常明显。
图 3-9
表 3-1 ,Java 代码中的列子类访问器
在前面的例子中,没有访问器的字段是不可访问的,因为子类是在不同的包中声明的。如果子类在同一个包中移动,那么表 3-1 中
Package列的规则适用。
表 3-1 中的第三列World适用于声明类PropProvider的包之外的所有类,它们不是该类的子类。图 3-10 描绘了一个名为AnotherPropRequester的类,它试图访问PropProvider中声明的所有字段。正如预期的那样,只有公共字段是可访问的,其余的都以红色显示。
图 3-10
表 3-1 ,Java 代码中的列世界访问器
如果你试图在像 IntelliJ IDEA 这样的智能编辑器之外构建一个项目,这是行不通的。这些错误消息会让您知道有一个编译错误,原因是什么,以及在哪里。例如,使用 Maven 构建工具构建包含
AnotherPropRequester类的chapter03子项目会失败。终端中显示以下错误消息:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project chapter03: Compilation failure: Compilation failure:
[ERROR] /Users/iulianacosmina/apress/workspace/java-17-for-absolute-beginners/chapter03/src/main/java/com/apress/bgn/three/other/AnotherPropRequester.java:[42,17] protectedProp has protected access in com.apress.bgn.three.same.PropProvider
[ERROR] /Users/iulianacosmina/apress/workspace/java-17-for-absolute-beginners/chapter03/src/main/java/com/apress/bgn/three/other/AnotherPropRequester.java:[44,17] defaultProp is not public in com.apress.bgn.three.same.PropProvider; cannot be accessed from outside package
[ERROR] /Users/iulianacosmina/apress/workspace/java-17-for-absolute-beginners/chapter03/src/main/java/com/apress/bgn/three/other/AnotherPropRequester.java:[46,17] privateProp has private access in com.apress.bgn.three.same.PropProvider
构建工具和编辑器非常善于让您知道 Java 代码中什么地方出错了。学会用好它们,信任它们,它们会提高你的生产力。当然,有打嗝,但没有那么多。
开始编写 Java 代码后,您可能会回到表 3-1 一两次。即使引入了模块(如果您正确地配置了模块访问),刚才提到的一切仍然有效。
模块
从 Java 9 开始,引入了一个新概念:modules. 11 Java 模块代表了一种更强大的机制来组织和聚合包。这一新概念的实施花了 10 多年时间。关于模块的讨论始于 2005 年,希望它们能在 Java 7 中实现。2008 年,一个名为“拼图计划”的探索阶段终于开始了。Java 开发者希望 Java 8 能提供模块化的 JDK,但这并没有发生。
经过三年的工作(差不多七年的分析),Java 9 终于推出了模块。支持他们把 Java 9 的正式发布日期推迟到了 2017 年 9 月。 12
Java 模块是对包进行分组和对包内容进行更细粒度访问的一种方式。Java 模块是一组唯一命名的、可重用的包和资源(例如,XML 文件和其他类型的非 Java 文件),由位于源目录根目录下的名为module-info.java的文件描述。该文件包含以下信息:
-
模块的名称
-
模块的依赖项(即该模块依赖的其他模块)
-
它显式地使其他模块可以使用的包(模块中的所有其他包隐式地对其他模块不可用)
-
它提供的服务
-
服务 it 消费者
-
它允许反射到哪些其他模块
-
本机代码
-
资源
-
配置数据
理论上,模块命名类似于包命名,并遵循反向域名约定。在实践中,只需确保模块名不包含任何数字,并且清楚地揭示了它的用途。module-info.java文件被编译成一个模块描述符,它是一个名为module-info.class的文件,与类一起打包成一个普通的旧 JAR 文件。该文件位于 Java 源目录的根目录下,在任何包之外。对于前面介绍的chapter03项目,module-info.java文件位于src/main/java目录下,与com目录处于同一层级;com.apress.bgn.three的根包图 3-11 。
图 3-11
module-info.java 文件的位置
任何带的文件。java 扩展,module-info.java被编译成。类文件。由于模块声明不是 Java 类型声明的一部分,module不是 Java 关键字,所以在为 Java 类型编写代码时仍然可以使用;例如,作为变量名。对于package来说,情况有所不同,因为每个 Java 类型声明都必须以包声明开始。看看清单 3-15 中声明的SimpleReader类就知道了。
package com.apress.bgn.three;
public class SimpleReader {
private String source;
// code omitted
}
Listing 3-15SimpleReader Class
可以看到包声明,但是模块在哪里?嗯,模块是一个抽象的概念,用module-info.java来描述。所以从 Java 9 开始,如果你在你的应用中配置 Java 模块,图 3-4 演变成图 3-12 。
图 3-12
可视化表示的 Java 模块
Java 模块是一种逻辑分组属于一起的 Java 包的方法。
模块的引入也允许将 JDK 划分成模块。java --list-modules命令列出了本地 JDK 安装中的所有模块。清单 3-16 描述了在我的个人电脑上执行的这个命令的输出,我的电脑目前安装了 JDK 17-ea。
$ java --list-modules
java.base@17-ea
java.compiler@17-ea
java.datatransfer@17-ea
java.desktop@17-ea
# output omitted
Listing 3-16JDK 17-ea Modules
在前面的清单中,每个模块名称后面都有一个版本字符串@17-ea,这意味着该模块属于 Java 版本 17-ea。
因此,如果一个 Java 应用不需要所有的模块,那么可以只使用它需要的模块来创建运行时,这就减小了运行时的大小。构建一个为应用定制的小型运行时的工具叫做jlink,是 JDK 可执行程序的一部分。这允许更高级别的可伸缩性和更高的性能。如何使用jlink不是本书的研究对象。这本书的重点是学习 Java 编程语言,因此 Java 平台的技术细节将保持在最低限度——仅够自信地开始编写和执行代码。
引入模块有很多好处,更有经验的开发人员已经等了很多年来利用这些好处。但是为更大更复杂的项目配置模块不是在公园散步,大多数软件公司要么坚持 JDK 8 要么完全避免配置模块。
module-info.java的内容可以简单到模块名和两个包含主体的括号,如清单 3-17 所示。
module chapter.three {
}
Listing 3-17A Simple module-info.java Configuration
高级模块配置
Java 模块声明体包含一个或多个使用表 3-2 中的关键字构建的directives。这些指令表示模块中包含的包和类的访问配置和依赖性要求。
这本书包含一个附录,提供了所有指令的例子。为了学习 Java 语言,你真正需要的只有requires和exports。在这本书的这一版中,我将在上下文允许完全理解它们的情况下深入解释每一个指令,并且我将添加与真实世界事件和场景的类比,以确保思想得到理解。在本章中,首先只解释两个主要指令。
模块可以相互依赖。本书的项目由 13 个模块组成,其中大部分依赖于模块chapter.zero。该模块包含用于在其他模块中构建更复杂组件的基本组件。例如,模块chapter.three中的类需要访问模块chapter.zero中的包和类。声明模块依赖关系是通过使用requires指令来完成的,如清单 3-18 所示。
module chapter.three {
requires chapter.zero;
}
Listing 3-18A Simple module-info.java Configuration
前面的依赖关系是一个显式的依赖关系。但是也有隐含的依赖性。例如,开发者声明的任何模块都隐式需要 JDK java.base模块。这个模块包含了 Java SE 平台的基础 API,没有它就不能编写 Java 应用。这个隐式指令确保了对 Java 类型的最小集合的访问,因此可以编写基本的 Java 代码。清单 3-18 等同于清单 3-19 。
module chapter.three {
requires java.base;
requires chapter.zero;
}
Listing 3-19A Simple module-info.java Configuration with an Explicit Directive of requires java.base
根据需要声明一个模块,意味着当代码被编译时需要这个模块——通常称为编译时,当代码被执行时——通常称为运行时。如果一个模块只在运行时需要,需要静态的关键字用于声明依赖关系。现在只要记住这一点,当我们谈论 web 应用时,它就有意义了。
现在chapter.three依赖于模块chapter.zero。但这是否意味着chapter.three可以访问模块chapter.zero中所有包中的所有public类型(及其嵌套的public和protected类型)?如果你认为这还不够,那你就对了。仅仅因为一个模块依赖于另一个模块,并不意味着它可以访问它实际需要的包和类。所需模块必须配置为暴露其内部。如何做到这一点?在我们的例子中,我们需要确保模块chapter.zero能够访问所需的包。这是通过添加exports指令,后跟必要的包名,为这个模块定制module-info.java来完成的。清单 3-20 描述了chapter.zero模块的module-info.java文件,该文件公开了它的单个包。
module chapter.zero {
exports com.apress.bgn.zero;
}
Listing 3-20The module-info.java Configuration File for the chapter.zero Module
请这样想:你在房间里裁剪圣诞装饰品,你需要一个装饰品模板。你的室友有所有的模板。但是你需要它并不代表它会神奇的出现。你需要去和你的室友谈谈。需要你室友的帮助可以被视为要求室友指令。和你室友聊完之后,他大概会说:当然可以,进来吧,它们在桌子上!你需要多少就拿多少。这可以被认为是出口所有桌面模板指令。桌子可能是包裹的一个很好的比喻。
使用清单 3-20 中的配置,我们刚刚授予了对com.apress.bgn.zero包的访问权,对任何配置了requires module.zero;指令的模块的访问权。如果我们不想这样呢?(考虑到前面的提示,您的室友只是让他房间的门开着,所以任何人都可以进入并获得那些模板!)
如果我们想将对模块内容的访问仅限于chapter.three模块呢?(所以你室友要把他的模板只给你。)这可以通过在模块名后添加to关键字来实现,以表明只有这个模块被允许访问组件。这是表 3-2 中提到的exports指令的合格版本。
表 3-2
Java 模块指令
|管理的
|
目的
|
| --- | --- |
| 需要 | 指定该模块依赖于另一个模块。 |
| 电子竞技 | 模块的一个包,它的public类型(以及它们的nested public和protected类型)应该可以被所有其他模块中的代码访问。 |
| 出口…到 | 这是exports指令的合格版本。它允许在逗号分隔的列表中精确地指定哪个模块或模块代码可以访问导出的包。 |
| 打开 | 用于模块级声明(open module mm {}),并允许反射访问所有模块包。Java 反射是在运行时分析和修改一个类的所有功能的过程,也适用于私有类型和成员。所以在 Java 9 之前,没有什么是真正封装的。 |
| 打开 | 在模块声明体中使用,通过反射有选择地配置对特定包的访问。 |
| 打开…到 | 这是opens指令的合格版本。它允许在逗号分隔的列表中精确地指定哪个模块或模块代码可以反射性地访问它的包。 |
| 使用 | 指定该模块使用的服务—使该模块成为服务消费者。在这种情况下,服务代表另一个模块为其提供实现的接口/抽象类的全名。 |
| 为…提供 | 指定模块提供具有特定实现的服务——使模块成为服务提供者。 |
| 过渡的 | 与requires一起使用,指定对另一个模块的依赖,并确保读取你的模块的其他模块也读取该依赖——被称为隐含可读性。 |
如果你很好奇并阅读了推荐的 Jar Hell 文章,你会注意到使用 Jar 中打包的 Java 源代码的一个问题是安全性。这是因为即使不访问 Java 源代码,通过添加一个 jar 作为应用的依赖项,也可以检查、扩展和实例化对象。因此,除了提供可靠的配置、更好的可伸缩性、平台的完整性和改进的性能,引入模块的目标实际上是更好的安全性。
清单 3-21 描述了chapter.zero模块的module-info.java文件,它只向chapter.three模块公开它的单个包。
module chapter.zero {
exports com.apress.bgn.zero to chapter.three;
}
Listing 3-21Advanced module-info.java Configuration File for the chapter.zero Module
通过用逗号分隔列出所需的模块,可以指定多个模块具有访问权限,如清单 3-22 所示。
module chapter.zero {
exports com.apress.bgn.zero to chapter.two, chapter.three;
}
Listing 3-22Advanced module-info.java Configuration File for the chapter.zero Module with Multiple Modules
模块的顺序并不重要,如果有很多模块,你可以把它们放在多行上。只要确保用一个。(分号)。
这就是在本书的这个阶段所能介绍的关于模块的全部内容,但是不用担心:所有其他的指令将会在适当的时候介绍。
如何确定 Java 项目的结构
有几种方法可以构建 Java 项目,这取决于以下几点:
-
项目范围
-
使用的构建工具
您可能想知道为什么项目范围会影响它的结构,并且您可能期望这应该有一个标准,对吗?标准不止一个,这取决于项目范围。创建 Java 项目的原因会影响它的大小。如果一个项目很小,它可能不需要您将源代码分成子项目,也可能不需要构建工具,因为构建工具自带了组织项目的标准方式。让我们从有史以来最小的 Java 项目开始,它应该只打印“Hello World!”到控制台。
“HelloWorld!”IntelliJ 理念中的项目
顺便说一下,你甚至不需要一个项目,因为你有jshell。只需打开一个终端(Windows 的命令提示符),打开jshell,并输入System.out.print("Hello World!")语句,如清单 3-23 所示。
jshell>
| Welcome to JShell -- Version 17-ea
| For an introduction type: /help intro
jshell> System.out.print("Hello World!")
Hello World!
Listing 3-23jshell Hello World!
既然您已经安装了 IntelliJ IDEA,那么让我们创建一个 Java 项目,并检查编辑器为我们选择了什么样的项目结构。从第一个 IntelliJ IDEA 对话窗口开始,点击Create New Project option。第二个对话窗口将出现在顶部,左侧列出了您可以创建的项目类型。这里提到的两个对话框如图 3-13 所示。
图 3-13
创建 IntelliJ IDEA 项目配置启动对话框窗口
从左边选择 Java 项目类型,点击Next,不选择右边列出的任何附加库和框架。
下一个对话窗口允许您为项目选择一个模板。我们将通过点击Next跳过它。
下一个对话窗口允许您选择项目名称和位置。由于我们使用的是 Java 17,您可以注意到底部有一个用于配置 Java 模块的部分。该配置窗口如图 3-14 所示。
图 3-14
IntelliJ IDEA 项目名称和位置配置对话框窗口
使用sandbox作为项目名和模块名,并点击Finish。下一个窗口是编辑器窗口。这是你写代码的地方。如果您展开左边的sandbox节点(该部分被称为项目视图,您可以看到该项目是使用您已经安装的 JDK(在本例中是 17)构建的。已经为您创建了一个src目录。你的项目应该看起来很像图 3-15 中描述的那个。
图 3-15
IntelliJ IDEA 项目视图
在编写代码之前,让我们看看还有哪些项目设置可用。IntelliJ IDEA 允许您通过File > Project Structure菜单项查看和编辑项目属性。如果点击它,将会打开一个类似于图 3-16 所示的对话框。
图 3-16
IntelliJ IDEA 项目设置选项卡
默认情况下,Project设置选项卡打开。在上图中,有两个箭头将您的注意力吸引到了Project SDK:部分和Project language level:部分,前者实际上描述了一个 Java 项目的 JDK 版本。在写这一章的时候,JDK 17 EA 是最新的版本。IntelliJ IDEA 的最新版本支持 Java 17 的语法和代码完成,这也是这里描述的原因。这就是项目语言级别设置的意义。
如果切换到名为Modules的选项卡,您将看到图 3-17 中描述的信息。
图 3-17
IntelliJ IDEA 项目模块选项卡
之前的图像需要澄清。除了将包包装在一起的 Java 模块之外,模块也是将 Java 源代码和资源文件包装在一起的一种方式,在项目中具有共同的目的。在 Oracle 引入模块概念作为模块化 Java 应用的方法之前,组成这些应用的代码已经被需要以某种实用方式构建大型项目的开发人员模块化了。
在Modules选项卡中,您可以看到一个项目有多少部分(模块)以及每个部分的设置。项目有一个部分,一个名为sandbox,的模块,这个模块的源代码包含在src目录中。如果我们想写一个打印“Hello World!”名为 HelloWorld.java 的文件必须放在它下面。如果右键单击src目录,出现图 3-18 所示的菜单。
图 3-18
IntelliJ IDEA 菜单列出了可以在 src 目录中创建的 Java 对象
除了Java Class选项之外,还有几个红色箭头向您展示了在src目录中还有哪些其他组件。让我们继续创建我们的类。点击Java Class菜单选项,引入类名后,从测试字段下方的列表中选择Class。在图 3-19 中,你可以看到你可以创建的所有 Java 类型。
图 3-19
创建 Java 类型的 IntelliJ IDEA 对话框窗口
在本章的开始,提到了 Java 应用的核心构件是class,但是除此之外,Java 中还有其他类型。上图中的列表显示了列出的五种 Java 类型。每一个都将在后面详细解释;现在,请注意在src目录下创建了一个名为HelloWorld.java的文件,该文件的内容如清单 3-24 所示。
public class HelloWorld {
}
Listing 3-24The HelloWorld Class
您已经在第一个非常简单的 Java 项目中创建了第一个 Java 类。它还没有做任何事情。通过从 IntelliJ IDEA Build 菜单中选择Build Project选项,或者按下每个操作系统不同的组合键来编译该类。编译该类会产生包含字节码的HelloWorld.class文件。默认情况下,IntelliJ IDEA 将编译结果存储在一个名为out/production的目录中。编译项目的菜单选项和结果如图 3-20 所示。菜单选项以绿色突出显示。
图 3-20
IntelliJ IDEA:如何编译 Java 项目
是时候让我们的班级出版 *Hello World 了!*为此,我们需要给这个类添加一个特殊的方法。任何 Java 桌面应用都有一个名为main的特殊方法,必须在顶级类中声明。JRE 调用这个方法来运行 Java 程序/应用,我称它为入口点。如果没有这样的方法,Java 项目只是一个不可运行、不能执行、不能执行某个功能的类的集合。
想象一下:这就像有一辆汽车,但你无法启动它,因为点火锁芯不见了。从所有的意图和目的来看,它是一辆汽车,但它不能执行汽车的主要目的,即实际上带你去某个地方。您可以将
main方法想象成点火锁圆柱体,JRE 将在其中插入钥匙以使您的应用运行。让我们将该方法添加到HelloWorld类中。
因为 IntelliJ IDEA 是一个很棒的编辑器,你可以通过输入
psvm并按下<tab>键来生成main方法。这四个字母代表方法声明的所有组件的起始字母:【p】ublic、sstatic、 v oid、 m ain。
带有打印文本 *Hello World 的main方法的HelloWorld类!*如清单 3-25 所示。
public class HelloWorld {
public static void main(String... args) {
System.out.println();
}
}
Listing 3-25The HelloWorld Class with the main Method
现在我们有了一个main方法,可以执行(或运行)代码了。为此,在 IntelliJ IDEA 中,您还有两个选项:
-
从
Run菜单中选择,选项Run [ClassName] -
或者在类体上单击右键,从出现的菜单中选择
Run [ClassName].main()。 13
图 3-21 描述了你可以用来执行类的两个菜单项,以及执行的结果。
图 3-21
IntelliJ IDEA:如何执行 Java 类
这是 Java 项目最基本的结构。这个项目非常简单,也可以从命令行手动编译。让我们开始吧!
“HelloWorld!”从命令行编译和执行的项目
你可能已经注意到了 IntelliJ 想法中的Terminal按钮。如果你点击那个按钮,在编辑器里会打开一个终端。对于 Windows,它将是一个命令提示符实例,对于 Linux,macOs将是默认的 shell。IntelliJ 将在项目根目录下打开您的终端。接下来你要做的是:
-
通过执行以下命令进入
src目录:cd src -
cd是一个也可以在 Windows 和 Unix 系统下工作的命令,是更改目录的缩写 -
通过执行
javac HelloWorld.java编译HelloWorld.java文件 -
是一个用于编译 Java 文件的 JDK 可执行文件,IntelliJ IDEA 也在后台调用它
-
通过执行:
java HelloWorld运行从HelloWorld.class文件得到的字节码
图 3-22 描述了在 IntelliJ IDEA 的终端中这些命令的执行。
图 3-22
在 IntelliJ IDEA 内部的终端中手动编译和运行 HelloWorld 类
看起来很简单,对吧?实际上就是这么简单,因为没有定义包或 Java 模块。但是等等,这可能吗?是的。如果您没有定义一个包,该类仍然是一个未命名的默认包的一部分,该默认包由 JSE 平台默认提供,用于开发小型临时教育应用,就像您在这里构建的应用一样。
所以让我们把我们的项目变得复杂一点,为我们的类添加一个命名的包。
将“HelloWorld”类放在一个包中
在图 3-18 中,所列菜单中包含一个Package选项。右键单击src目录并选择它。一个小的对话框将会出现,你必须在这里输入包名。输入com.sandbox。在图 3-23 中描述了对话窗口。如果您尝试创建的包已经存在,则会以红色显示一条错误消息。
图 3-23
在 IntelliJ IDEA 中创建重复的包
现在我们有一个包,但是类不在其中。要将类放在那里,只需点击并拖动它。将出现另一个对话框,确认这是您真正想要做的,如图 3-24 所示。
图 3-24
在 IntelliJ IDEA 中将类移动到包中
点击Refactor按钮,查看课程发生了什么变化。这个类现在应该以一个package com.sandbox;声明开始。如果您重新构建您的项目,然后查看production目录,您将会看到类似于图 3-25 中描述的内容。
图 3-25
添加com.sandbox包后的新目录结构
显然,如果您手动编译并执行该类,您现在必须考虑这个包,因此您的命令将变为:
~/sandbox/src/> javac com/sandbox/HelloWorld.java
~/sandbox/src/> java com/sandbox/HelloWorld
那么当模块也被配置时会发生什么呢?有一个默认的未命名模块,所有 jar(无论是否模块化)和类路径上的类都将包含在其中。这个默认且未命名的模块导出所有包并读取所有其他模块。因为它没有名称,所以命名的应用模块不能请求和读取它。因此,即使您的小项目似乎可以与版本 9 或更高版本的 JDK 一起工作,它也不能被其他模块访问,但是它可以工作,因为它可以访问其他模块。(这确保了向后兼容旧版本的 JDK。)也就是说,让我们在项目中添加一个模块。
配置“com.sandbox”模块
配置一个模块就像在src目录下添加一个module-info.java文件一样简单。在图 3-18 中,菜单包含一个module-info.java选项,如果你选择了它,IDE 会为你生成文件。一切都很好,如果您不喜欢为您生成的模块名,您可以更改它。我把它改成了com.sandbox,以尊重 Oracle 开发人员建立的模块命名约定。文件最初是空的,如清单 3-26 所示。
module com.sandbox {
}
Listing 3-26The com.sandbox Module Configuration File
现在我们有了一个模块,会发生什么呢?从 IDEs 的角度来看不多。但是如果你想手动编译一个模块,你必须知道一些事情。我使用清单 3-27 中的命令编译了我们的模块。
~/sandbox/src/> javac -d ../out/com.sandbox \
module-info.java \
com/sandbox/HelloWorld.java
Listing 3-27Manually Compiling a Package Enclosed within a Module
“”是一个 macOS/Linux 分隔符。在 Windows 上,要么在一行中写下整个命令,要么用“^".”替换“\”
前面的命令是根据清单 3-28 中的模板构建的。
javac -d [destination location]/[module name] \
[source location]/module-info.java \
[java files...]
Listing 3-28Template for Command to Manually Compile a Package Enclosed Within a Module
-d [destination]决定了执行结果应该保存在哪里。清单 3-27 中的命令行将输出文件夹指定为/out/com.sandbox的原因是为了明确com.sandbox是封闭模块。在这个目录下,我们将拥有com.sandbox包的正常结构。out 目录的内容如图 3-26 所示。
图 3-26
手工编译的 Java 模块com.sandbox
正如您在本例中注意到的,在我们编译源代码之前,模块并不真正存在,因为 Java 模块更多的是封装由module-info.class描述符描述的包的逻辑模式。创建com.sandbox目录的唯一原因是我们在javac -d命令中将它指定为参数。
既然我们已经成功编译了一个模块,清单 3-29 向您展示了当HelloWorld类被封装在一个模块中时如何运行它。
~/sandbox/> java --module-path out \
--module com.sandbox/com.sandbox.HelloWorld
Hello World! # result
Listing 3-29Manually Executing a Class Enclosed Within a Module
前面的命令是根据清单 3-30 中的模板构建的。
java --module-path [destination] \
--module [module name] /[package name].HelloWorld
Listing 3-30Template for Command to Manually Execute a Class Enclosed Within a Module
2017 年 9 月的 Oracle Magazine edition 第一次提到了示例,尽管 Oracle 开发人员已经决定模块名称应该遵循与包相同的规则,但对我来说,这似乎有点多余,特别是在复杂的项目中,包名称往往会变得很长。模块名应该一样长吗?
事实是,人们制定了标准,大多数时候,实用性成为了标准。自 2007 年以来,成功采用模块的项目选择了更简单、更实用的模块名称。例如,创建 Spring 框架的团队决定将他们的模块命名为spring.core而不是org.springframework.core,命名为spring.beans而不是org.springframework.beans,等等。因此,只要避免使用特殊字符和数字,就可以随心所欲地命名模块。
使用构建工具的 Java 项目,主要是 Maven
Apache Maven 是一个主要用于 Java 项目的构建自动化工具。尽管 Gradle 正在取得进展,Maven 仍然是最常用的构建工具之一。Maven 和 Gradle 等工具用于将应用的源代码组织在相互依赖的项目模块中,并配置一种自动编译、验证、生成源代码、测试和生成工件的方式。工件是一个文件,通常是一个 JAR,它被部署到 Maven 存储库中。Maven 仓库是硬盘上的一个位置,jar 保存在一个特殊的目录结构中。
任何关于构建工具的讨论都必须从 Maven 开始,因为这个构建工具标准化了我们今天在开发中使用的许多术语。分割成多个子项目的项目可以从 GitHub 下载,在命令行中构建,或者导入 IntelliJ IDEA。这种方法将确保您获得可以一次性编译的高质量源代码。这也很实用,因为我想你不希望每次开始阅读新的章节时都在 IntelliJ IDEA 中加载新的项目。此外,它使我更容易维护源代码并使它们适应新的 JDK,由于 Oracle 发布如此频繁,我需要能够快速完成这项工作。
您将用来测试本书中编写的代码(如果您愿意,也可以编写自己的代码)的项目称为java-17-for-absolute-beginners。这是一个多模块的 Maven 项目。项目的第一级是java-17-for-absolute-beginners project,它有一个名为pom.xml的配置文件。在该文件中,列出了所有依赖项及其版本。第二层的子项目是这个项目的模块。我们称它们为子项目,因为它们从父项目继承了那些依赖项和模块。在它们的配置文件中,我们可以从父类中定义的列表中指定需要哪些依赖项。
这些模块实际上是将每章的源代码打包在一起的一种方法,这就是为什么这些模块被命名为chapter00、chapter01等等。如果一个项目很大,需要编写大量的代码,那么这些代码会被拆分到另一个模块级别。模块chapter05就是这样一个例子,它被配置为其下项目的父模块。在图 3-27 中你可以看到这个项目在 IntelliJ IDEA 中加载后的样子,模块chapter05被展开,所以你可以看到第三层的模块。每一层都标有相应的数字。
图 3-27
Maven 多级项目结构
如果你已经像在第章 2 中被教导的那样将它加载到 IntelliJ IDEA 中,你可以通过构建它来确保一切正常工作。你可以这样做:
图 3-28
Maven 项目视图
- 你可以使用 IntelliJ IDEA 编辑器来完成,在它的右上方你应该有一个名为 Maven 的标签。如果项目如图 3-28 所示加载,则项目加载正确。如果 Maven 选项卡不可见,只需查找类似标有(1)的标签,然后单击它。展开
java-17-for-absolute-beginners(根)节点,直到找到标有(2)的构建任务。如果双击它,在编辑器底部的视图中看不到任何错误,那么您的所有项目都已成功构建。所以,是的,您肯定会看到构建成功(3)的消息。
确保 Maven 项目按预期运行的第二种方法是从命令行构建它。打开一个 IntelliJ IDEA 终端,如果你按照章节 2 的说明在系统路径上安装了 Maven,只需键入mvn并点击<Enter>即可。
位于项目根目录下的主
pom.xml文件有一个默认的目标,通过下面一行进行配置:
<defaultGoal>clean install</defaultGoal>
它声明了构建这个项目所需的两个 Maven 执行阶段。如果配置中没有这个元素,构建项目的两个阶段将在命令中指定,例如:mvn clean install。
在命令行中,如果 JDK 17 在你读到这本书时仍然不稳定,你可能会看到一些警告,但只要执行以构建成功结束,一切都没问题。
除了sandbox项目之外,这个项目非常简单,您可以自己创建,本节中提到的所有类、模块和包都是这个项目的一部分。chapter00和chapter01并不真正包含特定于这些章节的类;我只需要他们能够构建 Java 模块示例。IntelliJ IDEA 按照字母顺序对模块进行排序,所以章节模块的命名是这样选择的,这样它们就按照您应该使用它们的正常顺序排列。
到目前为止,本章一直关注 Java 应用的构建模块,我们创建了一个类来打印 *Hello World!*按照说明操作,但并没有解释所有的细节。让我们现在就这样做,甚至用新的细节来丰富课程。
解释和丰富“你好世界!”班级
之前,我们在sandbox项目中编写了一个名为HelloWorld的类。这个类被复制到包com.apress.bgn.three.helloworld中的chapter03项目中。本章从一个类的主要组件列表开始。HelloWorld类包含了一些将被详细解释的元素。在图 3-29 中,IntelliJ IDEA 编辑器中描述了HelloWorld类。
图 3-29
java-17 绝对初学者项目中的HelloWorld类
这些行包含不同的语句,这些语句在下面的列表中进行了解释,并且行号与列表中的编号相匹配。
-
**包声明:**当类是包的一部分时,它们的代码必须以声明包含它们的包的这一行开始。
package是 Java 中的保留关键字,除了声明包之外不能用于任何其他用途。<空为了方便> : 留空,这样画面看起来更好
-
**类声明:**这是我们声明类型的那一行:
-
它是
public,所以从任何地方都能看到它 -
这是一个
class -
它被命名为
HelloWord -
它有一个用花括号括起来的主体,左括号在这一行。但是它也可以在下一个上,因为空白空间被忽略。
-
-
main()方法声明 : 在 Java 中方法名及其参数的个数、类型、顺序被称为方法签名**。一个方法也有一个返回类型,就像它返回的结果类型一样。但是也有一种特殊的类型可以用来声明一个方法不返回任何东西。按照出现的顺序,下面是
main()方法的每个术语所代表的含义:-
公共方法访问器:
main方法必须是public否则 JRE 无法访问和调用。 -
**静态:**还记得本章开始时提到一个类有成员(字段和方法)吗?当创建该类类型的对象时,它具有该类声明的字段和方法。类是创建对象的模板。但是,因为有了
static关键字,main方法不与类类型的对象相关联,而是与类本身相关联。下一章会有更多的细节。 -
void: 这个关键字在这里用来告诉我们
main方法不返回任何东西,所以它就像是对 no type 的替换,因为如果不返回任何东西,就不需要类型。 -
string… args 或 String[] args: 方法有时被声明为接收一些输入数据;
String[] args表示文本值的数组。三个点是指定一个方法可以有多个相同类型的参数的另一种方式。三点符号只能在方法参数中使用,称为 varargs 。(varargs 参数也必须是该方法的唯一参数,或者是最后一个参数,否则解析参数就成了不可能完成的工作。)这意味着您可以传入一个参数数组,而无需显式创建该数组。数组是固定长度的数据集,在数学上它们被称为一维矩阵或向量。String是 Java 中表示文本对象的类。[]是阵列的意思,args是它的名字。但是等等,我们以前运行过这个方法,我们不需要提供任何东西!这不是强制性的,但是您将看到如何在这个列表之后为它提供参数(提供给方法的值,将由代码在其主体中使用)。
** *** **system . out . println(" hello world!");**是用来写 *Hello World 的语句!*在控制台中。
* } 是
main方法体的右括号。* } 是类体的右括号。
**
-
如果我们执行这个类,我们将会看到Hello World!被打印在控制台中。在图 3-21 中,你已经看到了如何执行一个包含main()方法的类。以这种方式执行一个类后,IntelliJ IDEA 会自动将该执行的配置保存在一个运行配置**中,并在一个下拉列表中显示它,旁边是一个三角形绿色按钮,通过单击它可以用来执行该类,这两个按钮都位于 IDE 的标题上,并在图 3-29 中夸张地指向您。
这两个元素非常重要,因为可以编辑运行配置,并且可以为 JVM 和main方法添加参数。让我们首先修改main方法,先用参数做些事情。
public class HelloWorld {
public static void main(String... args) {
System.out.println("Hello " + args[0] + "!");
}
}
Listing 3-31Main Method with varargs
使用数组元素的索引来访问数组,在 Java 中从 0 开始计数。因此,数组的第一个成员位于 0,第二个成员位于 1,依此类推。但是数组可以是空的,所以在前面的代码片段中,如果没有指定参数,程序的执行将会崩溃,并且在控制台中,将以红色显示一条显式消息。
当一个 Java 程序因为执行期间的错误而结束时,我们说抛出了一个异常。
当我们试图访问一个空数组或者一个不存在的数组元素时,JVM 抛出一个类型为ArrayIndexOutOfBoundsException的对象,该对象包含发生故障的行和我们试图访问的元素的索引。JVM 使用异常对象来通知开发人员 Java 执行没有按预期工作时的异常情况,这些对象包含有关代码中发生异常的位置以及导致问题的原因的详细信息。
我们在前面的代码片段中所做的修改将在执行该类时打印作为参数提供的文本值。让我们修改这个类的运行配置并添加一个参数。如果点击运行配置名称旁边的灰色小箭头,将会出现一个菜单。点击编辑配置并查看向您描述的对话窗口。图 3-30 描绘了菜单和对话框窗口。
图 3-30
自定义运行配置
在图像中,关键元素以浅蓝色突出显示。IntelliJ IDEA 保存了您之前的一些执行,包括 Maven 构建任务,因此您只需单击一下就可以再次运行它们。在运行/调试配置对话窗口的左侧,IntelliJ IDEA 组按其类型运行配置。默认情况下,最后一次运行配置在窗口右侧打开,在本例中为HelloWorld类的运行配置。正如您所看到的,有许多选项可以为执行进行配置,并且大多数选项都是由 IDE 自动决定的。程序参数或main()方法的参数在用红色标记的文本字段中介绍。在图中我们引入了 JavaDeveloper ,所以如果你点击Apply然后Ok按钮,然后在控制台中执行这个类,而不是 Hello World!你现在应该看到了你好 JavaDeveloper!
我们班还能做什么?还记得这本书开头的代码吗?我们姑且称之为这个类中的main()方法。清单 3-32 中再次描述了代码。
package com.apress.bgn.three.helloworld;
import java.util.List;
public class HelloWorld {
public static void main(String... args) {
//System.out.println("Hello " + args[0] + "!");
List<String> items = List.of("1", "a", "2", "a", "3", "a");
items.forEach(item -> {
if (item.equals("a")) {
System.out.println("A");
} else {
System.out.println("Not A");
}
});
}
}
Listing 3-32A More Complex main Method
import java.util.List;语句是包和类声明之间唯一存在的语句类型。这个语句告诉 Java 编译器一个对象类型java.util.List将在程序中使用。import关键字后面是类型的完全限定名。类型的完全限定名由包名(java.util)、点号(.)和类型的简单名(List)组成。没有它,HelloWorld类将无法编译。试试看:只要在语句前面加上“//”就行了,这会把这一行变成一个注释,被编译器忽略。你会看到编辑抱怨任何与列表相关的代码都是鲜红色的。
语句List<String> items = List.of("1", "a", "2", "a", "3", "a");创建一个文本值列表。Java 9 中引入了这种创建列表的方式。Java 5 中引入了使用<T>来指定列表中元素的类型,它被称为泛型。然后通过forEach方法逐个遍历列表中的元素,并对每个元素进行测试,看它们是否等于“ a 字符。用来做这件事的整个表达式被称为 lambda 表达式,这种类型的语法是在 Java 8 中与forEach方法一起引入的。
如果你现在运行这个类,在控制台中你应该看到一系列的*、和而不是*被打印出来,每一行都有一个。
Not A
A
Not A
A
Not A
A
到目前为止,我们编写的代码使用了相当多类型的对象在控制台中打印一些简单的消息。List对象用于保存几个String对象。使用在out对象上调用的println方法打印消息,该对象是System类中的一个静态字段。这些只是你在代码中看到的对象。在底层,List<T>元素由一个Consumer<T>对象处理,该对象是在 lambda 表达式为了简单起见而隐藏的地方创建的,因此前面的代码可以扩展,如清单 3-33 所示。
package com.apress.bgn.three.helloworld;
import java.util.List;
import java.util.function.Consumer;
public class HelloWorld {
public static void main(String... args) {
List<String> items = List.of("1", "a", "2", "a", "3", "a");
items.forEach(new Consumer<String>() {
@Override
public void accept(String item) {
if (item.equals("a")) {
System.out.println("A");
} else {
System.out.println("Not A");
}
}
});
}
}
Listing 3-33A More Complex main Method
在结束这一章之前,我想给你看另一个有趣的东西。forEach块的内容可以写成一行:
items.forEach(item → System.out.println(item.equals("a") ? "A" : "Not A"));
通过使用一个叫做方法引用的东西,前面的代码行可以变得更加简单。但在本书后面会有更多的介绍。
现在看起来可能很可怕,但我保证这本书在清晰的背景下介绍了每个概念,并与现实世界的物体和事件进行了比较,所以你可以很容易地理解它。如果这不起作用,总会有更多的书,更多的博客,当然还有每个 JDK 的官方甲骨文页面,那里有相当好的教程。有志者事竟成。
还有,利用你的 IDE!通过在按下 Control/Command 键的同时单击代码中的任何对象类型,打开对象类的代码,您可以看到该类是如何编写的,并且可以直接在编辑器中阅读它的文档。作为一个练习,为
forEach方法和System类做这件事。
大多数真正聪明的编辑器都有键位图:一组组合键,当它们被按在一起时会执行某些操作,比如导航、代码生成、执行等等。打印 IntelliJ IDEA 键图参考并熟悉它。你的大脑非常快,在编码时,目标是尽可能以你认为的速度打字。:)
摘要
本章向您介绍了 Java 应用的基本模块。还学习了如何使用 JShell 在应用的上下文之外执行 Java 语句。您了解了如何手工编译声明包和模块的 Java 代码。
你在学习本章时所做的许多事情,你可能会在获得一份 Java 开发人员的工作后每天都做(除了你将花费在搜索和修复现有代码中的 bug 的那几天)。您可能还会花很多时间阅读文档,因为 JDK 有很多类,其中包含您可以用来编写应用的字段和方法。随着每个版本的发布,事情都在变化,你必须让自己跟上时代。
大脑的容量有限;没有雇主会期望你知道每一个 JDK 类和方法,但是要聪明地工作,让这个 URL https://docs.oracle.com/en/java/javase/17/docs/api/index.html (或者与 JDK 版本匹配的那个)一直在你的浏览器中打开。当你对一个 JDK 类或方法有疑问时,只需当场阅读它。
用实际名称替换{filename}。
2
《Oracle JShell 用户指南》可在 2021 年 10 月 15 日访问的 Oracle“Java Shell 用户指南”, https://docs.oracle.com/javase/9/jshell/JSHEL.pdf 中找到。
3
例如接口、枚举、注释和记录。
4
chapter03.iml 是一个 IntelliJ IDEA 项目文件。
5
当 jar 托管在一个存储库(比如 Maven 公共存储库)上时,它们也被称为工件。您还可以在 Oracle 阅读更多关于 JAR 的信息,“JAR 文件概述”, https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jarGuide.html ,访问日期:2021 年 10 月 15 日。
6
最常用的库是日志库,如 Log4J、“Apache Log4j 2、 https://logging.apache.org/log4j/2.x 和 Logback、“LogBACK 项目、 https://logback.qos.ch ,都是在 2021 年 10 月 15 日访问的。
7
如果你想知道更多,一篇关于罐子地狱的文章是 Tech Read,“什么是罐子地狱?、“ https://tech-read.com/2009/01/13/what-is-jar-hell ,访问日期 2021 年 10 月 15 日(不过你可能想以后再看,在你自己写了一点代码之后)。
8
我们现在不会提到嵌套类。我们将在第四章中讲到。
9
除此之外,我们还可以定义其他被称为嵌套的 Java 类型,但是当我们遇到这种情况时,我们会跨过那座桥。
10
我们绘制了该表,以避免您在 Oracle 导航到此 URL 的麻烦,“控制对类成员的访问”, https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html ,访问日期为 2021 年 10 月 15 日。
11
Maven 或 Gradle 等构建工具也将子项目称为模块,但它们的用途与 Java 模块不同。
12
拼图项目的完整历史可以在开放的 JDK 找到,“拼图项目”, http://openjdk.java.net/projects/jigsaw ,2021 年 10 月 15 日访问。
13
在 Run 菜单项旁边,描述了可用于运行该类的组合键。
**