GroovyにはJava Development Kit(JDK)を拡張するGroovy Development Kit(GDK)があります。GDKのおかげで、私達はJDKクラスと一緒に多くの便利なメソッドを使えます。ではそれらのメソッドはどこに定義されているのでしょうか? どうやってJDKのクラスに追加しているのでしょうか?
1つ目の疑問の答えは"org.codehaus.groovy.runtimeパッケージ下のクラス内"です。 そのクラスのうちいくつかを紹介します。
- DefaultGroovyMethods: 汎用メソッドを定義します。但しいくつか非汎用メソッドも定義します。初期のバージョンでは、ほとんどのGDKメソッドがここに定義されており、Groovyチームはまだこれらの移動を完全には終わらせていません。
- DateGroovyMethods: 日付/時間関連メソッドを定義します。
- ProcessGroovyMethods: プロセス管理関連メソッドを定義します。
- SwingGroovyMethods: Swing関連メソッドを定義します。
これらのメソッドには全てpublic static修飾子がついています。例えば、DefaultGroovyMethodsはdump()メソッドを次のように定義しています(dump()メソッドは過去のポスト、"Groovyによる汎用toString()"で紹介しました):
----
public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
...
public static String dump(Object self) {
...
}
...
}
----
2つ目の疑問の答えは少し複雑です。なぜなら正確には追加されているわけではないからです。ではどのように呼び出されているのでしょうか? 例を出して説明します。次のようにjava.net.URLクラスのオブジェクトに対しdump()メソッドを呼ぶとします:
----
def url = new URL('http://groovy.codehaus.org/')
url.dump()
----
この場合、Groovyは次の2つのステップでdump()メソッドを呼びます:
1. DefaultGroovyMethods.dump()のメタデータ(groovy.lang.MetaMethod)を持つorg.codehaus.groovy.runtime.callsite.MetaMethodSiteクラスのオブジェクトを生成します。
2. そのMetaMethodSiteオブジェクトを経由し、MetaMethodオブジェクトのinvoke()メソッドをURLオブジェクト引数とともに呼び出します。
つまり、簡単に言うとGroovyはバイトコードの生成時にurl.dump()をDefaultGroovyMethod.dump(url)に置き換えるわけです。
このMetaMethodSiteクラスはGroovyランタイム専用のクラスなので直接使うことはできません。でもがっかりしないでください。Categoriesを使えば同じことができます:
----
class MyMethods {
public static String myDump(Object self) {
...
}
}
use (MyMethods) {
def url = new URL('http://groovy.codehaus.org/')
url.myDump()
}
----
Tuesday, January 11, 2011
Groovyにはアクセスコントロールがない、ではどうするか?
バージョン1.7.5現在、Groovyではアクセス修飾子が機能しません。これらはコメント同様、注意を促す役割しか持ちません。次の例を見てください。アクセス修飾子がその名を偽っていないのなら、このクラスの全てのフィールドとメソッドへのアクセスは制限されるはずです。
----
package foo
class Foo {
protected protectedField
@PackageScope packagePrivateField
private privateField
protected protectedMethod() { }
private privateMethod() { }
}
----
Groovyが期待どおりにGroovyコードをJavaバイトコードにコンパイルしているか確認する為に、classファイルを逆アセンブルしてみましょう。
----
javap -private Foo
----
結果はこうなりました。問題ありません。完璧です。期待どおり。
----
public class Foo extends java.lang.Object implements groovy.lang.GroovyObject{
protected java.lang.Object protectedField;
java.lang.Object packageScopeField;
private java.lang.Object privateField;
:
protected java.lang.Object protectedMethod();
private java.lang.Object privateMethod();
}
----
しかしこれらへのアクセスは制限されません。次の例のようにどこからでもアクセスできます。
----
package bar
def foo = new Foo()
foo.protectedField
foo.packagePrivateField
foo.privateField
foo.protectedMethod()
foo.privateMethod()
----
http://jira.codehaus.org/browse/GROOVY-1875によればこのバグは2.0までに直るようです。では今できることは?
命名規則はどうでしょうか? 事故から守るにはこれでほぼ充分かもしれません。例えばPythonやPerlのようにアンダースコアのプレフィクスを使います。
----
protected _protectedField
@PackageScope __packageScopeField
private ___privateField
protected _protectedMethod()
private ___privateMethod()
----
汚いし、複雑です。使いたくありません。
クロージャはどうでしょうか? もし必要となるのがprivateスコープだけなら、クロージャを使うことで実現できます。
----
package foo
class SecretiveFoo {
SecretiveFoo() {
def privateField;
getPrivateField = {
privateField
}
setPrivateField = { newValue ->
privateField = newValue
}
def privateMethod = {
println 'private method called'
}
publicMethod = {
privateMethod.call()
// do others
}
}
public getPrivateField
public setPrivateField
public publicMethod
}
----
----
package bar
def secretiveFoo = new SecretiveFoo()
secretiveFoo.setPrivateField(1)
println secretiveFoo.getPrivateField()
secretiveFoo.publicMethod()
----
結果はこうなります。
----
1
private method called
----
ただし、このやり方には高いコストがかかります。次のプログラムを動かすとわかります。Transparentは通常のGroovyクラスです。Serectiveはこのやり方をシミュレートするクラスで、指定された数だけprivateフィールドにアクセスするクロージャを生成します。これらを1000回インスタンス化するコストを計測します。
----
class Secretive {
Secretive(n) {
def privateField = 0
actions = []
(0..n).each {
actions << { privateField }
}
}
def actions
}
class Transparent {
private privateField = 0
}
def maxmem = Runtime.runtime.maxMemory()
def memBefore = maxmem - Runtime.runtime.freeMemory()
def timeBefore = System.nanoTime()
def list = []
(0..<1000).each {
list << new Secretive(10)
// list << new Transparent()
}
def timeAfter = System.nanoTime()
def memAfter = maxmem - Runtime.runtime.freeMemory()
def diff = memAfter - memBefore
println "time usage: ${ (timeAfter - timeBefore) * 0.000001 } ms"
println "memory usage: ${ (memAfter - memBefore) * 0.001 } kb"
----
次の結果が出ました。重すぎます。使えません。
Transparent | Secretive(10) | Serective(20) | Serective(30) | |
---|---|---|---|---|
Average Time Usage (ms) | 17.26 | 62.83 | 75.66 | 87.15 |
Average Memory Usage (kb) | 1133.67 | 2706.19 | 3720.96 | 5073.95 |
2.0のリリースをひたすら待つしかないんでしょうか。
誰か案あります?
Labels:
Groovy
Tuesday, January 4, 2011
URLConnection用Groovy builderを書きました
URLConnection用のbuilder、URLConnectionBuilderを書きました。同様の用途向けに既にあるHTTPBuilderは素晴らしいのですが、多くのサードパーティモジュールに依存する為、時たま少し大げさになります。またhttp専用という制限もあります。
URLConnectionBuilderはスタンダード・モジュールにしか依存していない為、気軽に使えます。そしてもちろんどんなURLにも使えます。
以下に簡単な例を示します:
----
import urlconnbuilder.URLConnectionBuilder;
def connBuilder = new URLConnectionBuilder()
connBuilder.url('http://groovy.codehaus.org/') {
connect {
configure (
requestProperties: [
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1',
],
connectTimeout: 1000,
readTimeout: 1000
)
communicate (
input: { conn, stream ->
println stream.text
}
)
}
}
----
試したい場合はhttp://urlconnbuilder.sourceforge.net/からダウンロードできます。
Labels:
Groovy,
URLConnectionBuilder
Saturday, January 1, 2011
GStringの扱いに注意
Groovyは文字列用に2つの型、StringとGStringを持ちます。 通常これらは透過的に扱えます。以下の例を見てください。
----
def s = "1"
println s
println s.class.name
def gs = "${1}"
println gs
println gs.class.name
println s == gs
----
この結果は以下になります。
----
1
java.lang.String
1
org.codehaus.groovy.runtime.GStringImpl
true
----
しかし例外があります。 次の例ではオフィサエージェントのペアをシャッフルしようとしています(彼らは漫画One Pieceのキャラクタです)。
----
def pairOfficerAgents(List femaleAgents) {
def pairs = [:]
(0..5).each{ no ->
pairs.put("Mr. $no", femaleAgents[no])
}
pairs
}
def femaleAgents = ["Ms. All Sunday", "Ms. Doublefinger", "None", "Ms. Golden Week", "Ms. Merry Christmas", "Ms. Valentine"]
def officerAgentPairs = pairOfficerAgents(femaleAgents)
println(officerAgentPairs)
println("Shuffle!")
Collections.shuffle(femaleAgents)
officerAgentPairs = pairOfficerAgents(femaleAgents)
println(officerAgentPairs)
----
結果は次のようになります。
----
[Mr. 0:Ms. All Sunday, Mr. 1:Ms. Doublefinger, Mr. 2:None, Mr. 3:Ms. Golden Week, Mr. 4:Ms. Merry Christmas, Mr. 5:Ms. Valentine]
Shuffle!
[Mr. 0:Ms. Merry Christmas, Mr. 1:Ms. Doublefinger, Mr. 2:None, Mr. 3:Ms. All Sunday, Mr. 4:Ms. Golden Week, Mr. 5:Ms. Valentine]
----
うまくシャッフルできました。ではMr. 0のパートナを出力してみましょう。
----
println officerAgentPairs.get("Mr. 0")
----
予想に反し、結果はこうなります。
----
null
----
なぜでしょうか? まず、StringとGStringは同じ文字列を表していても、同じハッシュコードは持ちません。GStringは可変なためです。次に、GroovyはHashMapのput()メソッドをGStringをキーとして扱えるように拡張していません(これは既知の問題で、Groovyチームはwon't fixにしています)。このため、自分自身でGStringをStringへ変えてあげる必要があります。
上記の例の中でGStringをStringへ変える方法はいくつかあります。
添え字かputAt()メソッドをput()メソッドの代わりに使う:
----
pairs["Mr. $no"] = femaleAgents[no]
----
----
pairs.putAt("Mr. $no", femaleAgents[no])
----
toString()メソッドを使う:
----
pairs.put("Mr. $no".toString(), femaleAgents[no])
----
asキーワードを使う:
----
pairs.put("Mr. $no" as String, femaleAgents[no])
----
Stringとして静的に型付けされた変数へ代入する:
----
String maleAgent = "Mr. $no"
pairs.put(maleAgent , femaleAgents[no])
----
個人的には添え字を使う最初の方法を推奨します。よりGroovy的ですし、上記の方法の中ではもっとも短いです。
----
def s = "1"
println s
println s.class.name
def gs = "${1}"
println gs
println gs.class.name
println s == gs
----
この結果は以下になります。
----
1
java.lang.String
1
org.codehaus.groovy.runtime.GStringImpl
true
----
しかし例外があります。 次の例ではオフィサエージェントのペアをシャッフルしようとしています(彼らは漫画One Pieceのキャラクタです)。
----
def pairOfficerAgents(List femaleAgents) {
def pairs = [:]
(0..5).each{ no ->
pairs.put("Mr. $no", femaleAgents[no])
}
pairs
}
def femaleAgents = ["Ms. All Sunday", "Ms. Doublefinger", "None", "Ms. Golden Week", "Ms. Merry Christmas", "Ms. Valentine"]
def officerAgentPairs = pairOfficerAgents(femaleAgents)
println(officerAgentPairs)
println("Shuffle!")
Collections.shuffle(femaleAgents)
officerAgentPairs = pairOfficerAgents(femaleAgents)
println(officerAgentPairs)
----
結果は次のようになります。
----
[Mr. 0:Ms. All Sunday, Mr. 1:Ms. Doublefinger, Mr. 2:None, Mr. 3:Ms. Golden Week, Mr. 4:Ms. Merry Christmas, Mr. 5:Ms. Valentine]
Shuffle!
[Mr. 0:Ms. Merry Christmas, Mr. 1:Ms. Doublefinger, Mr. 2:None, Mr. 3:Ms. All Sunday, Mr. 4:Ms. Golden Week, Mr. 5:Ms. Valentine]
----
うまくシャッフルできました。ではMr. 0のパートナを出力してみましょう。
----
println officerAgentPairs.get("Mr. 0")
----
予想に反し、結果はこうなります。
----
null
----
なぜでしょうか? まず、StringとGStringは同じ文字列を表していても、同じハッシュコードは持ちません。GStringは可変なためです。次に、GroovyはHashMapのput()メソッドをGStringをキーとして扱えるように拡張していません(これは既知の問題で、Groovyチームはwon't fixにしています)。このため、自分自身でGStringをStringへ変えてあげる必要があります。
上記の例の中でGStringをStringへ変える方法はいくつかあります。
添え字かputAt()メソッドをput()メソッドの代わりに使う:
----
pairs["Mr. $no"] = femaleAgents[no]
----
----
pairs.putAt("Mr. $no", femaleAgents[no])
----
toString()メソッドを使う:
----
pairs.put("Mr. $no".toString(), femaleAgents[no])
----
asキーワードを使う:
----
pairs.put("Mr. $no" as String, femaleAgents[no])
----
Stringとして静的に型付けされた変数へ代入する:
----
String maleAgent = "Mr. $no"
pairs.put(maleAgent , femaleAgents[no])
----
個人的には添え字を使う最初の方法を推奨します。よりGroovy的ですし、上記の方法の中ではもっとも短いです。
Labels:
Groovy
Subscribe to:
Posts (Atom)