倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

あんたに GDK の何が分かるっていうの!? (Object 編5) デバッグ用メソッド

今回は GDK が Object クラスに追加しているデバッグ用のメソッドを見ていきます。 主に print(), println() のような標準出力へメッセージを書き出すメソッドや、オブジェクトの情報を文字列に変換する toString(), dump(), inspect() などを扱います。 具体的には以下のメソッドを見ていきます:

// 標準出力への書き出し
void print(Object value)
void println()
void println(Object value)
void printf(String format, Object arg)
void printf(String format, Object[] values)

// PrintWriter への書き出し
void print(PrintWriter out)
void println(PrintWriter out)

// 文字列のフォーマット
String sprintf(String format, Object arg)
String sprintf(String format, Object[] values)

// オブジェクトの情報を文字列として返す
String toString()
String inspect()
String dump()

もう少し各メソッドを詳しく見ると

メソッド 返り値 since 説明
print(Object)
println()
println(Object)
printf(String, Object)
printf(String, Object[])
void 1.0 標準出力へメッセージを書き出す。
print(PrintWriter)
println(PrintWriter)
void 1.0 引数の PrintWriter へ文字列を書き出す
sprintf(String, Object)
sprintf(String, Object[])
String 1.5.0 フォーマットした文字列を返す
toString() String 1.0 Object#toString()。 ただし、GDK がオーバライドしているものもある
inspect() String 1.0 ターミナル上でそのオブジェクトを表現する文字列を返す
dump() String 1.0 オブジェクトのクラス、ハッシュコード、フィールドを示す文字列表現を返す。

inspect() や dump() は GroovyConsole とかでオブジェクトの内部情報を知りたいときなどに使うんじゃないかなぁ。 println(Object) と println(PrintWriter) は出力されるオブジェクトが引数かメソッドを呼び出されたオブジェクトかが異なるので注意(print() メソッドに関しても同じ)。 ではサンプルコードを見ていきましょう。

標準出力へ書き出す

まずは古き良きデバッグ方法、標準出力へメッセージを書き出すメソッド:

  • print(Object)
  • println()
  • println(Object)
  • printf(String, Object)
  • printf(String, Object[])

println() は改行のみ。 println(Object) は最後に改行も書き出します。 printf() は %s とか %d とかのプレースホルダーを使用してフォーマットを指定するやつです。 JDK 1.5 以降が必要です。

そういえば、Java では標準出力へ書き出すときに「System.out.println(...)」としないといけないけど、Groovy では「println(...)」だけでいいよ!という風に Groovy の簡潔さをアピールしていることがよくあるけど、この println() は GDK が Object クラス(よって全てのオブジェクト)に追加した println() メソッドを呼び出すようになってるんですよね。 なんというか、エレガントなような、力ずくなような実現方法っすねw 使い方に関してはあんまり説明いらないかと:

print 'Hello, GDK world!'
println()
println 'Hello, GDK world!'
printf "I am %s, %d years old.\n", 'waman', 100

最後の printf() では、配列やリストにしなくてもよさそうです。 これを実行すると以下のように表示されます:

Hello, GDK world!
Hello, GDK world!
I am waman, 100 years old.

printf() で使えるフォーマット指定は JavaDoc とか見てちょ。

PrintWriter へ書き出す

次は PrintWriter オブジェクトを指定して、メソッドを呼び出されたオブジェクトをそこへ書き出すメソッド。

  • print(PrintWriter)
  • println(PrintWriter)

の2つがあります

new File('log.txt').withPrintWriter{ out ->
    'Hello, GDK world!\n'.print(out)
    'Hello, GDK world!\'.println(out)
}

これを実行すると、log.txt ファイルに以下のように書き込まれます:

Hello, GDK world!
Hello, GDK world!

println() は改行を勝手に書き出してくれますよっと。

sprintf() メソッド

sprintf() メソッドは printf() メソッドと同じくフォーマットを指定しますが、標準出力へ書き出す代わりにフォーマットされた文字列を返します。

assert sprintf('I am %s.', 'waman') == 'I am waman.'
assert sprintf('I am %s, %d years old.', 'waman', 100) == 'I am waman, 100 years old.'

複数のプレースホルダーを使う場合は複数のオブジェクトを引数に渡す必要がありますが、printf() と同じく配列やリストにする必要はないようで。

toString(), inspect(), dump() メソッド

最後はオブジェクトの情報を文字列として返すメソッド。

  • toString()
  • inspect()
  • dump()

の3つがあります。 toString() は Java にもありますが、配列やコレクションの toString() メソッドは GDK でオーバーライドされてるようで、もっと意味ありげな文字列が返されます。 他の2つは GDK のドキュメントには説明が軽く書かれてますが、実際にいくつかのオブジェクトで試してみるのが一番かと。 ということでサンプルを:

def printStrings(arg){
    println "toString() : $arg"
    println "inspect()  : ${arg.inspect()}"
    println "dump()     : ${arg.dump()}"
    println()
}

//***** Integer *****
printStrings(100)
// toString() : 100
// inspect()  : 100
// dump()     : <java.lang.Integer@64 value=100>

//***** BigDecimal *****
printStrings(100.0)
// toString() : 100.0
// inspect()  : 100.0
// dump()     : <java.math.BigDecimal@7919 intVal=null scale=1 precision=4 stringCache=100.0 intCompact=1000>

//***** String *****
printStrings('倭マン')
// toString() : 倭マン
// inspect()  : '倭マン'
// dump()     : <java.lang.String@13314c2 value=倭マン hash=20124866 hash32=0>

//***** List *****
printIStrings([0, 1, 2, 3])
// toString() : [0, 1, 2, 3]
// inspect()  : [0, 1, 2, 3]
// dump()     : <java.util.ArrayList@e1b83 elementData=[0, 1, 2, 3] size=4 modCount=1>

//***** Set *****
printStrings([0, 1, 2, 3] as Set)
// toString() : [0, 1, 2, 3]
// inspect()  : [0, 1, 2, 3]
// dump()     : <java.util.LinkedHashSet@6 map=[0:java.lang.Object@826e7, 1:java.lang.Object@826e7, 2:java.lang.Object@826e7, 3:java.lang.Object@826e7]>

//***** Map *****
printStrings([a:0, b:1, c:2, d:3])
// toString() : [a:0, b:1, c:2, d:3]
// inspect()  : ['a':0, 'b':1, 'c':2, 'd':3]
// dump()     : <java.util.LinkedHashMap@18c header=null=null accessOrder=false table=[null, null, c=2, d=3] size=4 threshold=3 loadFactor=0.75 modCount=4 hashSeed=1408156617 entrySet=[a=0, b=1, c=2, d=3] keySet=null values=null>

//***** Array *****
printStrings([0, 1, 2, 3] as Object[])
// toString() : [0, 1, 2, 3]
// inspect()  : [0, 1, 2, 3]
// dump()     : <[Ljava.lang.Object;@a3d150>

//***** User-defined Class *****
class Person{
    String name
    int age
}

def waman = new Person(name:'倭マン', age:100)
printStrings(waman)
// toString() : Person@8f2016
// inspect()  : Person@8f2016
// dump()     : <Person@8f2016 name=倭マン age=100>

inspect() が返す文字列は toString() のものとそんなに変わらないけど、dump() は結構内部情報をさらけ出してるようですね。 プロパティですらない private なフィールドっぽいものも出力されてるよね・・・ まぁ、こんな感じで。

次回はメタプログラミング用メソッドの予定。

追記

println(Object) と println(PrintWriter) の GDK のドキュメントをみると

/**
 * Print a value formatted Groovy style to self if it is a Writer,
 * otherwise to the standard output stream.
 */
public void print(Object value);

/**
 * Print to a console in interactive format.
 */
public void print(PrintWriter out);

となってるんですが、英語力不足のためかいまいち何を言ってるのか・・・ とりあえず

def log = new File('log.txt')
log.withWriter{ writer ->
    writer.print('Hello, GDK world!')
}
assert log.text == 'Hello, GDK world!'

という風に writer オブジェクトに対して println(Object) を呼び出すと、標準出力ではなくその Writer へ引数のオブジェクトの文字列表現が書き出されるようです。 println(Object) も同じです。 print(PrintWriter) に関しては「interactive format」というのがよくわからん・・・

プログラミングGROOVY

プログラミングGROOVY

あんたに GDK の何が分かるっていうの!? (Object 編4) コンテナ・メソッド 後編

前回に引き続き、今回も GDK が Object クラスに追加しているコンテナ・メソッドを見てきます。 今回見ていくのは find で始まる名前のメソッド。 結構あれこれあるんですね。 今回見ていくメソッドは以下のもの:

Collection findAll(Closure closure)
Collection findAll()

Object find(Closure closure)
Object find()

Object findResult(Closure closure)
Object findResult(Object defaultResult, Closure closure)

int findIndexOf(Closure closure)
int findIndexOf(int startIndex, Closure closure)

int findLastIndexOf(Closure closure)
int findLastIndexOf(int startIndex, Closure closure)

