7.2 与Java互操作


7.2 与 Java 互操作

Scala 编译到同一套字节码,调用 Java 类通常零成本直接写;真正的摩擦在集合语义、函数类型与异常体系三处。本节给出双向调用的完整清单与缓冲层实践。

直接用:九成场景免费

import java.util.{HashMap => JHashMap} import java.time.LocalDateTime val now = LocalDateTime.now() // Java 类直接调用 val rnd = new java.util.Random() rnd.nextInt(100) // Java 的函数式接口(SAM)可用 Scala 函数字面量 val executor = new java.util.concurrent.Executor: def execute(r: Runnable): Unit = r.run()

反方向也成立:Scala 方法编译后就是普通字节码方法,Java 能直接调用;Scala 的函数值编译为 Function1 接口实例,Java 侧调用其 apply。企业里大量 Scala 服务与 Java 框架(Spring 全家桶除外)就是这么共存的。

摩擦点一:集合

import scala.jdk.CollectionConverters.* val jList = new java.util.ArrayList[String]() jList.add("a") val sList: List[String] = jList.asScala.toList // 转 Scala,转回去 .asJava

asScala/asJava 是 O(1) 的视图包装(大多数情况),不是拷贝。边界上立刻转成所在语言的惯用集合,别让 Java 集合渗进业务逻辑深处——两种集合的 API 差异会在每一次调用时收税。

摩擦点二:可空性

Java 引用可能为 null,Scala 类型系统不追踪它。被 Java 返回值击穿是 Scala 项目最常见的 NPE 来源:

val raw: String = legacyClient.getUserName(id) // 可能返回 null val safe: Option[String] = Option(raw) // null -> None

纪律:所有 Java 边界的返回值立刻 Option 包一层,此后系统内部恢复"类型即承诺"的秩序。Scala 3 还提供了显式可空类型工具,但 Option 包装是零依赖的最低配防线。

摩擦点三:受检异常

Scala 没有 checked exception,Java 的受检异常在 Scala 里就是普通异常,编译器不强迫处理:

try val text = java.nio.file.Files.readString(path) Right(text) catch case e: java.io.IOException => Left(e.getMessage)

推荐在边界处统一转成 Either[String, T] 或自定义错误类型——既接住 Java 异常,又不让 try-catch 在业务代码里蔓延。

互操作边界防腐层

图 7-1 Java 互操作防腐层结构

图 7-1 Java 互操作防腐层结构

完整案例:把 Scala 库暴露给 Java 调用方

背景:团队 Java 侧要复用你写的温度模块,默认导出的函数带 SAM 转换与默认参数,Java 侧读不懂。操作分三步:

package temp object TempApi: def toF(c: Double): Double = c * 9 / 5 + 32 // 直接可调 @annotation.varargs // 第一步:可变参数转 Java 数组风格 def average(xs: Double*): Double = xs.sum / xs.size private[temp] def helper(x: Int): Int = x * 2 // 不想暴露的收进私有

Java 侧对应写法:

import temp.TempApi; double f = TempApi.toF(36.6); double avg = TempApi.average(1.0, 2.0, 3.0); // varargs 生效

结果:Java 调用方零感知差异。解读:互操作的坑集中在四处——默认参数在 Java 里看不到(要显式传全)、Option 要转 java.util.Optional、集合要走 CollectionConverters、特质要靠 abstract class 版本。变式:给样例类加 @JavaDefaultValue 或提供重载工厂,是大型混编项目的常规补丁。

集合与 Optional 的转换表

Scala Java 转换方向
List / Seq java.util.List asJava / asScala
Map java.util.Map 同上
Option Optional toJava / toScala
Try CompletableFuture 手工包一层

方向感比背 API 重要:边界处转换、内部各用各的,转换只发生在模块接口那一行。

排错实录:三个高频互操作报错

第一个,NoSuchMethodError 出现在升级 Scala 版本后:Scala 字节码里的模块类名带 $ 后缀且与版本相关,混编项目必须让 Scala 侧先行发布、Java 侧再升级依赖。第二个,NullPointerException 出现在特质方法带默认参数时:Java 侧调不到默认参数合成的方法,需提供显式重载。第三个,集合转换后 ConcurrentModificationException:CollectionConverters 的 asJava 是视图不是拷贝,跨线程传递要 toList 落地。三个坑的共性解法都是"在边界收紧",边界越薄,坑越少。

本节要点回顾

  • 字节码同源,普通调用零成本,SAM 与函数值双向桥接。
  • 集合用 asScala/asJava 在边界一次转换,别让异种集合内渗。
  • 可空性用 Option(raw) 在入口消毒;受检异常转 Either 统一错误通道。
  • 核心策略是防腐层:摩擦全部消化在边界,核心域保持纯 Scala 风格。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U