S-JIS[2008-07-05/2008-12-31] 変更履歴
Javaで「こんな機能があったらいい(面白い)なぁ」とか?
自分のインスタンスを表すにはthis、親のインスタンスを表すにはsuperを使う。[2008-12-31]
しかし時には、インスタンスでなく自分のクラスを汎用的に表したい事がある。
import org.apache.log4j.Logger; //改善したい例 class Sample { private Logger log = Logger.getLogger(Sample.class); }
このソースをコピー&ペーストして新しいクラスを作った時に、ロガーの初期値のクラス名を変更し忘れることが結構あるので(苦笑)
例えば「This」というキーワードに現在のクラスを割り当てることにすれば、以下のように書ける。
import org.apache.log4j.Logger; class Sample { private Logger log = Logger.getLogger(This.class); }
同様に、親クラスは「Super」(あるいは「Parent」)で表すことにする。
ただ、ThisやSuperだと既存のプログラムでさんざん使われているような気がするので、キーワードを増やすのは好ましくない。
そこで、演算子で示すことを考えてみる。(これもC++を参考にして、)
import org.apache.log4j.Logger; class Sample { private Logger log = Logger.getLogger(this::class); }
C++ではクラス内のstatic変数を示すのに「::
」を使うので、似ていていいんじゃないかと思う。(C++でも「::
」の前に書くのはクラス名であって、thisとかのインスタンスではないけど…)
しかし、キーワードThis・Superを使う方式なら、instanceof演算子にも対応できる。
public boolean equals(Object obj) { if (obj instanceof This) { This other = (This)obj; 〜 } else { return super.equals(obj); } }
これを「::
」で表したら、「this::class
」から「class
」を抜いて、以下のような感じ?
if (obj instanceof this::) { this:: other = (this::)obj; 〜 } else {
なんかちょっと変だなー(苦笑)
単項演算子風にするなら、単項演算子はオペランドの左側にあるのが(ほぼ)普通なので、こんな感じにすべきか。
if (obj instanceof ::this) { ::this other = (::this)obj; 〜 } else {
すると、最初のロガーの取得は
private Logger log1 = Logger.getLogger(::this.class); private Logger log2 = Logger.getLogger(::super.class);
まぁ何となく理解できなくは無い。
C++で「::
」から始めると、「ネームスペース(いわばパッケージ)を使わない、グローバルな変数・クラス」という意味になるから、ちょっと違うけど…。
じゃ「#」に変えてみるか。(C言語のプリプロセッサの文字列化演算子風
。「#this」を自分のクラスに置き換えるのは、ある意味マクロなんだし)
でもそれだとJavadocでの「#」の使い方と紛らわしいから、もっとダメか。
ついでなので、メソッド名に変わってくれるのもあると便利。
void sample() { String name = ::method.name; } ↓ void sample() { String name = "sample"; }
あと、ソース内の行番号に変わってくれるものがあれば、デバッグログ出力に便利に使える(笑)
その辺りは、スタックトレースのElement(Throwable#getStackTrace())が、new Throwable()をせずに取れればいいのかもしれないけど。
(例外オブジェクトのインスタンス化はけっこうコストが高いので、ログ出力毎に作るのはちょっと…)
StringBuilderにappend()というメソッドがあるけれど、これの改行付き版が欲しい。[2008-07-26]
今は、改行コードを出力しようとすると、以下のようにするしかない。(もしくはPrintWriterを使う)
StringBuilder sb = new StringBuilder(); final String NL = Sytem.getProperty("line.separator"); sb.append("<table>"); sb.append(NL); sb.append("<tr>"); sb.append(NL); sb.append("<td>abc</td>"); sb.append(NL); sb.append("</tr>"); sb.append(NL); sb.append("</table>"); sb.append(NL);
※sb.append("<table>" + NL)は、そこで余分な文字列の結合が発生してしまうので、やりたくない
↓こんな風に出来ると便利
StringBuilder sb = new StringBuilder(); sb.appendln("<table>"); sb.appendln("<tr>"); sb.appendln("<td>abc</td>"); sb.appendln("</tr>"); sb.appendln("</table>");
ついでに、同じ文字(文字列・値)を指定した個数分入れてくれるメソッドもあると便利だなー。
StringBuilder sb = new StringBuilder(); sb.appendln("<table>"); sb.append('\t', 1); sb.appendln("<tr>"); sb.append('\t', 2); sb.appendln("<td>abc</td>"); sb.append('\t', 1); sb.appendln("</tr>"); sb.appendln("</table>");
ソースを整形して1行1命令にしたら、結局わかりにくくなるか(苦笑)
なら、タブ文字を事前に指定しておき、その個数を個別に指定できるようになればいいんだ!
StringBuilder sb = new StringBuilder(); sb.setTabString("\t"); sb.appendln("<table>"); sb.appendln(1, "<tr>"); sb.appendln(2, "<td>abc</td>"); sb.appendln(1, "</tr>"); sb.appendln("</table>");
こうなってくると、自分でStringBuilderを拡張したクラスを作ればいいような気がしてきた(爆)
…でもStringBuilderはfinalクラスだからお約束どおり変更できないし!
「直列化(シリアライズ)可能」を表すSerializable。
Serializableはインターフェースだけど、使い道は「印(マーカー)」なんだからアノテーションで表すのが筋だと思う。
同じく、Cloneableもアノテーションに出来るかな。
Serializableをアノテーション化したら、以下のような感じか。
@Retention(RetentionPolicy.RUNTIME) @interface Serializable { long serialVersionUID() default 1L; }
実行時に判定に使うので、RUNTIME指定は必須。
ObjectStreamField[] serialPersistentFieldsも入れられるかなーと思ったけど、アノテーションでは指定できる型(クラス)に制限があるので、無理だった。
それから、instanceofのような演算子が欲しくなるかな。annotationofとか?
if (obj annotationof Serializable) {
〜
}
↓一応、リフレクションで用意されているメソッドを使えば実現できるけど。
if (obj!=null && obj.getClass().isAnnotationPresent(Serializable.class)) { 〜 }
まぁ、今さらSerializableがインターフェースからアノテーションに変わるわけないけど^^;
今のオートボクシングは、プリミティブ型とそのラッパークラスの間でしか変換されない。[2008-12-31]
これを一般クラスにも適用できると便利そう。
対象は、valueOf(int)とintValue()を持つクラス。(ラッパークラスの共通の親であるNumberを参考に)
で、そういうクラスに対してはintに対してオートボクシング(アンボクシング)できるようにする。
class Sample { private int value; public Sample(int n) { this.value = n; } public static Sample valueOf(int n) { return new Sample(n); } public int intValue() { return this.value; } }
Sample s = 123; int n = s; Sample[] arr = { 1, 2, 3 };
↓(コンパイルすると、こう変換される感じ)
Sample s = Sample.valueOf(123); int n = s.intValue(); Sample[] arr = { Sample.valueOf(1), Sample.valueOf(2), Sample.valueOf(3) };
初期値つきの配列を用意するときに便利そう。
そういう意味だと、Stringも変換してくれると便利だなぁ。
Sample2[] arr = { "abc", "def" }; ↓ Sample2[] arr = { Sample2.valueOf("abc"), Sample2.valueOf("def") };
※valueOf()やintValue()をoperator=に置き換えれば、C++と同様の考えになる?
C++では、演算子のオーバーロードが出来る。自分ではあまり演算子を定義して使いたいとは思わないけど、
StringBuilderに対して+演算子が使えると便利だと思う。(文字列の+演算子(結合)としては、Stringに対してしか使えない)
StringBuilder sb; sb = 123; sb += "abc"; sb += "def" + str; for (int i=0; i<10; i++) { sb += i; }
↓実体としては こうなってくれると良い
StringBuilder sb;
sb = new StringBuilder(String.valueOf(123));
sb.append("abc");
sb.append("def").append(str); //+よりも+=の方が優先順位は低いので、こういう変換はルール化が特殊になるかも?
for (int i=0; i<10; i++) {
sb.append(i);
}
Stringに対して+演算子を使うとStringBuilder#append()に置き換えられるわけだが、+=演算子が複数行にまたがる場合等で別々のStringBuilderインスタンスが作られて、無駄。そういうのは無くして欲しいな。
String a, bに対し、「sb += a + b
」は「sb.append(a).append(b)
」になって欲しい。[2008-07-12]
しかし厳密に考えれば、「sb += s
」は「sb = sb + s
」なので、「sb +=
a + b
」は「sb = sb + (a + b)
」になる。
したがって先にa+bを行う、すなわち「new StringBuilder(String.valueOf(a)).append(b)
」となる(なってしまう)のは理屈に適っている。
しかし+演算子のみであれば、演算順位は変えても問題ないはず。
「sb = sb + (a + b)
」→ 「sb = (sb + a) + b
」→ 「sb.append(a).append(b)
」
という訳で、最適化でなんとか頑張ってくれないだろうか?
と思ったんだけど… [2008-07-18]
int n = 1, m = 2; StringBuilder sb = new StringBuilder(); sb += n + m;
この場合、sb.append(n + m)
となるべきか、sb.append(n).append(m)
となるか?
+=と+の関係を考えれば前者だが、String(プリミティブ以外?)の場合は後者になる、というのはやはりルールが統一されないなぁ…。
ついやりたくなること。
/** 親クラス */
abstract class Parent {
public static abstract Parent getInstance(); //ファクトリーメソッド
}
/** 子クラス */
class A extends Parent {
@Override
public static Parent getInstance() {
return new A();
}
}
あるいは
interface Parent { public static Parent getInstance(); } class A implements Parent { public static Parent getInstance() { return new A(); } }
そして、親クラスのファクトリーメソッドを指定して(実体として)子クラスのインスタンスを取得したい。
理由: Aは実装クラスなので、ユーザーの目からは隠したい。つまり、A.getInstance()
とはしたくない。
Parent obj = Parent.getInstance();
って、子クラスが複数あったら、どれを返すかどうやって判断するんだ?(苦笑)
なので、原理的にこれは出来ない。
Javaでは同期(排他)する為の文法としてsynchronizedがある。
で、JDK1.5でロック用クラスが増えたのだが、synchronizedは使われない。せっかく「Javaでは言語仕様で排他制御をサポートしている」と謳っていたんだから、新しいロックについても文法を作ればいいのに。
例えば読み書きロック(ReentrantReadWriteLock)用には
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); synchronized_read(rwlock) { 〜 } synchronized_write(rwlock) { 〜 }
てな感じ。
まぁ、こんな文法が本当に導入されたら、絶対「馬鹿か!!」って言うけどね!(爆)
それは冗談としても、ロックは基本的に、“ロックオブジェクトに対しlock()を呼び出して排他を開始し、unlock()を呼び出して終了する”という仕組みは同じ。
ということは、Lockableとかゆーインターフェースを作り、synchronizedにはそれを指定できる、とかすれば汎用的に出来るかも?
public interface Lockable { public void lock(); public void unlock(); }
/** 想像版ReadWriteLock */ public class ReentrantReadWriteLock { public class ReadLock implements Lockable { public void lock() { 〜 } public void unlock() { 〜 } } public class WriteLock implements Lockable { public void lock() { 〜 } public void unlock() { 〜 } } public ReadLock readLock() { return 〜; } public WriteLock writeLock() { return 〜; } }
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); synchronized(rwlock.readLock()) { 〜 } synchronized(rwlock.writeLock()) { 〜 }
↓こんな感じ(現実(JDK1.5)の使い方に近い)に変換できるはず
ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); { Lockable lk = rwlock.readLock(); lk.lock(); try { 〜 } finally { lk.unlock(); } } { Lockable lk = rwlock.writeLock(); lk.lock(); try { 〜 } finally { lk.unlock(); } }
しかしこうなると、当然現状のロック機構で使えるクラス、すなわちObjectにもLockableをimplementsすることになる。(今までObjectは何もimplementsしたこと無いのに!?)
ということは、あらゆるクラスがlock()とunlock()を持ち、自由に実装できるようになるなぁ…。
となると、Serializableと同様、Lockableアノテーション(継承できないタイプ)にして、それが付いているクラスにはlock()とunlock()が無いとコンパイルエラーにするとか?
すごい特殊文法だな(苦笑)
なんだかグダグダになりそうな気配が濃厚…。
C++にはfriendという構文がある。
自分がfriendという指定をした他クラスは、自クラスのprivateメンバーにもアクセスできるようになる。
友達には自分の全てを見せるよ、って訳だ。本当か?
Javaでもfriendが欲しいときがある。(C#の友達はVB.NETだとか、Javaの友達がJavaScript以外にも欲しいとか、そういう意味ではないよ)
ひとつは、一般プログラマーには公開したくないけどサブルーチン的なクラス。
packageスコープで作ればこの要件は満たせるけれども、数が増えたら別パッケージにまとめたくなる。しかし別パッケージにはpublicでないとアクセスできず、publicにしてしまうと一般プログラマーからもアクセスできてしまう。
ふたつめは、テストクラス。
JUnitによるテストは非常に便利だが、privateメンバーにはアクセスできない。
JUnitでテストする為には、privateを一切使わず、protectedもしくはpackageスコープにするのがいいとさえ思う。
(同一パッケージで物理的には別ディレクトリーにテストクラスを作る、というのは常套手段だが、メソッドやフィールドがprivateなのでは(一応)どうしようもない)
ついでに言うと、privateでは継承による拡張が出来ないので、大嫌い。そのせいでソースをコピペしないといけなくなるなんて、アホらしい。
C++では、newによる(ヒープ上への)インスタンス生成の他に、ローカルスコープだけ有効な(たぶんスタック上に生成される)オブジェクトを作ることが出来る。
これにより、ローカルスコープが終了したら 必ずデストラクターが呼ばれるので、とても便利。
{ FileInputSream is("…"); //C++では、これでスタック上にインスタンスが生成される 〜 } //このブロックが終わると、必ずisのデストラクターが呼ばれる
Javaではtry〜finallyを使うと似た状態に出来る。
しかしtry{ }とfinally{ }で新たなスコープを作ってしまう為、変数に着目したスコープとしては扱いづらい面がある。
InputStream is = null; try { is = new FileInputStream("…"); 〜 } finally { //必ずfinallyが実行されるが、ブロックが別なので、変数はtryより外側で宣言しなければならない if (is != null) { is.close(); } }
また、C#ではusingという構文がある。
この構文なら、少なくとも変数以外の余分なブロック(スコープ)が作られることは無い。
using(InputStream is = new FileInputStream("…")) { 〜 } //このブロックが終わると、必ずisのDispose()が呼ばれる
てな訳で、単純にC#の真似をするのは癪だから、「スコープが終わったときに呼ばれる文を定義する」という構文はどうだろう。
変数宣言時にそれを指定する、という感じで。
{
InputStream is = new FileInputStream("…") finally{ is.close(); }; ←これだと無名内部クラスっぽく見えちゃう?
あるいは
InputStream is finally{ is.close(); } = new FileInputStream("…");
あるいは
InputStream is finally{ is.close(); };
is = new FileInputStream("…");
〜
}
//スコープの終了時に、変数に指定されたfinallyが呼ばれる
変数の属しているスコープが終わるときにfinallyが呼ばれるということで。
(変数が初期化されていなかった場合は、finallyは呼ばれない方が便利?)
もしくは、その変数に新しいオブジェクトが代入されたときに、それまで入っていたインスタンスに対してfinallyが実行されるとか?
でもそのオブジェクトをreturnしたり他の変数に代入したりしたときのことを考慮しないといけないと思うと、考える気が失せる(爆)