位元詩人 [Groovy] 程式設計教學:基本概念

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

本文假定讀者沒寫過 Groovy 程式或是只寫過少量 Groovy 程式。會從頭開始講解 Groovy 程式的基本概念。

Groovy 程式碼的檔名和副檔名

Groovy 的副檔名為 .groovy 。這是因為 Groovy 誕生時,副檔名已經不受限於三至四個英文字母了,所以直接用 Groovy 的名稱做為副檔名。

Groovy 在檔名上沒有特別限制,只要符合檔案系統的規範即可。原本 Java 程式碼需要將公開類別名稱和檔案名稱連動,這項限制在 Groovy 已經取消了。

撰寫第一個 Groovy 程式

Groovy 不需要寫主函式。直接將程式碼寫在 Groovy 命令稿的頂層即可。以下是 Groovy 版本的 Hello World 程式:

println "Hello World"

如果剛從 Java 轉換過來,不習慣這種過於簡略的寫法。也可以把 Groovy 寫得很 Java:

public class MainProgram
{
    public static void main(String[] args) {
        println "Hello World"
    }
}

撰寫 Groovy 程式的好處,就是可以先用仿 Java 語法來寫,然後慢慢把冗餘的語法重構掉。之後就會越寫越簡潔,有倒吃甘蔗的感覺。

執行 Groovy 程式

原本 Java 要先編譯再執行,Groovy 則直接執行命令稿。參考以下虛擬指令:

$ groovy path/to/source.groovy

當然,在背後 Groovy 還是會將 Groovy 命令稿轉成等效的 Java 位元碼後委由 Java 虛擬機器執行。使用 Groovy 的好處在於節省程式設計者的時間,程式效能不是最重要的考量。

(選擇性) 將 Groovy 命令稿包成命令列工具

日後有機會撰寫 Groovy 命令列工具時,可以用系統附帶的命令列環境腳本把該命令列工具包起來。表面上,在不同系統上使用不同指令,但背後都是執行同一個 Groovy 命令稿,而 Groovy 是基於跨平台的 Java 平台,這樣的命令列工具在不同系統上皆可使用。

這個樣板專案位於這裡。在授權條款下,歡迎自由取用。

以下是該樣板專案的 Batch 命令稿:

@echo off
rem Wrapper for Groovy CLI Boilerplate

rem Check whether Groovy is available on the system.
where groovy >nul || (
    echo No Groovy on the system >&2
    exit /b 1
)

rem Get the path of current batch script.
set cwd=%~dp0
rem Get the directory to our Groovy script.
set libexec=%cwd%..\libexec

rem Run the Groovy script and pass command-line arguments to it.
groovy %libexec%\cli.groovy %*

這個 Batch 命令稿會先檢查系統上是否有 Groovy。確認系統上 Groovy 存在後,用 Groovy 執行目標 Groovy 命令稿。

以下則是該樣板專案的 POSIX shell 命令稿:

#!/bin/sh
#
# Wrapper for Groovy CLI Boilerplate

# Check whether Groovy is available on the system.
if ! command -v groovy 2>&1 >/dev/null;
then
    echo "No Groovy on the system" >&2;
    exit 1;
fi

# Get the path of current shell script.
cwd=$(dirname $0);
# Get the directory of our Groovy script.
libexec="${cwd}/../libexec";

# Run the Groovy script and pass command-line arguments to it.
groovy "${libexec}/cli.groovy" "$@";

這個 POSIX shell 命令稿和前述 Batch 命令稿做相同的事,只是用不同語法。

若小伙伴不想學 Batch Script 和 POSIX Shell Script,直接拿這個樣板專案去改就好。

在 Windows 上將 shell 命令稿改名會讓該命令稿失去可執行權限。解決的方式是將專案拷貝到 Unix 上,重新將該命令稿加上可執行權限,然後將修改過的專案重新推送 (push) 到遠端站台。

大小寫敏感性 (Case Sensitivity)

Groovy 承襲 Java 的特性,同樣具有大小寫敏感性。

空白 (Space)、縮進 (Indentation)、換行 (Newline)

Groovy 需要用至少一個空白來區隔保留字、識別字、函式等語法元素。除此之外,Groovy 對空白、縮進、換行等排版元素相對自由,程式設計者可根據自己的習慣來排版 Groovy 程式碼。

按照社群慣例,會用排版 Java 程式碼的方式來排版 Groovy 程式碼,因為後者的語法是基於前者的。

註解 (Comment)

Groovy 有三種註解可用:

  • 單行註解 (single-line comment):在 // 後的同一行文字視為註解。該特性承襲 C++
  • 多行註解 (multiline comment):在一對 /**/ 包住的文字視為註解。該特性承襲 C
  • Groovy 文件註解 (Groovydoc comment):在一對 /***/ 包住的文字視為文字註解。使用方式同 Javadoc

一般來說,前兩種註解就夠用了。第三種註解是寫 Groovy 函式庫時才會用到。

引入函式庫

使用 import 來引入函式庫。使用 Java 標準函式庫和 Groovy 標準函式庫不需要引入額外檔案,可隨需求直接引入。像是以下的例子:

import groovy.json.JsonSlurper

使用第三方函式庫則需引入額外檔案。Groovy 使用 @Grab 來自動下載所需的函式庫。像是以下例子:

@Grab(group='org.apache.poi', module='poi', version='4.1.2')
@Grab(group='org.apache.poi', module='poi-ooxml', version='4.1.2')