List findIndexValues(Closure closure)    // 返り値は List<Integer>
List findIndexValues(Number startIndex, Closure closure)

引数をとらない findAll(), find() メソッドは、前回と同じく恒等変換(引数をそのまま返す)クロージャ Closure.IDENTITY を引数として渡したのと同じです。

メソッド 返り値 since 説明
findAll(Closure)
findAll()
Collection 1.6.0
1.8.1
引数のクロージャを適用して true を返した要素を集めて Collection として返す
find(Closure)
find()
Object 1.0
1.8.1
引数のクロージャを適用して true を返した最初の要素を返す
findResult(Closure)
findResult(Object, Closure)
Object 1.7.5 要素に引数のクロージャを適用して null でなかった最初の結果を返す
findIndexOf(Closure)
findIndexOf(int, Closure)
int 1.5.0 引数のクロージャを適用して true を返す最初の要素の番号を返す
findLastIndexOf(Closure)
findLastIndexOf(int, Closure)
int 1.5.2 引数のクロージャを適用して true を返す最後の要素の番号を返す
findIndexValues(Closure)
findIndexValues(Number, Closure)
List 1.5.2 引数のクロージャを適用して true を返す要素の番号をリストにして返す
  • findAll() メソッドは関数型言語filter と同じですね。
  • findResult() は変換後の結果を返すところが find() と異なります。
  • findIndexOf(), findLastIndexOf(), findIndexValues() のインデックス検索メソッドに int 値を渡すと、その番号以降(指定した番号を含む)にインデックス検索が実行されます。

では各メソッドのサンプルを見ていきましょう。

findAll() メソッド

findAll() は関数型言語の filter に相当するメソッドで、引数のクロージャが true を返すかどうかでフィルタリングするメソッドです。 前回見た grep() と同じようなものですね。

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']
assert lang.findAll{ it.startsWith 'J' } == ['Java', 'Jython', 'JRuby']

assert (0..10).findAll() == 1..10    // 0(false と評価される)以外を通すフィルタ

2つ目の引数をとらない findAll() では Closure.IDENTITY が指定されているのと同じです。 findAll() は結構多用するメソッドかと。

find() メソッド

find() は findAll() 似ていますが、引数のクロージャを適用して true を返す要素を1つ返します。 ドキュメントで「最初の」と指定されているので、リストや配列では番号順で最初のものが返される仕様でしよう。 Groovy では Set もデフォルトで LinkedHashSet (順序を保持する Set)なので、この場合も同様。 普通の HashSet とかだとどれが返されるかは JVM のみぞ知る(知らない?):

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']

assert lang.find{ it.contains 'o' } == 'Groovy'
assert (0..10).find() == 1

findResult() メソッド

findResult() は引数のクロージャを各要素に適用して、返された値が null でない最初のオブジェクトを返します。 クロージャによる変換後のオブジェクトがカエサル点が find() と異なります:

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']

assert lang.findResult{ it.size() >= 7 ? it.toUpperCase() : null } == 'CLOJURE'
assert lang.findResult{ it.toLowerCase() } == 'java'
assert lang.findResult('javascript'){ it.endsWith('t') ? it.toLowerCase() : null } == 'javascript'

1つ目、3つ目のものは3項演算 ?: を使って無理矢理 null を返してるのがぎこちない(findResult() の前に findAll() とかを適用しておく方が自然)ですが、まぁ、あくまでサンプルですから。 3つ目のものは、全ての要素が null を返した場合のデフォルト値を指定('javascript')しています。

findIndexOf(), findLastIndexOf(), findIndexValue() メソッド

これらのメソッドは、条件に合う要素の番号を返すインデックス検索メソッド。 索引メソッドと言った方がいいか。 Java の String クラスに定義されている indexOf(), lastIndexOf() とかと同じっス。 String クラスのものは文字か文字列をしていますが、ここで扱うメソッドではクロージャを指定して true を返すかどうかで合ってるかどうかを指定しています。 サンプルはこんな感じ:

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']

// findIndexOf()
assert lang.findIndexOf{ it.contains 'o' } == 1
assert lang.findIndexOf(3){ it.contains 'o' } == 3

// findLastIndexOf()
assert lang.findLastIndexOf{ it.contains 'o'} == 5
assert lang.findLastIndexOf(3){ it.contains 'o' } == 5    // lastIndexOf() でも指定した int 値は検索開始位置
assert lang.findLastIndexOf(6){ it.contains 'o' } == -1

// findIndexValues()
assert lang.findIndexValues{ it.contains 'o' } == [1, 3, 4, 5]
assert lang.findIndexValues(3){ it.contains 'o' } == [3, 4, 5]

まぁ、特に問題はないかと。

次回デバッグ用のメソッド(予定)。

プログラミングGROOVY

プログラミングGROOVY

あんたに GDK の何が分かるっていうの!? (Object 編3) コンテナ・メソッド 前編

今回は GDK が Object クラスに追加しているコンテナ・メソッド。 ちょっと多かったので前後編に分けます(後編は findXxxx() メソッド)。 コンテナ・メソッドとはコレクションや配列のような、要素を保持する入れ物を指して使っています。 今回扱うメソッドは、通常のオブジェクトに対しては「要素がそれ自身だけのコンテナ」として機能するのであまり便利というわけではありませんが、コレクションや配列に使う目的で定義されているようです*1。 今回扱うメソッドは以下の通り:

Object each(Closure closure)
Object eachWithIndex(Closure closure)

Collection grep(Object filter)
Collection grep()

List collect(Closure transform)
Collection collect(Collection collector, Closure transform)
Collection collect()

Object inject(Closure closure)
Object inject(Object initialValue, Closure closure)

boolean every(Closure closure)
boolean every()

boolean any(Closure closure)
boolean any()

Collection split(Closure closure)

Iterator iterator()

引数をとらない grep(), collect(), every(), any() は、Closure.IDENTITY (恒等変換、引数をそのまま返すクロージャ)を渡したのと同じになります。 各メソッドをもう少し詳しく表にするとこんな感じ:

メソッド 返り値 since 説明
each(Closure)
eachWithIndex(Closure)
Object 1.0 各要素を走査する
grep(Object)
grep()
Collection 1.5.6
1.8.1
引数のオブジェクトを isCase() によるフィルターとして適用する
collect(Closure)
collect(Collection, Closure)
collect()
List
Collection
Collection
1.0
1.0
1.8.5
各要素に変換を施して、結果のオブジェクトを集めたコレクションを返す
every(Closure)
every()
boolean 1.0
1.5.0
各要素に boolean 値を返すクロージャを適用し、その論理積を返す
any(Closure)
any()
boolean 1.0
1.5.0
各要素に boolean 値を返すクロージャを提供し、その論理和を返す
inject(Closure)
inject(Object, Closure)
Object 1.8.7
1.5.0
各要素を走査して値を計算する(リダクション)
split(Closure) Collection 1.6.0 各要素に boolean 値を返すクロージャを適用し、
true/false で分割した(多重)コレクションを返す
iterator() Iterator 1.0 各要素を走査する Iterator を返す

collect(Closure) の返り値が Collection ではなく List なのは何か意味あるのかな? まぁ、スクリプトとして使う分には大して型を気にする必要はないんですが。 それはともかく、以下でサンプルコードを見ていくに際してちょっと前提を。 まずは単なるオブジェクトに対して。

class Person{
    String name
    int age
}

new Person(name:'倭マン', age:100).each{ println it }

これを実行すると

Person@a1234b

のような表示がされ、通常のオブジェクトは自分自身のみを要素とするコンテナ・オブジェクトとして扱われていることがわかります。 これだと全然面白くないので、以下では List オブジェクトに対して各メソッドを実行して行きます。

each(), eachWithIndex() メソッド

each() メソッドは各要素を列挙して引数のクロージャを適用するメソッドです。 eachWithIndex() はクロージャの第2引数にインデックスが割り当てられます:

def lang = ['Java', 'Groovy', 'Scala', 'Clojure']

lang.each{ println it }
// Java
// Groovy
// Scala
// Clojure
// と表示される

lang.eachWithIndex{ s, i -> println "$i) $s"}    // クロージャの第2引数にはインデックスが割り当てられる
// 0) Java
// 1) Groovy
// 2) Scala
// 3) Clojure
// と表示される

each() は結構多用するメソッドですね。

grep() メソッド

grep() は引数のオブジェクトを isCase() メソッドをもとにしたフィルターとして適用するメソッドです。 isCase() メソッドを条件式として扱う方法は以前の記事でも見ました。

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']
def ints = (0..10)

// grep()
assert lang.grep{ it.startsWith 'J' } == ['Java', 'Jython', 'JRuby']    // フィルターとしてのクロージャ
assert lang.grep(~/J\w*/) == ['Java', 'Jython', 'JRuby']    // フィルターとしての正規表現
assert ints.grep() == (1..10)    // Closure.IDENTITY をフィルタとして使用(0は false)

