読者です 読者をやめる 読者になる 読者になる

プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

スマホがパソコンに取って代わるべきではない理由

wpb.shueisha.co.jp

 上記の記事を読んだからという訳ではないですが、私は常々スマホはパソコンに取って代わるべきではないと思っています。

 取って代われ「ない」ではなくて、取って代わる「べきではない」です。技術的には近い将来、取って代わられるでしょう。(むろん、全てが、とは言いません。)

距離とダメージ

 さて、私が取って代わるべきでない、と考える理由はデバイスとの「物理的な距離」です。

 技術が発展するにつれて、計算機と人間の物理的な距離はドンドン近くなっています。最初はどでかい建物だった計算機がいつの間にか卓上に乗るようになり、気がつけば手に乗り、腕時計や眼鏡として体に密着するようになってきました。最終的に行き着く距離は0どころか、人間の体内に埋め込む同一化でしょう。

 同一化まで技術が進歩した場合どうなるのかは分かりませんが、現状の技術では、どうしても「眼」への負担が大きくなってきています。

 私なんかは

  • 遠視
  • 弱視
  • 斜視
  • 乱視
  • 不同視
  • 飛蚊症
  • ドライアイ

と、眼の異常のオンパレードなので、よく分かるんですが、スマホって1時間も弄ってると眼へのダメージがやばいです。あぁ、後、眼圧も昔から若干高いそうで。もう、吸血鬼みたく目からビーム出したり、邪気眼の1つぐらい生えててもおかしくないんじゃないんですかね。

 昔から、近いモノを見続けることは、目に良くないと言われています。特にスマホの距離は乱視とドライアイに大ダメージで、うっかりスマホゲーとかぽちぽちやってると、その後2,3時間はぶれまくって、まともに見えなくなります。PCは仕事で10時間ぐらいまでなら弄っててもそこまでじゃない(10時間を越えると視界がやばい)んですが、スマホなら大体30分ぐらいが視界維持の限界です。Surfaceとかベッドに寝っ転がりながらよく弄っているのですが、文字を大きくして使っていても、2時間も弄ると眼がやばいです。


スマホに取って代わられると長時間使用が強制になる

 PCやTV、ゲームは目に悪い。そんなの随分と昔から言われてきたことで、今更な話です。

 しかし、だからといって、「健康のため、我が社は社員にPCをさせません」という会社があるでしょうか?無いですよね。多くの人がドライアイ等に悩みながら、長時間のPC作業を強制されているはずです。

 今やPC市場は斜陽産業で、生産数が落ちていけばスマホとPCの値段が逆転することもいずれ起こるでしょう。経費の問題からPCからスマホに移行ということもあるかもしれません。そうなると、PCと同様に、誰もが長時間スマホを使用することが強制されるはずです。

 こうなってくると、今は特にスマホ操作で異常を感じていない健常な眼を持っている人であっても、おそらく私と同じ症状が出てくるんじゃないかと思います。もしかすると、近距離でも目に優しいデバイスってのが出てくるかも知れませんが、そんなにすぐに出てくるとは思えません。(網膜に直接像を結ぶとか?)


 これを回避する手段として、スマホの外部端子を充実させて、周辺デバイスを利用するということが考えられます。しかし、経費削減を求める昨今の企業で、スマホが一つあれば仕事できるのに、追加のモニタやらキーボードやらを社員に配ってくれるってことはまずないでしょう。私の眼の悪さを理由に巨大なモニタを頂いてはいますけど、未だにSSDにはしてくれないんだよなぁ。(27インチディスプレイを1mは離して、フォントサイズ13以上じゃないと読む気がしない。一応、小さい文字も、まだ若いので眼筋を酷使すれば見えることは見えるけど、普通の人だったら15cmぐらいの距離の文字を読むぐらいには力を入れないといけないのです。)

PCスキルを身につけたらいいんじゃないかなぁ

 スマホとPCのどっちが効率的か?これについては、流石に2017年現在の今はまだPCの方に軍配が上がると思います。上がるんじゃないかなぁ?いや、正直なところ私がスマホ使わないから、よく分からんケド。

 でも、そのうちスマホもPCも大して効率に差が無いか、逆転するという職種も増えてくるでしょう。とはいえども、紙と計算機に比べれば、スマホとPCにはそこまで大きな効率差はないんじゃないでしょうか。

 ならばスマホにPCが取って代わられようとしたとき、我々の眼を守ってくれるのは、スマホよりもPCの方が圧倒的に効率よく作業できるというユーザーのPCスキルです。スマホにした所為で効率が下がった、PCの方がスキル的に効率が良いとなると、経営者も一考せざるを得ないでしょう。

 といっても、昨今PCが使えない若者とか、第二のディジタルディバイド世代とか言われているように、スマホが主流で、PCが使えない人というのが増えているそうで、望みが薄いのも実情でしょう。

 このことは、レガシーデバイスは消える運命なので、特に問題があるとは思えません。PCが使えない奴は~とか言う老害は消えろと私は思います。(老害共は最低限アセンブラぐらい書けるんだよな?)
 スマホの方が学習コストが安く、PCとの効率がさして変わらないなら、スマホを使うというのは正しい選択でしょう。

 が、これが原因で現代人の眼がさらに過酷な環境に置かれることにならないことを、切に願うのみです。

近年神社に人が多いなぁと思うこの頃

雑記

あけましておめでとうございます。今年の抱負は再来年から本気出すです(・ω・)

元旦は皆さんはどのように過ごすんでしょうかね?

私は家族+犬とともに地元の神社に行ってあまりの参拝客の多さに何もせずに引き返してきました。別に、髪も仏も信じていませんし。死後の世界も知ったこっちゃなくて、信じているのは自らの脂肪のみです。あふん


