添削について確認してみた
([obj methodname & methodnames] `(concat (obj-values ~obj ~methodname) (obj-values ~obj ~@methodnames)))
マクロ練習第二弾 - Java オブジェクトのメソッド名とメソッドの値のマップを返す - 地獄のネコブログ
この場合、obj が複数回評価されうるのでよろしくないと思います。
`(let [obj# ~obj]
(concat (obj-values obj# ~methodname) (obj-values obj# ~@methodnames)))))
こんなんが良い、かも?
確認しました。
確かに私の実装ですと、インスタンスが2回生成されてしまいます。
(macroexpand-1 '(obj-values (java.io.File. ".") lastModified canRead)) ;↓結果 (clojure.core/concat (user/obj-values (java.io.File. ".") lastModified) (user/ob j-values (java.io.File. ".") canRead))
ちなみに引用先は私ができなかった分のメソッド名と値のMap版や更なる発展版もあって参考になります。素晴らしいですね。
Javaオブジェクトに対して指定のメソッド名に対する値を取ってリストで返すClojureのマクロを作った
いやbeanを使えばいいという話もあるのですが、ちょっと必要に迫られて作りました。
(defmacro obj-values "Takes a Java object and method names, returns a list of method values." ([obj] '[]) ([obj methodname] `[(. ~obj ~methodname)]) ([obj methodname & methodnames] `(concat (obj-values ~obj ~methodname) (obj-values ~obj ~@methodnames)))
結果はこんな感じ。beanでは取得できない値も取得できます。
user=> (obj-values (java.io.File. ".") lastModified canRead canWrite)
(1275298723575 true true)
何故こんなマクロを作ったか。
あるクラスにgetHoge()というメソッドがあったとして、うっかりそのサブクラスにisHoge()なるメソッドを作ってしまったところ、サブクラスに対してbeanするとisHoge()の結果しか取れなくなったためです orz
本当はmethodnameに相当するkeywordを作ってマップで返したかったのですが、symbolからkeywordを作る手段が思いつかず、断念しました。
とは言えとりあえず上記のマクロがあれば今回は事足りるので、よしとします。
※リフレクションを使えば可能ですが、速度面を考慮してやめました。
おっちょこちょい?なScalaソースを添削してClojureに書き換えてみた
If you have to learn just one programming language - Babu Srinivasan's blog
を見ていたが、最後に出てきたScalaのソースがダメなJavaソースそっくりなことにがっかり。
はてブには「Scalaサンプルがひどい。for文の中を、moneyを引数にして新しいmoneyを返す関数に書き換え、その関数の結果を次の関数の引数に渡す〜みたいなスタイルにすればいいのに。」と書いたが、それをClojureで書くとこんな感じか。
;28 - 36行目 (defn game [bet money] (let [dice (inc (rand-int 100))] (cond (<= dice 50) (- money bet) (and (>= dice 66) (<= dice 75)) (+ money (* 0.5 bet)) (and (>= dice 76) (<= dice 99)) (+ money bet) (= dice 100) (+ money (* bet 2)) :else money))) ;22行目 (defn init-money [bet num-plays] (* bet num-plays)) ;27行目のfor文の代わり (defn play-stream [bet num-plays] (iterate #(game bet %) (init-money bet num-plays))) ;27行目のfor文をnum-plays分実行した結果 (defn play [bet num-plays] (nth (play-stream bet num-plays) num-plays)) ;結果の表示用文字列 (defn report [init result num-plays] (format "%6.0f$ became %6.0f$ after %6d plays: You get %.2f for a dollar" init result num-plays (/ result init)))
結果は
(let [bet 1.0 num-plays 100000 init (init-money bet num-plays) stream (play-stream bet num-plays)] (println (report init (play bet num-plays) num-plays))) 100000$ became 80953$ after 100000 plays: You get 0.81 for a dollar nil
内部クラスのインスタンス生成は誰がインスタンス生成したかによって結果が異なる
今までインスタンスに対して、newを呼び出せるとは知らなかった。
内部クラスのインスタンス生成 - or1ko's diary
なにか、効果的に使う方法はあるのかな?
にコメントしたけど、インナークラスがエンクロージングクラスのインスタンス変数を参照するような場合、どのエンクロージングクラスがインナークラスのインスタンスを生成したかが問題になります。
public class EnclosingClass { private String enclosing; public EnclosingClass(String enclosing) { this.enclosing = enclosing; } public class InnerClass { private String inner; public InnerClass(String inner) { this.inner = inner; } @Override public String toString() { //InnerClassのインスタンスを生成したEnclosingClassの //インスタンス変数enclosingを参照していることに注意。 return enclosing + "," + inner; } }; public static void main(String[] args) { EnclosingClass c1 = new EnclosingClass("A"); EnclosingClass c2 = new EnclosingClass("B"); System.out.println(c1.new InnerClass("a").toString()); System.out.println(c2.new InnerClass("b").toString()); System.out.println(c1.new InnerClass("c").toString()); } }
結果はこんな感じ。コンストラクタに"a"を渡してる方と"b"を渡してる方では、エンクロージングクラスのインスタンスが違う(c1とc2)ので、結果も以下のようになります。
A,a
B,b
A,c
余談だけど、上記の投稿に対するコメントで、
「例えとしてはアレですが、自分を作ってくれた親が誰であるかが問題となるような場合ですね。」
という俺の例えはもう少し何とかならんかったのか orz パパ予備軍となった身としては情けない orz...
Java 6 Update 10 Kernel Online版インストーラ
JavaのJRE 6の方にだけ、Kernel Online版インストーラというものが用意されています。
Java SE - Downloads | Oracle Technology Network | OracleのJava Runtime Environment (JRE) 6 Update 10 を選び、環境を選んだあと、ダウンロードできるインストーラが出てきますが、少なくともWindows版だと、以下のようなインストーラが候補に出てきます。
Windows Online Kernel Installation 0.20 MB
jre-6u10-windows-i586-p-iftw-k.exe
インストーラは204.9KBしかありません。とは言うものの、以前のオンライン版インストーラも500KB程度なので、あまり違いが見出せないかもしれません。
で、インストーラを起動すると、早速ネットに接続し、インストール…
完了と同時にJavaFX関係のモジュールも何故か一緒にインストールされる。う、うん。抱き合わせですかね?
インストール直後のjre6フォルダは103MB。えーと、オフライン版のインストーラでインストールした場合だと86MB位だった気がするんですけど?
肝心のrt.jarのサイズは7.7MBしかありませんでした。これはかなり小さい。元々のrt.jarは40MB強あったので、rt.jarのロード時間は多少短くできそうです。
で、早速java.exe -version と打ってみると、初回だけ、いきなりネットに繋がりました。何と言う…。まぁその後は大丈夫でしたけど。
あとはNumbus LAFを見てみたり、アプレットを動かしてみたりして、一通り問題なく動くことを確認したのち、またjre6フォルダのサイズを見ると…、
「86.78MB」
ちょw インストール直後よりもサイズが小さいやんww
更にはrt.jarが41.7MBとかwwwwww
個人的には、rt.jarの差し替えはないわー派なんですけど、この挙動は何なんだろう。ちょっと納得いかない挙動でした。
ちなみに、認証付きプロキシ環境下でOnline Kernel版をインストールすることはもちろん可能ですが、初回のみ出るネット接続のダイアログが気になるかもしれませんので、そのような環境の場合は従来どおりのフルセットのオフライン版インストーラを使うのが妥当かと思います。
JDK 6 Update 10 / JRE 6 Update 10がリリースされました。
日本語のサイトからだと全然ダウンロードできる気配がないので、英語サイトからどうぞ。
Java SE - Downloads | Oracle Technology Network | Oracle
既存のSwingアプリをNimbus LAFで試したい場合は、以下のようなシステムプロパティを加えて起動すればOKみたい。綺麗だけど画面崩れまくりですね。やりましたね!
-Dswing.defaultlaf=com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel
StrutsからWicketに移行する際にとまどった部分とその解決方法
Strutsでこういう実装をしたのを、Wicketではどうやればいいんだろう?と私が思った内容をつらつら書きます。もうだいぶ前に実装完了しちゃったから、思い出せるものも多くないかも知れませんが、少しでも役に立つと幸い。
私が当時使っていたStrutsとWicketのバージョンはそれぞれ以下の通りです。
RequestProcessor#preprocess()*1でアクション実行前の前処理実施
WebApplication#newRequestCycle()をオーバーライドし、WebRequestCycleのサブクラスのインスタンスを逐一返すようにする。肝心のWebRequestCycleのサブクラスはonBeginRequest()をオーバーライドすればOK。
ただしこの方法だと、Strutsの場合はアクションのみ前処理を行い、JSP直アクセスの場合は前処理を行いませんでしたが、Wicketの場合はWicketのFilterに引っかかるすべてのリクエストに対して前処理を行うはず。
PlugInの替わりの機構
何がそれに該当するか分かりませんが、Webアプリケーションの初期化を行うのは、WebApplication#init()で実装するので、同じ風に実装すればいいんじゃないでしょうか。
DIを使いたい場合も、この辺で行いますし。
認証の仕組みを入れる
WebApplicationではなく、Wicket-auth-rolesで提供されている、AuthenticatedWebApplicationのサブクラスを実装しましょう。また、セッションはSessionではなく、AuthenticatedWebSessionのサブクラスを実装することになります。
public class SampleApplication extends AuthenticatedWebApplication { @Override protected Class<? extends AuthenticatedWebSession> getWebSessionClass() { //AuthenticatedWebSessionのサブクラス return SampleAuthenticatedWebSession.class; } @Override protected Class<? extends WebPage> getSignInPageClass() { //未認証時に遷移するログイン画面 return LoginPage.class; } } public class SampleAuthenticatedWebSession extends AuthenticatedWebSession { @Override public boolean authenticate(String username, String password) { //認証の実装を行う。 } @Override public Roles getRoles() { //認証成功時のロールを返す。ここではUSERとする。 return new Role("USER"); } }
で、最後に認可を行いたい各画面に対しては、アノテーション@AuthorizeInstantiationを、ロール名付きで付与すればOK。該当ロールを持っているセッションの場合は、表示されるべき画面が表示されます。ロールを持っていない場合は、SampleApplicationで指定しているログイン画面に遷移します。
何より嬉しいのが、ログイン後に以前の処理をそのまま継続するかしないかまで簡単に制御可能なところ。素晴らしいねWicketさん。
@AuthorizeInstantiation("USER") public class SamplePage extends WebPage { }
動的フォームの作成
例えばStrutsでList型のフォームを使う場合、ActionFormのインスタンス変数にListを用意、それらのgetter/setterも用意して、JSP側ではそのListに対してiteratorを回してフォームを生成する。フォームに入力された値は、直接Listに格納される、みたいなことができました。
Wicketの場合、私の場合ですが、直接値を格納できるListの変数を用意せず、List
public class HogeHogePanel extends Panel { //動的フォームの各要素を格納するモデルのリスト private List<IModel> models = null; //動的フォームを表現するフォームコンポーネントを格納するリスト private List<FormComponent> formComponents = null; public HogeHogePanel(String id) { //... for () { //動的フォームの各要素に紐付くモデルを作り、リストに入れておく Model model = new Model(""); models.add(model); //フォームコンポーネントにも紐付けておく FormComponent component = new 何がし(); component.setModel(model); formComponents.add(component); } //フォームコンポーネントの配置 ListView listView = new ListView("nanigashi", formComponents) { @Override protected void populateItem(ListItem item) { FormComponent component = (FormComponent) item.getModelObject(); item.add(component); } }; listView.setReuseItems(true); //入力した値を再利用可能に。 form.add(listView); } }
CompoundPropertyModelやその他の方法で直接インスタンス変数に値を格納する方法が分からなかったので、モデルのリストを用意しておいて、そこに値を格納する、という形を取りました。
ちょいと時間がなくなったので、思い出したらどんどん書いていきます。
*1:Struts1.2以前の場合。Struts1.3はRequestProcessorのサブクラスの実装、ではなく、Chains of Responsibilityパターンを使っているので、ActionCommandBase#execute()の実装になりますね。