译者 | 刘汪洋
审校 | 重楼
学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响较小)。我特别欣赏 Java 的平台独立性。
相比简单性,我更看重结构和一致性,这也是我坚持使用 Java 的主要原因。
在我的职业生涯中,我遇到了一些质量不高的 Java 代码库,这让我对 Java 产生了些失望。然而,在参与了许多其他优秀的项目之后,我重新燃起了对 Java 的热爱。
我注意到那些批评和抱怨 Java 的通常是一些年轻人,他们接触 JavaScript 的机会更多。与JavaScript 相比,Java 可能看起来更加笨重和受限——充斥着模板代码,拥有严格的类型系统等等。但如果让我选择,我无疑会更愿意处理一个次优的 Java 代码库,而不是 JavaScript 的。
只有在你积累了一定的工作经验,处理过分散在几十个甚至上百个文件中的代码后,你才会开始意识到,Java 所谓的“限制”其实是为了防止你陷入困境的安全措施。
“只有两种编程语言:被人们抱怨的和没人使用的。”这是 C++ 之父 Bjarne Stroustrup 的著名言论。
关于 Java 的看法,确实存在不少争议。相比之下,C++ 也有它的问题。这种语言深受爱恨交织的评价,仿佛是一种难以摆脱的复杂关系。
Python 也同样面临批评。
很多讨论集中在全局解释器锁(GIL)上,它被认为是阻碍有效多线程实现的主要障碍。由于Python 核心很多部分都依赖于 GIL,因此它可能无法被完全移除。为了解决这一问题,开发者不得不通过使用多个进程和创建消息传递机制来实现进程间的通信。此外,为了解决性能瓶颈,除非对关键代码使用C/C++进行重写,否则 Python 的执行速度通常较慢。当然,Python 2 到 3 的过渡也引起了一些问题。
此外,我曾经参与过一个 Django 项目。那时,我认为 Python 相比于类型严格的语言更有优势,尽管它适用于某些场景,但并不适合那些有着成千上万个类的复杂系统。
当这个项目由我一人扩展至多人参与,代码量超过 1 万行时,它变得极其难以维护。
之后我转向 Java,这是一个令人开眼界的经历。它让我深刻认识到自己对 Java 及其生态系统的热爱。因此,我决定记录下我所喜爱的 Java 生态系统方面的内容。这样,当有人对 Java 有所非议时,你就有 25 个理由来反驳他们。
1. 成熟的生态系统
Java 已经发展了 25 多年,作为一个在这个生态系统中工作的开发者,回顾其成熟过程非常有趣。
Java 广泛的生态系统最大的优势在于它提供了丰富的库、构建工具和框架选择。
JVM 生态系统极为丰富,几乎对于每个问题都有一个最佳的库解决方案,而且这些库通常都表现出高性能并得到良好维护。在构建工具方面,也有多种选择,例如 Gradle、Maven 和 Bazel 等,它们能够提供快速且可复制的构建过程。对于那些不太熟悉 Java 生态系统的人来说,Java 还为日志记录、数据库连接、消息传递和应用服务器等各种功能提供了默认实现,这些都是非常好的入门点。
以日志记录为例,假设你的应用程序需要进行日志记录。Java 提供了与 JDK 无缝集成的默认日志记录选项。如果你对默认选项不满意,或觉得它不够好,那么你还可以选择其他优秀的日志记录库,因为默认的日志记录仅仅是日志记录 API 的一个基本实现。
不仅仅是日志记录,Java 生态系统还为数据库连接、消息传递、应用服务器、Servlets 等提供了丰富的选择。
2. “一次编写,到处运行”(WORA)
“一次编写,到处运行”是用来描述 Java 语言跨平台优势的常用口号。现在学习 Java 的许多开发者可能没有意识到这一功能对软件开发的重大意义。
让我们来回顾一下背景。在 Java 诞生的十年前,C++ 是主流的编程语言。然而,开发者们面临的一个主要挑战是 C++ 的平台依赖性。用 C++ 编写的代码往往需要针对不同的操作系统或硬件架构进行重新编译和修改。
Geeks for Geeks
3. 向后兼容性
想象一下,如果每次 Java 发布新版本,都要求你重写程序代码,那将是多么昂贵和耗时的过程,尤其是对于大型组织而言。
Java 已经发展多年,这意味着有许多基于旧版本 Java 构建的软件产品,它们是许多企业的核心,扮演着关键角色。
在企业级开发中,项目规模通常庞大且复杂,这些系统要迁移到最新的 Java 版本需要细致的规划和执行。
Java 对向后兼容性的承诺极为重要,它向那些投入巨大资源开发系统的开发者或组织保证,他们的系统可以持续运行并维护,而无需进行全面重写。Java(JVM)的向后兼容性也简化了迁移过程,促进了新功能和改进的采用,同时保障了现有系统的稳定性。
4. Java 的强类型系统
Java 是一种强类型语言,与 Python 等松散类型的语言形成对比。如果你使用过 Python,你会立刻感受到将不同类型的值赋给同一变量的灵活性,而语言会动态适应。
int age = 25;
String name = "John";
但这种灵活性是有代价的。我曾在一个涉及复杂计算的金融应用程序上工作,该程序包含不同的数值数据类型。Java 的强类型特性意味着,编译器会标出任何尝试混合不兼容数据类型的操作,或是执行可能导致数据丢失或意外结果的行为。而使用像 Python 这样的语言时,一些这样的明显错误可能在运行时被忽略。
这也是 Java 在开发企业级应用程序,尤其是在像银行和金融等对可靠性和安全性有高要求的领域中,格外受欢迎的一个原因。除了减少运行时错误的需求,Java 的强类型系统通过明确变量、参数和返回值的预期数据类型,提高了代码的可读性。
5. 更快的发布周期 - 持续改进
过去,作为 Java 开发者,我们习惯于每隔几年等待一次重大更新来获得新的 Java 特性。但为了适应现代编程的需求,自 Java 9 发布以后,Java 的发布节奏改为了每六个月一次。对于那些不急于迅速迁移到新版本的企业组织,Oracle 提出了每三年发布一个长期支持(LTS)版本的策略。
更频繁且规模更小的发布减少了升级到新 Java 版本的复杂性和风险。开发者不太可能遇到重大的兼容性问题,因为这些渐进的变化被设计为更加向后兼容。
6. 优秀的集成开发环境(IDE)
Java 经历了漫长的发展,引入了许多变化和特性,使其非常适合现代开发。然而,这一切都得益于像 IntelliJ IDEA、Eclipse 和 NetBeans 这样的强大集成开发环境(IDE)的支持。
我无法想象在没有智能代码补全、自动重构、无缝版本控制集成等功能的环境下进行编程会是什么体验。然而,也很重要的是要认识到这并非始终如此,尤其是在 Java 早期。
快速发展到如今,像 IntelliJ IDEA 和 Eclipse 这样的现代 IDE 使 Java 开发变得更加轻松。这些IDE 与 Maven 和 Gradle 等构建工具无缝集成,处理编译、依赖解析和项目管理。像智能代码补全这样的功能减少了 Java 编写的繁琐性,内置的静态代码分析工具简化了调试过程,而插件系统则允许开发者根据个人偏好自定义他们的开发环境。
7. GraalVM 原生镜像的支持
我们已经探讨了 JVM 如何作为一个强大的平台推动 Java 的辉煌。然而,JVM 长期以来也面临着启动速度慢的问题,这在当前追求微服务、无服务器计算及快速启动和资源优化的开发环境中显得尤为突出。
为了解决这一问题,业界已在减少内存占用和加快 Java 应用启动速度方面做出了不懈努力。在这其中,我特别关注的是 GraalVM 原生镜像技术。Oracle GraalVM 是一种高性能 JDK,它通过采用 Graal 编译器这一替代的即时编译器(JIT),提升了 Java 及其他基于 JVM 应用的性能。
GraalVM 还引入了一种原生镜像工具,它能够实现 Java 字节码的提前编译(AOT),从而达到几乎瞬间启动应用的效果。Graal 编译器在此过程中既是 AOT 编译器,又能生成原生可执行文件。简而言之,它将 Java 字节码转化为原生可执行文件。
以下是一个使用递归反转字符串的简单 Java 程序示例:
public class Example {
public static void main(String[] args) {
String str = "Native Image is awesome";
String reversed = reverseString(str);
System.out.println("反转后的字符串是:" + reversed);
}
public static String reverseString(String str) {
if (str.isEmpty())
return str;
return reverseString(str.substring(1)) + str.charAt(0);
}
}
你可以对这个程序进行编译,然后基于 Java 类创建一个原生镜像。
javac Example.java
native-image Example
原生镜像构建器会将 Example 类提前编译成一个独立的可执行文件 example
,并保存在当前工作目录中。您接下来可以直接运行这个可执行文件:
./example
原生可执行文件体积小,启动迅速,同时显著减少了 CPU 和内存的需求,非常适合于容器和云部署环境,其中成本优化至关重要。
随着 JDK 21、项目 Loom 和 ZGC 的最新进展,我对这些技术的发展充满期待。GraalVM 的原生镜像现在也支持虚拟线程。我们可以创建使用 Spring Boot(通过 Spring Boot 3.2)和 Java 21 虚拟线程的 GraalVM 原生镜像。
8. 开源库和框架的重要性
开源库和框架是 Java 在我的工具集中占据重要位置的核心原因之一。
图片来源:Google
这些库和框架为我提供了一系列易于集成的构建模块,省去了自行为常见功能重新设计和实现的工作。它们就像一个随时可以取用的代码商店,其中存放着大量经过严格测试和精心编写的代码。
这些库的多样性意味着我不再受限于单一的解决方案。我总能找到最适合我的需求的库。开源性质还促进了透明度和责任感,意味着我可以深入探究源代码,了解其背后的工作机制,甚至可以做出自己的贡献。
Java 的开源库涵盖广泛,包括用于 JSON 解析的库(如 Jackson 和 Gson)、日志库(如 Log4j、SLF4j 和 LogBack)、单元测试库(如 JUnit、Mockito 和 PowerMock),还有数据库连接库、消息传递库等。
Java 的广泛框架生态系统也是其成功的部分原因。Spring 和 Spring Boot 是我偏爱的组合之一。此外,我还使用过 Jakarta Faces、Struts、Hibernate 和 Quarkus 等其他框架。
9. 多线程的支持
Java 对多线程的支持允许我设计能够同时处理多任务的应用程序,涵盖数据处理、用户交互管理和后台计算等方面。Java 通过实现 Runnable 接口或继承 Thread 类来实现多线程功能。
Java 的 java.util.concurrent 包提供了一系列先进的工具,进一步便利了并发应用程序的开发,包括 ExecutorService、ScheduledExecutorService、Future、CyclicBarrier 等。
public class MyRunnable implements Runnable {
public void run() {
// 在新线程中要执行的代码
}
}
MyRunnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.start();
现代计算标准中,多核处理器已成为常态,即一个芯片上集成了多个处理器核心。Java 对多线程的支持使得我们能够充分利用多核 CPU 的能力,这意味着我们可以开发出更高性能的 Java 应用程序,适用于游戏、视频编辑、科学模拟等资源密集型活动。
10. Java 面向对象的特性
你可能会想,Java 并非唯一的面向对象编程语言,它与 Python、C++ 等语言相比有何特殊之处?区别在于,Java 从一开始就是基于面向对象原则设计的,而不是像其他语言那样后来才加入面向对象编程(OOP)的元素或支持。
Java 对面向对象编程的四大原则——抽象、继承、多态和封装——的坚持,使它成为构建复杂、可扩展和易于维护的软件系统的理想选择。我认为,Java 对 OOP 范式的深入支持带来了许多优势,如有助于构建模块化、灵活、易读、易维护和可扩展的应用程序。
11. 内存管理和垃圾回收
图片来源:Digital Ocean
让我们以一个类比来看待这个问题:就像许多人都不喜欢倒垃圾一样,我也不喜欢手动管理内存。在需要手动内存管理的情况下,开发者需要负责分配和释放内存,这就像决定何时倒垃圾一样。如果忘记释放内存,就会导致内存泄漏和性能问题。
而 Java 的自动内存管理则像是一个可靠的垃圾回收服务。在 Java 中,垃圾收集器自动识别并处理不再需要的内存,从繁琐的内存管理中释放了开发者。
在使用 C++ 的过程中,我体验到了内存管理的两面性。C++ 提供了对内存管理的细致控制,但这也给开发者带来了避免内存泄漏的巨大责任。
相比之下,Java 让开发者不必担心底层系统的细节,也无需手动进行垃圾回收、处理底层操作系统的问题或追踪内存分配与释放。垃圾收集器会自动识别并回收不再使用的内存,减少了内存泄漏的风险。
作为开发者,这意味着我可以将更多精力集中在业务逻辑和应用程序的高层设计上。如果你是团队中的一员,Java 的自动内存管理还可以提升开发周期的效率和整体生产力。
12. 可观测性和监控
在过去,很多开发者主要致力于单体应用的开发和维护。处理故障和修复漏洞相对直接,因为整个应用都集中在一个统一的代码库中。这就像在一张详尽的地图上导航,追踪问题相对容易。
然而,随着微服务、无服务器计算和分布式系统的兴起,情况发生了显著的变化。由于微服务作为独立服务通过网络通信,识别和解决问题变得更加复杂。当问题发生时,它们可能不再局限于单个代码库。以下是我在使用 Java 进行开发时,最青睐的一些监控工具。
标准分析工具:
VisualVM.
VisualVM 对我来说就像是探索 Java 应用内部世界的可靠伙伴。它融合了 JConsole 和 VisualGC 的功能,为我提供了一个用于监控线程、堆使用情况和 CPU 性能的可视化平台。此外,它与各种 JDK 工具的兼容性极佳,是一款十分可靠的监控工具。
YourKit.
YourKit 在我的工具箱中就像一位秘密特工。它能够深入到方法级别,揭示程序的执行时间和内存分配情况。它提供了一种简单、易用且安全的方法,在云环境、容器和集群中进行性能分析。
应用性能监控(APM)工具:
New Relic.
New Relic 是我的首选应用性能监控工具。它就像一个全天候监视我的应用程序的私人助手,提供从实时洞察到详细的事务跟踪。它的警报系统就像我的安全网,确保我对任何异常行为及时获得通知。
AppDynamics.
AppDynamics 是我的性能监控艺术家!它采用全面的监控方式,观察并可视化整个技术栈,从数据库和服务器到云原生和混合环境。我特别欣赏它如何帮助我理解性能对最终用户体验的影响,让它不仅仅是一个监控工具,更是一种用户满意度的衡量工具。
日志解决方案:
Log4j.
Log4j 在日志框架界就像一位可靠的元老。无论遇到何种情况,它都能忠实地记录下事件和错误。其在配置 Appender 和过滤器方面的灵活性,使其成为适应各种日志需求的坚实选择。
SLF4J.
SLF4J 就像我手头的日志瑞士军刀。它本身并不直接记录日志,而是作为一个外观层,允许我无缝切换不同的日志实现。这种灵活性使它成为在多个库中进行日志管理的理想选择,尤其是当这些库有各自的日志偏好时。
Java 的可观测性:
Digma:连续反馈(CF)。
在软件开发中,如果不能直观地看到代码在实际环境中的运行效果,就难以做出明智的设计决策和评估更改的影响。Digma 通过在可观测性和代码之间建立联系,为一种新型的开发方法铺平了道路。
Digma是一个连续反馈工具 ,旨在简化从 OpenTelemetry 可观测性源收集和处理代码数据的过程。Digma 作为 IDE 插件在本地运行,边编码边收集代码的数据,从追踪到日志和指标,让你能够实时捕捉问题并获取深入洞察。
Prometheus 和 Grafana
Prometheus 和 Grafana 是我不可或缺的动态双人组合。Prometheus 负责从应用程序中抓取指标,而 Grafana 则将这些指标转换成美观且可自定义的仪表板。这些工具的开源性质和活跃的社区使它们成为我日常可观测性的重要组成部分。
Elastic Stack (ELK)
Elastic Stack 结合了基于 AI 和搜索的开放、可扩展的全栈可观测性功能。Elasticsearch、Logstash 和 Kibana 一起构成了一个强大的工具组合,用于搜索、分析和可视化日志。它们将日志、指标和追踪数据关联起来,为我提供了一个全面的调查工具包。
13. 函数式编程的支持
自从 Java 8 推出以来,函数式编程已成为 Java 支持的另一种编程范式。在函数式编程中,函数被视为“一等公民”,这意味着它们可以被赋值给变量、作为参数传递,甚至作为返回值。
Java 中的函数式编程特性为编程语言增添了吸引力。对我而言,采用 Java 中的函数式编程特性是一场规则改变的游戏。Lambda 表达式和函数接口的引入,不仅让我写出的代码更加简洁,而且表达力更强。利用 Stream API,我的应用程序得以在多核处理器上执行并行处理,从而获得性能上的提升。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
int sum = list.stream().reduce(0, (a, b) -> a + b);
System.out.println(sum);
}
}
我发现,函数式编程所倡导的声明式风格使代码更易于阅读和理解。对不可变性的重视以及避免副作用的原则,对我编写的代码产生了积极影响,使其更加可预测和易于维护。而在测试方面,纯函数的普遍应用使得测试工作更加容易。
14. Java 丰富的文档
团队项目的经历让人深刻理解文档的重要性。对我个人来说,我认为文档就像是代码的用户手册,它解释了代码的作用、实现方式及其原因。对于初学 Java 的人来说,这就像身边有一个导师。
Java 文档包括丰富的代码示例、教程、开发者指南和 API 文档,你可以借此快速开发原型,并将其扩展到真实世界的应用程序。
文档的时效性同样重要。Java 的文档始终保持最新,定期进行修订,以反映生态系统中的新发展。这些文档由开发者和专家编写,其结构也便于查找特定类别、方法或概念的信息。
15. 构建工具和依赖管理
在常规的 Java/Spring Boot 项目中,通常会遇到几十甚至几百个直接和间接依赖。尝试手动管理这些依赖可能会带来极大的挑战,尤其是在处理大型企业项目时,问题如版本兼容性等更加复杂。使用构建工具可以在团队成员之间统一构建过程,确保每个开发人员的本地运行环境保持一致。
image.png
构建工具如 Maven 和 Gradle 极大简化了构建、测试和管理依赖的过程,为开发人员节约了宝贵的时间和精力。这些工具能自动从仓库获取依赖项并检查更新,省去了跟踪依赖项更新和安全补丁的麻烦。
此外,构建工具还为项目结构和配置强制实施一定的约定和标准,使项目易于理解和与其他 Java 开发人员协作。
16. 强大的测试功能
虽然我们开发人员可能害怕解决漏洞和与 QA 工程师合作,但我们也认识到全面测试是确保我们的应用程序尽可能无漏洞的关键方法之一。
选择一个具有强大测试功能的编程语言可以大大减轻开发负担,并有助于构建更可靠、易于维护的代码库。这也是为什么我推崇 Java 作为构建稳定软件的首选编程语言的原因之一。
在 Java 领域,无论是单元测试、集成测试还是端到端测试,都有一整套成熟的工具集来编写全面的测试用例。在这些工具中,JUnit 被广泛认为是单元测试的行业标准。它提供了一种简洁且高效的方式来撰写和执行测试。JUnit 的核心特点是使用各种注解,如 @Test、@Before、@After、@BeforeClass 和 @AfterClass,来定义测试方法的生命周期。这种设计让开发者在执行测试前能够轻松地设定必要的前置条件。
// EmployeeServiceTest.java
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
@SpringBootTest
public class EmployeeServiceTest {
@Mock
private EmployeeRepository employeeRepository;
@InjectMocks
private EmployeeService employeeService;
@Test
public void testGetAllEmployees() {
// 模拟仓库响应
when(employeeRepository.findAll()).thenReturn(new ArrayList<>());
// 测试 getAllEmployees 方法
List<Employee> result = employeeService.getAllEmployees();
// 断言
assertEquals(0, result.size());
// 验证仓库方法调用情况
verify(employeeRepository, times(1)).findAll();
}
}
使用 JUnit 编写测试既简洁又高效,同时它能够与众多流行的 Java 集成开发环境(IDE),如 Eclipse、IntelliJ IDEA 和 NetBeans 完美兼容。
在 Java 生态系统中,还有其他几种对测试非常有帮助的工具。例如,TestNG 适用于集成测试,Cucumber 便于实现行为驱动开发,而 Selenium 则专门用于自动化功能测试和回归测试用例的编写。此外,Mockito 作为一款功能强大的模拟框架,能够与 JUnit、TestNG 等其他测试框架结合使用,以提高测试的灵活性和效率。
17. 庞大的社区
Java 社区在我作为一名 Java 开发者的成长过程中起着至关重要的作用。我已经无数次从这个社区中获得了帮助和指导。无论我面临着一个棘手的问题、探索新的库,还是寻求在实施新方案时的最佳实践,Java 社区都是一个宝贵的知识源泉。
Java 社区是最活跃和响应迅速的社区之一,我几乎总是能够迅速从社区中获得帮助。例如,在 Reddit 的 r/java 社区,有数以万计的 Java 开发者交流和分享。在 Stack Overflow、Github 等平台上,从初学者到资深专业人士的各种开发者都活跃在这些社区中。这种多样性是社区强大的基石,意味着你能够获得来自不同领域和经验层次的丰富专业知识。
除了在线社区,许多 Java 活动、会议和聚会为开发者提供了面对面交流、分享经验和学习的机会。这些聚会加强了人际网络和协作,并且为 Java 开发者间的社群感做出了贡献。
18. Java 注解的支持
Java 注解自 Java 5 引入以来,一直是一个受欢迎且有争议的话题。注解的引入标志着从 Hibernate 或 Spring XML 配置文件的广泛使用向更现代化的编程方法转变。注解使我们能够将信息、指令或配置直接嵌入到源代码中,放置在所需的确切位置。
// 预定义注解
@Deprecated
@Override
@SuppressWarnings
@SafeVarargs
@FunctionalInterface
// 元注解
@Retention
@Documented
@Target
@Inherited
@Repeatable
// 自定义注解
// 也可以创建我们自己的自定义注解。
同时,我们也能够创建自己的自定义注解。
Java 注解无疑增强了代码的清晰度和表达性,减少对外部文档的依赖。注解还有助于减少某些样板代码,例如,通过注解,我们可以定义依赖注入、ORM 映射和事务边界等方面的功能。
尽管注解方便并具有多种优势,但一些开发者对某些注解还持怀疑态度。
19. 安全功能
我们编写的程序不仅包含代码行,还处理着用户个人数据、财务信息和专有商业资料等敏感信息。用户期望我们保护他们的信息,提供安全的使用环境。对企业而言,特别是那些涉及软件开发或创新领域的企业,防止知识产权泄露尤为关键。有效保护源代码、算法和专有信息对于维持企业的竞争优势非常重要。
Java 提供了许多功能,使得开发安全应用程序变得更加容易。这些功能包括加密、公钥基础设施(PKI)、安全通信、认证和访问控制等。此外,您还可以使用丰富的 API 和工具,轻松地将安全措施集成到您的 Java 应用程序中,涵盖从加密到智能卡 I/O 以及其他确保通信安全的认证协议。
要了解更多可利用的安全功能,您可以参阅官方 Java 安全指南。
20. 丰富的 API 集
image.png
Java 以其丰富的应用程序编程接口(API)著称,为开发者提供了与各种软件组件、库和服务进行标准化交互的方式。这些 API 提供了丰富的类、接口和方法集合,以便直接使用。
假设你正在从零开始构建一辆汽车。使用 Java API 就像是在组装供应商生产的零部件,而不是自己制造这些零件。在这种情况下,您可以挑选完全满足您需求的 API。这些 API 是标准化的、文档完备的,且易于使用。
Java API 的优势在于它抽象了组件构建的复杂细节,让您能够专注于组装一个完全功能的汽车,即您的应用程序。这些 API 能够帮助您在网络、IO、文件处理、数据库连接、多线程等众多领域完成各种任务。
Java API 分为多个包,其中一些最常用的包括 java.lang、java.util、java.io和 java.net。
21. Java 的性能提升
我选择 Java 作为主要编程语言,部分原因是它不断提高的性能,这让我对 Java 更加忠诚和信任。
Java 性能方面的进展在解决问题和构建高性能应用程序方面表现卓越,这些应用能满足现代客户的需求。其中一些显著的改进包括:
Java 虚拟机(JVM)在每个新版本中都会推出一些重大优化。这些优化包括即时(JIT)编译器的提升、垃圾收集机制的加强,以及更为高效的运行时分析,共同作用于提高程序执行速度和降低内存消耗。
Project Valhalla 引入的值类型特性,为我们定义了更加高效、紧凑的数据结构,这不仅减少了内存消耗,还提高了缓存的局部性。在处理大规模数据时,这种改进带来了显著的性能提升。
最近,JDK 21 的发布引入了 15 项新特性,其中包括关键的封装机制 API、虚拟线程,以及字符串模板和结构化并发的预览功能,这些更新显著提升了 Java 的整体性能和功能。
JDK 21 中的虚拟线程和其他特性的飞跃
22. 结构化并发的发展
结构化并发 API,作为 JDK 21 中的一个预览特性,旨在简化并发编程。它通过将不同线程中的相关任务视为单一工作单位,增强了错误处理、取消、可靠性和可观测性。这一特性并非意在替代 java.util.concurrent 中现有的并发工具。
结构化并发特性将使开发者能够摆脱在使用如 ExecutorService 和 Future 这类现有构造进行并发编程时所面临的管理任务和子任务的复杂性。传统上,任务和子任务之间缺乏固有的关系,这导致在错误处理、取消和可观测性方面的挑战。提出的结构化并发方法寻求将代码的语法结构与任务的运行时层次结构对齐,使并发编程更加可读、可维护和可靠。这种方法在处理并发时将提供更清晰和更直观的编程模型。
23. 虚拟线程
JDK 21 正式推出了 Virtual Threads(虚拟线程)这一特性,该特性最初在 JDK 19 和 JDK 20 中作为预览功能亮相。传统上,每一个 java.lang.Thread 实例都绑定于一个平台线程,与一个底层操作系统线程相关联并贯穿其整个生命周期。
然而,虚拟线程带来了一种新的编程范式。尽管 java.lang.Thread 的实例依然存在,但它们以一种不同的方式运行,允许在底层操作系统线程上执行 Java 代码,而非独占一个线程。这一创新实现了多个虚拟线程高效地共享同一个操作系统线程的能力。不同于平台线程,虚拟线程不会占用珍贵的操作系统线程资源,并且它们的数量可以远超操作系统线程的限制。
虚拟线程是 JDK 21 中的一大亮点,对于需要处理大量并发任务的应用程序来说,尤其是任务数量可能超过数千的场景,它们能够显著提升程序性能。
如今,根据应用程序的具体需求,开发者可以在虚拟线程和传统的平台线程之间做出选择。
24. Switch 语句的模式匹配
这一特性源于 JDK 17 的提案,并在 JDK 18、JDK 19 和 JDK 20 中经历了一系列的完善和改进。现在,它将成为 JDK 21 的一部分,基于来自 Java 社区的反馈和经验,进行了进一步的优化。
这个特性旨在增强 switch 表达式和语句的功能和通用性。它着重于增强 case 标签中模式的作用,以提供处理 null 值的更大灵活性,并通过要求模式匹配的 switch 语句全面覆盖所有可能的输入值,提高了 switch 语句的安全性。
如果你已经在使用 switch 表达式和语句,无需担忧;其目标是确保现有代码能够继续像目前一样正常运行,无需作出任何修改。
25. 字符串模板
JDK 21 终于引入了长期以来开发者期盼的字符串模板特性,支持字符串插值。此前,唯一的选择是拼接多个字符串或使用 string.format,这些方式不免显得繁琐。但在 Java 21 中,这一切将成为过去。新特性允许开发者将 Java 的字符串字面量和文本块与字符串模板结合使用。
例如:
int x = 10;
int y = 20;
String s = STR."\{x} + \{y} = \{x + y}";
该特性的主要目的是简化动态字符串的创建,通过在运行时编译表达的值来实现。它还旨在提高代码的可读性,并解决与 StringBuilder 和 StringBuffer 类相关的冗余问题。此外,字符串模板还旨在解决与其他编程语言中现有的字符串插值技术相关的安全问题。
总结
Java 的易用性和性能都有了显著的提升,这使得代码更容易阅读和维护。在 Java 中,你不必担心自己或同事的代码是否够高级或复杂。我们也不妨承认,Java 开发人员的收入通常很高,这是因为许多大型的公司和组织都选择 Java 作为他们的主要编程语言,从而创造了很多的工作机会。我认识的一些人还在使用 Java 8,他们对此很满意。
常见问题解答
为什么 Java 成为一种流行的编程语言?
Java 凭借其平台独立性、稳健性以及强大的社区支持而广受欢迎。它在从网站开发到企业级应用各个领域广泛应用。
使用 Java 进行企业级应用开发有哪些优势?
Java 提供了稳定性强、易于扩展的开发环境,支持多线程处理,并配备了众多框架和库,例如 Spring Boot,这些特性极大地简化了企业级应用的开发。
Java 如何支持跨平台开发?
Java 通过“一次编写,到处运行”的理念实现了跨平台兼容性,允许开发者在任何装有 Java 虚拟机 (JVM) 的设备上运行 Java 代码。
译者介绍
刘汪洋,51CTO社区编辑,昵称:明明如月,一个拥有 5 年开发经验的某大厂高级 Java 工程师,拥有多个主流技术博客平台博客专家称号。
原文标题:25 REASONS WHY JAVA IS STILL AROUND IN 2023,作者:Digma