杰瑞科技汇

Java如何调用Python脚本?

我将为你详细介绍几种主流的方法,从最简单到最专业,并分析它们的优缺点和适用场景。

Java如何调用Python脚本?-图1
(图片来源网络,侵删)

核心思想

无论使用哪种方法,Java 调用 Python 的核心思想都是:

  1. Java 进程 启动一个 Python 子进程
  2. Java 进程 向 Python 子进程传递输入数据(通过命令行参数、标准输入流)。
  3. Python 脚本 执行其逻辑,并产生输出结果(打印到标准输出、写入文件)。
  4. Java 进程 从 Python 子进程捕获输出结果(从标准输出流读取、读取文件)。
  5. Java 进程 解析输出结果,并将其转换为 Java 对象。

使用 Runtime.exec()ProcessBuilder (最基础)

这是最直接、最底层的方法,Java 提供了 Runtime.exec() 和更强大的 ProcessBuilder 来执行外部命令。

工作原理

Java 启动一个新的 Python 解释器进程,并传入你的 Python 脚本路径作为参数,Java 程序可以等待 Python 进程执行完毕,然后读取其标准输出和错误流。

示例代码

Python 脚本 (hello.py)

Java如何调用Python脚本?-图2
(图片来源网络,侵删)

这个脚本接受一个命令行参数,并打印出结果。

# hello.py
import sys
# 从命令行参数获取输入
name = sys.argv[1] 
greeting = f"Hello, {name}! This message is from Python."
# 将结果打印到标准输出,Java可以捕获这个输出
print(greeting)
# 模拟一个计算
number = int(sys.argv[2])
result = number * number
print(f"The square of {number} is: {result}")