まぁ、そんなに難しくはないかと。 ただし、次回やる予定の findAll() メソッドもフィルターとして使えるメソッドですが、少々区別が必要。 クロージャをフィルタとして使う分には同じように使えばいいんですが、findAll() メソッドはクロージャ以外のオブジェクトを引数としてとれません:

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']

assert lang.findAll{ it.startsWith 'J' } == ['Java', 'Jython', 'JRuby']    // grep() と同じ

// 次のようにはできない! findAll() はクロージャのみを引数としてとる
// assert lang.findAll(~/J\w*/) == ['Java', 'Jython', 'JRuby']

collect() メソッド

collect() メソッドは各要素に変換を施して新たなコレクションを生成するメソッド。

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']

assert lang.collect{ it.toLowerCase() } == ['java', 'groovy', 'scala', 'clojure', 'kotlin', 'jython', 'jruby']

assert lang.collect(new LinkedList()){ it.toUpperCase() } instanceof LinkedList

assert lang.iterator().collect() == ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']

使用したいコレクションの型があるなら、2つ目のようにそのインスタンスを第1引数として渡します。 引数を録らない collect() は、Iterator のようなコレクションではないコンテナからコレクションを生成したいときや、コレクションのコピーが欲しいときに使うとよいようです。 ちなみに Groovy 特有の演算子*.」も collect() と似たような働きをします:

assert lang*.size() == [4, 6, 5, 7, 6, 6, 5]

collect() メソッドも結構多用するメソッド。

inject() メソッド

inject() メソッドは、リダクション用のメソッドです。 つまり、数値のリストに対してその要素の和を計算したりするのに使います。 和を計算する sum() メソッドは Collection クラスに定義されていますが、同じことを inject() メソッドで簡単に実行できます:

def ints = (0..10)

// inject()
assert ints.inject{ sum, i -> sum + i } == 55    // 0から10までの和
assert ints.grep().inject(1){ prod, i -> prod * i } == 3628800    // 1から10までの積

2つ目では0をかけるとまずいので grep() で取り除いています(0は false と評価されるので grep() のフィルターを通らない)。 1は初期値です。

every(), any() メソッド

次は論理演算の every() と any()。 各要素に対して引数のクロージャ(boolean 値を返す)を適用し、その結果に対して論理演算を施します。 every() が論理積(AND)、any() が論理和(OR)です。 英単語の意味を考えれば分かるかと。 サンプルはこんな感じ:

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']
def falseObjects = [0, 0.0, '', [], [:], [].iterator(), null]
def ints = (0..10)

// every()
assert lang.every{ it.size() >= 4 }
assert !lang.every{ it.contains 'oo' }

assert !falseObjects.every()    // すべて false
assert !ints.every()    // 0が false

// any()
assert lang.any{ it.contains 'oo' }
assert !lang.any{ it.contains 'z' }

assert !falseObjects.any()    // すべて false
assert ints.any()    // 0が false

split() メソッド

split() メソッドは各要素に引数のクロージャ(boolean 値を返す)を適用し、true を返すもの、false を返すものをそれぞれまとめてリストにして、そのリスト(リストのリスト)を返します:

def lang = ['Java', 'Groovy', 'Scala', 'Clojure', 'Kotlin', 'Jython', 'JRuby']
assert lang.split{ it.contains 'o' } == [
    ['Groovy', 'Clojure', 'Kotlin', 'Jython'],    // it.contains('o') == true
    ['Java', 'Scala', 'JRuby']    // it.contains('o') == false
]

まぁ、やってることは簡単ですね。

どれもコンテナ・オブジェクトのためにあるメソッドばかりでしたね。 次回はコンテナ・メソッドの後編。 findXxxx() という名前のメソッドを見ていきます。

プログラミングGROOVY

プログラミングGROOVY

*1:コレクションと配列には継承関係がないので、Object クラスにこれらを定義するしかなかったんじゃないかと。

あんたに GDK の何が分かるっていうの!? (Object 編2) プロパティ、リフレクション

今回は GDK が Object クラスに追加しているプロパティ、リフレクション関連のメソッド。 (正式な仕様はともかく)Groovy でのプロパティとは getter/setter メソッド*1で値の取得・設定ができるメンバという感じでしょうか。 Groovy ではフィールドを定義すると自動的に getter/setter メソッドが追加される*2ので、フィールドもプロパティになります。 これを踏まえて、今回見ていくメソッドは以下のもの:

// プロパティ
Map getProperties()
Object getAt(String name)
void putAt(String name, Object value)

// メソッド名によるメソッド実行
Object invokeMethod(String name, Object args)

Object identity(Closure closure)
Object with(Closure closure)

各メソッドをもう少し詳しく見てみるとこんな感じ:

メソッド 返り値 since 説明
getProperties() Map 1.0 クラスに定義されている全プロパティを Map として取得
getAt(String) Object 1.0 「obj['prop']」形式でプロパティの値を取得する
putAt(String, Object) void 1.0 「obj['prop'] = value」形式でプロパティの値を設定する
invokeMethod(String, Object) Object 1.0 メソッド名を指定してメソッドを実行する
identity(Closure) Object 1.0 with() と同じ
with(Closure) Object 1.5.0 with() を呼び出されたオブジェクトを delegate 対象としてクロージャを実行する

identity() メソッドは with() メソッドと同じなので以降では扱いません。 以下で簡単なサンプルコードを見ていきましょう。

プロパティ


まずはプロパティに関するメソッド。 以下のように Person クラスが定義されているとしましょう:

class Person{
    String name
    int age
}

このとき、以下のようにプロパティにアクセスすることができます:

def waman = new Person()

// putAt() によるプロパティ値の設定
waman['name'] = '倭マン'
waman['age'] = 100

// getAt() によるプロパティ値の取得
assert waman['name'] == '倭マン'
assert waman['age'] == 100

// プロパティを Map オブジェクトとして取得
waman.getProperties().each{ key, value ->    // 各プロパティを列挙
    println "$key : $value"
}
// class : class Person
// age : 100
// name : 倭マン
// と表示(順序は違うかも)

どのクラスにも「class」プロパティは常に存在します(getClass() メソッドがあるので)。 ちなみに、final フィールド、getter のみのプロパティなども getAt() や getProperties() で取得できます:

class Person{

    String name
    int age
    final String state = 'alive'

    String getSex(){
        return 'male'
    }

    boolean isMale(){
        return sex == 'male'
    }
}

new Person(name:'倭マン', age:100).getProperties().each{ key, value ->
    println "$key : $value"
}
// class : class Person
// male : true
// sex : male
// age : 100
// state : alive
// name : 倭マン
// と表示

ちなみに properties という名前のプロパティはありませんが、あたかもあるかのようにプロパティを参照することができます。 つまり、以下の3つの呼び出しは同じです:

waman.getProperties()
waman['properties']
waman.properties

最後のものは今回扱うメソッドと関係ありませんが、フィールドアクセスのように記述できるので一番使いやすいかと。 実際には1つ目の getter によるアクセスをしているのと同じですが。 この記法による挙動を変更したい場合は、getAt() / putAt() ではなく

Object getProperty(String name)
void setProperty(String name, Object value)
Object propertyMissing(String name, Object value)

というメソッドを使います。 詳しくは『プログラミングGROOVY』参照。

メソッドの実行


次はメソッドをメソッド名で実行する invokeMethod() メソッド。 Person クラスにいくつかメソッドを定義しておきましう:

class Person{

    String name
    int age

    // 引数なし
    def introduce(){
        println "拙者の名前は${name}でござる。 齢${age}で候。"
    }

    // 引数1つ
    def greeting(Person someone){
        println "こんにちは、${someone.name}さん。"
    }

    // 引数2つ
    def greeting(String name, int age){
        greeting(new Person(name:name, age:age))
    }
}

invokeMethod() メソッドはメソッド名以外に引数を渡す場合に1つのオブジェクトをとるだけなので、ちょっと注意が必要かな:

def waman = new Person(name:'倭マン', age:100)

// 引数なし -> null
waman.invokeMethod('introduce', null)

// 引数1つ -> オブジェクト
waman.invokeMethod('greeting', new Person(name:'サムワン', age:99))

// 引数2つ(複数) -> リスト(配列でもよさそう)
waman.invokeMethod('greeting', ['そもさん', 1000])

引数が2つ(以上)の場合、第2引数は List や配列なら大丈夫そうです。 ただし、後日やる printf() メソッドのように invokeMethod('greeting', 'そもさん', 1000) という風にはできませんでした。 第2引数が Object の配列だったらできるのかな? ちなみに、メソッドがない場合の挙動を変更したい場合、propertyMissin() メソッドと同様の

Object methodMissing(String name, Object args)

メソッドを定義することで好きな処理を行えます。

with() メソッド


with() メソッドは、クロージャの delegate 対象、つまりプロパティアクセスやメソッド実行を行われるオブジェクトを指定して実行するメソッドです。 最初は使い勝手が分かりにくいメソッドですが、使えるようになると何かと便利。

def waman = new Person()