さて、その地元の神社というのは、別に有名な神社とかじゃなくって、ほんとに地元に密着したというか、もう、本当に普通の神社。の、はずなんですが、なんと道にあふれるほどの行列。年々なんか人が増えている気がします。

単に私の地元の人口が増えてきているのか、それとも、SNS等の効果なのか、不景気で神頼みなのか、不毛で髪頼みなのか。


靖国神社の参拝客が年々増えているのと何らかの関係性はあるのでしょうかね?観測しているのが一つの神社しかないし、ちょっとググってみても資料が見当たらないので、ほかの神社や地域でどうなっているのか少し興味があります。




という感じで、今年からは、割と雑記的な内容や不確実だったり、すごく短い技術記事を増やしていこうかなと思います。


これまでこのブログは、それなりに調べてまとめた技術記事ばかりで、私事は書かない方針でやってきました。でも、大学生のころと違ってそんなに時間かけて書くがしんどくなって、更新回数がめちゃめちゃ減ってたんですよね。一つの記事が5000文字とか、1万文字とか多すぎかなと。私の妹の卒論が5万文字だったそうで、それでも多いほうだったそうですよ。なるべく、1000文字以内で収めたいですね。

あ、でも私クソニートなので時間だけは糞あるんですけどね。体重の問題ですね。間違えました、体力の問題ですね。

あとはツイッターがいつ潰れるのかわからん、ってのもありますね。ツイッターで済ましてたようなことも全部ブログのほうに持ってこようかなと。


後は今年の夏ぐらいにブログの内容をまとめたHPを作りたいなぁなんて考えています。これは体力いるからやるかどうかわからないけど。今のところあまりやる気なし。



というわけで、今年もよろしくお願いします。

なお、空白や改行、HTML含めてちょうどこの記事が1000文字です。だいぶ楽。

JavaFX9からPlatformに追加されるAPIについて

JavaFX

 この記事はJavaFX Advent Calender 2016の17日目の記事になります。
 前日はid:skrbさんのSooner or Later - JavaFX in the Boxでした。
 明日はid:aoe-tkさんです。


 前回今年最後とか言ったな。あれは嘘だ。

 はい、というわけで、なんとJavaFX Advent Calender 2016に二度も顔出しですよ。この引きこもりの王者クソニート様が。誰かお仕事くらさい

 というのもですね、JavaFX9で追加される新機能は前回紹介した内容以外にも、PulseListenerの追加やPlatform.startupやら、他にも小さな機能の追加があるのです。これらの使い道が私には思いつかなかったか、そもそもあまり興味を持っていなかったかなどの理由でスルーしたのですが、実はスルーしてた中でPlatformにNested Event Loop関連のAPIが追加になっていたようなのです!

 いや、もうマジでこれ知らなかった。知ってたら前回の記事に絶対追加してた。というわけで、興奮してついうっかり2回も顔出しすることにしました。ほんとスマンかった。

 なお、このことはJavaFX: New & Noteworthy(※PDF)にも書いてあります。しかし、Nested Event Loopの話がPulseとPulse Listenerのページの間に挟まれているんですよね。Pulseとかあまり興味ないせいか、読むのをすっ飛ばした様で気がつかなかったんですね~。アホですね~。

 というわけで、Platformに追加されるAPI(主にNested Event Loop)について解説します。



Platform.startup

 Application.launch使う限り使わないと思われるAPI。SWTとかSwingから使うときとかを想定している模様。

 JavaFX Application Threadを生成直後に引数に渡したRunnableを実行するとかそんな感じっぽいけど、普通に使う場面はないと思う。ないよね?思いつかなかったから前回もスルーしたんだけど。





Platform.requestNextPulse

 Scene.addPostLayoutPulseListener,Scene.addPreLayoutPulseListenerと共に追加されたPulse関連のAPI。次のPulseの要求が出来る。

 JavaFXには我々が扱うJavaFX Application Threadの他にPrism Render Threadという、画面描画用のスレッドがあるのですが、この描画スレッドとJavaFX Application Threadを同期するイベントがPulseになります。

 JavaFXってマウスイベントとかにNodeを追随させようとしても、妙にマウスの動きからNodeの動きがずれるのが、改善されたりしないかとか思ったけど、全くそんなことはなかったぜ。

 これらPulse関連APIの使い道は、今のところ私には思いつきません。PulseListenerの方はレイアウトにどれぐらい時間かかってるか、デバッグ的な使い方くらいかなぁ。私の頭じゃ。



 Pulseについての詳細は以下のページを参照してください。
docs.oracle.com


Nested Event Loop

 さて、今回の記事の本題です。といっても、実はこのNested Event LoopはJavaFX初期からあって、3年前の私の記事でも実はついでに紹介しています。
nodamushi.hatenablog.com


 大ざっぱに書くと、JavaFX Application Threadは通常以下のような処理をしています。

 Nested Event LoopのAPIを使うと、この流れを以下の図のように、複製することが可能になります。

 複製と言っても、新しいスレッドが走っているとかではなくて、単純に呼び出した関数の中でイベント処理のループが走り始めると言うだけです。


 この機能がおもにどこで使われているかというと、showAndWaitメソッドです。showAndWaitでウィンドウが閉じるまで処理を待機しても、JavaFX Application Threadは停止することなく、他の処理を続けることが出来ます。この時に使われているのがNested Event Loopです。


 今までは、Stage.showAndWaitからしかアクセスできなかったこのNested Event LoopのAPIが公開され、いつでもどこでも使えるようになります。
 一番簡単に思いつく使い道で、かつ、効果が高いのは、Stageのダイアログと同様にNode単位でのモーダルダイアログでしょう。