讀者可能會覺得 @Grab 的語法不易撰寫。實際上不需要背誦 @Grab 的語法。Java 社群函式庫的集中地 Maven Repository 會列出抓取各個函式庫時使用的 @Grab 語法。

專門為 Groovy 撰寫的函式庫甚少。實務上,多直接使用 Java 函式庫,但是可以用 Groovy 來簡化語法。可直接用 Java 函式庫是 Groovy 的優勢之一。

主函式 (Main Function)

Groovy 不需要撰寫主函式,直接把主程式的程式碼寫在 Groovy 命令稿的頂層即可。把 Groovy 視為 PerlPythonRuby 這類命令稿語言即可輕易理解。

撰寫敘述 (Statement)

如同 Java,Groovy 是基於敘述的語言。但 Groovy 不需要在敘述尾端加上 ;。這項特性和 Python 或 Ruby 雷同。

程式的離開狀態 (Exit Status)

沒有明確寫出來的話,預設的程式離開狀態是 0,表示程式成功執行。

如果想要在程式中某個點明確地結束程式,可以用 System.exit() 函式。像是以下例子:

System.exit(0)

按照程式設計的慣例,0 表示程式成功執行。1 表示程式異常結束。

有些程式會刻意使用不同的回傳值來表達不同的狀態。但回傳值的數字在不同程式間沒有一致性,需要在程式使用手冊中明確記載每個回傳值的意義。

Groovy 程式的構造

綜合前述內容,我們來看一個 Groovy 程式的構造。由於我們才剛學 Groovy,重點不是馬上看懂每一行程式碼,而是理解 Groovy 程式的構造:

/* Create a SQLite Database with Groovy

   Copyright (c) 2022 OpenTechTutorials. Licensed under MIT */

/* The namespace of a Groovy program. */
package com.example.MainProgram

/* Fetch third-party libraries with `@Grab`. */
@Grab(group='org.xerial', module='sqlite-jdbc', version='3.36.0.3')
@GrabConfig(systemClassLoader=true)

/* Import third-party libraries. */
import java.sql.DriverManager

/* ========================== The main program ========================== */
/* Load a SQLite database. */
def conn
try {
    conn = DriverManager.getConnection("jdbc:sqlite:database.sqlite")
}
catch (e) {
    /* The program fails. */
    System.exit(1)
}

/* Close the SQLite database. */
conn.close()

由於註解不影響程式的執行,可以在 Groovy 命令稿的最上方寫些簡單的註解,讓閱讀程式碼的人理解命令稿。

Groovy 程式碼最上方為 package 敘述。這個敘述是用來建立獨立的命名空間 (namespace)。使用命名空間區隔程式碼對函式庫及大型程式很重要,但對單一的 Groovy 命令稿沒差。若不想寫的話,可以省略該敘述。

若有用到第三方函式庫,接下來會寫上 @Grab 敘述。Groovy 會根據該敘述自動抓取相對應的 Java 套件,執行程式時即可讀入該套件。

除了內定的函式庫外,若有用到函式庫,要用 import 敘述引入該函式庫。

由於 Groovy 程式不需要主函式,寫完前述指令後,自動進入主程式的部分。本文的目的是理解 Groovy 的基本概念,故不逐一說明每一行指令的意義。

Groovy 的 REPL 環境

REPL 環境是指 Read-Eval-Print-Loop,就是即時回饋的互動式程式設計環境。這類環境主要是用來測試簡短的程式碼。本節說明 Groovy 的 REPL 環境。

Groovy Shell

有時候,我們只是想嘗試一下 Groovy 語法,但不想開編輯器,這時候,可以用 groovysh 指令進入 Groovy Shell。這是一個互動式的 REPL (Read-Evaluate-Print-Loop) 環境,在一些現代的主流程式語言中很流行,像是 Python 或 Ruby 都有這種模式。

在終端機輸入 groovysh 進入 Groovy Shell:

$ groovysh
Groovy Shell (3.0.9, JVM: 17.0.1)
Type ':help' or ':h' for help.
-------------------------------------------------------------------------------
groovy:000>

在大於 > 符號後即可輸入 Groovy 程式碼,前綴冒號的指令則是 Groovy Shell 特有的指令,像是輸入 :exit 或是 :quit 即可離開 Groovy Shello。

我們看一下以下兩行 Groovy Shell 命令:

groovy:000> "Hello World"
===> Hello World
groovy:000> println "Hello World"
Hello World
===> null
groovy:000>

在第一個指令中,我們僅輸入 "Hello World" 字串,此環境就回傳 "Hello World" 字串;但在第二個指令中,系統先印出字串,卻回傳 null (空值),因為在 Groovy 程式中,其實 println 指令沒有回傳值,故回傳 null

我們也可以把 Groovy 當成簡易型計算機:

groovy:000> (1 + 2) * (4 - 2)
===> 6

當然,Groovy Shell 主要只是用來試語法,無法取代真正的開發環境。

Groovy Console

如果覺得前述的 Groovy Shell 是終端機軟體,用得不習慣,也可以用圖形介面的 Groovy Console,這個軟體可以當成簡易的 Groovy 編輯器,只是要嘗試一下語法可以使用。

Groovy web console

Groovy web console 是一個網站,可以線上練習 Groovy 語法,偶爾要試語法時,也是另一個不錯的選擇,讀者可自行嘗試。

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。