waman.with{
    name = 'Waman'    // waman オブジェクトに対して name プロパティの値が設定される
    age = 50
    introduce()    // waman オブジェクトに対して introduce() メソッドが実行される
}

assert waman.name == 'Waman'
assert waman.age == 50

waman.with{ ... } の部分で waman オブジェクトに対してプロパティアクセスやメソッド実行が行われます。

今回扱ったメソッドは Groovy のコードを見易くしてくれ、またメタプログラミングの入門にもなると思うので、Groovy を使うなら把握しておきたいメソッドばかり。 次回はコンテナ・メソッドの予定。

プログラミングGROOVY

プログラミングGROOVY

*1:getter だけでもよさそう。 boolean 値の isXxxx() 形式でも OK。

*2:フィールドに final 宣言を付けておくと、getter のみが追加される

あんたに GDK の何が分かるっていうの!? (Object 編1)

最近 Java 8 の Stream による Map/Reduce 処理の API をあれこれイジってて、GDK の同様のメソッド群と比べてみようと思い GDK の API 見てたんですが、each() とか collect() とかが Object クラスに定義されてて List や Collection の前に Object クラスから見ていかないといけないみたい。 まぁこれは配列でも同じメソッド使いたいからとかいうことなんでしょうかね。 ということで、今回から何回かにわたって GDK が Object クラスに追加しているメソッドを見ていきます。

目次

一応、第1目的はコンテナ・メソッドなので、それ以外のものは軽く流す感じで。 使用している Groovy のバージョンは 2.1.4 です。

言語サポート

まずは Groovy の制御構造などとともに使える特殊メソッド。 なんか呼び方があったような・・・ 扱うメソッドのシグニチャは以下の通り:

boolean asBoolean()
Object asType(Class type)
boolean isCase(Object switchValue)

boolean is(Object other)

それぞれのメソッドをもう少し詳しく見てみると

メソッド 返り値 since 説明
asBoolean() boolean 1.7.0 オブジェクトを boolean 値に変換。 if の条件式などで使える。
asType(Class) Object 1.0 強制型変換を行う。 as キーワードとともに使える。
isCase(Object) boolean 1.0 オブジェクトが表す条件に合うかどうかをテストする。 in や case とともに使える
is(Object) boolean 1.0 Java の == 演算子と同じ*1

といった感じ。 では、それぞれのメソッドを試してみましょう。 まずは asBoolean() メソッド:

assert 1.asBoolean() == true
assert 0.asBoolean() == false    // 整数値 0 は false

assert 1.0.asBoolean() == true
assert 0.0.asBoolean() == false    // 浮動小数点数の 0.0 も false

assert 'a'.asBoolean() == true
assert ''.asBoolean() == false    // 空文字列は false

assert ['a'].asBoolean() == true
assert [].asBoolean() == false    // 空リストは false

assert [a:3].asBoolean() == true
assert [:].asBoolean() == false    // 空マップも false

assert null.asBoolean() == false    // null も false、というかメソッド呼び出しできる

空文字列、0, 0.0, 空リスト、空マップ、null などは false を返します。 他にも Matcher や Iterator が false を返す場合もあるようです。 詳しくは『プログラミングGROOVY』を参照のこと。 上記のコードでは、asBoolean() を明示的に呼び出す必要がないものもありますが、こちらの方が値が分かりやすいかと思って付けてます。 次は asType() メソッド。 これは直接 asType() メソッドを呼び出すよりも as キーワードで使う方が分かりやすいかと:

// char 型の定数(Groovy ではシングルクォートも文字列を生成)
def ch = 'a' as char

// 配列の生成
def array = (0..10) as double[]
array.each{ println it }

// 文字列から BigDecimal へ変換
def d = "1.0" as BigDecimal
assert d + 2.0 == 3.0

// SAM (single-abstract-method) 型への変換
import java.awt.event.ActionListener
def al = { e -> println("I'm ActionListener") } as ActionListener
assert al instanceof ActionListener

Java 8 で導入されるラムダ式は SAM (single-abstract-method) 型へ自動で変換してくれますが、もちろん Groovy でも(既に)サポートされてます。 次は isCase() メソッド。 in, case 文とともに用いることが多いと思いますが、一見分かりにくいので isCase() メソッドを使ったものも書いてます:

assert (~/\w+/).isCase("abc")    // 正規表現
assert "abc" in ~/\w+/

assert String.isCase("abc")    // 型テスト instanceof
assert "abc" in String

assert [0, 1, 2, 3].isCase(3)    // リストの要素か?
assert 3 in [0, 1, 2, 3]

assert (0..10).isCase(3)    // 範囲内の要素か?
assert 3 in (0..10)

assert [a:1, b:2, c:3].isCase('a')    // 得られる値の asBoolean() が true を返すか?
assert 'a' in [a:1, b:2, c:3]

assert { it.size() == 3 }.isCase("abc")    // クロージャの評価が true か? (it が "abc")
assert "abc" in { it.size() == 3 }

in キーワードを使うと isCase() が呼ばれるオブジェクトと引数の位置が逆転するので注意。 右結合演算子っぽい感じ(Groovy に右結合とかないけど)。 最後は is() メソッド。 これは Java の == 演算子と同じです。 Groovy では == は Object#equals() メソッドによる評価になってるので、代わりに is() メソッドが定義されてます:

def s0 = new String('a'), s1 = new String('a')
assert s0 == s1    // 値が同じ
assert !s0.is(s1)    // 別のオブジェクト

プロセス・スレッド用メソッド

プロセスやスレッドに関するメソッド:

static void sleep(long milliseconds)
static void sleep(long milliseconds, Closure onInterrupt)

void addShutdownHook(Closure closure)

sleep() は「Thread.sleep(...)」と書くのが面倒なので「sleep(...)」だけでスリープできるようにしてるんでしょうね。 インターラプトされたときの処理も指定できるようになってます。

メソッド 返り値 since 説明
sleep(long) void 1.0 引数で指定されたミリ秒だけスリープする。
sleep(long, Closure) void 1.0 引数で指定されたミリビョウだけスリープする。 スリープ中にインターラプトされた場合は、第2引数のクロージャが実行される。
addShutdownHook(Closure) void 1.5.0 JVM が終了する際に実行される処理を追加する。

サンプルはこんな感じ:

// sleep(long)
sleep(1000)

// sleep(long, Closure)
import java.util.concurrent.*
def service = Executors.newCachedThreadPool()
service.submit{
    sleep(60000){
        println("I'm interupted")
        throw new RuntimeException()
    }
}
service.shutdownNow()    // 実行中のスレッドはキャンセル(インターラプト)される

// addShutdownHook()
addShutdownHook{
    println("shut down now.")    // JVM 終了時に出力
}

// たぶん、こんなのと同じ
Runtime.getRuntime().addShutdownHook(new Thread({ println("shut down now!") }))

まぁ、デバッグ時に使うことが多いんじゃないでしょうか。

かなりザッとですが Groovy 言語的に特別な意味をもったメソッドと、プロセス・スレッド関連のメソッドを見てきました。 次回はプロパティ・リフレクション関連のメソッドを見る予定。

追記
isCase() メソッドの箇所で、Map の isCase() が「キーとして値を含むかどうか」の真偽値を返すと書いてましたが、正しくは「引数をキーにして得られた値の asBoolean() が true かどうか」を返します。 キーとして存在しない場合に false を返すのは同じですが、キーとして存在する場合でも false を返すことがあります:

def map = [a:1, b:0]

assert map.isCase('a') == true
assert map.isCase('b') == false
assert map.isCase('c') == false

プログラミングGROOVY

プログラミングGROOVY

  • 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/07/06
  • メディア: 単行本(ソフトカバー)
  • 購入: 6人 クリック: 392回
  • この商品を含むブログ (155件) を見る

*1:Groovy では == 演算子は equal() メソッドによる評価と同じ

Groovy JDK? It's GDK! (File 編7) traverse() 再訪

今回は、以前の記事で簡単に見た traverse() メソッドをもう少しきちんと見て行きます(目次)。

参考 URL

オーバーロードされた3つの traverse()

まずは traverse() メソッドのオーバーロードされているシグニチャを見てみましょう:

void traverse(Map options)
void traverse(Closure closure)
void traverse(Map options, Closure closure) 

Map のみを引数にとる traverse() は、何の処理もなく何の値も返さないので一見無意味そうですが、あとで見るようにオプションを表す引数の Map に preDir, postDir というキーで Closure による処理を渡せます。

Closure のみを引数にとる traverse() はすべてのファイル・ディレクトリを走査して、引数の Closure の処理を行います。 走査する順序は深さ優先 (in a depth-first fashion) だそうです。 ただし、ちょっと試してみたところ

  • サブディレクトリよりも親ディレクトリの方が先に走査
  • 1つのディレクトリ下ではファイルよりもディレクトリが先に走査

といった感じです。

Map と Closure を引数にとる traverse() はオプションを表す Map によって指定された走査方法に基づいてファイル・ディレクトリを列挙し、それぞれに対して引数の Closure の処理を実行します。

