Download - Java開発の強力な相棒として今すぐ使えるGroovy
Yasuharu Nakano / @nobeans
Java開発の強力な相棒として今すぐ使える
2015-04-11 JJUG CCC 2015 Spring #ccc_g6
中野 靖治@nobeans
所属:NTTソフトウェア株式会社 Grails推進室
職業:Grails Advocate
概要
Groovyとは
“Javaをもっとすごくしたもの” 「ポストJava」ではなく「Java強化外骨格」もしくは「Java拡張パック」のイメージ
Java VM上で動作する動的型付け言語 Rubyによく似た文法を持ち、生産性が高い Groovy界隈はRubyへのリスペクト成分が高め Grailsの旧名「Groovy on Rails」
2003年生まれ(平成生まれ)
in Java
// HelloWorld.java public class HelloWorld { public static void main(String... args) { System.out.println("Hello, World!"); } }
//=> Hello, World!
in Groovy
// HelloWorld.groovy public class HelloWorld { public static void main(String... args) { System.out.println("Hello, World!"); } }
//=> Hello, World!
Groovyとしてもvalidなコードなので拡張子を変えるだけでOK
in Groovy
// HelloWorld.groovy public class HelloWorld { public static void main(String... args) { System.out.println("Hello, World!"); } }
//=> Hello, World!
セミコロンは省略可能
in Groovy
// HelloWorld.groovy public class HelloWorld { public static void main(String... args) { System.out.println("Hello, World!") } }
//=> Hello, World!
Object#println()が使える(GDKのひとつ)
in Groovy
// HelloWorld.groovy public class HelloWorld { public static void main(String... args) { println("Hello, World!") } }
//=> Hello, World!
スクリプト形式で実行可能(クラスが不要)
in Groovy
// HelloWorld.groovy println("Hello, World!")
//=> Hello, World!
メソッド呼び出しの丸括弧は省略可能
in Groovy
// HelloWorld.groovy println "Hello, World!"
//=> Hello, World!
動的型付け言語
Groovyは基本的に動的型付け言語
動的型付けの例
Integer num = "Hello"
//=> GroovyCastException: Cannot cast object 'Hello' with class 'java.lang.String' to class 'java.lang.Integer'
groovycでコンパイルには成功する
しかし、実行すると例外が発生する
静的型付けサポート
Groovy 2.0から静的型付けのための機能が追加 @TypeChecked コンパイル時に静的に型チェックする ただし、実行時は動的型付けのまま
@CompileStatic 静的にコンパイルする 実行時に動的型付けによる機構は使われない Javaとほぼ同等レベルの性能に(理論上)
どちらもクラス単位、メソッド単位で指定できる ただし、明示的な型指定が省略できない 動的を前提とした一部の機能も使えなくなる
http://beta.mybetabook.com/showpage/508402720cf2ffb79bb046db
Java言語との密な関係
Java(7以前)との文法の互換性が高い Java開発者なら非常にとっつきやすい ただし、Java 8のラムダ記法は未対応 代わりにクロージャを使う
すべてのJava APIがシームレスに呼び出せる 標準API、サードパーティのクラスもすべて普通に使える 更に「Groovy JDK(GDK)」と呼ばれる超便利メソッド群が標準クラスに追加されている!
コンパイルするとclassファイルになる 最低限、Java VMとgroovy-all.jarだけあれば実行できる
Groovyの使いどころ
メインのプログラミング言語 Grails/Spring Boot/Vert.x/Android
設定ファイルの記述言語(as DSL) Gradle/Spring Boot/Spring Framework
システムのプラグイン機構/拡張ポイント Javaから外部のGroovyスクリプトを簡単に実行できる Jenkins
プロジェクトの開発支援スクリプト DBのセットアップ、Excel操作等
Groovyの使いどころ
メインのプログラミング言語 Grails/Spring Boot/Vert.x/Android
設定ファイルの記述言語(as DSL) Gradle/Spring Boot/Spring Framework
システムのプラグイン機構/拡張ポイント Javaから外部のGroovyスクリプトを簡単に実行できる Jenkins
プロジェクトの開発支援スクリプト DBのセットアップ、Excel操作等
今日は後半でこの辺を中心に紹介します
Groovy入門その前にまずは
Javaとの文法の違い
Javaとの文法の違い
セミコロンは省略可能 returnも省略可能 チェック例外のthrows宣言も省略可能 型宣言も省略可能 プリミティブ型はラッパー型 各種リテラル 数値/文字列/リスト/マップ
アクセス修飾子 メソッド呼び出し
セミコロンは省略可能
// セミコロンは不要
int a = 1
// セミコロンはあってもOKだが、付けない方がお勧め
int b = 2;
returnも省略可能
String hello() { // returnは省略できる
// Ruby等と同様に最後に評価された値が返る
"Hello" }
assert hello() == "Hello"
チェック例外のthrows宣言も省略可能
String doWithoutThrows() { throw new Exception("チェック例外!")
}
try { doWithoutThrows() assert false } catch (Exception e) { assert e.getMessage() == "チェック例外!"
}
throwsがなくてもOK!
型宣言も省略可能def キーワード Object型の別名として宣言時に使える
以下の場合、型宣言自体を省略可能(→Object型) メソッドや変数で、privateやstaticなどの修飾子が1つ以上ある場合 メソッドの仮引数
final helloPrefix = "Hello, "
public hello(name) { def message = helloPrefix + name return message }
省略はできるが、ドキュメンテーションとして考えると、公開APIのシグネチャでは型を明記する方が好ましい
def hoge = "ほげ"
プリミティブ型で宣言しても実体はラッパー型 ただし、nullは代入不可能 また、最適化機構により、可能な場合において内部的にもプリミティブ型のまま扱われる場合もある
プリミティブ型はラッパー型
assert 1.getClass() == java.lang.Integer
double d = 1.0 assert d.getClass() == java.lang.Double
assert true.getClass() == java.lang.Boolean
// もちろんメソッドも呼べる
assert d.plus(2.5) == 3.5
// nullは代入不可能
d = null //=> GroovyCastException: Cannot cast object 'null' with class 'null' to class 'double'.
リテラル:数値
Groovyの浮動小数リテラルはBigDecimal型 桁あふれや誤差を気にしないで演算できる プリミティブ型のように通常の中置演算子が使える
assert 5.3.getClass() == java.math.BigDecimal
// 普通に中置演算子も使える
assert (1.0 / 3) == 0.3333333333 assert (1.0 / 3).getClass() == java.math.BigDecimal
// Javaでは誤差が発生するが、GroovyはBigDecimalなので一致する
assert (1.0 -‐ (1.0 / 3.0)) == (2.0 / 3.0)
#ccc_cd3 でBigDecimal話がありましたね
リテラル:文字列// 基本はJavaと同じくダブルクォーテーション
println "Hello, World!" //=> Hello, World!
// ${}によって変数を展開できる(紛らわしくなければ{}も省略できる)
String name = "World" assert "Hello, ${name}!" == "Hello, World!"
// 厳密にはGString型のリテラル
assert "Hello, ${name}!".getClass() == org.codehaus.groovy.runtime.GStringImpl
// 純粋にString型が必要な場合はシングルクォーテーションを使う
// Javaではchar型リテラルだったが、Groovyではchar型リテラルは無し
assert 'Hello, ${name}!' != "Hello, World!"
リテラル:文字列
// トリプル「ダブルクォート」(ややこしい)
// 改行や単体のクォート文字列自体を含められる(GString型)
// トリプル「シングルクォート」にすると単なるString型になる(展開無し)
def s = "Third" println """First, "Second", ${s}. """
//=> // First, // "Second", // Third.
ヒアドキュメント風に使える
リテラル:文字列
// 開始行の行末エスケープとString#stripMargin()を使うと、
// いい感じにインデントできて、読みやすい
def s = "Third" println """\ |First, |"Second", |${s}. |""".stripMargin()
//=> // First, // "Second", // Third.
GDKのString#stripMargin()と合わせて使うとインデントもいい感じにできる
リテラル:文字列
// スラッシュクォート
// 単体バックスラッシュを特殊文字として扱わないため、エスケープが不要。
// 正規表現をシンプルに書ける
assert (/This is backslash '\n'/ == "This is backslash '\\n'") assert "Hello, World!".matches(/Hello,\s.*/)
// Pattern型ではなく、あくまでString型
assert (/Not Pattern, But String/.getClass() == String)
// 丸括弧で囲まないとコンパイルエラーになるケースがあるので注意
assert /Naked Slashed/ //=> "1 compilation error: unexpected token: /"
正規表現を書くときは断然コレ
リテラル:リスト// 専用リテラルの導入により、シンプルにリストを記述できる
def l = [1, 2, 3, 4, 5]
assert l.size() == 5 assert l.getClass() == java.util.ArrayList
// 空リスト
assert [].size() == 0
// 要素へのアクセス
assert l[0] == 1 assert l[2..3] == [3, 4]
assert l.first() == 1 assert l.last() == 5
assert l.head() == 1 assert l.tail() == [2, 3, 4, 5]
(参考)リテラル:セット
// セット専用のリテラルはないが、asによる型変換で表現できる
def s = [1, 2, 3, 4, 5] as Set
assert s.size() == 5 assert s.getClass() == java.util.LinkedHashSet
リテラル:マップ
// 専用リテラルの導入により、シンプルにマップを記述できる def m = ['a': 1, 'b': 2, 'c': 3]
assert m.size() == 3 assert m.getClass() == java.util.LinkedHashMap
// 空マップ
assert [:].size() == 0
// キーに文字列を指定する場合はクォートを省略できる
assert m == [a: 1, b: 2, c: 3]
// 要素へのアクセス
assert m['a'] == 1 // 連想配列風
assert m.a == 1 // プロパティアクセス風
アクセス修飾子
アクセス修飾子は基本的に飾りです privateメンバにもアクセスできる IDEなどで一応警告が出るぐらい
無印=public パッケージプライベートは存在しない 一応、アノテーションで強引に宣言できる
@PackageScope
基本は無印(public)でよい その他、ドキュメンテーション目的として... クラス内部だけで利用するものにprivateを付けたり 積極的に継承を想定しているものにprotectedを付けたり
メソッド呼び出し
// 引数の丸括弧が省略できる
println("Hello") println "Hello"
// 引数がない場合は省略できない
println()
メソッド呼び出し
def hello() { "Hello!" }
// Stringでメソッド名を指定できる
assert "hello"() == "Hello!"
// GStringを使ってもOK
def methodName = "hello" assert "$methodName"() == "Hello!"
メソッド呼び出し
// 名前付き引数?
def hello(Map map) { "Hello, ${map.name}${map.period}" }
assert hello(period: "!", name: "World") == "Hello, World!"
// これの[]を省略できるイメージ
assert hello([period: "!", name: "World"]) == "Hello, World!"
メソッド呼び出し
// 引数のデフォルト値
def bye(name = "World") { "Good-‐bye, ${name}." }
assert bye() == "Good-‐bye, World." assert bye("Yesterday") == "Good-‐bye, Yesterday."
コンストラクタ呼び出し
class Sample { String a String b int c }
// 引数を受け取るコンストラクタを自前で書いていなければ、
// パラメータ付き引数を指定できる便利なコンストラクタが自動生成される
def sample = new Sample(a: "A", c: 3, b: "B")
println sample.dump() // デバッグ時に便利なGDK: Object#dump()
// => <Sample@3909c9e9 a=A b=B c=3>
Groovyの各種機能
クロージャ
他言語の第1級関数オブジェクトやクロージャと同等 JavaScript, Ruby, Perl, Lisp, ....
第一級オブジェクトとして、変数に代入したり引数に渡したりできる
高階関数
無名内部クラスやJava 8のラムダ記法よりも次の点で強力 クロージャを宣言した文法上のスコープ(レキシカルスコープ)にある変数(ローカル変数を含む)を参照・変更できる
クロージャ:定義と実行
// クロージャを定義する
Closure c = { return "Hello, Closure!" }
// 実行する
assert c.call() == "Hello, Closure!"
// callを省略することもできる
assert c() == "Hello, Closure!"
クロージャ:クロージャでの引数の受け取り// 引数なし
def c0 = {-‐> "Hello, Closure!" } assert c0.call() == "Hello, Closure!"
// 引数1つ
def c1 = { String name -‐> "Hello, ${name}!" } assert c1.call("Closure") == "Hello, Closure!"
// 引数2つ(片方を型省略してみる)
def c2 = { greeting, String name -‐> println "${greeting}, ${name}!" } assert c2.call("Hello", "Closure") == "Hello, Closure!"
// 引数宣言部「xx.. -‐>」を省略した場合、暗黙引数の「it」が使える
def c = { "Hello, ${it}!" } assert c.call("Closure") == "Hello, Closure!" assert c.call() == "Hello, null!" // 引数を省略時はnull
クロージャ:クロージャを引数として渡す
// 渡されたクロージャを指定されたnameで実行するだけのメソッド
def justDoIt(String name, Closure closure) { closure.call(name) }
// 普通に丸括弧の中にクロージャを書いた場合
justDoIt("Taro", { name -‐> println "Hello, ${name}!" }) //=> Hello, Taro!
// 最後の引数がクロージャの場合はこのように丸括弧の外に出して書ける
justDoIt("Taro") { name -‐> println "Hello, ${name}!" } //=> Hello, Taro!
// このように、専用の文法があるかのように独自APIを定義できる
justDoIt("Taro") { name -‐> println "Hello, ${name}!" }
最後の引数がClosure型であるところがポイント
(参考)クロージャ:高階関数とレキシカルスコープの例
def createIdGenerator(prefix) { // この実引数と
int counter = 1 // このローカル変数が(finalではない!)
Closure c = { "${prefix}-‐${counter++}" // クロージャごとに個別に保持される
} return c // クロージャを返す
}
def genA = createIdGenerator("A") assert genA() == "A-‐1" assert genA() == "A-‐2" assert genA() == "A-‐3"
def genB = createIdGenerator("B") assert genB() == "B-‐1" assert genB() == "B-‐2" assert genB() == "B-‐3"
assert genA() == "A-‐4"
元々createIdGeneraterメソッドのローカル変数であるcounter
が、クロージャごとに個別に管理されているのがわかる
assertキーワード/Power Assert
Spockから正式採用したPower Assertが使える Spockの方が更に先に進んでいて若干高機能(適合率を表示するとか)
Assertion failed:
assert a.collect { it * 2 }.reverse() == [6, 4, 0] | | | | | [2, 4, 6] [6, 4, 2] false [1, 2, 3]
at ConsoleScript78.run(ConsoleScript78:2)
// わざと3つ目を間違えてみる
def a = [1, 2, 3] assert a.collect { it * 2 }.reverse() == [6, 4, 0]
Groovy JDK/GDK
GroovyがJavaの標準APIに追加した便利なAPI群 Javaの不便な点をAPIレベルで改善できる
printlnもObjectに実装されたGDK APIのひとつ System.out.printlnに委譲しているだけ
後述するコレクションAPIもGDKとして実装されている
すべての品揃えは以下のAPIドキュメントを参照のこと http://docs.groovy-lang.org/docs/latest/html/groovy-jdk/
GDKの例
// Groovy本家サイトのHTMLを表示する
println new URL("http://groovy-‐lang.org/").getText()
// ファイルの中身を表示する
println new File("/my/some.text/").getText()
コレクション操作
各種の内部イテレーション記法が使える 外部イテレーション forやiteratorなどで外側から要素を回す
内部イテレーション 各要素に適用すべき処理をクロージャ/ラムダなどでコレクションに渡して、コレクション自体が内部でループする GroovyのコレクションAPIは、GDKとして提供されている Java 8のStream APIもこちら
コレクション操作:リスト
List l = [1, 2, 3, 4, 5]
// 各要素に対してクロージャの処理を適用する
l.each { number -‐> print number } //=> 12345
// 各要素に対してクロージャの処理を適用した結果のリストを取得する
assert l.collect { it * 2 } == [2, 4, 6, 8, 10]
// 各要素に対してクロージャの処理を適用した結果が真の要素のみを残す
assert l.findAll { it % 2 == 0 } == [2, 4]
// 指定したセパレータで結合した文字列を返す
assert l.join(":") == "1:2:3:4:5"
// 合計値を算出する
assert l.sum() == 15
コレクション操作:マップMap m = [a: 1, b: 2, c: 3]
// クロージャ引数を1つだけ宣言すると、Map.Entryが受け取れる
m.each { Map.Entry entry -‐> print entry.key + entry.value } //=> a1b2c3
// クロージャ引数を2つ宣言すると、それぞれキーと値が受け取れる
m.each { key, value -‐> print key + value } //=> a1b2c3
// 各要素に対してクロージャの処理を適用した結果のリストを取得する
assert m.collect { "${it.key}:${it.value}" } == ["a:1", "b:2", "c:3"]
// クロージャの返す値が最も大きい要素を取得する
assert m.max { it.value }.key == "c"
マップにも基本的に同じAPIが用意されている(コレクションの特性上、微妙に品揃えは違う)
デフォルトimport済みパッケージ
高頻度で使うこれらのパッケージはデフォルトでimport済み java.lang.* java.io.* java.net.* java.util.* groovy.lang.* groovy.util.* java.math.BigDecimal java.math.BigInteger
(参考)別名をつけてimportすることもできるimport java.lang.NullPointerException as NPE
throw new NPE("aliased import sample")
GroovyBeans
JavaBeansに対するGroovyの強化サポート プロパティに対するgetter/setterの自動生成
class Book { String title } def book = new Book(title: "プログラミングGROOVY")
// getterが自動生成されている
assert book.getTitle() == "プログラミングGROOVY"
// setterが自動生成されている
book.setTitle("JavaからGroovyへ")
assert book.getTitle() == "JavaからGroovyへ"
GroovyBeansclass GroovyBeansSample { public String p = "PUBLIC" final String f = "FINAL" String g = "GetterImplemented" String getG() { "getGの戻り値: $g" }
}
def sample = new GroovyBeansSample()
// publicなどをアクセス修飾子を付けるとgetter/setterは自動生成されない
sample.getP() //=> groovy.lang.MissingMethodException sample.setP("x") //=> groovy.lang.MissingMethodException
// finalを付けるとsetterは自動生成されない
assert sample.getF() == "FINAL" sample.setF("x") //=> groovy.lang.MissingMethodException
// 自前で実装したアクセサメソッドは自動生成されない
assert sample.getG() == "getGの戻り値: GetterImplemented"
sample.setG("xxx") assert sample.getG() == "getGの戻り値: xxx"
常に無条件で自動生成するわけではない
GroovyBeans
プロパティアクセスの簡略記法class Book { String title } def book = new Book(title: "プログラミングGROOVY")
// インスタンス変数への直接参照のようにみえるがgetterを経由している
assert book.title == "プログラミングGROOVY"
// インスタンス変数への直接代入のようにみえるがsetterを経由している
book.title = "JavaからGroovyへ"
assert book.title == "JavaからGroovyへ"
// getXxx()という名前の引数なしのメソッドであればプロパティ記法でアクセスできる
// つまり、GroovyBeansに限らず名前規約が一致していれば、この簡略記法は使える
assert book.class == book.getClass()
Grape
Mavenリポジトリから直接Jarをダウンロードし、クラスパスに通してからスクリプトを実行できる ダウンロードしたファイルは $HOME/.groovy/grapes配下に格納される
@Grab('org.apache.commons:commons-‐lang3:3.2') import org.apache.commons.lang3.StringUtils
List l = [1, 2, 3, 4, 5]
assert StringUtils.join(l, ':') == "1:2:3:4:5"
// 本当はこの程度の処理であればGDKで十分
assert l.join(':') == "1:2:3:4:5"後半で示すサンプルはGrapeを使って即実行できるスクリプト形式にしている
便利な演算子
等値演算子 == Javaではインスタンスの同一性のための使うが、Groovyではequals呼び出しになっている Stringの比較で普通に==が使える! インスタンス同一性のチェックにはObject#is()を使う
エルビス演算子 ?: A ?: B
三項演算子 A ? A : B の省略形
セーフナビゲーション演算子 ?. a?.b?.c()
nullではない場合のみ、右側の呼び出しを続行する
展開ドット演算子 *. [a:1, b:2, c:3]*.value == [1, 2, 3]
collect { it.value } の短縮形のイメージ
#ccc_cd1 で紹介されました!
#ccc_cd1 で紹介されました!
レンジ(範囲)
範囲を表すコレクション groovy.lang.Range 始点と終点を指定してその間の要素をIterableに扱う
for (int i : 1..9) { // 閉区間(9を含む)
print i } //=> 123456789
for (int i : 1..<9) { // 半開区間(9を含まない)
print i } //=> 12345678
// Range#toList()でリストに変換できる
assert (1..5).toList() == [1, 2, 3, 4, 5]
正規表現サポート
正規表現をサポートする演算子やリテラルがある
// 「==~」完全一致によるマッチング結果の真偽値を返す
assert "abc" ==~ /a.c/ assert ! ("abc" ==~ /bc/) // 部分一致ではない
// 実際は、Matcher#matches()の呼び出しになっている
assert "abc".matches(/a.c/) assert ! "abc".matches(/bc/)
// 「=~」部分一致によるマッチング結果のMatcherオブジェクトを返す
assert "abc" =~ /a.c/ assert "abc" =~ /bc/ // 部分一致OK
// 実は、演算自体の戻り値はMatcherオブジェクトになっている
// Matcherが真偽値判定されるタイミングで、
// Matcher#find()が裏で呼ばれてその結果が返っている
assert ("abc" =~ /a.c/).getClass() == java.util.regex.Matcher
論理値/Groovy Truth
Groovyではboolean/Boolean以外の値についても、真偽判定できる
assert ! 0 // 整数: 0は偽
assert 1 // 整数: 0以外は真
assert ! [] // リスト: 空リストは偽
assert ["a"] // リスト: 空リスト以外は真
assert ! [:] // マップ: 空マップは偽
assert [key:'value'] // マップ: 空マップ以外は真
assert ! "" // 文字列: 空文字列は偽
assert "a" // 文字列: 空文字列以外は真
assert !("abcdefg" =~ /a.*X/) // 正規表現: マッチしないと偽
assert "abcdefg" =~ /cde/ // 正規表現: マッチすると真
assert ! null // nullは偽
assert new Object() // それ以外のオブジェクト: null以外は真
演算子オーバーロード
GroovyではJavaと同等の各種演算子が使える 実は、それぞれの演算子は対応する特定のメソッド呼び出しに変換されている つまり、演算子オーバロードが可能 GDKでも多数活用されている 例えば、「+」→「plus()」、「-」→「minus()」、「<<」→「leftShift()」
演算子と対応するメソッドの一覧 http://groovy-lang.org/operators.html#Operator-Overloading
class MyObject { String name MyObject(name) { this.name = name } MyObject plus(MyObject o) { return new MyObject(name + ":" + o.name) } }
def obj1 = new MyObject("OBJECT_1") def obj2 = new MyObject("OBJECT_2")
def obj3 = obj1 + obj2 assert obj3.name == "OBJECT_1:OBJECT_2"
AST変換
抽象構文木(Abstract Syntax Tree)レベルでのコード変換技術 コンパイルの過程で、アノテーションでマーキングした対象箇所に、任意のコードの追加・置換ができる Groovyシンタックスの意味自体を改変する「グローバルAST変換」技術もあるがあまり使われない
例 Grape(@Grab) ロギング(@Log, @Commons, @Log4j, @Slf4j) シングルトン(@Singleton) 不変オブジェクト(@Immutable) 自前クラスの基本要素(@ToString, @EqualsAndHashCode, @Canonical, @InheritConstructors)
AST変換: @Grabと@Log
// Mavenセントラルリポジトリからダウンロード&クラスパスに指定
@Grab("log4j:log4j") import groovy.util.logging.Log4j
@Log4j class Sample { def doIt() { // ロガーとしてlog変数が使える
log.fatal "Hello" } }
new Sample().doIt() //=> FATAL -‐ Hello
AST変換: @Singleton
@groovy.lang.Singleton class Sample { String value }
// インスタンス初期化機構とgetInstance()クラスメソッドが
// 追加されるので、シングルトンとしてすぐに使える
assert Sample.getInstance().value == null Sample.instance.value = "シングルトン"
assert Sample.instance.value == "シングルトン"
// もちろん同一インスタンス
assert sample.instance.is(Sample.instance)
AST変換: @Immutable
@groovy.transform.Immutable class Sample { String value }
def sample = new Sample(value: "HOGE")
sample.value = "変更できない"
//=> groovy.lang.ReadOnlyPropertyException: // Cannot set readonly property: value for class: Sample
AST変換: @ToString, @EqualsAndHashCode, @TupleConstructors
@groovy.transform.TupleConstructor @groovy.transform.ToString @groovy.transform.EqualsAndHashCode class Sample { String a String b int c }
// @TupleConstructorによって宣言したフィールドの順に引数を受け取るコンストラクタが生成される
def sample = new Sample("A", "B", 3)
// @ToStringによってそれっぽい文字列を構成するtoString()が生成される
println sample.toString() // => Sample(A, B, 3)
// @EqualsAndHashCodeによって、プロパティの値をベースにした等値判定をするequals()が生成される
assert sample == new Sample("A", "B", 3) assert sample != new Sample("A", "B", 123)
// もちろん、equals()の実装契約として必須であるhashCode()も合わせて実装されている
assert sample.hashCode() == new Sample("A", "B", 3).hashCode() assert sample.hashCode() != new Sample("A", "B", 123).hashCode()
// (参考) @Canonicalは、@ToStringと@EqualsAndHashCodeの組み合わせのエイリアス
静的Groovy
@TypeChecked コンパイル時に静的型チェックする lintのようなもの 実行時はいつも通り動的に
@CompileStatic 指定した範囲を静的型付けとしてコンパイルする 実行時も静的に(性能的に有利) Groovyの動的な側面に依存した機能が使えない ダックタイピング、privateの呼び出し、など
性能的にカリカリにしたい箇所に、部分的に付けると良い
静的Groovy: @TypeCheckedclass MyList { private List<String> list = [] def store(name) { list << name } }
def list = new MyList() list.store "A" list.store "B" list.store "C"
println list.dump() //=> <MyList@5c042a81 list=[A, B, C]>
StringインスタンスをObject型で受けとって、listに追加する
期待通り動く
静的Groovy: @TypeCheckedclass MyList { private List<String> list = [] @groovy.transform.TypeChecked def store(name) { list << name } }
def list = new MyList() list.store "A" list.store "B" list.store "C"
println list.dump() //=> [Static type checking] -‐ Cannot call <T> java.util.List <String>#leftShift(T) with arguments [java.lang.Object] at line: 7, column: 9
コンパイル時にエラーが発生する
このメソッドを静的型チェックすると...
静的Groovy: @TypeCheckedclass MyList { private List<String> list = [] @groovy.transform.TypeChecked def store(String name) { list << name } }
def list = new MyList() list.store "A" list.store "B" list.store "C"
println list.dump() //=> <MyList@5c042a81 list=[A, B, C]>
String型を明示すると...
また動くようになった!
静的Groovy: 非@CompileStaticlong fib(long n) { if (n < 2) return 1 return fib(n -‐ 2) + fib(n -‐ 1) }
def start = System.currentTimeMillis()
def num = 40 def ans = fib(num) println "fib($num) = ${ans}"
println "Total: ${System.currentTimeMillis() -‐ start} msec"
//=> fib(40) = 165580141 // Total: 1575 msec
静的Groovy: @CompileStaticimport groovy.transform.CompileStatic
@CompileStatic long fib(long n) { if (n < 2) return 1 return fib(n -‐ 2) + fib(n -‐ 1) }
def start = System.currentTimeMillis()
def num = 40 def ans = fib(num) println "fib($num) = ${ans}"
println "Total: ${System.currentTimeMillis() -‐ start} msec"
//=> fib(40) = 165580141 // Total: 569 msec 約3倍!<1575 msec
(参考)AST変換: @Memoizedimport groovy.transform.Memoized
@Memoized long fib(long n) { if (n < 2) return 1 return fib(n -‐ 2) + fib(n -‐ 1) }
def start = System.currentTimeMillis()
def num = 40 def ans = fib(num) println "fib($num) = ${ans}"
println "Total: ${System.currentTimeMillis() -‐ start} msec"
//=> fib(40) = 165580141 // Total: 4 msec !!!!!!?????? <569 msec <1575 msec
参照透過な関数の場合、メモ化(引数と結果の組み合わせをキャッシュ)の効果が強力に活
用できる場合がある
Groovy コードの実行
実行可能なコード形式
実行可能なコード形式
Groovyプログラムとして実行可能なのは... mainメソッド持つクラス(Javaと同等) Groovyスクリプト JUnitのテストケース Runnableの実装クラス
mainメソッド持つクラス
// in HelloMain.groovy
class HelloMain { static void main(String... args) { println "Hello, Main!" } }
//=> Hello, Main!
Groovyスクリプト
// in hello.groovy
println "Hello, Script!"
//=> Hello, Script!
クラスの外側のルートレベルで直接コードが書かれている=Groovyスクリプト
ちなみに、コンパイルされると、Scriptクラスのインスタンスになる
public class hello extends groovy.lang.Script { public static transient boolean __$stMC; public hello(); public hello(groovy.lang.Binding); public static void main(java.lang.String...); public java.lang.Object run(); protected groovy.lang.MetaClass $getStaticMetaClass(); }
JUnitのテストケース
// in HelloTest.groovy
import junit.framework.TestCase
class HelloTest extends TestCase { void testHello() { println "Hello, JUnit!" } }
//=> .Hello, JUnit! // // Time: 0.047 // // OK (1 test) //
GroovyがJUnitを同梱しているので別途JARを用意する必要はない
Runnableの実装クラス
// in HelloRunnable.groovy
class HelloRunnable implements Runnable { void run() { println "Hello, Runnable!" } }
//=> Hello, Runnable!
実行可能なコード形式
Groovyプログラムとして実行可能なのは... mainメソッド持つクラス(Javaと同等) Groovyスクリプト JUnitのテストケース Runnableの実装クラス
今回のケースではとりあえず、これで十分
実行方法
各種実行方法
groovy java -jar groovy-all.jar
groovyc + java groovyConsole groovysh GroovyServ スクリプト起動の高速化
Groovy Web Console https://groovyconsole.appspot.com/
各種実行方法
groovy java -jar groovy-all.jar
groovyc + java groovyConsole groovysh GroovyServ スクリプト起動の高速化
Groovy Web Console https://groovyconsole.appspot.com/
だいたいこの辺を使っておけばOK
groovyコマンド
java -jar groovy-all.jar == groovyコマンド
All-In-OneのJARファイルさえあれば、groovyコマンドがなくても実行できる!
groovyc + java
滅多に使わない
groovyConsole
編集して、Ctrl+Rで実行できる
実行結果を表示Ctrl+Wでクリア
引数なしでもOK
groovysh
限定的ながらもTabでコード補完ができる
Groovy web console
クリックで実行
https://groovyconsole.appspot.com/
インストール不要で手軽に試せる
ユースケース
(再掲)Groovyの使いどころ
メインのプログラミング言語 Grails/Spring Boot/Vert.x/Android
設定ファイルの記述言語(as DSL) Gradle/Spring Boot/Spring Framework
システムのプラグイン機構/拡張ポイント Javaから外部のGroovyスクリプトを簡単に実行できる Jenkins
プロジェクトの開発支援スクリプト DBのセットアップ、Excel操作等
開発支援ツールとしての活用
Groovyは、たとえ対象システムの開発言語として採用しない場合でも、Javaによる開発を支援するツールとして非常に有用
なるほどね
Java APIのお試し
例えば
標準APIを試す ○○の場合はどういう動作をするんだったっけ? 正規表現はこれで良いのかな?
初めて使うサードパーティ製のライブラリを試す
お試し環境
groovyConsole上で試す シンプルなエディタで試行錯誤できる Grapeを使ってサードパーティライブラリも試せる わかりやすくてお手軽
groovyshで試す JavaとGroovyの標準APIであれば、Tabによるコード補完が効く点でちょっと便利
エディタ+groovy (or GroovyServ) エディタの機能ですぐに実行できる環境をつくると便利 (例)vim + quickrun.vim + GroovyServ http://nobeans.hatenablog.com/entry/20111024/1319435155
ちなみに、この資料のサンプルコードはほとんどgroovyConsoleで書きました
groovyConsoleの例
XMLのパース
例えば
設定ファイル内容の一覧を出力する JUnitのテストレポートをパースして自動的に進捗報告資料に追加する Rest APIの結果をチェックする
XmlParser/XmlSlurper
Groovy標準APIのXmlParserが便利 兄弟分として XmlSlurper(~すらーぱー)がある XmlParser:DOM的(インメモリ、変更可能) XmlSlurper:SAX的(ストリーム処理、参照専用)
細かい点で差異はあるものの、シンプルな参照に限定したAPIの使い勝手はほぼ同じ メモリ展開が難しい巨大なファイルを対象とするのでなければ、XmlParserを選択しておけばOK
XMLParserで属性やテキストを参照する例def inputXml = """ <root> <items> <item id="1" name="あいうえお">OK</item>
<item id="2" name="かきくけこ">NG</item>
<item id="3" name="さしすせそ">OK</item>
</items> </root> """ def root = new XmlParser().parseText(inputXml) // <root>に対応するgroovy.util.Nodeオブジェクト
// すべてのitem要素をフォーマットして出力する
root.items.item.each { Node item -‐> println "${item.@id}: ${item.@name} => ${item.text()}" } //=> 1: あいうえお => OK
// 2: かきくけこ => NG
// 3: さしすせそ => OK
// OKのitem要素のみをフォーマットして出力する
root.items.item.findAll { it.text() == "OK" }.each { println "${it.@id}: ${it.@name} => ${it.text()}" } //=> 1: あいうえお => OK
// 3: さしすせそ => OK
この時点でXMLはパースされて、Nodeツリーがメモリ上に展開済み
GPathという記法でNodeのコレクションを特定
属性値は「@+属性名」、子要素のテキストは「text()」で参照
each, findAllなどのコレクション操作がそのまま適用できる
HTML/XMLの出力
例えば
自動的に収集した情報を元にHTMLレポートを出力する システムにインポートするXMLファイルを生成する
MarkupBuilder
Groovy標準APIのMarkupBuilderが便利 タグでマークアップされたテキストを生成する 波括弧のブロックで階層構造を表現できる if文やループ制御構文が使える
マップの情報を元にHTMLを出力する例def generateHtml = { data, writer -‐> def reportTitle = '試験実施日時報告'
def formatCount = { now, latest -‐> def diffCount = now -‐ latest return "${now} (${diffCount > 0 ? "+" : ""}${diffCount})" } new groovy.xml.MarkupBuilder(writer).html { head { title reportTitle } body(style: "background: #afa") { h1 reportTitle h2 '試験件数等'
ul { li "試験項目数: ${formatCount(data.tests, data.latest.tests)}"
li "終了件数: ${formatCount(data.done, data.latest.done)}"
li "バグ件数: ${formatCount(data.issues, data.latest.issues)}"
} h2 '備考'
if (data.remarks) { ul { data.remarks.each { li(it) } } } } } }
def data = [ tests: 1230, done: 350, issues: 123, latest: [tests: 1235, done: 320, issues: 93], remarks: ["進捗は特に問題なし", "インフルエンザが流行中なので不安"]
] new File("/tmp/daily-‐test-‐report.html").withWriter { writer -‐> generateHtml(data, writer) }
マップの情報を元にHTMLを出力する例 //... new groovy.xml.MarkupBuilder(writer).html { head { title reportTitle } body(style: "background: #afa") { h1 reportTitle h2 '試験件数等'
ul { li "試験項目数: ${formatCount(data.tests, data.latest.tests)}"
li "終了件数: ${formatCount(data.done, data.latest.done)}"
li "バグ件数: ${formatCount(data.issues, data.latest.issues)}"
} h2 '備考'
if (data.remarks) { ul { data.remarks.each { li(it) } } } } } //...
MarkupBuilderについてもう少し補足def builder = new groovy.xml.MarkupBuilder()
def existMethod(a) { "EXIST_METHOD: ${a}" }
// 最初の呼び出しで使った名前がルート要素のタグ名になる
builder.aaa { // 存在しないメソッド名で呼び出すと要素宣言と見なされる。引数は子要素になる。
bbb "BBB" // 引数をマップで指定すると属性値になる
ccc(c1: "C1", c2: "C2")
// 引数としてクロージャを渡すとネストになる
ddd { xxx "XXX" }
// 制御構文がそのまま使える
if (false) { // ここが実行されなければ出力されない
ignoredThis "IGNORED" } // 存在するメソッドであれば単にそのメソッドが呼ばれるだけで要素は生成されない
// 存在しないメソッドを実行しようとすることが、要素生成のトリガとなる
existMethod "ただのメソッド呼び出し"
}
<aaa> <bbb>BBB</bbb> <ccc c1='C1' c2='C2' /> <ddd> <xxx>XXX</xxx> </ddd> </aaa>
このように、Groovyの「*Builder」は存在しないメソッド呼び出し(methodMissing)をトリガにして構造をビルドしていくクラスになっている
HTMLスクレイピング
例えば
定期レポートのための情報を収集する 開発中のWebアプリのテストの一環として、ある画面のHTMLを取得して、内容をチェックする
HTMLスクレイピング
URL#getText() HTMLテキストを取得すればいい場合は一番簡単
XmlParser/XmlSlurper 整形式であるXHMLの場合は単品でOK 非整形式のHTMLの場合はNekoHTMLを併用すればOK http://nekohtml.sourceforge.net/
jsoup jQuery風のセレクタAPIが使える http://jsoup.org/
HTMLスクレイピング: URL#getText()String html = new URL("http://groovy-‐lang.org/").text
// 1行ずつチェックしてURLをパースする
def urls = html // 1行ずつのリストにする
.readLines() // 行中にURLパターンがあったらそれを返す(なければnull)
.collect { line -‐> if (line =~ $/.*(https?://[a-‐zA-‐Z0-‐9%?._]+).*/$) { return java.util.regex.Matcher.lastMatcher.group(1) } } // nullを除外する
.findAll { it != null } // 重複したURLを除去する
.unique()
// ソートして表示する
urls.sort().each { println it }
HTMLスクレイピング: XmlParser + NekoHTML@Grab('net.sourceforge.nekohtml:nekohtml:1.9.21') import org.cyberneko.html.parsers.SAXParser import groovy.util.Node
def parser = new XmlParser(new SAXParser())
Node html = parser.parse("http://groovy-‐lang.org/")
// 1行ずつチェックしてURLをパースする
def urls = html // html配下のすべての子要素のaタグのhref属性値を集める
.'**'.A.@href // http(s)のものだけ抽出する
.findAll { it ==~ /https?:.*/ } // 重複したURLを除去する
.unique()
// ソートして表示する
urls.sort().each { println it }
HTMLスクレイピング: jsoup@Grab('org.jsoup:jsoup:1.8.1') import org.jsoup.Jsoup import org.jsoup.nodes.Document
Document doc = Jsoup.connect("http://groovy-‐lang.org/").get()
def urls = doc // すべてのaタグのhref属性を集める
.select('a')*.attr("href") // http(s)のものだけ抽出する
.findAll { it ==~ /https?:.*/ } // 重複したURLを除去する
.unique()
// ソートして表示する
urls.sort().each { println it }
Groovy SQLによる RDBMS操作
例えば
テスト用ダミーデータをDBに大量投入する 定期レポートのための情報を収集する 保守のためデータベースの内容を定期的に監視する あるシステムから別のシステムにデータを移行する
Groovy SQL
Groovy標準APIのGroovy SQLが便利 クエリ発行と結果のResultSetからの値の取り出しなどが簡単に実行できる DataSetを使うと、コレクション操作のようにレコードの追加やイテレーションが実行できる
Groovy SQLでH2を操作する@Grab('com.h2database:h2') @GrabConfig(systemClassLoader=true) // JDBCはシステムクラスローダから探されるので必要
import groovy.sql.Sql
// 別途DataSourceやConnectionを用意するなら、Sqlのコンストラクタに渡せばOK
def db = Sql.newInstance("jdbc:h2:mem:sample", "org.h2.Driver")
// Sql#execute()でDDLを実行する
db.execute """ create table person ( name varchar(255), age int ) """
// Sql#executeUpdate()使うと、変更した行数が返る
insertSql = "insert into person (name, age) values (?, ?)" assert db.executeUpdate(insertSql, ['Mike', 13]) == 1 assert db.executeUpdate(insertSql, ['Junko', 14]) == 1
// Sql#eachRow()は1行ごとに処理する(リソース低負荷)
db.eachRow('select * from person') { row -‐> println row.name } //=> Mike // Junko
// Sql#rows()は結果をすべてメモリ上に取得する(リソース高負荷)
println db.rows('select * from person').collect { it.name } //=> [Mike, Junko]
DataSetを使う例@Grab('com.h2database:h2') @GrabConfig(systemClassLoader=true) // JDBCはシステムクラスローダから探されるので必要
import groovy.sql.Sql
// 別途DataSourceやConnectionを用意するなら、Sqlのコンストラクタに渡せばOK
def db = Sql.newInstance("jdbc:h2:mem:sample", "org.h2.Driver")
// Sql#execute()でDDLを実行する
db.execute(""" create table person ( name varchar(255), age int ) """)
// DataSetを使うとコレクションオブジェクト風にレコード操作ができる
import groovy.sql.DataSet
DataSet people = db.dataSet('person') people.add(name: 'Mike', age:13) people.add(name: 'Junko', age: 14) people.add(name: 'Ken', age: 23)
people.each { println "${it.name} (${it.age})" } //=> Mike (13) // Junko (14) // Ken (23)
GExcelAPIによる Excel操作
例えば
ダミーデータの定義表として読み込む Excelデータから特定セルの値を読み込んで集計する コード生成の入力データとして
GExcelAPI
https://github.com/nobeans/gexcelapi Apache POIのGroovy風ラッパーライブラリ
POIを直接使った場合、セルを特定するインデックス指定がとても分かりづらい
GExcelAPIを使うと、Excelのセルラベルである「A1」でアクセスできる!
GExcelAPI@GrabResolver(name="bintray", root="http://dl.bintray.com/nobeans/maven") @Grab("org.jggug.kobo:gexcelapi:0.4") import org.jggug.kobo.gexcelapi.GExcel
def book = GExcel.open("/tmp/sample.xlsx") def sheet = book[0] // 1シート目
// Excel風なDSLでセルの値を取得する
println sheet.A1.value //=> A1の値
println sheet.B2.value //=> B2の値
println sheet.C3.value //=> 303.0 println sheet.D4.dateCellValue.format("yyyy-‐MM-‐dd") //=> 2015-‐02-‐29
// 指定した矩形内でイテレーション
sheet.A1_D4.each { row -‐> println row } //=> [[A1の値, B1の値, 301.0, 42041.0],
// [A2の値, B2の値, 302.0, 42042.0],
// [A3の値, B3の値, 303.0, 42043.0],
// [A4の値, B4の値, 304.0, 42044.0]]
日付型のセルはプログラマが日付であることを意識して取得しなければならない
テンプレートエンジン
例えば
定型レポートをテンプレートから生成する 何らかの定義情報を元に、ソースコードを生成する
テンプレートエンジン?
複数行文字列リテラル+ String#stripMargin() SimpleTemplateEngine
複数行文字列リテラル+stripMargin()
String applyTemplate(name, title) { return """\ |こんにちは、${name}さん
| |本日は、${title}をご案内させていただきます。
|... |""".stripMargin() }
println applyTemplate("日本 太郎", "プログラミングGROOVY")
//=> こんにちは、日本 太郎さん
// // 本日は、プログラミングGROOVYをご案内させていただきます。
// ...
先頭行のエスケープ付き改行とstripMarginを使うことで、
インデントを自然な形に揃えられる
SimpleTemplateEngine
import groovy.text.SimpleTemplateEngine
String applyTemplate(name, title) { String templateText = '''\ |こんにちは、${name}さん
| |本日は、<%= title %>をご案内させていただきます。
|... |'''.stripMargin() def template = new SimpleTemplateEngine().createTemplate(templateText)
def binding = [name: name, title: title] return template.make(binding)
}
println applyTemplate("日本 太郎", "プログラミングGROOVY")
//=> こんにちは、日本 太郎さん
// // 本日は、プログラミングGROOVYをご案内させていただきます。
// ...
JSP形式の変数展開もできる
テンプレート文字列を外部ファイルから読み込むようにすると良い(例)new File("template.tpl").text
#ccc_g4 でGradleからの利用例が紹介されたらしい?
(再掲)Groovyの使いどころ
メインのプログラミング言語 Grails/Spring Boot/Vert.x/Android
設定ファイルの記述言語(as DSL) Gradle/Spring Boot/Spring Framework
システムのプラグイン機構/拡張ポイント Javaから外部のGroovyスクリプトを簡単に実行できる Jenkins
プロジェクトの開発支援スクリプト DBのセットアップ、Excel操作等
万が一時間があれば紹介したい
Javaから Groovyを使う
例えば
システムにプラグイン機構を追加する 頻繁に変更されるビジネスロジック部分をGroovyスクリプトとして外だしする アプリの一部だけでも良いからGroovyで書きたい
JavaからGroovyを使う
Groovyで実装したクラスをJavaから利用する コンパイルしたクラスを同梱する Groovyのコンパイルには、groovycコマンドやGradleを使う クラスパスにgroovy-all.jarが含まれてさえいれば、普通に使える
Groovyソースコードから動的にクラスをロードする groovy-all.jarはもちろん必要
GroovyスクリプトをJavaから動的に実行する(CGI風) GroovyShell ちょっとスクリプトを実行するぐらいの用途ならば手軽で便利
GroovyScriptEngine 対象ディレクトリを事前指定するため、複数のGroovyスクリプトを取り扱うようなプラグイン機構などに向いている
コンパイルしたクラスを同梱する
コンパイルしたGroovyのクラスがクラスパスに通っていれば、普通に使える
Groovyソースコードから動的にクラスをロードする
GroovyClassLoaderを使うと、Groovyソースコードから実行時に動的にクラスロードできる
GroovyスクリプトをJavaから実行する: GroovyShell
Bindingというマップ的なオブジェクトを利用して、スクリプト上で利用可能な暗黙変数を渡すことができる
GroovyスクリプトをJavaから実行する: GroovyScriptEngine
スクリプトのルートディレクトリをあらかじめ指定しておく
まとめ
Groovyの使いどころ
メインのプログラミング言語 Grails/Spring Boot/Vert.x/Android
設定ファイルの記述言語(as DSL) Gradle/Spring Boot/Spring Framework
システムのプラグイン機構/拡張ポイント Javaから外部のGroovyスクリプトを簡単に実行できる Jenkins
プロジェクトの開発支援スクリプト DBのセットアップ、Excel操作等
というわけで、まずはこの辺から始めてみませんか?
メインのプログラミング言語 Grails/Spring Boot/Vert.x/Android
設定ファイルの記述言語(as DSL) Gradle/Spring Boot/Spring Framework
システムのプラグイン機構/拡張ポイント Javaから外部のGroovyスクリプトを簡単に実行できる Jenkins
プロジェクトの開発支援スクリプト DBのセットアップ、Excel操作等
これもおすすめ
参考情報
プログラミングGroovy
Java技術者を対象に、Groovyの基本から応用までを丁寧に説明しています 対象のGroovyバージョンが1.8とだいぶ古いのですが、記載されている内容はほとんど陳腐化してないので、今でもお勧めの一冊
http://gihyo.jp/book/2011/978-4-7741-4727-7
Gradle徹底入門
2014/11発売 世界に現存するGradle本の中でここまで突っ込んで説明したものはほぼないレベル 分厚いですが、その分情報がたっぷりです (私もレビュアで参加)
http://www.shoeisha.co.jp/book/detail/9784798136431
その他、参考URL
サンプルコード https://github.com/nobeans/jjug-ccc-2015-spring-groovy
Groovy Home http://groovy-lang.org/
Groovy JDK http://docs.groovy-lang.org/docs/latest/html/groovy-jdk/
Groovy 演算子オーバロード http://groovy-lang.org/operators.html#Operator-Overloading
GVM http://gvmtool.net/
GroovyServ http://kobo.github.io/groovyserv/
GExcelAPI https://github.com/nobeans/gexcelapi
NekoHTML http://nekohtml.sourceforge.net/
jsoup http://jsoup.org/
プログラミングGROOVY別冊:第8章 Groovy 2.0の新機能
http://beta.mybetabook.com/showpage/506162510cf2ffb79bb046b1
実用的なGroovy: JavaアプリケーションにGroovyを混ぜ込む
http://www.ibm.com/developerworks/jp/java/library/j-pg05245/
Embedding Groovy(JavaからGroovyを使う)
http://groovy.codehaus.org/Embedding+Groovy
Groovyの使いどころ~7つの導入パターン~ http://www.slideshare.net/nobeans/the-report-of-javaone2011-about-groovy
Java開発の強力な相棒として今すぐ使えるGroovy
Yasuharu Nakano / @nobeans2015-04-11 JJUG CCC 2015 Spring #ccc_g6