f:id:nodamushi:20161211183827p:plain


 今までは、このようなことを実現しようとしたら、次のような2つに分かれた手順が必要でした。

  1. なんらかのイベントハンドラ等からダイアログ用のNodeを作り、対象ノードの操作を不可能にする。(ここで最初のイベントハンドラの処理終了)
  2. ダイアログのYesやCancelといったボタンが押されたときに動作するイベントハンドラが、残りの処理を行い、ダイアログを消去する。

f:id:nodamushi:20161211183828p:plain

 しかし、Nested Event Loopを使うと、一つのイベントハンドラの中で処理が完結します。

f:id:nodamushi:20161211183829p:plain


 これはかなりプログラムを書くのが楽ですね。
 最近はJavaもラムダを導入したり、実質finalが導入されたりと色々工夫されて、イベントハンドラを書くのが楽になってきましたが、それでも処理が二分割されるようなプログラムを書くのって面倒くさいんですよね。
 処理が二分割で済めば良いですが、何回かダイアログを表示して、しかもそのダイアログ内容が途中で分岐するような処理だったら、面倒なことこの上ないですよね。

 一つのEvent Handlerで処理が済むというのは、それだけで実装面からも保守管理の点からもメリットです。




Nested Event Loopの使い方

Nested Event Loopを利用するには、以下のフィールド変数(ローカル変数は駄目)が必要です。

private Object waitKey = null;
  • waitKey: Nested Event Loopを解除するのに必要になるキー。なくしたらexitできないので要注意。final宣言してしまっても良い。

 フィールド変数を用意したら、後は以下の関数を定義しておけば良いでしょう。
 なお、基本的にはJavaFX Application Threadからしか操作しないので、waitKeyをvolatile等にする必要はありません。

private Object waitKey = null;

public boolean isWait(){return waitKey!=null;}

// 戻り値はintなどに決まっているなら、それに変換しても良い。
protected Object enterWait(){
  if(!isWait()){
    waitKey = new Object();
    Object result = Platform.enterNestedEventLoop(waitKey);
    return result;
  }
  return null;
}
// 引数のresultは上↑のresultになる。
protected void exitWait(Object result){
  if(isWait()){
    Object key=waitKey;
    waitKey=null;
    Platform.exitNestedEventLoop(key,result);
  }
}

 そしたら、後はこんな感じ。

//ダイアログのNodeを作成完了後
cancelButton.setOnAction(e->exitWait(0));
okButton.setOnAction(e->exitWait(1));
noButton.setOnAction(e->exitWait(2));

//何かにダイアログを追加
targetPane.getChildren().add( dialog );

//ダイアログのボタンが押されるまで待機
boolean isOK = enterWait() == 1;

//ダイアログを削除
targetPane.getChildren().remove( dialog );

if(isOK){//OKなら処理
…
}

 むろん、Dialogクラスのように、ダイアログ表示機能と待機機能を何かのクラスにまとめておいても良いでしょう。



 このように、Nested Event Loopを使えば、処理の途中でGUIを介した処理の結果が必要である場面の記述が簡単になります。色々夢が広がリングですね。


 というわけで、今度こそ、良いお年を。

JavaFX9が良い感じになってきた件

JavaFX

 この記事はJavaFX Advent Calender 2016の9日目の@arachan@githubさんの記事になるそうです。
 前日は@skht777さんのJavaFXで動くプロ生ちゃんデスクトップマスコットを作る - Qiitaでした。
 明日は@Yucchi_jpさんのHitInfoを少しだけ…です。


 はい、テンプレってこんなもんで良いんですかね。
 何か今年やたらと参加者が少なくて盛り上がってる風がないですよね。寂しいですな。
 普段はこういうのは眺めてるだけで、関連記事とか書いてても参加しない奴なんですが、人が少なくて寂しいので、珍しく参加してみようかと。




今、JavaFX9が静かに熱い

 
 世にJavaFXが出てからというもの、派手に目立ったことはないように思います。

 私はJavaFX2からさわり初めて、本格的にJavaFX8から利用をしていますが、これまでの印象はおおよそこんな感じ。

  • JavaFX Script : そんな子はいなかった
  • JavaFX2 : え?WebView?あぁ、Qtですか?
  • JavaFX8 : そんな子がいるような気がする


 なんというか、なんだかんだちょっと凝ったことをしようとするとすぐに制限に引っかかったり、実装を追ってハックするような真似をしないといけなくて、正直に言えば、Swingの方が使いやすいと思っていました。

 しかし、そんなこれまでのバージョン達であっても、登場前や直後は話題にする人は結構話題に挙げていたのですが、JavaFX9の話題は全く盛り上がっていないようです。「JavaFX9」でググってみても、2016年12月現在、id:aoe-tkさんの記事ぐらいしかトップに出てきません。なお、JavaFX8ではいろんな記事が出てきます。

 たしかに、新しいコントローラなどが一切追加されない、非常に地味なアップデートしかありません。派手な話題は描きにくいかもしれません。


 だが、私は言いたい。


 JavaFX9はこれまでJavaFXにあった不満を多く解決してきたと。

 これまであってもなくてもぶっちゃけ変わらない、実に微妙な立ち位置でしかなかったあのJavaFXが。
 あのJavaFXがついにJavaFX9で、僅かに痒いところに手が届かないところまで来たと!



 そんなJavaFX9の良いところをちょっとだけ、紹介いたします。