オプション

次は引数の Map オブジェクトに指定できる項目を見ていきましょう。 これらはディレクトリ階層を走査する方法を指定するオプションという位置づけです。

名前 値の型 デフォルト値 説明
type FileType ANY 走査するファイルの種類
preDir Closure ディレクトリを走査する際の前処理
preRoot boolean false preDir の処理をルートディレクトリに対しても行うかどうか
postDir Closure ディレクトリを走査する際の後処理
postRoot boolean false postDir の処理をルートディレクトリに対しても行うかどうか
visitRoot boolean false Closure の処理をルートディレクトリに対しても行うかどうか
maxDepth int -1(infinite) 再帰を行うディレクトリ階層の深さ
filter Object 走査するファイルに対するフィルター
nameFilter Object 走査するファイルの名前に対するフィルター
excludeFilter Object 走査しないファイルに対するフィルター
excludeNameFilter Object 走査しないファイルの名前に対するフィルター
sort Closure 同一ディレクトリ内のファイル、ディレクトリを操作する順序
  • FileType は groovy.io.FileType です
  • type を指定しない場合は、ファイル、ディレクトリの両方が走査対象になります
  • preDir, postDir に渡す Closure は groovy.io.FileVisitResult オブジェクトを返すことによって、走査を終了 (FileVisitResult.TERMINATE) したり、サブディレクトリの走査をスキップ (FileVisitResult.SKIP_SUBTREE) したり、同ディレクトリ内の走査をスキップ (FileVisitResult.SKIP_SIBLINGS) したりすることができます*1
  • filter, nameFilter, excludeFilter, excludeNameilter の型は Object になっていますが、isCase() メソッドが呼べる(switch 文の case に指定できる・・・って感じ)ものであることが想定されています

groovy.io.FileType, groovy.io.FileVisitResult には、以下のような定数が定義されています:

package groovy.io

enum FileType{
    FILES,
    DIRECTORIES,
    ANY
}

enum FileVisitResult{
    CONTINUE,
    SKIP_SIBLINGS,
    SKIP_SUBTREE,
    TERMINATE
}

FileVisitResult は、java.nio.file にもほぼ同じ列挙型が定義されてますね。 まぁ、Visitor パターンの実装だから特に不思議でもないですが。

サンプルコード

以上を踏まえていくつかサンプルコードを見ていきましょう。 以前に java.nio の記事で扱ったサンプルを焼き直します(元ネタは Java のチュートリアルですが)。 まずは適当にディレクトリ階層を作っておきます。 もしサンプルを動かしたい場合はどうぞ:

// ***** 準備 *****
def ant = new AntBuilder()
ant.with{
    mkdir dir :'src/org/sample'
    mkdir dir :'src/org/sample/child1'
    mkdir dir :'src/org/sample/child2'
    mkdir dir :'src/org/sample/child2/grandchild2'

    touch file:'src/org/sample/Main.java'
    touch file:'src/org/sample/Sub.java'
    touch file:'src/org/sample/child1/Child11.java'
    touch file:'src/org/sample/child1/Child12.java'
    touch file:'src/org/sample/child1/Child1.groovy'
    touch file:'src/org/sample/child2/Child21.java'
    touch file:'src/org/sample/child2/Child22.java'
    touch file:'src/org/sample/child2/Child2.groovy'
    touch file:'src/org/sample/child2/grandchild2/Child22.groovy'
}

ファイルの内容は全て空です。 もうちょっとスマートに書けるでしょうけど、まぁあまり気にしないで下さい。 生成されるディレクトリ階層とファイルは以下のようになります:

.
  +src/
    +org/
      +sample/
        +Main.java
        +Sub.java
        +child1/
          +Child11.java
          +Child12.java
          +Child1.groovy
        +child2/
          +Child21.java
          +Child22.java
          +Child2.groovy
          +grandchild2/
            +Child22.groovy

これを踏まえて、いざサンプルコードへ。

traverse(Closure)
オプション指定なしの traverse() :

// find
new File('src').traverse{ println "[FIND] $it" }

出力結果は以下のようになります:

[FIND] src\org
[FIND] src\org\sample
[FIND] src\org\sample\child1
[FIND] src\org\sample\child1\Child1.groovy
[FIND] src\org\sample\child1\Child11.java
[FIND] src\org\sample\child1\Child12.java
[FIND] src\org\sample\child2
[FIND] src\org\sample\child2\Child2.groovy
[FIND] src\org\sample\child2\Child21.java
[FIND] src\org\sample\child2\Child22.java
[FIND] src\org\sample\child2\grandchild2
[FIND] src\org\sample\child2\grandchild2\Child22.groovy
[FIND] src\org\sample\Main.java
[FIND] src\org\sample\Sub.java
  • 同一ディレクトリ内なら、ファイルよりもサブディレクトリの方が先に走査されています
  • 深さ優先 (depth-first) で走査

filter
次は filter オプション:

// find
new File('src').traverse(filter:{ it.isDirectory() }){ println "[FIND] $it" }

「type:DIRECTORIES」 を指定すればいいじゃん、っていう指摘は無視w flter の値に指定する Closure には File オブジェクトが渡されます。 結果は以下の通り:

[FIND] src\org
[FIND] src\org\sample
[FIND] src\org\sample\child1
[FIND] src\org\sample\child2
[FIND] src\org\sample\child2\grandchild2

なんてことないですね。

nameFilter
次は filter に似た nameFilter オプション。 filter との違いは、ファイル名(ディレクトリ名)の String に対するフィルターだというところ:

// find
new File('src').traverse(type:FILES, nameFilter:~/.*\.java/){ println "[FIND] $it" }

ここでは正規表現を用いて「.java」という拡張子をもったファイルのみ走査しています。 出力結果は以下の通り:

[FIND] src\org\sample\child1\Child11.java
[FIND] src\org\sample\child1\Child12.java
[FIND] src\org\sample\child2\Child21.java
[FIND] src\org\sample\child2\Child22.java
[FIND] src\org\sample\Main.java
[FIND] src\org\sample\Sub.java

preDir, preRoot
preDir オプションは各ディレクトリを走査する際に行う前処理を指定します。 preRoot オプションはルートディレクトリに対して同じ前処理を行うかどうかを boolean 値によって指定します。 デフォルトは false なので注意。 サンプルでは、src ディレクトリ下の内容を dest ディレクトリにコピーしています:

def toDest = { dir -> new File(dir.toString().replace('src', 'dest')) }    // コピー先の File オブジェクトを返す

// copy
new File('src').traverse(type:FILES, preDir:{ dir -> toDest(dir).mkdir() }, preRoot:true){ file ->
    def copy = toDest(file)
    copy.createNewFile()
    println "[COPY] $copy"
    //copy.bytes = file.bytes    // ファイルに内容がないので意味がないよう
}

traverse() の引数の Closure ではファイルのみをコピーするようにして、ディレクトリの作成は preDir オプションによって行っています(ファイルコピーのにディレクトリ作成しないといけないので)。 ルートディレクトリも作成する必要があるので「preRoot:true」を指定しています。 traverse() の引数の Closure でファイルかディレクトリかを場合分けするよりもスマートに書けてるんじゃないかと。 出力結果は以下の通り:

[COPY] dest\org\sample\child1\Child1.groovy
[COPY] dest\org\sample\child1\Child11.java
[COPY] dest\org\sample\child1\Child12.java
[COPY] dest\org\sample\child2\Child2.groovy
[COPY] dest\org\sample\child2\Child21.java
[COPY] dest\org\sample\child2\Child22.java
[COPY] dest\org\sample\child2\grandchild2\Child22.groovy
[COPY] dest\org\sample\Main.java
[COPY] dest\org\sample\Sub.java

postDir, postRoot
今度は各ディレクトリを走査する祭の後処理を行う postDir, postRoot。 使い方は preDir, preRoot と同じです。 ここでは先ほど作成したコピー(dest ディレクトリ下)を削除しています:

// delete
new File('dest').traverse(type:FILES, postDir:{ dir -> dir.delete() }, postRoot:true){ file ->
    file.delete()
    println "[DELETE] $file.canonicalPath"
}

File.delete() はディレクトリの場合にはサブファイルやサブディレクトリがあると削除が失敗するので、まずそれらのサブファイル、サブディレクトリを削除した後、postDir でディレクトリの後処理としてそのディレクトリを削除しています。 ルートディレクトリも削除したいので「postRoot:true」を指定しています。 出力結果は以下の通り:

[DELETE] dest\org\sample\child1\Child1.groovy
[DELETE] dest\org\sample\child1\Child11.java
[DELETE] dest\org\sample\child1\Child12.java
[DELETE] dest\org\sample\child2\Child2.groovy
[DELETE] dest\org\sample\child2\Child21.java
[DELETE] dest\org\sample\child2\Child22.java
[DELETE] dest\org\sample\child2\grandchild2\Child22.groovy
[DELETE] dest\org\sample\Main.java
[DELETE] dest\org\sample\Sub.java

