LoginSignup
0
0

More than 1 year has passed since last update.

Gradleを理解するためのGroovy学習

Last updated at Posted at 2023-04-22

DSL(Domain Specific Language)

Gradleで使用するスクリプトはDSL(Domain Specific Language)というGroovyを基礎にした、Gradle専用の言語、文法が使用される。
Groovyで記述されていると思ってGradleの学習を進めていくと、???となる時がある。
GroovyはJavaと同様にJVMによって実行される。

Groovyの実行

$ cd ファイルを配置したディレクトリのパス
$ groovy ファイル名

ファイルのあるフォルダまで移動後、拡張子を含むファイル名をコマンドの引数に指定することで、スクリプトとして実行できる。

$ groovysh

コマンドライン上で対話型のスクリプトを実行できる。

$ groovyConsole

Groovy実行用のシェルアプリが起動する。

Groovyの型宣言

型を使用した変数宣言
String a = 'hello';
String b = "good bye" // Groovyでは ; は省略できる
String c = "${a} world"
String d = "$b world"
型を指定しない変数宣言
def e = 'hello'

defを使用した場合は、Object型になる。
このように、型を宣言するかしないかを選択できる型づけをオプショナルな型づけという。
ただし、明示的に型を宣言して、異なるデータ型が代入された場合でも、コンパイルエラーは発生しない。
型エラーは実行時に発生する。
また、メソッドのオーバーライド時には、明示的に型を宣言する必要がある。

メソッドの戻り値、引数の型
String hello(String name) {
    return "hello $name"
}

def bye(def name) {
    "bye $name" // Groovyではreturnが省略できる
}

returnの省略には条件があるので、基本的には省略しないほうが良いらしい。

Groovyのメソッド

()の省略
println('hello')
println 'hello' // Groovyでは()が省略できる
println hello('taro') // 省略できない場合もある
String greet = hello('taro') // 省略できない場合もある

void helloWorld(){
    println 'hello world'
}
helloWorld() // 引数がないメソッドでも省略できない

Gradleでも省略できない()は度々見かける。

build.gradle
repositories {
    mavenCentral()
}

メソッドにMap型の仮引数を用意することで、呼び出し時の実引数に対して、名前付き引数を使用できる。
名前付き引数はコンストラクタに対しても使用できる。

名前付き引数
void hello2(Map params) {
    println "I'm ${params.name}. $params.age years old."
}
hello2(name: 'taro', age: 10) // I'm taro. 10 years old.
hello2 name: 'hanako', age: 5 // I'm hanako. 5 years old.
名前付き引数(コンストラクタ)
class Person {
    int age
    String name
}
Person taro = new Person(age: 10, name: 'taro')

Gradleでは、次のように使用されている。

build.gradle
apply plugin: 'java'

メソッドの引数のデフォルト値を設定しておくと、実引数が渡されなくてもエラーが起きない。

引数のデフォルト値
void hello3(String name = 'world') {
    println "hello $name"
}
hello3()

引数のdefキーワードも省略することができる。defを省略するとObject型として扱われる。

defの省略
void hello4(name){
    println "hello $name"
}

Groovyの変数

ローカル変数
int x = 10
def y = 20
バインディング変数
z = 30

変数の宣言をせずに使用できる変数をバインディング変数と言う。型を指定する、もしくはdefを使用して宣言した変数はローカル変数と言う。
バインディング変数は常にObject型として扱われる。
ローカル変数はメソッドからアクセスすることができない。

ローカル変数とバインディング変数
def localVariable = 40 // ローカル変数
bindingVariable = 50 // バインディング変数
void compareScope() {
    println localVariable // エラー
    println bindingVariable // 実行できる
}
compareScope()

Groovyはスクリプトとして実行することができる言語であり、クラスを宣言しなくてもコードのトップレベルに処理を記述できる。ただし、Groovyをスクリプトとして実行させる場合、コンパイル時にファイル名と同じ名前のクラスがコンパイラによって作成されている。

Sample.groovyをコンパイルした後、逆アセンブルを行う
$ groovyc Sample.groovy
$ javap Sample
Compiled from "Sample.groovy"
public class Sample extends groovy.lang.Script {
  public static transient boolean __$stMC;
  public sample();
  public sample(groovy.lang.Binding);
  public static void main(java.lang.String...);
  public java.lang.Object run();
  protected groovy.lang.MetaClass $getStaticMetaClass();
  public static java.lang.invoke.MethodHandles$Lookup $getLookup();
}

defで宣言した変数はコンパイラによって作成されるメソッドのローカル変数になるため、別のメソッドからアクセスができないものとなる。
ただし、ローカル変数はクロージャーからはアクセスが可能。

ローカル変数とクロージャー
def localVariable2 = 'hello'
callback = {
    println localVariable2
}

Groovyのクロージャー

