Java

解决 Java 异常:“class file has wrong version”

1、概览 在处理 Java 项目时,建立新项目或更新或添加依赖时常见的错误是 “class file has wrong version(类文件版本错误)”。 本文将带你了解出现这个错误的原因以及解决办法。 2、原因 要了解根本原因,我们需要更多地了解类文件。 每当我们编译 Java 代码时,都会为编译文件中包含的每个类创建一个 .class 文件。这些 .class 文件的部分元数据是一个版本号,该版本号与用于编译代码的 Java 当前主版本号相对应。 对于最常见的 Java 版本,这些类的版本号分别为:Java 21 = 65.0、Java 17 = 61.0、Java 11 = 55.0 和 Java 8 = 51.0。如果我们使用的是不同版本的 Java,可以根据需要加减一个版本号,从而计算出相应的类文件版本号。 在运行时,Java 会查看每个 .class 文件,并要求运行环境的版本号大于或等于编译 .class 文件时使用的版本号。这种版本号要求是出于 Java 的向后兼容性机制。也就是说,如果我们运行的是 Java 17 代码,我们就可以使用任何用 Java 17 或以前版本的 Java 编译的类文件。但是,如果我们遇到一个用 Java 21 编译的 .class 文件,就会遇到 “class file has wrong version 65.0, should be 61.0”(class 文件版本号错误,应为 61.

Java 中的 Objects.requireNonNull() 指南

1、简介 NullPointerException(空指针异常)是 Java 最常见的异常之一。它发生在访问或与指向空(null)的引用变量交互时。验证对象非空(尤其是当它们是方法或构造函数中的参数时)对于确保代码的健壮性和可靠性非常重要。我们可以通过编写自己的 null 检查代码、使用第三方库或选择更方便的方法来实现这一点。 本文将带你了解 Java 中内置的一种灵活的解决方案 - Objects.requireNonNull()。 2、Null 值处理 简单回顾一下,有很多方法可以避免手动空值检查。我们可以从各种库中进行选择,而不是用 if 语句来封装我们的代码,这可能会导致错误和耗时。Spring、Lombok(@NonNull) 和 Uber 的 NullAway 就是其中的几种。 相反,如果我们想保持一致性,避免在代码库中引入额外的库,或者使用普通 Java,我们可以选择 Optional 类或 Objects 方法。虽然 Optional 可简化空值处理,但它往往会增加编码开销,并使简单用例的代码变得杂乱无章。由于它是一个独立的对象,因此也会消耗额外的内存。此外,Optional 只能将 NullPointerException 转换为 NoSuchElementException,即不能解决缺陷。 而,Objects 类提供了静态工具方法,可以更高效地处理对象。该类在 Java 7 中引入,并在 Java 8 和 Java 9 中多次更新,它还提供了在执行操作前验证条件的方法。 3、Objects.requireNonNull() 的优点 Objects 类通过提供一组 requireNonNull() 静态方法,简化了对 null 值的检查和处理。这些方法提供了一种简单明了的方法,可在使用前检查对象是否为空。 requireNonNull() 方法的主要目的是检查对象引用是否为空,如果为空,则抛出 NullPointerException 异常。通过明确抛出异常,我们传达了检查的意图,使代码更易于理解和维护。这也会告知开发人员和维护人员,该错误是故意的,这有助于防止随着时间的推移对行为进行无意的更改。 Objects 类是 java.util 包的一部分,因此无需外部库或依赖关系即可轻松访问。它的方法都有详细的文档说明,为开发人员提供了正确使用的明确指导。 4、使用案例 现在,来看看 requireNonNull() 方法的各种用例(包括其各种重载方法)。 这些方法允许在不同情况下处理空值,从简单的空值检查到带有自定义信息的更复杂的验证。 此外,还有两个方法支持默认值 - requireNonNullElse() 和 requireNonNullElseGet()。

把 Future 转换为 Completablefuture

1、简介 本文将带你了解如何将 Future 转换为 CompletableFuture。通过这种转换,我们可以利用 CompletableFuture 的高级功能,如非阻塞操作、链式任务和更好的错误处理,同时仍可使用返回 Future 的 API 或库。 2、为什么要把 Future 转换为 CompletableFuture? Java 中的 Future 接口表示异步计算的结果。它提供了检查计算是否完成、等待计算完成和检索结果的方法。 不过,Future 也有其局限性,比如阻塞调用需要使用 get() 来获取结果。此外,它还不支持链式调用多个异步任务或处理回调。 而,Java 8 中引入的 CompletableFuture 解决了这些缺陷。它通过用于任务链和回调的 thenApply() 和 thenAccept() 等方法支持非阻塞操作,并使用 exceptionally() 进行错误处理。 通过将 Future 转换为 CompletableFuture,我们可以在使用返回 Future 的 API 或库时利用这些功能。 3、逐步转换 来看看如何将 Future 转换为 CompletableFuture。 3.1、使用 ExecutorService 模拟 Future 要了解 Future 如何工作,我们首先要使用 ExecutorService 模拟异步计算。ExecutorService 是一个用于管理和调度独立线程中任务的框架。这将有助于我们理解 Future 的阻塞特性: @Test void givenFuture_whenGet_thenBlockingCall() throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.

使用 Java 在 PostgreSQL 中存储日期和时间

1、简介 在数据库中存储日期(Date)和时间(Time)信息是软件开发中的一项常见任务。由于有许多不同的格式、时区和存储格式,处理日期和时间可能是一项复杂的任务,如果处理不慎,可能会导致许多问题。 本文将带你了解 Java Date / Time API 提供的日期和时间类,以及 PostgreSQL 如何持久化这些类。 2、设置 本文使用 Spring Boot 和 Spring Data JPA 在 PostgreSQL 数据库中持久化日期和时间值。 首先,创建一个实体,其中包含 Java Date / Time API 中不同日期和时间类的字段: @Entity public class DateTimeValues { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private Date date; private LocalDate localDate; private LocalDateTime localDateTime; private Instant instant; private ZonedDateTime zonedDateTime; private LocalTime localTime; private OffsetDateTime offsetDateTime; private java.sql.Date sqlDate; // Getter / Setter 省略 } 此外,还要添加一个默认构造函数,以固定时间初始化所有日期/时间字段:

解决 Java 异常 “java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking”

1、简介 Spring Webflux 是一个非阻塞的 Web 框架,从底层开始构建,旨在利用多核、下一代处理器的优势,处理大量并发连接(既然是非阻塞框架,线程就不应该被阻塞)。 本文将带你了解在使用 Spring Webflux 时常犯的一个错误。 2、Spring Webflux 线程模型 为了更好地理解这个问题,我们需要了解 Spring Webflux 的线程模型。 在 Spring Webflux 中,一个小型工作线程池负责处理传入请求。这与 Servlet 模型不同,在 Servlet 模型中,每个请求都有一个专用线程。因此,框架会保护(隔离)这些接受(处理)请求的线程。 理解了这一点后,继续往下看。 3、通过线程阻塞了解 IllegalStateException 让我们通过一个示例来了解 Spring Webflux 中何时以及为何会出现异常:“java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread”。 以文件搜索 API 为例。该应用从文件系统读取文件,并在文件中搜索用户提供的文本。 3.1、FileService 先定义一个 FileService 类,它能以字符串形式读取文件内容: @Service public class FileService { @Value("${files.base.dir:/tmp/bael-7724}") private String filesBaseDir; public Mono<String> getFileContentAsString(String fileName) { return DataBufferUtils.read(Paths.get(filesBaseDir + "/" + fileName), DefaultDataBufferFactory.sharedInstance, DefaultDataBufferFactory.DEFAULT_INITIAL_CAPACITY) .

Java 异常:IncompatibleClassChangeError

1、概览 本文将带你了解 Java 中的 IncompatibleClassChangeError 异常,这是一种运行时异常,当 JVM 检测到类的更改与之前加载的类不兼容时就会发生。 本文将带你了解出现这个异常的原因以及解决办法。 2、IncompatibleClassChangeError 类 IncompatibleClassChangeError 是 Java 中的一种 LinkageError。该异常通常表示一个或多个依赖类出现了问题。 IncompatibleClassChangeError(不兼容类变更错误)是 LinkageError 的子类,当一个或多个从属类的类定义发生不兼容变更时会导致该异常。 注意,这是 Error 的子类,因此不应该试图 catch 这些异常,因为这意味着应用程序或运行时出现异常需要进行处理。 3、异常的产生 接下来,让我们模拟一种会导致 IncompatibleClassChangeError 的情况。 3.1、预定义一个第三方库 首先创建一个简单的库(项目),其中有一个父类 Dinosaur 和一个子类 Coelophysis,后者继承自 Dinosaur: public class Dinosaur { public void species(String sp) { if(sp == null) { System.out.println("I am a generic Dinosaur"); } else { System.out.println(sp); } } } public class Coelophysis extends Dinosaur { public void mySpecies() { species("My species is Coelophysis of the Triassic Period"); } public static void main(String[] args) { Coelophysis coelophysis = new Coelophysis(); coelophysis.

在 Java 中优雅地操纵时间

在开发时候,发现有很多需要用到时间的地方,例如记录操作的时间、比较时间判断产品是否有效等。总而言之,时间是我们业务开发必须关注、时刻注意的点。但目前工程的代码中使用了非常多时间的工具类,一会儿用 java.util.Date 记录时间,一会用 java.time.LocalDateTime 记录时间,怎么才能在 Java 中优雅的操纵时间呢,我整理了相关的概念和工具类,希望帮助大家在代码开发的过程中对对时间的使用更加优雅。 这里先写一个结论: 建议使用 java8 的时间 API,在安全性和易用性上都远高于 java.util.Date。 目前比较流行的封装 java API 的时间工具类大都基于 java.util.Date,建议在开发过程中根据业务需要基于 java.time.* 的方法封装工具类(文末给出了一个简单的实现)。 时间在计算机中的存储和展示 时间以整数的方式进行存储:时间在计算机中存储的本质是一个整数,称为 Epoch Time(时间戳),计算从 1970 年 1 月 1 日零点(格林威治时间/GMT+00:00)到现在所经历的秒数。 在 java 程序中,时间戳通常使用 long 表示毫秒数,通过 System.currentTimeMillis() 可以获取时间戳。时间戳对我们人来说是不易理解的,因此需要将其转换为易读的时间,例如,2024-10-7 20:21:59(实际上说的是本地时间),而同一时刻不同时区的人看到的本地时间是不一样,所以在时间展示的时候需要加上时区的信息,才能精准的找到对应的时刻。 时区与世界时间标准相关: 世界时间的标准在 1972 年发生了变化,但我们在开发程序的时候可以忽略 GMT 和 UTC 的差异, 因为计算机的时钟在联网的时候会自动与时间服务器同步时间。 本地时间等于我们所在(或者所使用)时区内的当地时间,它由与世界标准时间(UTC)之间的偏移量来定义。这个偏移量可以表示为 UTC- 或 UTC+,后面接上偏移的小时和分钟数。 例如:GMT+08:00 或者 UTC+08:00 表示东八区,2024-10-7 20:21:59 UTC+08:00 便可以精准的定位一个时刻。 日期 API JDK 以版本 8 为界,有两套处理日期/时间的 API。 简单的比较如下: 特性 java.util.Date java.util.Date.Calendar java.time.LocalDateTime 线程安全 ❌ ❌ ✅ 时间运算 ❌ ✅ ✅ 可读性 Tue Oct 08 00:11:16 CST 2024 易读性较低 ❌不易读 ✅ yyyy-MM-dd’T’HH:mm:ss 常量设计 需要对获取的年份(+1900)月份(0-11)进行处理 需要对获月份(0-11)进行处理 ✅ 不需要额外处理,符合常识 时间精度 精确到毫秒 精确到毫秒 精确到纳秒 时区 具体的时间调用 不 - 特性 java.

通过 JDBC 驱动连接 Oracle 数据库

1、概览 Oracle 数据库是最流行的关系数据库之一。本文将带你了解如何使用 JDBC 驱动连接到 Oracle 数据库。 2、数据库 首先,我们需要一个数据库。如果没有,则可以从 Oracle Database Software Downloads 下载并安装免费版本,或者使用 Oracle Database Container Images 上的 Docker 镜像。 本文中使用的是 Oracle Database 23ai (23.5.0) 的 Docker 镜像。 3、Maven 依赖 数据库就绪后,在项目中添加 Oracle 的 JDBC 驱动依赖,本文使用 ojdbc11 连接 Oracle 23ai: <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc11</artifactId> <version>23.5.0.24.07</version> </dependency> ojdbc11 的最新版本可在 Central Maven Repository 中找到。该依赖项要求 Java 11 或更高版本,是较新版本 Java 的推荐驱动。 为了向后兼容,ojdbc8 可用于 Java 8。也推荐使用 ojdbc10 作为 Oracle 19c 的驱动程序。 4、连接到数据库 首先,创建一个 OracleDataSource(Oracle 数据源接口的实现)。这比使用 DriverManager 更好,因为 DataSource 更具可扩展性,也更易于设置。

将虚拟线程与 ScheduledExecutorService 结合使用

1、简介 虚拟线程是 JDK 21 中正式引入的一项杀手锏级别的功能,是提高应用程序性能、吞吐量的一种解决方案。 但是,JDK 没有内置的使用虚拟线程的任务调度器。因此,我们得自己编写使用虚拟线程运行的任务调度器。 本文将带你了解如何使用 Thread.sleep() 方法和 ScheduledExecutorService 类为虚拟线程创建自定义调度器。 2、虚拟线程是什么? JEP-444 中引入了虚拟线程,作为线程类的轻量级版本,提高了应用程序的并发性和吞吐量。 虚拟线程占用的空间比通常的操作系统线程(或平台线程)要少得多。因此,我们可以在应用程序中同时产生比平台线程更多的虚拟线程。毫无疑问,这增加了并发单元的最大数量,也提高了应用程序的吞吐量。 关键的一点是,虚拟线程并不比平台线程更快(任务的耗时不会有改变)。在我们的应用中,虚拟线程的数量只比平台线程多,这样它们就能执行更多的并行工作。 虚拟线程的成本很低,因此我们不需要使用资源池等技术来为数量有限的线程安排任务。相反,我们可以在现代计算机中几乎无限地生成虚拟线程,而不会出现内存问题。 最后,虚拟线程是动态的,而平台线程的大小是固定的。因此,虚拟线程比平台线程更适合小型任务,如简单的 HTTP 或数据库调用。 3、虚拟线程调度 如上所述,虚拟线程的一大优势是体积小、成本低。我们可以在一个简单的机器中有效地生成数百万个虚拟线程,而不会出现内存不足的错误。因此,像使用平台线程、网络或数据库连接等更昂贵的资源那样池化虚拟线程并没有太大意义。 如果使用线程池,就会产生另一种开销,即为池中可用的线程调度任务,这将更加复杂,速度也可能更慢。此外,Java 中的大多数线程池都受到平台线程数的限制,而平台线程数总是小于程序中可能存在的虚拟线程数。 因此,我们必须避免使用带有线程池 API(如 ForkJoinPool 或 ThreadPoolExecutor)的虚拟线程。相反,我们应该始终为每个任务创建一个新的虚拟线程。 目前,Java 并没有提供使用虚拟线程进行调度的标准 API,就像我们使用其他并发 API(如 ScheduledExecutorService 的 schedule() 方法)一样。因此,为了有效地让虚拟线程运行计划任务,我们需要编写自己的 Scheduler(调度器)。 3.1、使用 Thread.sleep() 调度虚拟线程 创建自定义 Scheduler 的最直接方法是使用 Thread.sleep() 方法,让程序在当前线程执行时等待: static Future<?> schedule(Runnable task, int delay, TemporalUnit unit, ExecutorService executorService) { return executorService.submit(() -> { try { Thread.sleep(Duration.of(delay, unit)); } catch (InterruptedException e) { Thread.

Java 中的日期和时间处理类:从传统到现代

1、概览 处理 Date(日期)和 Time(时间)是许多 Java 应用程序的基本组成部分。多年来,Java 在处理日期方面不断发展,引入了更好的解决方案来简化开发者的工作。 2、传统的日期和时间处理类 在 java.time 包出现之前,Java 主要使用 Date 和 Calendar 类来处理日期。尽管它们现在也可以使用,但是有一些缺陷。 2.1、java.util.Date 类 java.util.Date 类是 Java 最初处理日期的解决方案,但它有一些缺点: 它是可变的,这意味着可能会遇到 线程安全 问题。 不支持时区。 它使用了令人困惑的方法名称和返回值,比如 getYear(),它返回的是自 1900 年以来的年数。 许多方法已废弃。 使用无参数构造函数创建 Date 对象,表示当前日期和时间(对象创建时)。 如下,实例化一个 Date 对象并打印其值: Date now = new Date(); logger.info("Current date and time: {}", now); 这将输出当前日期和时间,如 Wed Sep 24 10:30:45 PDT 2024。虽然该构造函数仍然有效,但由于上述原因,这里不再建议新项目使用该构造函数。 2.2、java.util.Calendar 类 由于 Date 的局限性,Java 引入了 Calendar 类,对其进行了改进: 支持各种日历系统。 时区管理。 更加直观的日期操作方法。 我们可以使用 Calendar 操作日期。 Calendar cal = Calendar.