visitRoot
visitRoot オプションはルートディレクトリを走査するかどうかを boolean 値によって指定します:

new File('src').traverse(visitRoot:true){ println "[FIND] $it" }

出力結果は以下のようになります:

[FIND] src\org
[FIND] src\org\sample
[FIND] src\org\sample\child1
[FIND] src\org\sample\child1\Child1.groovy
[FIND] src\org\sample\child1\Child11.java
[FIND] src\org\sample\child1\Child12.java
[FIND] src\org\sample\child2
[FIND] src\org\sample\child2\Child2.groovy
[FIND] src\org\sample\child2\Child21.java
[FIND] src\org\sample\child2\Child22.java
[FIND] src\org\sample\child2\grandchild2
[FIND] src\org\sample\child2\grandchild2\Child22.groovy
[FIND] src\org\sample\Main.java
[FIND] src\org\sample\Sub.java
[FIND] src

ルートディレクトリの走査はなぜか一番最後になってますね。

maxDepth
maxDepth は走査するディレクトリ階層の深さを指定します:

// find
new File('src').traverse(maxDepth:2){ println "[FIND] $it" }

ルートから数えて、指定した値のサブフォルダ、サブディレクトリまで走査されるようです:

[FIND] src\org
[FIND] src\org\sample
[FIND] src\org\sample\child1
[FIND] src\org\sample\child2
[FIND] src\org\sample\Main.java
[FIND] src\org\sample\Sub.java

maxDepth に0を指定すると再帰走査を行わない(ルートディレクトリのサブディレクトリ下を走査しない)という指定と同じになります。 GDK が File クラスに追加しているメソッド eachFile(), eachFileMatch() などはこれと同じだと思えます(実装は違うでしょうけど)。

あとは excludeFilter, excludeNameFilter, sort が残ってますが

  • excludeFilter, excludeNameFilter は filter, nameFilter と使い方は同じ(除外するファイル、もしくはファイル名を指定する)
  • sort はサンプルが「Groovy JDK」 の File#traverse(Map, Closure) に載っているので、興味のある方はそちらをどうぞ

という感じで。 traverse() 使えると結構融通が利きますが、知らないとコードを書くのも読むのも大変そう。

これでとりあえず GDK の File 編は終了。

プログラミングGROOVY

プログラミングGROOVY

  • 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/07/06
  • メディア: 単行本(ソフトカバー)
  • 購入: 6人 クリック: 392回
  • この商品を含むブログ (155件) を見る
Groovyイン・アクション

Groovyイン・アクション

  • 作者: Dierk Konig,Andrew Glover,Paul King,Guillaume Laforge,Jon Skeet,杉浦孝,櫻井正樹,須江信洋,関谷和愛,佐野徹郎,寺沢尚史
  • 出版社/メーカー: 毎日コミュニケーションズ
  • 発売日: 2008/09/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 5人 クリック: 146回
  • この商品を含むブログ (121件) を見る

*1:これは traverse() の引数に渡せる Closure についても同じ。

Groovy JDK? It's GDK! (File 編6) その他のメソッド

さぁ、今回は GDK が File クラスに追加するメソッドのうち、今まで扱ってなかった残りのメソッドを見ていきます(目次)。 残りのメソッドは以下の通り:

メソッド 返り値 since
ファイル操作 size()
renameTo(String newPathName)
long
boolean
1.5.0
1.7.4
ディレクトリ操作 directorySize()
deleteDir()
createTempDir()
createTempDir(String prefix, String suffix)
long
boolean
File
File
2.1
1.6.0
?
?

2つの createTempDir() は static メソッドです。

今まで見てきたメソッドを合わせても、実は Groovy 2.0 以降で追加されたメソッドは directorySize() のみなのです(Groovy 2.1.3 時点)。 Groovy 1.8 使ってればほとんどの機能が使えるってことね。

ファイル操作

まずは(ディレクトリではなく)ファイルに関するメソッドを使ったサンプルコード:

// ***** 準備 *****
def file = new File('sample.txt')
file.text = ('a'..'z').join()
assert file.text == 'abcdefghijklmnopqrstuvwxyz'

// ファイル・サイズ取得 size()
println file.size()

// 名前変更 renameTo()
def file2 = new File('sample2.txt')
if(file2.exists())file2.delete()    // 変更先のファイルがあるとまずいので削除

assert file.renameTo('sample2.txt')    // 名前変更 成功すれば true が返される
assert file2.text == 'abcdefghijklmnopqrstuvwxyz'
assert !file.exists()    // 変更元のファイルはもちろんなくなる

renameTo() メソッドの返り値が名前変更後の File オブジェクトだと使いやすい気がするんだけど。 ちなみに、File オブジェクトを引数にとる renameTo() はもともと File クラスに定義されてますけどね。

ディレクトリ操作

次はディレクトリに関するメソッドのサンプルコード:

// ***** 準備 *****
new AntBuilder().with{
    mkdir dir :'src/org/sample/child1'
    mkdir dir :'src/org/sample/child2'
    touch file:'src/org/sample/Main.java'
    touch file:'src/org/sample/child1/Child1.java'
    touch file:'src/org/sample/child2/Child2.java'
}

def src = new File('src')

// ディレクトリ・サイズ取得
println src.directorySize()
assert src.size() == 0    // size() だと 0 が返されるようで・・・

// ディレクトリ全削除 deleteDir()
assert src.deleteDir()    // 中身あっても関係ないヨ
assert !src.exists()

// テンポラリ・ディレクトリ作成 createTempDir()
def dir1 = File.createTempDir()    // 接頭辞:'groovy-generated-, 接尾辞:'-tmpdir' でファイル作成
println dir1.canonicalPath

def dir2 = File.createTempDir('pre-', '-post')    // 接頭辞、接尾辞を指定してファイル作成
println dir2.canonicalPath

ディレクトリを表す File オブジェクトに対して size() を呼び出しても0が返されるだけのようですね。 そのための directorySize() なんでしょう。 deleteDir() はそのディレクトリがファイルやサブディレクトリを持っていても関係なく削除します。 使用時にはご注意を。 createTempDir() はテンポラリ(一時)ディレクトリを作成する static メソッドです。 テンポラリ・ファイルを作成するメソッドはもともと File クラスに定義されていますが、それのディレクトリバージョンです。

以上、GDK が java.io.File クラスに追加しているメソッドをザッと見てきました。 記事書いてて、へぇ~こんなメソッドあったんや、みたいなのが結構ありましたが皆さんはいかがでしたでしょうか。 次回はサラッと流してしまった traverse() メソッドをもう少し真面目に見て、このシリーズを終えたいと思います。

プログラミングGROOVY

プログラミングGROOVY

  • 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/07/06
  • メディア: 単行本(ソフトカバー)
  • 購入: 6人 クリック: 392回
  • この商品を含むブログ (155件) を見る
Groovyイン・アクション

Groovyイン・アクション

  • 作者: Dierk Konig,Andrew Glover,Paul King,Guillaume Laforge,Jon Skeet,杉浦孝,櫻井正樹,須江信洋,関谷和愛,佐野徹郎,寺沢尚史
  • 出版社/メーカー: 毎日コミュニケーションズ
  • 発売日: 2008/09/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 5人 クリック: 146回
  • この商品を含むブログ (121件) を見る

Groovy JDK? It's GDK! (File 編5) データストリーム・オブジェクトストリーム

今回はデータストリームとオブジェクトストリームに関連するメソッド(目次)。 あんまり詳しくないんで、サラッと流しマース。

データストリーム

まずはデータストリームに関連するメソッド。 データストリームとは、Java のプリミティブ型のストリームって感じの認識でいいんじゃないでしょうかね。 このストリームの読み書きは java.io パッケージに定義されている DataInputStream / DataOutputStream を使用します。 これらのクラスには readLong() や writeDouble() のようなプリミティブ型の読み書きメソッドが定義されています。 使い方の詳細は JavaDoc 参照のこと。 GDK が File クラスに追加しているデータストリーム関連のメソッドは以下の通り:

メソッド 返り値 since
入出力オブジェクトの取得 newDataInputStream()
newDataOutputStream()
DataInputStream
DataOutputStream
1.5.0
読み込み withDataInputStream(Closure c) Object 1.5.2
書き出し withDataOutputStream(Closure c) Object 1.5.2

まぁ、InputStream / OutputStream や Reader / Writer と同じような感じですね。 使い方も同じです。 ではサンプルコード: 

def data = new File('data-source')

// DataOutputStream
data.withDataOutputStream{ dos ->
    for(i in 0..20) dos.writeLong(i)
}

// DataInputStream
data.withDataInputStream{ dis ->
    while(dis.available() > 0){    // いつも上手くいくとは限らないかも。
        println dis.readLong()
    }
}

標準出力には0から20までの数字が書き出されます。 また、「data-source」ファイルにも内容が書き込まれますが、数字がそのまま書き出されるわけではないので、普通のテキストファイルなどで開いても読めません。

