Java調(diào)用Python腳本實(shí)現(xiàn)HelloWorld的示例詳解
作為程序員,我們經(jīng)常會(huì)遇到需要在Java項(xiàng)目中調(diào)用Python腳本的場(chǎng)景??赡苁菫榱藦?fù)用現(xiàn)成的Python工具庫(kù),也可能是需要利用Python在數(shù)據(jù)處理上的優(yōu)勢(shì)。本文不聊太多理論,直接上手三種實(shí)用的調(diào)用方式,從基礎(chǔ)到進(jìn)階,一步步實(shí)現(xiàn)Java與Python的"HelloWorld"交互。
一、環(huán)境準(zhǔn)備
在開始之前,確保你的開發(fā)環(huán)境滿足以下條件:
- Java環(huán)境:JDK 8+(推薦11),配置好
JAVA_HOME
- Python環(huán)境:Python 3.6+,配置好
PATH
(命令行輸入python --version
能正常顯示版本) - 開發(fā)工具:任意IDE,文本編輯器(用于寫Python腳本)
驗(yàn)證環(huán)境的小技巧:
# 檢查Java java -version # 檢查Python python --version # 或python3 --version(根據(jù)系統(tǒng)配置)
如果Python命令無(wú)法識(shí)別,大概率是沒(méi)配置環(huán)境變量。Windows用戶可以在"設(shè)置-系統(tǒng)-關(guān)于-高級(jí)系統(tǒng)設(shè)置-環(huán)境變量"中添加Python安裝路徑;Linux/Mac用戶可以在~/.bashrc
或~/.zshrc
中添加export PATH=$PATH:/usr/local/python3/bin
(替換為實(shí)際路徑)。
二、基礎(chǔ)調(diào)用:使用 Runtime.exec()
這是最直接的調(diào)用方式,通過(guò)Java的Runtime
類啟動(dòng)Python進(jìn)程執(zhí)行腳本。適合簡(jiǎn)單場(chǎng)景,無(wú)需復(fù)雜交互。
2.1 實(shí)現(xiàn)步驟
步驟1:編寫Python腳本
創(chuàng)建hello.py
,放在Java項(xiàng)目的根目錄(或指定絕對(duì)路徑):
# 接收J(rèn)ava傳遞的參數(shù) import sys if __name__ == "__main__": # 獲取Java傳入的參數(shù)(第0個(gè)參數(shù)是腳本名) name = sys.argv[1] if len(sys.argv) > 1 else "World" # 輸出結(jié)果(會(huì)被Java捕獲) print(f"Hello, {name}! From Python")
步驟2:編寫Java調(diào)用代碼
創(chuàng)建JavaCallPythonByRuntime.java
:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class JavaCallPythonByRuntime { public static void main(String[] args) { // 1. 定義Python腳本路徑和參數(shù) String pythonScriptPath = "hello.py"; String param = "Java"; // 要傳遞給Python的參數(shù) // 2. 構(gòu)建命令數(shù)組(推薦用數(shù)組形式,避免空格問(wèn)題) String[] cmd = new String[]{"python", pythonScriptPath, param}; try { // 3. 啟動(dòng)Python進(jìn)程 Process process = Runtime.getRuntime().exec(cmd); // 4. 讀取Python的輸出(必須讀取,否則可能導(dǎo)致進(jìn)程阻塞) InputStream inputStream = process.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream, "UTF-8") // 指定編碼,避免中文亂碼 ); String line; while ((line = reader.readLine()) != null) { System.out.println("Python輸出:" + line); } // 5. 等待進(jìn)程執(zhí)行完成并獲取退出碼(0表示成功) int exitCode = process.waitFor(); System.out.println("進(jìn)程退出碼:" + exitCode); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }
2.2 代碼解析
- 命令數(shù)組:用
String[]
而不是單字符串,避免路徑或參數(shù)包含空格時(shí)解析錯(cuò)誤(比如腳本路徑是D:\my script\hello.py
)。 - 輸入流處理:Python的
print
輸出會(huì)寫入進(jìn)程的輸入流,Java必須讀取這些內(nèi)容,否則緩沖區(qū)滿了會(huì)導(dǎo)致進(jìn)程卡住。 - 編碼設(shè)置:
InputStreamReader
指定UTF-8
,解決Windows系統(tǒng)下默認(rèn)GBK編碼導(dǎo)致的中文亂碼問(wèn)題。 - 退出碼:
process.waitFor()
返回的退出碼能幫我們判斷腳本是否正常執(zhí)行(非0通常表示出錯(cuò))。
三、進(jìn)階調(diào)用:使用 ProcessBuilder
ProcessBuilder
是JDK 5引入的類,比Runtime.exec()
更靈活,支持設(shè)置工作目錄、環(huán)境變量等,適合復(fù)雜場(chǎng)景。
3.1 實(shí)現(xiàn)步驟
步驟1:復(fù)用Python腳本
繼續(xù)使用前面的hello.py
,稍作修改支持從環(huán)境變量讀取配置:
import sys import os if __name__ == "__main__": name = sys.argv[1] if len(sys.argv) > 1 else "World" # 讀取Java設(shè)置的環(huán)境變量 app_name = os.getenv("APP_NAME", "UnknownApp") print(f"[{app_name}] Hello, {name}! From Python")
步驟2:編寫Java代碼
創(chuàng)建JavaCallPythonByProcessBuilder.java
:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Map; public class JavaCallPythonByProcessBuilder { public static void main(String[] args) { try { // 1. 創(chuàng)建ProcessBuilder,指定命令和參數(shù) ProcessBuilder pb = new ProcessBuilder( "python", "hello.py", "JavaDeveloper" ); // 2. 設(shè)置工作目錄(腳本所在目錄,不設(shè)置則默認(rèn)當(dāng)前目錄) pb.directory(new java.io.File("./scripts")); // 假設(shè)腳本放在scripts子目錄 // 3. 設(shè)置環(huán)境變量(給Python腳本傳遞配置) Map<String, String> env = pb.environment(); env.put("APP_NAME", "JavaCallPythonDemo"); // 4. 重定向錯(cuò)誤流到輸入流(方便統(tǒng)一處理輸出和錯(cuò)誤) pb.redirectErrorStream(true); // 5. 啟動(dòng)進(jìn)程 Process process = pb.start(); // 6. 讀取輸出 try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream(), "UTF-8") )) { String line; while ((line = reader.readLine()) != null) { System.out.println("Python輸出:" + line); } } // 7. 等待進(jìn)程完成 int exitCode = process.waitFor(); System.out.println("進(jìn)程退出碼:" + exitCode); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }
3.2 代碼解析
- 工作目錄:
pb.directory()
指定腳本所在目錄,避免路徑混亂。比如腳本放在./scripts
,就不用寫全路徑了。 - 環(huán)境變量:通過(guò)
pb.environment()
設(shè)置的變量,Python可以用os.getenv()
獲取,適合傳遞配置信息(如API密鑰、環(huán)境標(biāo)識(shí))。 - 錯(cuò)誤流重定向:
redirectErrorStream(true)
將錯(cuò)誤信息合并到輸入流,不用單獨(dú)處理getErrorStream()
,簡(jiǎn)化代碼。 - 資源自動(dòng)關(guān)閉:
try-with-resources
語(yǔ)法確保BufferedReader
自動(dòng)關(guān)閉,避免資源泄漏。
四、服務(wù)化調(diào)用:HTTP接口通信
當(dāng)Java和Python需要頻繁交互,或需要跨服務(wù)器調(diào)用時(shí),將Python腳本封裝成HTTP服務(wù)是更優(yōu)的方案。這里用Flask搭建簡(jiǎn)單接口。
4.1 實(shí)現(xiàn)步驟
步驟1:搭建Python HTTP服務(wù)
首先安裝Flask:
pip install flask
創(chuàng)建hello_service.py
:
from flask import Flask, request app = Flask(__name__) @app.route('/hello', methods=['GET']) def hello(): # 從請(qǐng)求參數(shù)獲取name name = request.args.get('name', 'World') return f"Hello, {name}! From Python Service" if __name__ == '__main__': # 啟動(dòng)服務(wù),允許外部訪問(wèn) app.run(host='0.0.0.0', port=5000, debug=True)
步驟2:編寫Java HTTP客戶端
創(chuàng)建JavaCallPythonByHttp.java
,使用JDK自帶的HttpClient
(JDK 11+):
import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; public class JavaCallPythonByHttp { public static void main(String[] args) { // 1. 創(chuàng)建HttpClient HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5)) .build(); // 2. 構(gòu)建請(qǐng)求(Python服務(wù)地址) String name = "JavaHttpClient"; HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:5000/hello?name=" + name)) .timeout(Duration.ofSeconds(3)) .GET() .build(); try { // 3. 發(fā)送請(qǐng)求并獲取響應(yīng) HttpResponse<String> response = client.send( request, HttpResponse.BodyHandlers.ofString() ); // 4. 處理響應(yīng) if (response.statusCode() == 200) { System.out.println("Python服務(wù)響應(yīng):" + response.body()); } else { System.err.println("請(qǐng)求失敗,狀態(tài)碼:" + response.statusCode()); } } catch (Exception e) { e.printStackTrace(); } } }
4.2 代碼解析
- Python服務(wù):用Flask創(chuàng)建
/hello
接口,通過(guò)request.args
獲取Java傳遞的參數(shù),返回字符串。debug=True
方便開發(fā)時(shí)自動(dòng)重啟。 - Java客戶端:JDK 11的
HttpClient
比傳統(tǒng)的HttpURLConnection
更簡(jiǎn)潔,支持異步調(diào)用(sendAsync
),這里用同步send
更直觀。 - 跨機(jī)器調(diào)用:只要Python服務(wù)的
host
設(shè)為0.0.0.0
,并開放5000端口,Java可以通過(guò)服務(wù)器IP訪問(wèn)(如http://192.168.1.100:5000/hello
)。
五、三種方法對(duì)比與選擇建議
調(diào)用方式 | 優(yōu)點(diǎn) | 缺點(diǎn) | 適用場(chǎng)景 |
---|---|---|---|
Runtime.exec() | 簡(jiǎn)單直接,無(wú)需額外依賴 | 靈活性差,不便于設(shè)置環(huán)境和工作目錄 | 簡(jiǎn)單腳本,一次性調(diào)用 |
ProcessBuilder | 支持環(huán)境變量、工作目錄,錯(cuò)誤流合并 | 代碼稍復(fù)雜,仍需處理進(jìn)程通信 | 復(fù)雜腳本,需要傳遞配置信息 |
HTTP接口 | 松耦合,支持跨機(jī)器,可異步 | 需要維護(hù)HTTP服務(wù),有網(wǎng)絡(luò)開銷 | 頻繁交互,分布式系統(tǒng),跨語(yǔ)言調(diào)用 |
實(shí)際開發(fā)中:
- 臨時(shí)腳本調(diào)用用
ProcessBuilder
; - 長(zhǎng)期維護(hù)的功能推薦HTTP服務(wù)化,便于獨(dú)立部署和升級(jí);
- 避免在高并發(fā)場(chǎng)景用進(jìn)程調(diào)用(頻繁創(chuàng)建銷毀進(jìn)程開銷大)。
六、避坑指南
1.路徑中的空格:Windows路徑如果有空格(如Program Files
),用ProcessBuilder
時(shí)會(huì)自動(dòng)處理,但用Runtime.exec()
單字符串命令可能出錯(cuò),推薦始終用數(shù)組形式傳參。
2.Python輸出緩沖區(qū):如果Python腳本輸出大量?jī)?nèi)容,會(huì)先存到緩沖區(qū),Java讀取不及時(shí)會(huì)導(dǎo)致腳本卡住。解決方法:
- Python中手動(dòng)刷新緩沖區(qū):
print(..., flush=True)
- Java中用線程異步讀取輸出
3.版本兼容:確保Java調(diào)用的Python版本和開發(fā)時(shí)一致(比如同時(shí)用Python 3.9),避免語(yǔ)法不兼容問(wèn)題。
4.異常捕獲:生產(chǎn)環(huán)境中必須捕獲所有可能的異常(IOException
、InterruptedException
等),并記錄日志,方便排查問(wèn)題。
總結(jié)
本文介紹了三種Java調(diào)用Python腳本實(shí)現(xiàn)HelloWorld的方法,從簡(jiǎn)單的進(jìn)程調(diào)用到服務(wù)化通信,覆蓋了不同場(chǎng)景的需求。核心是根據(jù)項(xiàng)目實(shí)際情況選擇合適的方案:簡(jiǎn)單場(chǎng)景用ProcessBuilder
,分布式場(chǎng)景用HTTP接口。
實(shí)際開發(fā)中,建議先做小范圍測(cè)試,重點(diǎn)關(guān)注參數(shù)傳遞、異常處理和性能表現(xiàn)。只要把這些細(xì)節(jié)處理好,Java和Python的協(xié)作會(huì)非常順暢。
到此這篇關(guān)于Java調(diào)用Python腳本實(shí)現(xiàn)HelloWorld的示例詳解的文章就介紹到這了,更多相關(guān)Java調(diào)用Python腳本內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)XML格式與JSON格式互相轉(zhuǎn)換的方法
這篇文章主要介紹了Java實(shí)現(xiàn)XML格式與JSON格式互相轉(zhuǎn)換的方法,方法通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),選擇使用哪種格式通常取決于項(xiàng)目的需求和上下文,所以格式轉(zhuǎn)換就成了我們必備的技能,具體實(shí)現(xiàn)代碼跟隨小編一起看看吧2023-10-10JavaWeb搭建網(wǎng)上圖書商城畢業(yè)設(shè)計(jì)
這篇文章主要介紹了JavaWeb搭建網(wǎng)上圖書商城框架,特別適合正在為網(wǎng)上商城畢業(yè)設(shè)計(jì)煩惱的同學(xué),需要的朋友可以參考下2015-11-11Eclipse將Maven項(xiàng)目打成jar包的方法
這篇文章主要介紹了Eclipse將Maven項(xiàng)目打成jar包的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2007-09-09Java?將PDF轉(zhuǎn)為HTML時(shí)保存到流的方法和步驟
本文介紹如何通過(guò)Java后端程序代碼將PDF文件轉(zhuǎn)為HTML,并將轉(zhuǎn)換后的HTML文件保存到流,下面是實(shí)現(xiàn)轉(zhuǎn)換的方法和步驟,感興趣的朋友一起看看吧2022-01-01springboot項(xiàng)目啟動(dòng)的時(shí)候參數(shù)無(wú)效的解決
這篇文章主要介紹了springboot項(xiàng)目啟動(dòng)的時(shí)候參數(shù)無(wú)效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09java.lang.IllegalStateException異常原因和解決辦法
這篇文章主要給大家介紹了關(guān)于java.lang.IllegalStateException異常原因和解決辦法,IllegalStateException是Java標(biāo)準(zhǔn)庫(kù)中的一個(gè)異常類,通常表示在不合適或無(wú)效的情況下執(zhí)行了某個(gè)方法或操作,需要的朋友可以參考下2023-07-07永中文檔在線轉(zhuǎn)換服務(wù)Swagger調(diào)用說(shuō)明
這篇文章主要為大家介紹了永中文檔在線轉(zhuǎn)換服務(wù)Swagger調(diào)用說(shuō)明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06解決若依微服務(wù)框架啟動(dòng)報(bào)錯(cuò)的問(wèn)題
Invalid bound statement錯(cuò)誤通常由MyBatis映射文件未正確加載或Nacos配置未讀取導(dǎo)致,需檢查XML的namespace與方法ID是否匹配,確認(rèn)資源目錄配置,確保bootstrap.yml中Nacos配置路徑正確加載2025-08-08