【初心者でも】やろうぜGroovy!〜ファイル読み書きしたり、Web APIたたいたり、レスポンスの中身確認したり〜編【今すぐ使える】

  • 83
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

【初心者でも】やろうぜGroovy!〜ファイル読み書きしたり、Web APIたたいたり、レスポンスの中身確認したり〜編【今すぐ使える】

はじめに

 JVM言語 Groovy。

 この投稿では、Groovy初心者の投稿者が、Javaを知っている方向けに、Groovyですぐ使えそうなこと、そして簡潔なコーディングを可能にするGroovyの言語機能を紹介します。

 Groovyを知らなかった方が、この投稿を読んで「Groovyいいな!面白そうだな。触ってみよう。」と、思ってもらえたらうれしいです。

 また、業務でメインに使わなくても、何かちょっとしたことをする時に使う言語としてGroovyを使う方が増えるとうれしいです。

概要

 この投稿では、Groovyを使って以下のことをやります。

  • Hello World
  • ファイルの読み書き
  • Web APIをたたく
  • Javaのライブラリを使う
  • JSONをパースする

 また、その途中で寄り道をし、Groovyの文法・記法・言語機能を紹介します。紹介するものは投稿者の独断と偏見で選んでいます。

想定読者

  • 授業・講義でJavaを学んだ学生の方
  • 研修でちょっとJavaを習ったことがある方
  • 業務でJavaを使っているor使ったことがある方

補足

 この投稿で紹介する内容と最後に記載するコードは、投稿者がWeb APIを使うことがテーマのハッカソンにおいて、初期段階で実際に書いたものがベースになっています。この投稿ではその時のハッカソンのWeb APIでなく、Qiita API v2を使いました。

 そのような時に(そのような時でなくても)、すぐ使えそうなコードスニペットも記載します。

Groovyとは?をちょっとだけ

 Groovyについて非常に簡単にですが説明します。気になった方・更に詳しく知りたい方は、技術評論社から出版されている「プログラミングGroovy」を読んで下さい。

 GroovyはJavaプラットフォーム上で動くプログラミング言語です。(JVM言語)

 すばらしい機能や特性がいくつもあるGroovyですが、本投稿を読む上で知っておいて欲しいのは次の2点です。

  • 構文も含め、きわめて高いJavaとの親和性を持つ
  • 非常に直感的で簡潔な記述が可能

 一つ目、「構文も含め、きわめて高いJavaとの親和性を持つ」。JavaのコードはほぼそのままでもGroovyのソースコードとして動作します。この特性により、Javaを知っている方には、とても少ない学習コストでGroovyを使い始めることが可能です。

 二つ目、「非常に直感的で簡潔な記述が可能」。GroovyではJavaの既存のクラスにメソッドが追加されたり、演算子に機能が追加されていたり、他にも様々な機能により非常に直感的で簡潔な記述をすることが可能になっています。これについては、この投稿でいくつか紹介します。

Hello World

 それではまず、Helo Worldするコードの書き方を紹介します。

JavaのHello World

 Javaで書く場合、IDEを立ち上げて、名前空間定義して、次のようなクラスを書くことが多いかと思います。

package com.mrstar.sample;

public class Main{
    public static void main(String[] args) {
        System.out.println("Hello Java world!");
    }
}

 Javaの場合、たいしたことをしなくても、クラスを定義し、メソッドを定義する必要がありますね。

GroovyのHello World

 Groovyの場合のHello Worldするコードは、

GroovyでHelloWorld!(1)
println "Hello Groovy world!"

 これだけです。クラスを定義する必要はありません。メソッドを定義する必要もありません。これだけです。(実は内部的にはクラスもメソッドも定義されていますが)

 Groovyはクラスやメソッドを書かなくても、いきなりやりたい処理のコードを書くことができます。これをスクリプトと言います。

GroovyでHelloWorld!(1)
println "Hello Groovy world!"

は、

GroovyでHelloWorld!(1)
System.out.println("Hello Groovy world!");

と本質的には同じです。

  • printlnメソッドはSystem.out.printlnメソッドのシンタックスシュガー
  • ()の省略
  • ;の省略

を活用しています。

 ちなみに実行は、先ほどのスクリプトをhello_world.groovy という名前で保存し、ターミナルで次のように入力します。