Java 代码 (JavaPythonCaller.java)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class JavaPythonCaller {
    public static void main(String[] args) {
        try {
            // 1. 定义要执行的命令
            // 注意:这里的 "python" 或 "python3" 需要在系统环境变量中配置好
            String[] command = {
                "python", 
                "C:/path/to/your/script/hello.py", // Python 脚本的绝对路径
                "JavaUser",                       // 第一个参数
                "10"                              // 第二个参数
            };
            // 2. 使用 ProcessBuilder 启动进程(推荐比 Runtime.exec())
            ProcessBuilder pb = new ProcessBuilder(command);
            pb.redirectErrorStream(true); // 将错误流合并到标准输出流,方便统一处理
            Process process = pb.start();
            // 3. 读取 Python 脚本的输出
            InputStream inputStream = process.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            StringBuilder output = new StringBuilder();
            System.out.println("--- Output from Python script ---");
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // 打印从Python读取的每一行
                output.append(line).append("\n");
            }
            System.out.println("---------------------------------");
            // 4. 等待进程执行完毕,并获取退出码
            int exitCode = process.waitFor();
            System.out.println("Python process exited with code: " + exitCode);
            // 5. 处理输出结果 (这里只是简单打印,实际项目中需要解析)
            // 你可以用正则表达式或JSON来解析output字符串
            if (exitCode == 0) {
                System.out.println("Successfully received output from Python.");
                // System.out.println("Full output:\n" + output.toString());
            } else {
                System.err.println("Python script execution failed.");
            }
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

优点

  • 简单直接:不需要任何额外的第三方库。
  • 通用性强:任何操作系统只要安装了 Python 都可以运行。

缺点

  • 性能开销大:每次调用都会创建一个新的 Python 解释器进程,启动和销毁进程都有开销,不适合高频调用。
  • 数据传递复杂:复杂数据(如对象、列表、字典)需要手动序列化(转为 JSON 字符串)才能通过命令行参数或标准流传递,接收方也需要手动反序列化。
  • 错误处理麻烦:需要手动管理进程的生命周期和错误流。
  • 环境依赖:需要确保目标机器上安装了正确版本的 Python,python 命令在系统 PATH 中。

使用进程间通信 (IPC) - 通过 Socket

如果数据量较大或调用频繁,直接通过命令行或标准流传递数据效率低下,这时可以使用 Socket 进行进程间通信。

工作原理

  1. Java 端:启动一个 Socket 服务器,监听某个端口。
  2. Python 端:作为 Socket 客户端,连接到 Java 服务器的指定端口。
  3. Java 端:向 Python 客户端发送需要处理的数据(JSON 格式)。
  4. Python 端:接收数据,进行处理,然后将结果(同样用 JSON 格式)发送回 Java 端。
  5. Java 端:接收并解析结果。

优点

  • 适合大数据量:可以高效地传输序列化的数据。
  • 解耦:Java 和 Python 可以部署在不同的机器上。
  • 长连接:避免了频繁创建销毁进程的开销。

缺点

  • 实现复杂:需要自己编写 Socket 通信的代码,处理连接、数据格式、异常等。
  • 需要额外服务:Python 脚本需要被包装成一个持续运行的 Socket 服务。

使用第三方 Java 库 (推荐)

这是最优雅、最强大的方法,社区已经有很多成熟的库封装了底层细节,提供了类似调用本地 Java 方法一样的体验。

Java如何调用Python脚本?-图3
(图片来源网络,侵删)

Jython (已不推荐,但值得了解)

Jython 是一个用 Java 实现的 Python 解释器,它允许你在 JVM 上直接运行 Python 代码,并且可以无缝地调用 Java 类库。

  • 优点
    • 真正的“混合”,性能比进程间调用高。
    • Python 代码可以直接导入和使用 Java 包。
  • 缺点
    • 不支持 Python 3!这是一个致命的缺陷。
    • 对 C 语言扩展(如 NumPy, Pandas 的底层)支持不佳。
    • 项目更新缓慢,社区不活跃。

仅适用于遗留的 Python 2 项目,新项目不推荐使用。

Py4J (强烈推荐)

Py4J 是一个非常流行的库,它使用了一种巧妙的方式:Java 启动一个网关服务器,Python 客户端通过这个网关来访问 Java 对象,反之亦然。

工作流程

  1. Java 应用程序启动一个 Py4J 网关服务器。
  2. Python 脚本连接到这个网关服务器。
  3. Python 可以调用 Java 对象的方法,就像调用 Python 对象一样。
  4. Java 也可以调用 Python 端注册的函数。

示例

Java 端 (JavaGatewayServerApp.java)

import py4j.GatewayServer;
// 一个简单的 Java 类,供 Python 调用
class Calculator {
    public int square(int number) {
        return number * number;
    }
    public String greet(String name) {
        return "Hello, " + name + " from Java!";
    }
}
public class JavaGatewayServerApp {
    public static void main(String[] args) {
        // 创建一个 Calculator 实例
        Calculator calculator = new Calculator();
        // 启动 GatewayServer,默认端口 25333
        // 并将 calculator 实例暴露给 Python
        GatewayServer server = new GatewayServer(calculator);
        server.start();
        System.out.println("Gateway Server Started. Python can now connect.");
        // 保持程序运行
        try {
            Thread.sleep(Long.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Python 端 (call_java.py)

from py4j.java_gateway import JavaGateway
# 连接到 Java 网关服务器
gateway = JavaGateway()
# 获取 Java 端暴露的 Calculator 对象 (就是上面 new Calculator() 的实例)
java_calculator = gateway.entry_point
# 像调用 Python 方法一样调用 Java 方法
result_square = java_calculator.square(10)
print(f"Result from Java square(10): {result_square}")
greeting_message = java_calculator.greet("Python World")
print(f"Greeting from Java: {greeting_message}")

如何运行

  1. 将 Py4J 的 JAR 包(py4j-0.10.9.7.jar)添加到 Java 项目的 classpath 中。
  2. 编译并运行 JavaGatewayServerApp.java
  3. 在另一个终端中,运行 python call_java.py

优点

  • 双向调用:Java 和 Python 可以互相调用对方的对象和方法,非常灵活。
  • 高性能:基于 Socket,但比手动处理简单得多,避免了进程创建开销。
  • 类型自动转换:基本数据类型(int, String, list等)会自动在 Java 和 Python 之间转换。
  • 活跃的社区:持续更新,支持 Python 3。

缺点

  • 架构稍复杂:需要启动一个网关服务,需要管理 Java 和 Python 两端的代码。
  • 依赖:需要在 Java 和 Python 两端都安装 Py4J 库。

使用 GraalVM Polyglot (新兴且强大)

GraalVM 是一个高性能的 JDK,它最大的特点之一就是支持 Polyglot,即在一个 JVM 中运行多种语言(包括 JavaScript, Python, R, Ruby 等)。

工作原理

GraalVM 内置了一个 Python 解释器,你可以在 Java 代码中直接创建一个 "Python 上下文",并执行 Python 代码字符串或调用 Python 脚本。

示例

环境准备 你需要下载 GraalVM,并安装 Python 语言包。

# 使用 gu 工具安装
gu install python

Java 代码

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
public class GraalPythonCaller {
    public static void main(String[] args) {
        // 创建一个隔离的上下文
        try (Context context = Context.create("python")) {
            // --- 方式一:执行 Python 字符串 ---
            System.out.println("--- Executing Python code from string ---");
            Value result = context.eval("python", "x = 10; y = 20; print('Calculating in Python...'); x + y");
            System.out.println("Result from Python code: " + result.asInt());
            // --- 方式二:执行 .py 文件 ---
            System.out.println("\n--- Executing Python file ---");
            Source source = Source.newBuilder("python", "C:/path/to/your/script/hello.py").build();
            context.eval(source);
            // --- 方式三:调用 Python 函数并传递参数 ---
            System.out.println("\n--- Calling Python function with arguments ---");
            // 假设你的 hello.py 中有一个 def add(a, b): return a + b
            Value addFunction = context.eval("python", "add = lambda a, b: a + b");
            Value sum = addFunction.execute(15, 25);
            System.out.println("Sum from Python lambda: " + sum.asInt());
            // --- 方式四:在 Java 和 Python 之间传递复杂数据 ---
            System.out.println("\n--- Passing complex data structures ---");
            Value pyList = context.eval("python", "[1, 2, 'hello', True]");
            System.out.println("Python list in Java: " + pyList); // 会打印出列表的表示
            System.out.println("Second element: " + pyList.getArrayElement(1).asInt());
            Value javaList = context.eval("python", "java.util.Arrays.asList(100, 200, 300)");
            System.out.println("Java list passed to Python: " + javaList);
        }
    }
}

优点

  • 极致性能:Python 代码直接在 JVM 上运行,没有进程间通信的开销,性能非常高。
  • 无缝集成:Java 和 Python 之间的数据交换非常自然,支持复杂数据结构。
  • 单一进程:无需管理多个进程,架构更简单。
  • 未来趋势:是 Oracle 主推的多语言技术栈。

缺点

  • 环境配置复杂:需要安装和配置 GraalVM,以及其语言组件,对新手不友好。
  • 兼容性:不是所有的 Python C 扩展都能在 GraalVM 的 Python 环境中完美运行。
  • 项目相对年轻:虽然发展迅速,但生态和工具链相比传统 JVM 还在建设中。

总结与选择建议

方法 性能 易用性 数据传递 适用场景
Runtime.exec() 简单 复杂(需手动序列化) 一次性、简单的脚本调用,快速原型验证。
Socket IPC 中等 高效(需自定义协议) Java 和 Python 服务分离,大数据量,低频到中频调用。
Py4J 较简单 自动类型转换 强烈推荐,需要高频、双向、复杂对象交互的场景,如数据分析、机器学习集成。
GraalVM Polyglot 极高 简单(概念上) 无缝 未来趋势,对性能要求极致,且能接受复杂环境配置的场景。

如何选择?

  • 新手或一次性任务:从 Runtime.exec()ProcessBuilder 开始,最简单。
  • 构建生产级应用,需要高性能和双向交互首选 Py4J,它在易用性、性能和功能之间取得了最好的平衡。
  • 追求极致性能,且愿意投入精力进行环境配置研究 GraalVM Polyglot,它是技术的前沿,代表了未来的方向。
  • 避免使用 Jython,除非你被困在 Python 2 的世界里。
分享:
扫描分享到社交APP
上一篇
下一篇