DataInputStream によってデータを読み込む際に DataInputStream#available() メソッドを用いていますが、このメソッドは「ブロックせずに読み込むバイト数を返す」メソッドなので、いつも上手くいくとは限りません。 ちょっと使ってみただけ。

オブジェクトストリーム

次はオブジェクトストリームに関連するメソッド。 オブジェクトストリームは、データストリームに加えてオブジェクトや配列、文字列なども読み書きできるようにしたストリームです。 java.io.Serializable を実装したオブジェクトを読み書きするやつです。 読み書きに使用するクラスは java.io パッケージの ObjectInputStream / ObjectOutputStream です。 GDK が File クラスに追加しているオブジェクトストリーム関連のメソッドは以下の通り:

メソッド 返り値 since
入出力オブジェクトの取得 newObjectInputStream()
newObjectInputStream(ClassLoader cl)
newObjectOutputStream()
ObjectInputStream
ObjectInputStream
ObjectOutputStream
1.5.0
読み込み withObjectInputStream(Closure c)
withObjectInputStream(ClassLoader cl, Closure c)
eachObject(Closure c)
Object
Object
void
1.5.2
1.5.2
1.0
書き出し withObjectOutputStream(Closure c) Object 1.5.0

だいたいデータストリームの場合と同じですが、ClassLoader を指定できる withObjectInputStream() メソッドと、オブジェクトを反復読み込みする eachObject() メソッドもあります。 ではサンプルコード:

def obj = new File('object-source')

// ObjectOutputStream
obj.withObjectOutputStream{ oos ->
    for(o in ['abc', 'def', 'ghi', 'jkl', 'mno'])
        oos.writeObject(o)
}

// ObjectInputStream
obj.withObjectInputStream{ ois ->
    5.times{
        println ois.readObject()
    }
}
println()

// eachObject() でオブジェクトを読み込んで列挙
obj.eachObject{
    println it
}

オブジェクトストリームからオブジェクトを読み込む場合は、eachObject() メソッドを使うのが簡単そう。 Serializable を実装した独自クラスの読み書きとかできるはずなんですが・・・

まぁ、次回は残ってるメソッドをまとめてやっつけちゃう予定。
プログラミングGROOVY Groovyイン・アクション Effective Java 第2版 (The Java Series)

Groovy JDK? It's GDK! (File 編4) ファイル・ディレクトリの走査

今回は GDK が java.io.File クラスに追加している、ファイル・ディレクトリの走査に関するメソッドを見ていきます(目次)。 扱うメソッドは下表の通り:

メソッド 返り値 since
非再帰 eachFile(Closure c)
eachFile(FileType type, Closure c)
eachDir(Closure c)
eachFileMatch(Object nameFilter, Closure c)
eachFileMatch(FileType type, Object nameFilter, Closure c)
eachDirMatch(Object nameFilter, Closure c)
void 1.5.0
1.0
1.7.1
1.5.0
1.5.0
1.7.1
再帰 eachFileRecurse(Closure c)
eachFileRecurse(FileType type, Closure c)
eachDirRecurse(Closure c)
void 1.7.1
1.5.0
1.0
traverse
(再帰)
traverse(Closure c)
traverse(Map options)
traverse(Map options, Closure c)
void 1.7.1

大雑把に言って

  • eachFile() ・・・ ディレクトリ下のファイル・サブディレクトリを走査(再帰なし)
  • eachFileMatch() ・・・ ディレクトリ下のファイル・サブディレクトリのうち、フィルターを通ったもののみ走査(再帰なし)
  • eachFileRecurse() ・・・ ディレクトリ階層を再帰的に走査
  • traverse() ・・・ ディレクトリ階層を再帰的に走査

の4種類があり、each で始まる3つにはそれぞれ

  • ディレクトリを走査する eachDirXxxx()
  • groovy.io.FileType (後述)でファイルの種類を指定して走査する eachFileXxxx(FileType type, ...)

があります。

groovy.io.FileType 定数(enum)

走査するファイルのタイプ(ファイル / ディレクトリ)を指定するために、groovy.io パッケージに FileType という列挙型が定義されています:

package groovy.io

enum FileType {
    FILES,
    DIRECTORIES,
    ANY
}

Any はファイルとディレクトリ両方を走査します。 FileType を指定しない eachFile(), eachFileMatch(), eachFileRecurse() はファイルタイプとして ANY が指定されているのと同じになります。 つまり、ファイルだけでなくディレクトリも走査されるのでご注意を。

eachFileXxxx() メソッド

あれこれ説明するよりもサンプル見た方が手っ取り早いかと思うので、サンプルコードを書こうかと。 で、チョット前に何人かの方が書いてらっしゃった「GroovyでJavaのpackage-info.javaを一括生成するスクリプト」というお題を(というかコード自体を)拝借して、ちょっと書き換えてみました。 拝借もとは

でーす。 ポツポツと余計なものを付け加えてますが、あまり気にしないで下さいな:

import static groovy.io.FileType.FILES

final String PACKAGE_INFO = 'package-info.java'

def template = { pkg -> """\
/**
 * $pkg パッケージ。
 *
 * <pre>
 * // TODO パッケージ内容の詳細を記述してください
 * </pre>
 * 
 */
package $pkg;
""" }

// 実行時に起点となるディレクトリを指定できるようにしてみた
def rootPath = args ? args[0] : 'src'

def srcRoot = new File(rootPath)
println "[SOURCE ROOT] $srcRoot.canonicalPath"
println()
int n = srcRoot.canonicalPath.size() + 1

srcRoot.eachDirRecurse{ dir ->    // eachDirRecurse() ディレクトリを再帰的に走査
    // *.java ファイルがないディレクトリはスキップ
    boolean hasJavaSrc = false
    dir.eachFileMatch(FILES, ~/.*\.java/){ hasJavaSrc = true }    // eachFileMatch() のフィルターに正規表現を使用
    if(!hasJavaSrc)return

    // package-info.java が既にあるディレクトリもスキップ
    def packageInfo = new File(dir, PACKAGE_INFO)
    if(packageInfo.exists())return

    // package-info.java の生成
    def packageName = dir.canonicalPath[n..-1].tr(File.separator, '.')
    packageInfo.text = template(packageName)
    println "[GENERATE] $packageInfo.canonicalPath"
}
println()

// package-info.java の列挙
println '''Existing 'package-info.java' List'''
srcRoot.eachFileRecurse(FILES){ file ->
    if(file.name == PACKAGE_INFO)
        println "+ $file.canonicalPath"
}

3ヶ所でファイルを走査するメソッドを使ってます:

  1. package-info.java を生成するために eachDirRecurse() でディレクトリのみを再帰的に走査
  2. ディレクトリ内に *.java ファイルがないかどうかを eachFileMatch() で走査(無理矢理使ってる感が否めないけどw)
  3. 存在している package-info.java を列挙するために eachFileRecurse() に FileType.FILES を指定して走査

説明が必要そうなのは eachFileMatch() の第2引数に指定しているフィルターでしょうか。 JavaDoc では引数の型として Object が指定されていますが、このオブジェクトには isCase() メソッドが定義されていることが想定しています。 まぁ言ってみれば、Groovy の switch 文で case の後に指定できるものなら何でも OK だということかと。 正規表現や Closure、文字列なんかもいけるようです。 詳しくは「Groovy JDK」の File クラス eachFileMatch() メソッドを参照のこと。

上記のサンプルを実行するとこんな感じ(Windows で C:\workspace\groovy にて):

[SOURCE ROOT] C:\workspace\groovy\src

[GENERATE] C:\workspace\groovy\src\org\sample\package-info.java
[GENERATE] C:\workspace\groovy\src\org\sample\child1\package-info.java
[GENERATE] C:\workspace\groovy\src\org\sample\child2\package-info.java

Existing 'package-info.java' List
+ C:\workspace\groovy\src\org\sample\child1\package-info.java
+ C:\workspace\groovy\src\org\sample\child2\package-info.java
+ C:\workspace\groovy\src\org\sample\package-info.java

traverse() メソッド

traverse() は引数の Map に色々と値を設定して、走査方法をある程度細かく設定できる走査メソッドです。 設定できる項目は「Groovy JDK」参照のこと。 では、上記と同様のサンプルを traverse() を使って書いてみます:

import static groovy.io.FileType.*

final String PACKAGE_INFO = 'package-info.java'

def template = { pkg -> """\
/**
 * $pkg パッケージ。
 *
 * <pre>
 * // TODO パッケージ内容の詳細を記述してください
 * </pre>
 * 
 */
package $pkg;
""" }

def rootPath = args ? args[0] : 'src'

def srcRoot = new File(rootPath)
println "[SOURCE ROOT] $srcRoot.canonicalPath"
println()
int n = srcRoot.canonicalPath.size() + 1