Skinが公開された!

 皆さんもご存じ、ControlのSkinがついに、ついに、ついにjavafx.scene.control.skinパッケージとして公開されます。
 ほんと、ついにですよ。なんで最初から公開していないんですかね。


 本当にジミーな内容ですね。これが目玉の変更点なんだから、まー盛り上がりませんわな。(´・д・`)


 でも、今まではちょっと見た目を追加したいとか、ちょっと機能を追加したい等々、「ちょっとレールから外れたい」ということをやろうと思えば、com.sunパッケージをいやーな気分で操作するか、多大なコストを払って実装するか(それでも結局com.sumパッケージに行き着くんだけど)しかなかったんです。
 いや、もう、ホント、これで大手を振って色々実装できますよ。


 でも、今回公開されるのはSkinだけで、Behaviorは公開されません。BehaviorはControlの機能、動作を決定する重要なクラスです。com.sunパッケージ時代のSkinだと、コンストラクタの引数にBehaviorが要求されていたんですが、これらがなくなってしまいました。


 じゃぁ、Behaviorそのものがパージされてしまったのかというと、そうでもなくて、結局com.sunパッケージとして残っています。で、コンストラクタの引数ではなくて、コンストラクタの中で生成するようになってしまいました。うわぁ~ぃ(泣
 一応、privateなメソッドでgetBehaviorとかがあるので、そのうち公開する気なのかも知れませんね。気長に待っています。



 あ、そういえば、(少なくとも私が読んだ)JavaFX8までのBehaviorの動作って、次のようになっていたと思うんです。

  1. マウスイベントなどを受け取る
  2. マウスイベントなどからアクションに対応する「String」に変換する
  3. BehaviorにStringを投げる
  4. Stringに対応する処理を実行する


 全部が全部って訳ではないと思いますが、少なくともTextAreaのBehaviorは次のような感じに変わったようです。

  1. Behaviorがマウスイベントなどを受け取る
  2. イベントをキーとして、登録しているラムダを取得する
  3. ラムダを実行する


 見えないところも整理が進んでいるようですね。



Tooltipが良くなった!

 去年、私はこんな記事を書きました。
nodamushi.hatenablog.com

 要約すると、ざっと以下の二つが不満点。

  1. 表示の遅延、表示時間が変更できなくて不満だ
  2. フォーカス持っていないウィンドウでもツールチップが表示される上に、画面の最上にウィンドウが表示されてうざい。


 しかし、JavaFX9からは次の3つのPropertyが追加されます。

  • showDelay : 表示するまでの遅延時間。デフォルトは1秒
  • showDuration : 表示時間。デフォルトは5秒
  • hideDelay : マウスがNodeから離れてからTooltipが消えるまでの時間差。デフォルトは0.2秒
Label label = new Label("マウスを置いているとポップアップするよ。");
Tooltip tooltip = new Tooltip("すぐに現れて消えないTooltip!");
tooltip.setShowDuration(Duration.INDEFINITE);
tooltip.setShowDelay(Duration.ZERO);
label.setTooltip(tooltip);
Scene s = new Scene(label);
stage.setScene(s);
stage.show();

f:id:nodamushi:20161208203008p:plain

 いや~、ツールチップが消えないからキャプチャするのも急がなくて良いし楽だね。


 でも、残念なことに二つ目の不満点である、フォーカスを持たないウィンドウでもポップアップする挙げ句、ウィンドウを最上位まで持ち上げてきてしまう問題点は解決されていませんでした。



 これ結構な問題だと思うんだけど、中の人達に認識されていないのかな?



 あ、あと、これの解決方法が新しく追加されていないかと色々見てたときに気がつきましたが、JavaFX9からは現在表示しているJavaFXウィンドウの一覧をWindow.getWindows()で取得できるようになりました。これ、地味に嬉しい。
 私、これを自前で実装してたからね。

ObservableList<Window> windows = Window.getWindows()



子要素の順番を入れ替えなくても表示順序を変えられる!

 いままで、重なっている子要素の表示上下関係を入れ替えようと思ったら、getChildren()で子要素のリストを取得して、並びを入れ替えるしかありませんでした。
 Swingだったらzindexで変化するのに!と思っていたら、ついにJavaFX9でViewOrderプロパティが出来ました!


 ViewOrderは小さいほど手前に来ます。0が基準値です。負数も可能の様です。
 同じViewOrderの場合は子要素のリストに挿入された順番が反映される。

  Circle c1 = new Circle(100, 100, 100, Color.AQUA);
  Circle c2 = new Circle(100, 150, 100, Color.RED);
  Circle c3 = new Circle(300, 100, 100, Color.AQUA);
  Circle c4 = new Circle(300, 150, 100, Color.RED);
  pane.getChildren().addAll(
          c1,c2,//アクア色の円は赤より下
          c3,c4 //アクア色の円は赤より下
  );
  //c3,c4の表示順序を入れ替え、アクア色の円を赤の上に持ってくる
  c3.setViewOrder(0);//※0は初期値です
  c4.setViewOrder(1);

 実行結果の下図をご覧下さい。右側の二つの円はアクア色の円が赤色の円より上に表示され、左側の二つとは重なり方が変わっています。






念願の行番号付きテキストエディタが使える!

 JavaFX9で行番号付きのピュア(com.sunパッケージやリフレクションを利用しないという意味)なエディタが使えるようになります。
 主に私が作っていたアプリケーションの関係でどうしても欲しかったけど諦めてListViewとTextEditorを行き来することでなんとなくそれっぽい動作で諦めた、あの行番号付きエディタが!当時の私が知ったら号泣して喜んだでしょう。




 まぁ、これ、私が実装したんだけどね。ソースコードはGistにおいておきました。
JavaFX9でTextAreaに行番号がついたTextAreaを作ってみた · GitHub


 さて、先にも書いたように、ついにJavaFX9で今まで秘匿されてきたSkinの実装が公開されました。まだまだ公開して欲しいAPIはいっぱいあるのですが、重要なAPIは結構出そろってきました。その中の一つがText関連のAPIです。
 座標とTextの文字の位置を紐付けるHitInfoクラスTextInputControlSkinのgetCharacterBoundsメソッドが公開されたことにより、文字のインデックスとTextシェイプの座標内の対応が取れるようになりました。

 今回はこの二つを利用して行番号を表示すべき位置を取得し、TextAreaに行番号を付けてみました。


手順

 いい加減記事が長いので、詳細はソースコードを適当に眺めていただくとして、実装したものはザックリと説明すると以下の3手順です。

  1. まずは、TextAreaの文字列を行単位に分割する
  2. TextAreaの左上の座標と左下の座標の挿入位置を検出し、その範囲に入る行について3を処理する
  3. 各行の行頭文字のBoundsを取得し、その位置に会うように行番号を配置(空行などは上手いこと何とか処理する)


 まず、1の文字列を行単位に分割するのは別に説明は要らないでしょう。
 3の、表示位置の取得は、各行の行頭文字インデックスが分かれば、getCharacterBounds(index)で簡単に取得可能です。で、問題は2でした。


HitInfoの落とし穴

 さて、TextInputControlSkinから挿入位置を取得するには、getIndex(x,y)でHitInfoを取得するか、getInsertionPoint(x,y)で取得します。これらはどっちも結果は同じです。
 さて、画面左上の座標の挿入位置と、左下の座標の挿入位置を検出したいのだから、単純に考えるとこうなるはず。

int topInsert = getInsertionPoint(0,0);
int bottomInsert = getInsertionPoint(0,height);


 私は最初これで実装して、行番号が表示されなくて悩みました。

 これで取得されるtopInsertとbottomInsertは、topInsertは0で固定、bottomInsertは-1もしくは、画面の大きさ依存の値で飽和するかになります。

 と、言うのもここでの(0,0)や(0,height)の座標は、TextAreaの座標ではなく、あくまでText内での座標を指しています。従って、Textの原点位置での挿入場所は当然最初の0になってしまうし、(0,height)と指定しても、スクロールバーとか考慮されていないので、ある値で飽和してしまう。-1が返ってくるのは、(0,height)の範囲にTextシェイプが存在していなかったからかも知れないが、これに関しては詳しくはまだ追っていません。

 従って、目的のことがしたい場合は次のようにオフセットを考慮してあげる必要があります。

double offsetY = getCharacterBounds(0).getMinY();
int topInsert = getInsertionPoint(0,-offsetY);
int bottomInsert = getInsertionPoint(0,height-offsetY);
if(bottomInsert < 0)bottomInsert = textArea.getLength();

 最初の文字のY座標をオフセットとして引いてやると、上手くいく。
 bottomInsertは-1になる可能性があるから、-1になっていたら、ひとまずTextAreaの文字列長に設定してやれば良いと思われる。



 まぁ、これはHitInfoの落とし穴というよりは、getIndex(x,y)の仕様ミスだと思うけど。
 実際に公開するまでに変わったりするかもね?変えた方が良いと思うよ?


みんなJavaFX9使おうぜ!

 はい、というわけで、どうでしたでしょうか?
 これまでは、「はぁ~、これもできねぇのかよ」とか、「あ~、動きがもっさり」とか、「JavaでもQtでよくね?Swingでよくね?」「男なら黙ってCUI」などなど、散々(主に私に)捻られてきたJavaFXですが、かなり良い子になってきたと思います。

 Swingとは使い勝手がだいぶ違うので、今までSwingをやってたって人だと、それなりに学習コストを払わなくてはなりません。しかし、それを払うだけの価値はJavaFX9でそれなりに出てきたと思います。


 まだ画面がなーんかちらつくとか、3DShapeとかいらねーからOpenGL使わせろとか、シェーダー書かせろとか、KyeEventが使いにくいとか、Behaivor公開しろとか、言いたいことは山ほどありますが。まぁ、要求なんて何年たっても0になることはないので、焦ってもしょうがないですね。



 ぜひ、この機会にJavaFXを本格的に使いはじめてみませんか?ということで、今回の記事はおしまいです。

 最後まで読まれた方がいましたら、長々とおつきあいただきありがとうございました。





 実は、この記事は5日前に最後の行番号付きTextAreaの話だけで公開しようとしてたんです。でも、ついでにAdventカレンダーに登録してやろっかなーと思ったところ、明日のYucchi_jpさんと内容被りそうだったので、誤魔化すために色々追加したらこんなに長くなりましたとさ。も~、今年のブログ更新はこれで最後でいいな。良いお年を。

Javaにlambda修飾子が欲しい

JAVA

 機能(関数)をクラス内に実装せずに、外部に委譲することがある。単純な例ではこんな感じ。

public class Action{
  private Runnable action;
  public void setAction(Runnable action){this.action = action;}
  public void action(){  if(action != null) action.run(); }
}


 Runnableのように引数を取らないような関数なら別に問題ないが、大概の場合は委譲元のインスタンスを引数にとりたい。
 すると、こうなる。

public class Action{
  private Consumer<Action> action;
  public void setAction(Consumer<Action> action){this.action = action;}
  public void action(){if(action!=null)action.accept(this);}
}




委譲は継承が面倒

 機能の委譲をしている場合でも、データの拡張などでクラスを継承する方が合理的な場合もある。

public class Action{
  private Consumer<Action> action;
  public void setAction(Consumer<Action> action){this.action = action;}
  public void action(){action.accept(this);}
}
class ActionB extends Action{
  public int b;
}

 委譲した関数が、ActionBにしか対応する必要がない場合、このままだと一々キャストが発生する。

actionB.setAction(a->{
  ActionB b = (ActionB)a;//キャストめんどい
  System.out.println(b.b);
})   

 かといって<? extends Action>にしておくのも上手くない。

public class Action{
  private Consumer<? extends Action> action;
  public void setAction(Consumer<? extends Action> action){this.action = action;}
  public void action(){if(action!=null)((Consumer)action).accept(this);}
}
public class ActionB extends Action{
 public int b;
}
public class ActionC extends ActionB{
 public int c;
}


ActionB actionB;
actionB.setAction((ActionB b)->System.out.println(b.b));//ActionBの型を書くのめんどい
actionB.setAction((ActionC c)->System.out.println(c.c));//これも通る





安全だし、ラムダで型を書かなくて良い楽な方法もあるにはある

 ただし、マジでめんどい

public class Action <V extends Action<?>>{
  private Consumer<? super V> action;
  protected void setAction(Consumer<? super V> action){this.action = action;}
  public void action(){if(action!=null)((Consumer)action).accept(this);}
}

class ActionB<V extends ActionB<?>> extends Action<V>{//<V extends ActionB<?>>がめんどい
  int b;
}
class ActionC<V extends ActionC<?>> extends ActionB<V>{//<V extends ActionC<?>>がめんどい
  int c;
}


ActionB<?> actionB = new ActionB<>();//<?>と<>を書くのがめんどい
actionB.setAction(b->System.out.println(b.b));//ラムダ式は楽
actionB.action();




ならば、こう書ければ楽なはずだ

 そもそも人間がガンガるから面倒くさいのだ。コンパイラが頑張れば良いのだ。

 たとえば、以下のようなアノテーションとジェネリックを書いておけば、Setter,Getter,メソッドコールの部分は自動的にJavaコンパイラが頑張ってくれればいいんじゃないのかな。

public class Action{
  @TransferMethod //委譲メソッドであること宣言する的な
  private Consumer<this> action;
}
public class ActionB extends Action{
  public int b;
}

ActionB actionB = new ActionB();
actionB.setAction( b -> System.out.println(b.b) );
actionB.action(); 

 

lambda修飾子があればもっと楽なはずだ

 だが、ここまで考えて気がついたのだが、Consumer<this>も書くのがめんどい
 Consumerならまだ楽だが、引数が増えたときは考えたくない。


 コンパイラが頑張るなら、もはやこれで良いはずだ。

public class Action{
  public lambda void action();
}
public class ActionB extends Action{
  public int b;
}

ActionB actionB = new ActionB();
actionB.action = b -> System.out.println(b.b) ;
actionB.action(); 

 なんと、実に楽だ。ついでにdefaultも使えるとさらに良いかもしれない。

public class Action{
  public default lambda void action(){System.out.println("Action");}
}

 マルチスレッド対応のためにvolatileとかsynchronizedとか修飾できても楽しいかも知れない。


 というわけで、nativeとか、abstractみたいな感じで、lambda修飾子があるとラムダがよりいっそう楽しそうな気がするのですが、どうでせうか。Lombokみたいな感じで実現できないだろうか?
 絶対、どっかの誰かが提案してるんじゃないかな?

jshellを自分のプログラムに組み込みたかった

JAVA

はい。タイトルの通りです。しょーもないことしてました。


jshell

JDK9から付属することになっているちまたで話題のJavaのREPL。
私は全く興味がなかったんですけど、ふと思ったんですよ。これ自分のプログラムに組み込めないかな?


GUI系のプログラムは毎回実行して動作確認して終了して直して実行して………が面倒くさいんですよ。
JavaScriptとかGroovyとかを実行できるような簡易GUIを搭載したりしてそこでチマチマ弄れるようにしたりしてるんですけど、これをjshellに置き換えられないかな?と調べてみました。


自分のプログラム内部でJShellを作成して実行する

JShellは以下のような感じで作成できるようです。

try(JShell shell = jdk.jshell.JShell.create()){
}

 というわけで、以下のようなコードを試してみました。

package test;

import java.util.List;
import jdk.jshell.*;

public class TestApplication {
    
    public static String PRINT = "------Not Change-------";
    
    public String method(){//JShellでこのメソッドを呼ぶ
        System.out.println(PRINT);
        return "Test Application";
    }
    
    public static void main(String[] args){
        PRINT = "Print this text?";//ここでPRINTを入れ替えている
        try(JShell shell = JShell.builder()
                //err,outで出力先を変えられる
                .err(System.out)
                .out(System.err)
                .build()){
            shell.addToClasspath(System.getProperty("java.class.path"));
            List<SnippetEvent> evs =  shell.eval("new test.TestApplication().method();"); 

            for(SnippetEvent e:evs){
                if (e.causeSnippet() == null) {
                    switch (e.status()) {
                        case VALID:
                            if(e.value()!=null)
                                System.out.println("eval result = "+e.value());
                            else
                                System.out.println("Success.(no result)");
                            break;
                    }
                }
            }
        }
    }
}

 もし、私の望み通りの動作をするならば、「Print this text?」という文字が出力された後に、「eval result = Test Application」となるはずです。

結果

f:id:nodamushi:20161130221852p:plain

 ………残念でした。------Not Change-------とプリントされてしまいました。
 つまり、JShellエンジンではmain関数は実行されていないことになっているのです。(main関数の一番最初にPRINT変数を入れ替えている)


 どうもJShellは実行時にもう一個別のJVMを立ち上げて、そちらをリモートで操作するような処理をしているようです。
 上記のプログラムのmethod()でsleepするように書き換えてから実行すると、2個Javaのプロセスが増えるのを確認できます。

f:id:nodamushi:20161130221853p:plain


それでもjshellを組み込みたい

>jshell -cp 必要なクラスパス

jshell>test.TestApplication.main(new String[0]);

ん~~………

Java9のVarHandleを使ってみた

JAVA

 AtomicIntegerを使う必要が出てきた場面で、ふとVarHandleのことを思い出し、ちょっと試してみました。
 試したコードは最後に載せておきますが、やってみたことはこんな感じ。

  • staticなint型変数のVarHandleの取得
  • setとsetVolatileの実行
  • int,volatile int,int(synchronized),AtomicIntegerで並列カウントし、結果の出力と簡易的な時間の計測



VarHandleの取得

 まずこの時点でつまった。JavaDocに書いておいてよ、取得方法。
 たぶん、こんな感じ。まず、Hogeにstaticなaと、フィールドメンバのbがあるとする。

public class Hoge{
  static int a;
  int b;
  int[] arr;
}

 これの取得はたぶん、こんな感じ。(後述するテストコードではlookup().in(Test.class)というのをキャッシュしてから実行してる)

java.lang.invoke.MethodHandles.lookup()
                .findStaticVarHandle(Hoge.class,"a",int.class);
java.lang.invoke.MethodHandles.lookup()
                .findVarHandle(Hoge.class,"b",int.class);
//配列の「要素」を操作したい場合は以下のメソッドでVarHandleを取得する
java.lang.invoke.MethodHandles.arrayElementVarHandle(int[].class);




代入

 VarHandleのJavaDocを眺めていると、値を代入する単純なメソッドはsetとsetOpaque,setReleaseとsetVolatileがあるっぽい。

  • setは普通の代入の代わりっぽい。
  • setOpaqueはプログラムオーダーは保証されるけど、スレッド間のメモリの整合性を考慮しない普通の代入?
  • setReleaseはリオーダーがされないことが保証されるっぽい。setReleaseを使えばJavaでダブルチェックロッキングパターンが実装可能な感じかな?
  • setVolatileがvolatileが付与されているかのごとく代入するっぽい。setReleaseとの差はよく分からん。

 まぁ、よくわからんけど、volatile修飾子がついていてもsetで代入できるし、volatile修飾子がついていなくてもsetVolatileが実行できるっぽい。

        //------ set COUNTER ----------------------
        System.out.println("---------Method set------");
        COUNTER_HANDLE.set(10); // volatileではない変数のハンドル
        V_COUNTER_HANDLE.set(10);//volatileな変数のハンドル。setVolatileじゃなくても代入可能

        System.out.println("---------Method setVolatile------");
        COUNTER_HANDLE.setVolatile(0);//volatileではない変数のハンドル。setじゃなくても代入可能
        V_COUNTER_HANDLE.setVolatile(0);////volatileな変数のハンドル。

 なお、staticな変数でない場合は、以下のようにする

handle.set(obj,0)

 配列の要素を操作したい場合は、以下のようにする

handle.set(arr,index,value)




カウンターの測定

 以下の4つの条件でカウンターを動かし、実行時間の測定と、最終結果が正しいかどうかを確認する。

  • volatileな変数をVarHandleでインクリメント
  • volatileな変数をVarHandleでインクリメント
  • 非volatileな変数をsynchronizedした関数でインクリメント
  • AtomicIntegerでインクリメント

 ちなみに、2016/11/28日現在のJDK9のソースコードではAtomicIntegerはまだUnsafeを使っているようです。

 まずは、シングルスレッドで20億回インクリメントして測定したもの。(※後述のソースコードには存在していない

内容 最終結果 計測時間
volatileな変数をVarHandleでインクリメント 2000000000 約10秒
volatileな変数をVarHandleでインクリメント 2000000000 約10秒
非volatileな変数をsynchronizedした関数でインクリメント 2000000000 約40秒
AtomicIntegerでインクリメント 2000000000 約10秒

 
結果からシングルスレッドではVarHandleとAtomicIntegerの速度は同等であることが言える。


 次に200万回インクリメントする関数を、1000スレッド起動して実行し、最終結果と実行時間を計測した。実行時間の計測には本当ならJVMが安定するまでの時間等を考慮するべきだけど、結構時間かかる処理だから誤差でしょってことでその辺は考慮していない。

内容 最終結果 計測時間
volatileな変数をVarHandleでインクリメント 2000000000 約40秒
volatileな変数をVarHandleでインクリメント 2000000000 約40秒
非volatileな変数をsynchronizedした関数でインクリメント 2000000000 約38秒
AtomicIntegerでインクリメント 2000000000 約40秒

 おんや?volatileでなくても結果が正しく、また、何度実行してもsynchronizedが他のよりも2,3秒早いという結果になりました。
 前者についてはJavaDocを読んでみると、VarHandle.getAndAdd()が内部でVarHandle.setVolatile()を使っているようで、volatileがついていなくてもvolatileがついているのと同等に扱われたためかと思います。
 後者については、AtomicIntegerとかVarHandleはCPUが常に100%になるのにたいして、synchronizedするとスレッドが待機状態になるから、あまりCPUリソースを使わないのが原因かと思います。
 
 これではちょっと結果としてどうかという気がしたので、条件を変更して250000000回インクリメントする関数を8スレッド動かしてみました。この位のスレッド数なら、通常でもあり得るでしょう。

内容 最終結果 計測時間
volatileな変数をVarHandleでインクリメント 2000000000 約38秒
volatileな変数をVarHandleでインクリメント 2000000000 約38秒
非volatileな変数をsynchronizedした関数でインクリメント 2000000000 約64秒
AtomicIntegerでインクリメント 2000000000 約38秒

 おんやぁ?私の予想に反して、synchronizedがえっらい遅くなっただけですね。(VarHandleとかがもうちょっと早くなるかと思ってた)
 ま、まぁ、synchronizedが遅いという結果が得られたので良しとしましょう。

 上記の結果をまとめると以下のようになる。

  • AtomicInteger(Unsafeを使用)とVarHandleは速度が同等
  • volatileであってもなくても、VarHandle.getAndAddが中でsetVolatileを使っているためか結果は同じ
  • volatileであってもなくても、VarHandleを使った場合実行時間は同じ



使ってみた所感


 ぶっちゃけ使いにくい。使うの面倒くさいわー。AtomicIntegerで良いと思うわー。


 かつて、以下↓のような構文が使えるようになるとか何とか聞いた気がしたんだけど、早くプリーズ。

 field.volatile.incrementAndGet();



テストコード

 

import java.lang.invoke.*;
import java.util.concurrent.atomic.*;

public class Test{

    private int a = 0;
    private int[] arr = new int[10];
    private final VarHandle aHandle,arrHandle;

    public Test(){
        VarHandle ahandle=null,arrhandle=null;
        try{
            ahandle=MethodHandles.lookup().findVarHandle(Test.class,"a",int.class);
            arrhandle=MethodHandles.arrayElementVarHandle(int[].class);
        }catch(Throwable t){
            t.printStackTrace();
        }
        this.arrHandle =arrhandle;
        this.aHandle =ahandle;
        aHandle.set(this,100);
        System.out.println("a = "+a);
        arrHandle.set(arr,5,100);
        System.out.println("arr[5] = "+arr[5]);
    }



    private static int COUNTER=1234;
    private static final VarHandle COUNTER_HANDLE;//COUNTERのハンドル

    private static volatile int V_COUNTER=5678;//※ volatileを付加
    private static final VarHandle V_COUNTER_HANDLE;//V_COUNTERのハンドル

    private static int S_COUNTER = 0; // synchronizedな関数でカウントする為の変数

    private static final AtomicInteger ACOUNTER=new AtomicInteger(0);



    //----  VarHandleの初期化。たぶんこんな感じ---------
    static{
        VarHandle handle = null,vhandle=null;
        try{
            MethodHandles.Lookup lookup = MethodHandles.lookup().in(Test.class);
            handle = lookup.findStaticVarHandle(Test.class,"COUNTER",int.class);
            vhandle = lookup.findStaticVarHandle(Test.class,"V_COUNTER",int.class);
        }catch(Throwable t){
            t.printStackTrace();
        }
        COUNTER_HANDLE = handle;
        V_COUNTER_HANDLE = vhandle;
    }
    //-----------------------------------

            


    public static void count(){
        COUNTER_HANDLE.getAndAdd(1);
    }

    public static void vcount(){
        V_COUNTER_HANDLE.getAndAdd(1);
    }

    public static synchronized void scount(){
        S_COUNTER++;
    }



    public static final int LOOP = 2000000000/8;
    public static final int THREADS = 8;
    public static void run1(){
        for(int i=0;i<LOOP;i++){
            count();
        }
    }
    public static void run2(){
        for(int i=0;i<LOOP;i++){
            vcount();
        }
    }
    public static void run3(){
        for(int i=0;i<LOOP;i++){
            scount();
        }
    }
    public static void run4(){
        for(int i=0;i<LOOP;i++){
            ACOUNTER.incrementAndGet();
        }
    }


    public static void main(String[] args){
        new Test();


        showCounter();
        showVCounter();
        
        //------ set COUNTER ----------------------
        System.out.println("---------Method set------");
        COUNTER_HANDLE.set(10);
        V_COUNTER_HANDLE.set(10);//setVolatileじゃなくても代入可能
        showCounter();
        showVCounter();
        System.out.println("---------Method setVolatile------");
        COUNTER_HANDLE.setVolatile(0);//setじゃなくても代入可能
        V_COUNTER_HANDLE.setVolatile(0);
        showCounter();
        showVCounter();


        System.out.println("---------Run1 start------");
        long t = System.currentTimeMillis();
        Thread[] ths  = new Thread[THREADS];
        for(int i=0;i<THREADS;i++){
            ths[i] = new Thread(Test::run1);
            ths[i].start();
        }
        for(int i=0;i<THREADS;i++){
            try{
                ths[i].join();
            }catch(InterruptedException e){}
        }
        t = System.currentTimeMillis() - t;
        showCounter();
        System.out.println("Time = " + t/1000d);


        System.out.println("---------Run2 start------");
        t = System.currentTimeMillis();
        for(int i=0;i<THREADS;i++){
            ths[i] = new Thread(Test::run2);
            ths[i].start();
        }
        for(int i=0;i<THREADS;i++){
            try{
                ths[i].join();
            }catch(InterruptedException e){}
        }
        t = System.currentTimeMillis() - t;
        showVCounter();
        System.out.println("Time = " + t/1000d);



        System.out.println("---------Run3 start------");
        t = System.currentTimeMillis();
        for(int i=0;i<THREADS;i++){
            ths[i] = new Thread(Test::run3);
            ths[i].start();
        }
        for(int i=0;i<THREADS;i++){
            try{
                ths[i].join();
            }catch(InterruptedException e){}
        }
        t = System.currentTimeMillis() - t;
        System.out.println("S_COUNTER="+S_COUNTER);
        System.out.println("Time = " + t/1000d);

        System.out.println("---------Run4 start------");
        t = System.currentTimeMillis();
        for(int i=0;i<THREADS;i++){
            ths[i] = new Thread(Test::run4);
            ths[i].start();
        }
        for(int i=0;i<THREADS;i++){
            try{
                ths[i].join();
            }catch(InterruptedException e){}
        }
        t = System.currentTimeMillis() - t;
        System.out.println("ACOUNTER="+ACOUNTER.get());
        System.out.println("Time = " + t/1000d);
        
    }


    public static void showCounter(){
        System.out.print("COUNTER =");
        System.out.println(COUNTER_HANDLE.get());
    }
    public static void showVCounter(){
        System.out.print("V_COUNTER =");
        System.out.println(V_COUNTER_HANDLE.get());
    }

}