ToDo:
見ればわかりますが、ただの委譲です。今回の例ならこれで済んじゃうので、もっとこう、ただの委譲じゃ実現できないことが実現できるぞという例がみたいです。
こんな感じでいかがでしょうか。RubyのEnumerableのようなものをJavaで実装してみました。これだと単なる委譲では実現するのは面倒だと思います。 あと、MyListのフィールドmixinをEnumerable型を引数に取るuseEnumerableに渡している所にも注意してください。
import java.util.*;
public class UseMixin {
//Enumerableを使って何かする
static void useEnumerable(Enumerable<Integer> enumerable){
//全部の要素を出力する
enumerable.each(new Procedure<Integer>(){
public void call(Integer arg){
System.out.println(arg);
}
});
//リストの全ての要素を2倍する
List<Integer> list = enumerable.collect(new Function<Integer, Integer>(){
public Integer call(Integer arg){
return arg * 2;
}
});
System.out.println("list = " + list);
//リストの各要素の和を計算する
int sum = enumerable.inject(0, new Function2<Integer, Integer, Integer>(){
public Integer call(Integer arg1, Integer arg2){
return arg1 + arg2;
}
});
System.out.println("sum(1..10) = " + sum);
}
public static void main(String[] args){
MyList<Integer> nums = new MyList<Integer>();
for(int i = 1; i <= 10; i++){
nums.add(i);
}
useEnumerable(nums.mixin);//((Enumerable)nums)に相当
}
}
interface Function<T, R> {
R call(T arg);
}
interface Function2<T1, T2, R> {
R call(T1 arg1, T2 arg2);
}
interface Procedure<T> {
void call(T arg);
}
//変更可能なT型の値を作るためのクラス
class Mutable<T> {
T value;
Mutable(T value) { this.value = value; }
}
abstract class Enumerable<E> {
abstract void each(Procedure<E> proc);
<R> List<R> collect(final Function<E, R> func){
final List<R> list = new ArrayList<R>();
each(new Procedure<E>(){
public void call(E e){
list.add(func.call(e));
}
});
return list;
}
<R> R inject(final R init, final Function2<E, R, R> func){
final Mutable<R> var = new Mutable<R>(init);
each(new Procedure<E>(){
public void call(E e){
var.value = func.call(e, var.value);
}
});
return var.value;
}
}
//Enumerableを使用EnumerableをMixin
class MyList<T> extends ArrayList<T> {
Enumerable<T> mixin = new Enumerable<T>(){
//collectとinjectは実装しなくてOK
public void each(Procedure<T> proc){
for(T t : MyList.this){
proc.call(t);
}
}
};
}
出力結果:
1 2 3 4 5 6 7 8 9 10 list = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] sum(1..10) = 55
maedaさんからのツッコミがあったので、それを受けて 昨日の例を改良してみた。また、ただ改良するだけでは面白くないので、Fileクラスを継承したクラスにもMixinしてみた。
import java.io.*; import java.util.*;
public class UseMixin { //ディレクトリ直下のファイルサイズの合計を調べる static long sumSize(EnumerableSrc<File> src){ return src.asEnumerable().inject(0L, new Function2<File, Long, Long>(){ public Long call(File f, Long r){ return f.length() + r; } }); }
//ファイルサイズとファイル名をつなげた文字列のリストを返す static List<String> fileSizeAndNames(EnumerableSrc<File> src){ return src.asEnumerable().collect(new Function<File, String>(){ public String call(File f){ return "ファイルサイズ: " + f.length() + " バイト\t" + "ファイル名: " + f.getName(); } }); }
public static void main(String[] args){ EnumerableFile file = new EnumerableFile("."); System.out.printf("合計サイズ: %d%n", sumSize(file)); for(String s : fileSizeAndNames(file)) System.out.println(s); } }
//1引数の関数を表すインタフェース interface Function<T, R> { R call(T arg); }
//2引数の関数を表すインタフェース interface Function2<T1, T2, R> { R call(T1 arg1, T2 arg2); }
//1引数の手続きを表すインタフェース interface Procedure<T> { void call(T arg); }
//変更可能なT型の値を作るためのクラス class Var<T> { private T value; private Var(T value) { this.value = value; } public T value() { return value; } public void value(T value) { this.value = value; } public static <T> Var<T> ref(T value){ return new Var<T>(value); } }
interface EnumerableSrc<T> { Enumerable<T> asEnumerable(); }
abstract class Enumerable<E> { abstract void each(Procedure<E> proc);
<R> List<R> collect(final Function<E, R> func){ final List<R> list = new ArrayList<R>(); each(new Procedure<E>(){ public void call(E e){ list.add(func.call(e)); } }); return list; }
<R> R inject(final R init, final Function2<E, R, R> func){ final Var<R> var = Var.ref(init); each(new Procedure<E>(){ public void call(E e){ var.value(func.call(e, var.value())); } }); return var.value(); } }
//ディレクトリの要素を列挙可能なFile class EnumerableFile extends File implements EnumerableSrc<File> { EnumerableFile(String path){ super(path); } private Enumerable<File> enumerable = new Enumerable<File>(){ //collectとinjectは実装しなくてOK public void each(Procedure<File> proc){ for(File f : EnumerableFile.this.listFiles()){ proc.call(f); } } }; public Enumerable<File> asEnumerable() { return enumerable; } }
実行結果:
合計サイズ: 11371 ファイルサイズ: 819 バイト ファイル名: Enumerable$1.class ファイルサイズ: 841 バイト ファイル名: Enumerable$2.class ファイルサイズ: 1006 バイト ファイル名: Enumerable.class ファイルサイズ: 652 バイト ファイル名: EnumerableFile$1.class ファイルサイズ: 609 バイト ファイル名: EnumerableFile.class ファイルサイズ: 235 バイト ファイル名: EnumerableSrc.class ファイルサイズ: 255 バイト ファイル名: Function.class ファイルサイズ: 301 バイト ファイル名: Function2.class ファイルサイズ: 217 バイト ファイル名: Procedure.class ファイルサイズ: 777 バイト ファイル名: UseMixin$1.class ファイルサイズ: 967 バイト ファイル名: UseMixin$2.class ファイルサイズ: 1536 バイト ファイル名: UseMixin.class ファイルサイズ: 2528 バイト ファイル名: UseMixin.java ファイルサイズ: 628 バイト ファイル名: Var.class
Javaでは、無名クラスから外側のスコープのローカル変数にアクセスする場合、final修飾子をつける必要がある、つまり、無名クラスから外側のスコープのローカル変数を変更することはできない。多くのケースでは、ローカル変数の値を変更しなくてもなんとかなるので、困ることは案外少ない。しかし、ときどき無名クラスから外側のスコープのローカル変数にアクセスしたい場合もある。そこで、どのようにしてそのようなことを実現すれば良いかというのが今回のお話。
要点は、変更できないのは「ローカル変数」であって、ローカル変数の 値となっている「オブジェクト」自体は変更可能であるということだ。
変更したいローカル変数をあらかじめ配列として宣言しておくという方法。 配列はJavaに最初から組み込まれているので手軽に扱えるという利点があるが、何のために配列を使っているのか、コードをぱっと見ただけでは把握しにくいという欠点がある。
class Main {
public static void main(String[] args){
final boolean[] var = new boolean[]{false};
System.out.printf("var: %s%n", var[0]);
new Runnable(){
public void run(){
var[0] = true;
}
}.run();
System.out.printf("var: %s%n", var[0]);
}
}
実行結果:
var: false var: true
変更したい型の値をラップするクラスを作り、無名クラスからはそのオブジェクトを経由して変数の値を参照するという方法。適切なクラス名を付けることによって、意図が伝わりやすくなる(かも)というのが利点だが、配列を使う場合に比べて、クラスを1つ余計に定義する必要があるのが欠点か。
//Boolean型の値をラップするクラス
class BooleanVar {
boolean value;
BooleanVar(boolean value){
this.value = value;
}
}
class Main {
public static void main(String[] args){
final BooleanVar var = new BooleanVar(false);
System.out.printf("var: %s%n", var.value);
new Runnable(){
public void run(){
var.value = true;
}
}.run();
System.out.printf("var: %s%n", var.value);
}
}
Java5で導入されたGenericsを使えば、次のように型に依存しない形でラップ用のクラスを定義することもできる。これならクラスは一度だけ定義しておけばいいので、随分楽だ。
//任意のT型の値をラップ可能なクラス
class Var<T> {
T value;
Var(T value){ this.value = value; }
}
class Main {
public static void main(String[] args){
final Var<Boolean> var = new Var<Boolean>(false);
System.out.printf("var: %s%n", var.value);
new Runnable(){
public void run(){
var.value = true;
}
}.run();
System.out.printf("var: %s%n", var.value);
}
}
Var型のオブジェクトを生成するときに、new Var<T>(value)のようにするのが面倒ならば、次のようにGenericメソッドを使って簡略化することもできる。
class Var<T> {
T value;
Var(T value){ this.value = value; }
//Genericメソッド
static <T> Var<T> ref(T value){ return new Var<T>(value); }
}
これならいちいちnew Var<T>(value)のようにしなくても、Var.ref(value)とするだけで良い。
class Main {
public static void main(String[] args){
final Var<Boolean> var = Var.ref(false);
System.out.printf("var: %s%n", var.value);
new Runnable(){
public void run(){
var.value = true;
}
}.run();
System.out.printf("var: %s%n", var.value);
}
}
さらに、Java 5で導入されたstatic importを使えば、もっと簡略化することができる。ちなみに、このプログラムでpackage宣言をしているのは、static importで無名パッケージのクラスのstaticメソッドを取り込むことができなかったためだ。
package example;
import static example.Var.*;
class Var<T> { T value; Var(T value){ this.value = value; } static <T> Var<T> ref(T value){ return new Var<T>(value); } }
class Main { public static void main(String[] args){ final Var<Boolean> var = ref(false); System.out.printf("var: %s%n", var.value); new Runnable(){ public void run(){ var.value = true; } }.run(); System.out.printf("var: %s%n", var.value); } }
A Diary Which Heads for Convergence (Naist branch)より。
javacの-targetオプションでjsr14と指定すれば、Java 5.0の機能を使ったソースコードをコンパイルしたコードが1.4のVM上で動作するようだ。早速試してみたところ、確かにコンパイルしたコードを1.4のVMで動作させることができた。非公開オプションのようだが、Java5.0の機能を使ったソースコードを1.4で動作させたい場合には使えるかもしれない。
追記:当たり前だが、Typesafe Enumのように5.0で新しく追加されたAPIを使っている場合は実行できない。Generics、static improt、拡張for文などは新しいAPIを使っていないので、動作する。
ナンセンス不定記より引用。
javaでインスタンスを生成する際に、実装クラスをnewするのではなく、インターフェイスをnewする。
・・・
DIコンテナをひとつの外部ライブラリとするのではなく、言語実装の一部とする。つまり、DIは空気のようなもの。
これは面白いかも。元の話と動機は少し違うが、
List list = new ArrayList();
のようなコードを書くときって、大抵の場合、実装クラスがArrayListであるっていうのはどうでもいいのであって、そういうときに
List list = new List();
のように書けると気持ちよさそう。
もしないのなら、これこそすぐにJavaSEレベルで欲しい機能だと思うんだけどね。
Javaに導入するのは、既存のソースコードの互換性を保て無さそうなので、難しいのではないだろうか。
Warning: there is an important change in scripts between "def-ed" variables and not "def-ed" variables. Defining a variable with the def keyword or with a concrete type creates a local variable which won't be stored in the binding. However, if you assign a variable which wasn't defined in your script, it will be stored in the binding of the script.
というのが気になる。defで宣言した変数は、Java VMのローカル変数を使って実装されるということだろうか?
ファクトリーメソッドと言うと、すっかり「単にインスタンスを生成するメソッド」という意味になってしまった感があるのだが、何故こうなったのだろうか。「ファクトリーメソッド」の出典と思われるGoF本では、単にインスタンスを生成するメソッドのことをファクトリーメソッドと呼んでいたわけではないはずなんだけど。
まあ、ファクトリーメソッドを単にインスタンスを生成するメソッドという意味で使っても、それ自体は大して困ったことでは無いかもしれない。しかし、「ファクトリーメソッド」とGoFの「ファクトリーメソッドパターン」を混同して、「ファクトリーメソッドパターン」を単に「インスタンスを生成するメソッドを使うパターン」の意味で使うのはどうかと思う。
2/15のエントリで書いた件が気になったので、ちょっと調べてみた。 まずは、変数宣言にdefをつけた場合。
def x = "Hello" x
public java.lang.Object run(); Code: 0: ldc #51; //String Hello 2: astore_1 3: aload_1 4: areturn
次は、変数宣言にdefをつけない場合。
x = "Hello" x
public java.lang.Object run(); Code: 0: ldc #51; //String Hello 2: dup 3: aload_0 4: ldc #53; //String x 6: invokestatic #57; //Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.setGroovyObjectProperty:(Ljava/lang/Object;Lgroovy/lang/GroovyObject;Ljava/lang/String;)V 9: pop 10: aload_0 11: ldc #53; //String x 13: invokestatic #61; //Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.getGroovyObjectProperty:(Lgroovy/lang/GroovyObject;Ljava/lang/String;)Ljava/lang/Object; 16: areturn
これらの結果を見ると、"def"を付けた変数はJava VMの通常のローカル変数として保存され、"def"を付けない変数はオブジェクトのプロパティとして保存される、ということのようだ。
オレンジニュース経由。Yahoo!テキスト翻訳で「ぬるぽ」を日英翻訳すると…という話題。早速、「ぬるぽ」と入力して試してみたところ、「ガッ」と表示された。よくやるなあ(笑)。ところで、こういうのもイースターエッグと言うのだろうか。
追記:Excite翻訳でも、同じことを試してみたが「ぬるぽ」のままだった。残念。
という話題がgroovy-user MLにあった。とりあえず今のバージョンだと、こんな感じで書けるようだ。
def fact = {x ->
if(x < 2) return 1 else return n * this.call(x - 1)
}
ただ、groovy-user MLでの情報によると、1.0までにクロージャ中のthisの仕様が変わるらしく、このコードは将来的には動かなくなるらしい。解決するには、次のようにすればいいとのこと。
def fact = {}
fact = {x ->
if(x < 2) return 1 else return n * fact(x - 1)
}
再帰関数を定義する前に、代入する変数を宣言して初期化しているのがポイント。次のようにした場合、クロージャ中のfactの呼び出しでエラーになってしまう。
def fact = {x ->
if(x < 2) return 1 else return n * fact(x - 1)
}
_ nt [なんかしらんけどすごいな. mix-inって要するにどういう目的の概念なん? ]
_ maeda [nums.mixinよりnums.asEnumerable()の方が良くない? んで、asEnumerable()を..]
_ みずしま [> nums.mixinよりnums.asEnumerable()の方が良くない? 確かにそうですね。mixinが..]
_ みずしま [ntさん > mix-inって要するにどういう目的の概念なん? 返事遅くなってごめん。一言で言うと、クラスの多重継..]