srcRoot.traverse(type:DIRECTORIES){ dir ->    // ディレクトリ階層の走査
    boolean hasJavaSrc = false
    dir.traverse(type:FILES, maxDepth:0, nameFilter:~/.*\.java/){ hasJavaSrc = true }    // *.java ファイルの走査
    if(!hasJavaSrc)return

    def packageInfo = new File(dir, PACKAGE_INFO)
    if(packageInfo.exists())return

    def packageName = dir.canonicalPath[n..-1].tr(File.separator, '.')
    packageInfo.text = template(packageName)
    println "[GENERATE] $packageInfo.canonicalPath"
}
println()

println '''Existing 'package-info.java' List'''
srcRoot.traverse(type:FILES, nameFilter:PACKAGE_INFO){ file ->    // package-info.java の走査
    println "+ $file.canonicalPath"
}

最初のサンプルコードで使っていた eachDirRecurse(), eachFileMatch(), eachFileRecurse() の3ヶ所を traverse で書き換えました:

  1. ディレクトリ階層の走査 ・・・ ディレクトリのみを走査したいので、type に FileType.DIRECTORIES を指定しています
  2. *.java ファイルの走査 ・・・ サブディレクトリを走査しないので maxDepth に0を指定、ファイル名が .java で終わるファイルのみを走査するため nameFilter に対応する正規表現を指定
  3. package-info.java の走査 ・・・ 「package-info.java」という名前のファイルを走査したいので、nameFilter に文字列 "package-info.java" を指定

といったところでしょうか。 「package-info.java の走査」の場合のように、traverse() を使うとディレクトリ階層を走査しつつ名前フィルターなども使えるので、込み入った走査が必要な場合は traverse() を使うとよいかと。

ついでに package-info.java を削除するコードも書いてみました:

import static groovy.io.FileType.FILES

def rootPath = args ? args[0] : 'src'

def srcRoot = new File(rootPath)
println "[SOURCE ROOT] $srcRoot.canonicalPath"
println()

srcRoot.traverse(type:FILES, nameFilter:~/package-info\.java/){ file ->
    file.delete()
    println "[DELETE] $file.canonicalPath"
}

ディレクトリ階層を走査しつつ名前フィルターも使うので、traverse() を使うと簡単に書けます。 いぇ~いっ!

次回は Object input/output stream, Data input/output stream ・・・かな?

プログラミングGROOVY

プログラミングGROOVY

  • 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/07/06
  • メディア: 単行本(ソフトカバー)
  • 購入: 6人 クリック: 392回
  • この商品を含むブログ (155件) を見る
Groovyイン・アクション

Groovyイン・アクション

  • 作者: Dierk Konig,Andrew Glover,Paul King,Guillaume Laforge,Jon Skeet,杉浦孝,櫻井正樹,須江信洋,関谷和愛,佐野徹郎,寺沢尚史
  • 出版社/メーカー: 毎日コミュニケーションズ
  • 発売日: 2008/09/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 5人 クリック: 146回
  • この商品を含むブログ (121件) を見る

Groovy JDK? It's GDK! (File 編3) 行処理

前回に引き続き、今回も GDK が java.io.File に追加している文字列関連のメソッドを見ていきます(目次)。 readLines() メソッドは各行を String オブジェクトとする、List<String> オブジェクトを返す「一括読み込み」メソッドです。 それ以外のメソッドは、Closure オブジェクトを引数にとり、各行に対して処理を行う「反復読み込み」メソッドです。 定義されているメソッドは以下の通り:

メソッド 返り値 since
一括読み込み readLines()
readLines(String cs)
List 1.0
1.6.8
反復読み込み eachLine(Closure c)
eachLine(int firstLine, Closure c)
splitEachLine(String regex, Closure c)
splitEachLine(Pattern p, Closure c)
filterLine(Writer w, Closure c)
filterLine(Closure c)
Object
Object
Object
Object
Object
Writable
1.5.5
1.5.7
1.5.5
1.6.8
1.0
1.0
反復読み込み
(文字セット指定)
eachLine(String cs, Closure c)
eachLine(String cs, int firstLine, Closure c)
splitEachLine(String regex, String cs, Closure c)
splitEachLine(Pattern p, String cs, Closure c)
filterLine(Writer w, String cs, Closure c)
filterLine(String cs, Closure c)
Object
Object
Object
Object
Object
Writable
1.6.8
  • cs は文字セット (character set) を指定する文字列です。 色を変えているのは見やすさのためだけです。
  • eachLine() の引数の Closure は、引数として「1つの String」もしくは「1つの String と 1 つの int」をとります。 String には行の内容が、int には行番号(1から始まる)が割り当てられます。
  • splitEachLine() の引数の Closure は引数として1つの(String の)java.util.List をとり、第1引数の正規表現にマッチする部分で分割された行の内容が割り当てられます。
  • filterLine() の引数の Closure は引数として1つの String をとり、行の内容が割り当てられます。 この Closure は boolean 型を返す必要があり、true の場合のみ読み取りを行います。

ではサンプルコード:

// ***** 準備 *****
def src = new File('source.txt')
src.text = // 文章は Groovy の HP より (http://groovy.codehaus.org/)
'''Groovy...
* is an agile and dynamic language for the Java Virtual Machine
* builds upon the strengths of Java but has additional power features inspired by languages like Python, Ruby and Smalltalk
* makes modern programming features available to Java developers with almost-zero learning curve
* provides the ability to statically type check and statically compile your code for robustness and performance
* supports Domain-Specific Languages and other compact syntax so your code becomes easy to read and maintain
* makes writing shell and build scripts easy with its powerful processing primitives, OO abilities and an Ant DSL
* increases developer productivity by reducing scaffolding code when developing web, GUI, database or console applications
* simplifies testing by supporting unit testing and mocking out-of-the-box
* seamlessly integrates with all existing Java classes and libraries
* compiles straight to Java bytecode so you can use it anywhere you can use Java'''

def String head(String line){
    // 標準出力への結果が見やすくなるように処理。 本筋とは無関係っす。
    return line.replaceAll('[*]', 'Groovy')[0..<20] + '...'
}


// ***** 一括読み込み readLines() *****
assert src.readLines().size() == 11    // 行数

// ***** 反復読み込み1 eachLine(Closure), eachLine(int, Closure) *****
src.eachLine{ line, no ->  // no には行番号(1から始まる)が割り当てられる。 なくても OK。
    if('Groovy...' == line)return
    else println "$no) ${head(line)}"
}
// 出力結果
// 2) Groovy is an agile a...
// 3) Groovy builds upon t...
// ...
// 11) Groovy compiles stra...
println()

src.eachLine(0){ line, no ->    // 0 は行番号の初期値
    if(no == 0)return
    else println "${no}) ${head(line)}"
}
// 出力結果
// 1) Groovy is an agile a...
// 2) Groovy builds upon t...
// ...
// 10) Groovy compiles stra...
println()


// ***** 反復読み込み2 splitEachLine(Pattern, Closure) *****
src.splitEachLine(~/\s+/){ words ->  // 空文字列で分割。 words は List
    if(words.size() == 1)return
    println head(words.join('_'))
}
// 出力結果
// Groovy_is_an_agile_a...
// Groovy_builds_upon_t...
// ...
// Groovy_compiles_stra...
println()


// ***** 反復読み込み3 filterLine(Writer, Closure)、 filterLine(Closure) *****
def dest1 = new File('dest1.txt')
dest1.withWriter{ writer ->
    src.filterLine(writer){ it.size() > 20 }    // boolean を返す Closure。 true なら読み込み(writer へ書き出し)
}
// 出力結果
// * is an agile and dynamic language for the Java Virtual Machine
// * builds upon the strengths of Java but has additional power ...
// ...
// * compiles straight to Java bytecode so you can use it anywhere you can use Java
println()

def dest2 = new File('dest2.txt')
dest2 << src.filterLine('UTF-8'){ it.startsWith('*') }    // boolean を返す Closure。 true なら読み込み(dest2 へ書き出し)
// 出力結果
// * is an agile and dynamic language for the Java Virtual Machine
// * builds upon the strengths of Java but has additional power ...
// ...
// * compiles straight to Java bytecode so you can use it anywhere you can use Java

filterLine(Closure), filterLine(String, Closure) で返される Writable オブジェクトの扱い方が最初戸惑うかも。 まぁ、Writable が出てきたら << 演算子で何とかすればいいんじゃないかなぁ~

次回はファイル・ディレクトリの走査に関するメソッドを見ていく予定。

プログラミングGROOVY

プログラミングGROOVY

  • 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
  • 出版社/メーカー: 技術評論社
  • 発売日: 2011/07/06
  • メディア: 単行本(ソフトカバー)
  • 購入: 6人 クリック: 392回
  • この商品を含むブログ (155件) を見る
Groovyイン・アクション

Groovyイン・アクション

  • 作者: Dierk Konig,Andrew Glover,Paul King,Guillaume Laforge,Jon Skeet,杉浦孝,櫻井正樹,須江信洋,関谷和愛,佐野徹郎,寺沢尚史
  • 出版社/メーカー: 毎日コミュニケーションズ
  • 発売日: 2008/09/27
  • メディア: 単行本(ソフトカバー)
  • 購入: 5人 クリック: 146回
  • この商品を含むブログ (121件) を見る