groovy hello_world

 もちろんIDEを利用することもできます。また、GroovyConsoleというものも便利です。

ファイルの読み書き

 Groovyでファイルの読み書きを行う方法を紹介します。その途中で寄り道をし、Groovyの記法・言語機能をいくつか紹介します。

ファイルに文字列を書き込む

 さて、早速ファイルの書き込みをしてみましょう。次のコードは、result.txtというファイルにHello Groovy world!を書き込むコードです。

ファイル書き込み
new File("result.txt").text = "Hello Groovy world!"

 これだけです。これだけ書けばOKです。

 疑問がわきませんか?

  • Fileクラスってjava.io.Fileクラスかな?でも、import文がないぞ?
  • Fileクラスだとしても、Fileクラスにtextなんてpublicなフィールドないぞ?

 これについてちょっと解説します。

デフォルトでインポートされているパッケージ

 実はGroovyでは、下記に示すパッケージとクラスはデフォルトでインポートされています。明示的にインポート文を書く必要はありません。

  • java.io.*
  • java.lang.*
  • java.math.BigDecimal
  • java.math.BigInteger
  • java.net.*
  • java.util.*
  • groovy.lang.*
  • groovy.util.*

 先ほどのスクリプト中のFileクラスは、java.io.Fileクラスです。java.io.* は、デフォルトでインポートされるので、import文を明示的に書く必要はありませんね。

拡張されたJavaのクラス

 実は、GroovyではJavaの既存のクラスに、いくつかメソッドが追加されています。java.io.Fileクラスにも便利なメソッドがいくつか追加されています。その一つが、setTextメソッドです。

ファイル書き込み
new File("result.txt").setText("Hello Groovy world!")

 さて、先ほどの例だとsetTextメソッドを使っているようには見えませんでした。textというpublicなフィールドにアクセスをしているように見えました。Groovyには、プロパティアクセスの簡略記法という言語機能があり、これを活用しています。

プロパティアクセスの簡略記法

 Groovyはhoge.getXxx()というメソッド呼び出しを、hoge.xxxというフィールドにアクセスする記法で呼び出すことができます。同様に、hoge.setYyy(fuga)は、hoge.yyy = fugaという記法で呼び出すことができます。

ファイル書き込み
new File("result.txt").text = "Hello Groovy world!"

 この例では、FileクラスのsetTextというメソッドを、プロパティアクセスの簡略記法により、textというフィールドにアクセスする形式で呼び出しています。

 また、今回は触れませんがGroovyBeansのゲッター、セッターの自動生成というも言語機能もあります。(こちら)

 Groovyのコードを読んでいて、「こんなメソッドないぞ?」と思っても焦らないでください。もしかしたら、それはJavaのクラスにGroovyで追加されたメソッドかもしれません。そしてgetXxxというメソッドを、xxxという形式で呼んでいるだけかもしれません。

defとは?

 続いてファイル読み込みをしましょう。ファイル読み込みもこれだけです。

ファイル読み込み
def text = new File("result.txt").text

 ここで新しいキーワード「def」が出てきました。

 defは、型を指定せず変数を定義するためのキーワードです。Groovyは「任意の型付け」です。変数などを定義するときに、型を指定してもいいですし、しなくてもいいです。

 型を指定した場合、エラーになるタイミングに注意が必要です。(標準では)型が違っていた場合でも、コンパイルエラーにはなりません。(Groovy2.0から静的型チェック静的コンパイルという機能が加わりました)

==とassert文

 さて、正しくファイルの読み書きができているのかを確認しましょう。

ファイル書き込みの動作確認
def fileContent = "Hello Groovy world!"

// 書き込み
new File("result.txt").text = fileContent

// 読み込み
def loadedContent = new File("result.txt").text

assert fileContent == loadedContent

 最後の一行、==assertがミソです。

 Groovyで==は、Javaのequals()メソッドの呼び出しと同じです。

 assert文は、Javaよりも強力になっていて、Javaと違い常に有効です。サンプルコードなどで良く出てきますので、Groovyを勉強するのであれば、覚えておいて損はありません。

Web APIをたたく

 次にQiitaのWeb APIをたたいてみます。途中で寄り道もします。

URLクラスで、Web APIをたたく

 下記のコードは、タグ一覧を取得するコードです。