宣言された場所のコンテキストを保持した実行可能な関数オブジェクト。
{}を使用して宣言する。
メソッドの引数として渡したり、動的な関数を生成できる。

クロージャー
Closure saySomething = { word -> println word }
saySomething 'Hi!'
saySomething.call('Thank you!')
コンテキストを保持したクロージャー
Closure incrementFactory() {
    int i = 0
    return { println i++ }
}
Closure increment = incrementFactory()
increment() // 0
increment() // 1
increment() // 2
クロージャーを利用した動的な関数
Closure greetFactory(greet) {
    return { name -> println "$greet $name"}
}
Closure goodMorning = greetFactory 'Good mornig'
goodMorning 'taro'
Closure goodAfternoon = greetFactory 'Good Afternoon'
goodAfternoon 'taro'

クロージャーを引数に受け取るメソッドを実行する際、メソッドの最後の引数がクロージャーであれば、引数に渡すクロージャーを()の外に記述することができる。
GradleをGroovyの文法から理解しようとした時に、非常に重要な記法

クロージャーを引数にとるメソッド
void method(Closure closure) {
    println 'The closure will be executed.'
    closure()
    println 'The closure has been executed.'
}

method({ println 'hi' })
method() { println 'hi' } // ()の外に出せる
method { println 'hi' } // ()を省略する

Groovyのオブジェクトのメンバへのアクセス方法

フィールド
class Product {
    String name
    int price
}
Product pen = new Product(name: 'pencile', price: 100)
println pen.name // pencile
println pen.'name' // pencile
println pen."name" // pencile
String fieldName = 'name'
println pen."$fieldName" // pencile
メソッド
class Human {
    void sayHello() {
        println 'hello'
    }
}
Human man = new Human()
man.sayHello() // hello
man.'sayHello'() // hello
String say = 'say'
String hello = 'Hello'
man."$say$hello"() // hello

コレクション

リスト
List list = [1, 2, 3, 4]
println list[0] // 1
マップ
String key2 = 'key2'
Map map1 = [('key1'): 10, (key2):20, ('key3'): 30] // ()内では変数や式を使用できる
println map1['key1'] // 10

Map map2 = [key1: 40, key2:50, key3:60] // キーが文字列の場合、()が省略できる
println map2['key1'] // 40
println map2.key2 // 50 オブジェクトのフィールドのようにアクセスできる
String key = 'key3'
println map2[key] // 60
println map2."$key" // 60

Gradleに登場するGroovyの演算子

<<

メソッドleftShift()は演算子<<で表現することができる。
コレクションに対して、要素を追加する働きがある。

<<
List words = ['hi', 'hey']
words.leftShift('hi there')
words << 'hello'
println words // [hi, hey, hi there, hello]

Gradleではタスクの定義時に目にする。
Gradle5.0で廃止になったらしい。

build.gradle
task hi << {
    println 'hi'
}

*

演算子*は、リストを展開する。
別のリストのリストリテラル[]内、メソッドの引数などに利用できる。

*
List parts = [0, 1, 2, 3, 4]
List whole = [*parts, 5, 6, 7, 8, 9]
println whole // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

*:

演算子*:は、マップを展開する。
別のマップのマップリテラル[:]内で利用できる。

*:
Map m = [a: 10, b: 20]
Map n = [c: 30, d: 40]
Map mAndN = [*:m, *:n]
println mAndN // [a:10, b:20, c:30, d:40]

*.

演算子*.は、リストの各要素に対して指定した処理を実行し、新たなリストを返す。

*.
List fruits = ['apple', 'orange']
println fruits*.toUpperCase() // [APPLE, ORANGE]

.&

演算子.&は、Closureクラスのメソッドを、Closure型ではないインスタンスメソッドが使用できるようにすることができる。例えばClosure#curry()を使用する際に利用する。

Closure#curry()

  • クラスClusureにはcurry()メソッドが存在する
  • curry()は引数を束縛した状態で、クロージャを生成する(戻り値として返却する)メソッド
.&
class Member {
    void introduce(String name, int age) {
        println "My name is ${name}.I'm $age years old."
    }
}
Member taro = new Member()
Closure introduceTaro = taro.&introduce.curry('taro')
introduceTaro(10) // My name is taro.I'm 10 years old.

変数taroMember型のインスタンスであり、Closure型ではないが、演算子.&の働きによってClosureクラスのcurry()が使用できるようになっている。これを利用して、第一引数を'taro'に束縛した、新たなクロージャーであるintroduceTaroを生成している。

参考

プログラミングGROOVY 関谷 和愛 (著), 上原 潤二 (著), 須江 信洋 (著), 中野 靖治 (著) 技術評論社
Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築 綿引 琢磨 (著), 須江 信洋 (著), 林 政利 (著), 今井 勝信 (著) 翔泳社

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0