QiitaのAPIをたたく
def urlString = "http://qiita.com/api/v2/tags?page=1&per_page=100"
def response = new URL(urlString).text
println response

 こんなに簡潔なコードで、QiitaのWeb APIをたたいて、タグ一覧を取得することができています。

  • java.netに属するjava.net.URLクラスは、デフォルトでインポートされている
  • URLクラスに、getTextメソッドがGroovyでは追加されている
  • プロパティアクセスの簡略記法を用いている

 というのがポイントですね。

GStringを使う

 次は特定のタグの情報をQiitaのWeb APIで取得し、そのレスポンスをファイルに保存してみます。

def urlString = "http://qiita.com/api/v2/tags/Qiita"
new File("Qiita.json").text = new URL(urlString).text

 Qiita.jsonというファイルに、レスポンスを保存しました。では次に、Groovyのタグのレスポンスを保存しましょう。

def urlString = "http://qiita.com/api/v2/tags/Groovy"
new File("Groovy.json").text = new URL(urlString).text

 二カ所変えています。URLと保存するファイル名です。これをGStringという機能を使って書き換えます。

def tagName = "Groovy"
def urlString = "http://qiita.com/api/v2/tags/${tagName}"
new File("${tagName}.json").text = new URL(urlString).text

 変更箇所がtagNameの一カ所でよくなりました。

 GStringの書き方は、"${式}"もしくは"$変数名"です。他の例も見てみましょう。

def name = "Ryota"
assert "Hello $name!" == "Hello Ryota!"
assert "${5 * 3 + 1}" == "16"

 GStringにより、文字列を生成するコードが非常に簡潔に、そして読みやすくなります。

ファイルをダウンロード

 ファイルをダウンロードする方法を紹介します。

Groovyのタグの画像をダウンロード
def tagImageUrl = "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/4e19a0be49d1f319a6504116d90570f878262afd/medium.jpg?1364839430"
new File("GroovyTag.jpg") << new URL(tagImageUrl).openStream()

 これだけでファイルのダウンロードが可能です。ちなみに、QiitaにおけるGroovyのタグ画像をダウンロードしています。

 <<が出てきましたね。Javaでこれはビットシフトでしたね。ここではビットシフトをしているわけではありません。

 Groovyでは、java.io.Fileクラスに、leftShiftというメソッドが追加されています(こちら)。いくつかオーバーロードがありますが、ここでは、java.io.InputStreamを引数にとるオーバーロードです。

 上記のコードでは、leftShiftメソッドを呼び出しているようには見えませんね。しかし実は<<で、leftShiftメソッドを呼び出しているのです。Groovyでは、いくつかの演算子は特定のメソッドの呼び出しのシンタックスシュガーです。<<は、leftShiftメソッドの呼び出しのシンタックスシュガーです。

 後でまた違うクラスの<<や、+がメソッド呼び出しのシンタックスシュガーになっている例を紹介します。

Javaのライブラリを使う

 さて、Qiita Web APIのレスポンスの中身を見ていきます。JSONを扱う必要がありますね。ライブラリは何を使いましょうか?

 今回はまずGsonを使うことにしてみましょうか。では何をしましょうか?Jarファイルをダウンロードしましょうか?

Grapeでライブラリを利用

 実は、GroovyにはGrapeという便利な機能があります。Grapeを使えば、Jarファイルを手動でダウンロードする必要はありません。

@Grab(group='com.google.code.gson', module='gson', version='2.3')

 こんなにも短い記述でスクリプトでGsonを使えるようになります。

 またちょっとだけ脇道にそれて、Gsonを使ってリストやマップをJSON形式の文字列を出力してみます。

@Grab(group='com.google.code.gson', module='gson', version='2.3')

import com.google.gson.*

List<Integer> list = new ArrayList<Integer> ()
list.add (1)
list.add (2)
list.add (3)

assert new Gson().toJson(list) == "[1,2,3]"

Map<String, Integer> map = new LinkedHashMap<String, Integer> ();
map.put ("Taro", 20)
map.put ("Jiro", 19)
map.put ("Saburo", 18)

assert new Gson().toJson(map) == '{"Taro":20,"Jiro":19,"Saburo":18}'

 assert文で期待通りのJSON形式の文字列に変換されていることがわかると思います。短いGrapeの記述で、ばっちりGsonが使えていますね。

 java.util.Listなどのimport文が無いのに気がつきましたか?java.util.*もデフォルトでインポートされるのでしたね。

 実は上記のコードはもっと短くすることができます。Groovyでは、リストやマップを使う際、とても簡潔に記述できるのです。さらに脇道にそれてGroovyらしいコードに変えていきましょう。

Groovyでリストやマップを作る

 リストとマップのインスタンスの生成もGroovyらしく書くことでもっと短くできます。

リストとマップのインスタンス生成(1)
def list = []
list.add(1)
list.add(2)
list.add(3)

// 略

def map = [:]
map.put ("Taro", 20)
map.put ("Jiro", 19)
map.put ("Saburo", 18)

 初期値込みの初期化もこんな感じでスッキリと。

リストとマップのインスタンス生成(2)
def list = [1, 2, 3]
def map = [Taro:20, Jiro:19, Saburo:18]

 結局こんな感じで短くなります。

@Grab(group='com.google.code.gson', module='gson', version='2.3')

import com.google.gson.*

def list = [1, 2, 3]
assert new Gson().toJson(list) == "[1,2,3]"

def map = [Taro: 20, Jiro: 19, Saburo: 18]
assert new Gson().toJson(map) == '{"Taro":20,"Jiro":19,"Saburo":18}'

 とても短くなりましたね。

 さて本筋にもどりましょう。Qiita Web APIのレスポンスのJSONのパースでしたね。Gsonを使うならば、パースするためにクラスを定義しないといけません。若干面倒ですね。

 実は、GroovyにはJSONを扱うための便利なクラスが存在します。次の節ではそれを使ってJSONを扱う方法を紹介します。

 結局今回Gsonは使いません。この節では、Grapeを紹介するためGsonを使いました。短い記述のGrapeを利用して、普段使い慣れているJavaのライブラリを使えることをわかっていただけたでしょうか?投稿者は、Javaのライブラリを試す際、Grapeが使えるGroovyは非常に便利だと思っています。そのあたりを、こちら(Javaのライブラリをチョロっと試すなら、Groovyで試すといいと思うんです!)にまとめましたので、興味ある方は見て下さい。

JSONをパースする

 さてGroovyでJSONをパースしましょう。

JsonSlupperでJSONをパース

 パースには、JsonSlupperというクラスを使います。

import groovy.json.*

def qiitaTagJsonString = '''{
    "followers_count":8915,
    "icon_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/d6b60fd11159d42e0f9ad4edaeda2c0dbdec013c/medium.jpg?1364837545",
    "id":"Qiita",
    "items_count":434
}
'''

def qiitaTagJson = new JsonSlurper().parseText (qiitaTagJsonString)

assert qiitaTagJson.getClass() == groovy.json.internal.LazyMap

assert qiitaTagJson.get("id") == "Qiita"
assert qiitaTagJson["id"] == "Qiita"
assert qiitaTagJson.id == "Qiita"

 JsonSlurperクラスのインスタンスを作り、文字列をparseTextメソッドでパースすればOKです。Gsonの様に事前にクラスを作る必要はありません。

 '''で囲った文字列が初めて出てきました。'''(もしくは""")で囲った文字列には、文字列に改行を含めることができます。

 さて最初のassert文の結果からも分かりますが、パースした結果のインスタンスはgroovy.json.internal.LazyMapクラスのインスタンスですね。

 その後の3個のassert文を見てください。最初の1個はいいですね。Mapインターフェースにあるgetメソッドを呼び出していますね。問題はその後です。[]は配列の要素にインデックスでアクセスする時に使いますね?idというのは、groovy.json.internal.LazyMapクラスのフィールドでしょうか?

リストやマップの便利な記法・機能

 Groovyのマップ (Map)はJavaに対して便利な記法・機能が追加されています。

// 普通のgetメソッドです
assert qiitaTagJson.get("id") == "Qiita"
// C#のインデクサーの用に、`[]`の中にキーを指定します
assert qiitaTagJson["id"] == "Qiita"
// フィールドの用にアクセスできます。
assert qiitaTagJson.id == "Qiita"

 リスト(List)も便利な記法・機能が追加されています。

// C#のインデクサーの用に、`[]`の中にキーを指定します
def list = [0, 1, 2]
assert list[0] == 0
assert list[1] == 1
assert list[2] == 2

 もう少しだけ紹介します。

def list = []
list << 1
assert list == [1]
list << 2
assert list == [1, 2]

def newList = list + 3
assert newList == [1, 2, 3]
assert !(newList == list)

newList = list + [3, 4]
assert newList == [1, 2, 3, 4]

 何をやっているか直感的に想像できると思います。

 先ほども紹介しましたが、<<+は、メソッド呼び出しのシンタックスシュガーになっています。それぞれの演算子によりリストに対して良く行う操作を非常に簡潔に記述することが可能です。

 このように、Groovyではマップ(Map)やリスト(List)などが非常に簡潔に扱えるようになっています。

クロージャを使った便利なメソッド

 続けて、クロージャを使った便利なメソッドをすこしだけ紹介します。

 Groovyにはクロージャがあります。Groovy公式ページ(日本語約)の「クロージャ」によると

Groovyのクロージャは「コードブロック」やメソッドポインタのようなものです。ひと固まりのコードとして定義され、後になって実行されます。

 とのことです。Groovyにはこのクロージャを引数にとるメソッドが多く存在します。また、多くのJavaクラスにもクロージャを引数にとるメソッドが多く追加されています。

 例えば、FileクラスのeachLineメソッドがあります。

FileクラスのeachLineメソッド(1)
new File("result.txt").eachLine( { String line ->
    println line
})

 上記のメソッドは、ファイルを読み込み一行ずつ表示するコードです。

 上記のコードは、次のように書くことも可能です。

FileクラスのeachLineメソッド(2)
new File("result.txt").eachLine{
    println it
}

 実はGroovyのクロージャには様々な書き方があります。こちらに書き方をまとめたので、興味がある方は読んでみてください。

 続けてクロージャを用いるコレクション関連のメソッドを紹介します。C#のLINQ、Java8のストリームAPIのような、コレクションを扱う便利なメソッドがGroovyにはあります。JavaのObject[]、Collection、Listなど、さまざまなクラス・インターフェースにメソッドが追加されています。

 たくさんありますが、その中で二つだけ紹介します。

 まずfindAll。C#のLINQのWhere、Javaのfilterのように、特定の条件を満たす要素だけにフィルタリングします。

def list = [0, 1, 2, 3, 4, 5]
assert list.findAll { it % 2 == 0 } == [0, 2, 4]

 続けてcollect。C#のLINQのSelect、Javaのmapのように、それぞれの要素を変換します。

def list = [0, 1, 2, 3, 4, 5]
assert list.collect { it * 2 } == [0, 2, 4, 6, 8, 10]

コードスニペット

この投稿で紹介したコードスニペットを再掲します。
(JSON形式で文字列出力は、次の節で出てきます。)

HelloWorld

HelloWorld
println "Hello Groovy world!"

ファイル書き込み

ファイル書き込み
new File("result.txt").text = "Hello Groovy world!"

ファイル読み込み

ファイル読み込み
def text = new File("result.txt").text

Web APIをたたく

WebAPIをたたく
def urlString = "http://qiita.com/api/v2/tags?page=1&per_page=100"
def response = new URL(urlString).text
println response

Web APIのレスポンスをテキストでファイルに保存

WebAPIのレスポンスをテキストでファイルに保存
def urlString = "http://qiita.com/api/v2/tags/Groovy"
new File("Groovy.json").text = new URL(urlString).text

ファイルダウンロード

ファイルダウンロード
def tagImageUrl = "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/4e19a0be49d1f319a6504116d90570f878262afd/medium.jpg?1364839430"
new File("GroovyTag.jpg") << new URL(tagImageUrl).openStream()

JSONをパース

JSONをパース
import groovy.json.*

def qiitaTagJsonString = '''{
    "followers_count":8915,
    "icon_url":"https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/d6b60fd11159d42e0f9ad4edaeda2c0dbdec013c/medium.jpg?1364837545",
    "id":"Qiita",
    "items_count":434
}
'''

def qiitaTagJson = new JsonSlurper().parseText (qiitaTagJsonString)

JSON形式で文字列出力

JSON形式で文字列出力
import groovy.json.*

def qiitaTagMap = [
    followers_count:8915,
    icon_url: "https://s3-ap-northeast-1.amazonaws.com/qiita-tag-image/d6b60fd11159d42e0f9ad4edaeda2c0dbdec013c/medium.jpg?1364837545",
    id: "Qiita",
    items_count: 434
]

def qiitaTagJsonString = new JsonBuilder(qiitaTagMap).toString()

Javaのライブラリを使う

Javaのライブラリを使う(Gson)
@Grab(group='com.google.code.gson', module='gson', version='2.3')

import com.google.gson.*

assert new Gson().toJson([1, 2, 3]) == "[1,2,3]"
assert new Gson().toJson([Taro: 20, Jiro: 19, Saburo: 18]) == '{"Taro":20,"Jiro":19,"Saburo":18}'

WebAPIたたいたり、ファイル保存したり、いろいろしてみる

 この節では、3個のサンプルコードを示します。今までで紹介したGroovyの書き方を多く用いています。

 Wen APIを用いるハッカソンの初期段階などで、似たようなことをする際に参考に慣れば幸いです。

その1

 特定ユーザーの投稿の一覧を取得し、IDとタイトルのリストを生成し、JSON形式でファイルに保存します。(投稿上限は100件)

import groovy.json.*

def userId = "???" // 誰かのQiita IDを入れてください
def urlString = "http://qiita.com/api/v2/users/${userId}/items?per_page=100"
def responseString = new URL(urlString).text
def response = new JsonSlurper().parseText (responseString)

def idTitleList = response.collect { [id:it.id, title:it.title] }
def idTitleListJson = new JsonBuilder(idTitleList).toPrettyString()

new File("idTitleList.json").text = idTitleListJson

その2

 特定のユーザーが今までの投稿タグ一覧を表示します。(タグ上限は100個)

import groovy.json.*

def userId = "???" // 誰かのQiita IDを入れてください
def urlString = "http://qiita.com/api/v2/users/${userId}/items?per_page=100"
def responseString = new URL(urlString).text
def response = new JsonSlurper().parseText (responseString)

def tags = response.collectMany { it.tags.collect { it.name } }.unique()
tags.each {
    println it
}

その3

 特定ユーザーがフォローしているタグの画像をダウンロードします。(タグ上限は100個)

import groovy.json.*

def userId = "???" // 誰かのQiita IDを入れてください
def urlString = "http://qiita.com/api/v2/users/${userId}/following_tags?per_page=100"
def responseString = new URL(urlString).text
def response = new JsonSlurper().parseText (responseString)

response.each {
    def tagIconUrl = it.icon_url
    def tagName = it.id
    if (tagIconUrl) {
        new File("${tagName}.jpg") << new URL(tagIconUrl).openStream()
    }
}

まとめ

 この投稿では、

  • Hello World
  • ファイルの読み書き
  • Web APIをたたく
  • Javaのライブラリを使う
  • JSONをパースする

 のやり方を示しながら、Groovy初心者の投稿者が独断と偏見で選んだGroovyの言語機能を紹介しました。

 また、Web APIを使うハッカソンなどで、今すぐ使えるコードスニペットと、ちょっとの改変で使えたり、やりたいことの参考になりそうな、サンプルコードも掲載しました。(ちなみに、実際に参加したハッカソンの初期段階で使ったコードをベースにしています。)

 投稿者はメインで使っている言語はC#です。(過去にJavaもちょっとだけ)。実はGroovyは何かちょっとしたことをやる時に使うくらいです。ちょっと前に出場した、Web APIを使うハッカソンで実際にGroovyを使ってみて、その簡潔さと柔軟さ、すばらしさを再認識しました。

 Javaプログラマーの皆様、Groovyに少しでも興味を持っていただけたでしょうか?

 興味を持った方、Groovy触ってみましょう。Ruby、Pythonもいいですが、1回Groovyも選択肢に入れてみてください。プログラミングGroovyを読んでみましょう!早速、GVMをインストールしてみましょう!(@uehajさんのこちらのブログがとても分かりやすいです。)

 そして、ブログ書きましょう!今、Groovyをやっているみなさん、レベルが高すぎて...Groovyを勉強し始めた方が増え、「あ〜、それちょうど俺も知りたかったやつ!」みたいなブログが出てくると、投稿者はとてもうれしいです。

関連投稿