Java
Java NetBeans
早いもので今日は五月五日 こどもの日です。
祝日法2条によれば、「こどもの人格を重んじ、こどもの幸福をはかるとともに、母に感謝する」となっています。
5月5日は古来から端午の節句として日本では男子の健やかな成長を祈願し各種の行事を行う風習があります。
ということで、私も男子の健やかな成長を祈願するプログラムを組むことにしました。
今さらですが、J2SE5.0 で追加された java.util.concurrent.Semaphore をつかってふざけた 夢のようなプログラム組みました。
男子の健やかな成長には素敵な女性との出会いは必要不可欠です。
だから4人の素敵な女性にデートに誘われると言う内容です。
しかし、体は一つしかありませんので一度に4人では収集付かなくなります。
そこで恭平さんにヘルプをお願いしました。(^_^;)
これでも、素敵な女性4人に対して男子は二人です。
これでなんとかするために Semaphore を使って二組のカップルまでがデートに出かけられるとします。
残りの二人の女性は先に出かけた二組のカップルのどちらか片方が帰ってきたらデートに出かけるという設定です。
つまり、男子二人というリソースに同時アクセス出来るのは素敵な女性スレッド二つまでということになります。
それが Semaphore を使えば実現できてしまうのです。
Semaphore はざっくり説明するとリソースに対するアクセスが可能か不可能かのいずれかの値をとる Binary Semaphore と
アクセス可能なリソース数を任意に設定することが出来る Counting Semaphore があります。
Java で採用された Semaphore は Counting Semaphore にあたります。
有限リソースに対して並行アクセス可能なスレッド数を自由に設定できます。
今回組むプログラムにうってつけの優れものとなっています。(^_^)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
package jp.yucchi.playboy; import java.util.concurrent.Semaphore; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Yucchi */ class Guy { private final String[] guys = {"ゆっち", "恭平"}; private final int MAX_AVAILABLE = guys.length; private final boolean[] played = new boolean[MAX_AVAILABLE]; private final Semaphore semaphore = new Semaphore(MAX_AVAILABLE, true); // 公平性あり // private final Semaphore semaphore = new Semaphore(MAX_AVAILABLE); // 公平性なし String getGuyName() { System.out.println("待っているのは、" + Thread.currentThread().getName()); try { semaphore.acquire(); System.out.println("今からデートに行くのは、" + Thread.currentThread().getName()); } catch (InterruptedException ex) { Logger.getLogger(Guy.class.getName()).log(Level.SEVERE, null, ex); } return getGuy(); } void byeGuy(String guysName) { if (isplayed(guysName)) { semaphore.release(); } } // ReentrantLock を使う場合 ReentrantLock lock = new ReentrantLock(true); // 公平性あり private String getGuy() { try { lock.lock(); for (int i = 0; i < MAX_AVAILABLE; i++) { if (!played[i]) { played[i] = true; return guys[i]; } } } finally { lock.unlock(); } return null; } private boolean isplayed(String guysName) { try { lock.lock(); for (int i = 0; i < MAX_AVAILABLE; i++) { if (guys[i].equals(guysName)) { if (played[i]) { played[i] = false; return true; } else { return false; } } } } finally { lock.unlock(); } return false; } // // synchronized を使う場合 // private synchronized String getGuy() { // for (int i = 0; i < MAX_AVAILABLE; i++) { // if (!played[i]) { // played[i] = true; // return guys[i]; // } // } // return null; // } // // private synchronized boolean isplayed(String guysName) { // for (int i = 0; i < MAX_AVAILABLE; i++) { // if (guys[i].equals(guysName)) { // if (played[i]) { // played[i] = false; // return true; // } else { // return false; // } // } // } // return false; // } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
package jp.yucchi.playboy; import java.security.SecureRandom; import java.util.Random; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Yucchi */ class Lovers { private final String loversName; private final Guy guy; Lovers(String loversName, Guy guyName) { this.loversName = loversName; this.guy = guyName; } void play() { Thread.currentThread().setName(loversName); try { Random random = new SecureRandom(); String guysName = guy.getGuyName(); System.out.println(loversName + " は " + guysName + " とデートに行きました。"); TimeUnit.SECONDS.sleep(random.nextInt(7)); guy.byeGuy(guysName); System.out.println(loversName + " は " + guysName + " とデートから帰ってきました。"); } catch (InterruptedException ex) { Logger.getLogger(Lovers.class.getName()).log(Level.SEVERE, null, ex); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
package jp.yucchi.playboy; import java.lang.management.ManagementFactory; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * * @author Yucchi */ public class PlayBoy { public static void main(String[] args) { final int procs = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); ExecutorService executor = Executors.newFixedThreadPool(procs); Guy guy = new Guy(); Lovers maki = new Lovers("堀北真希", guy); Lovers yui = new Lovers("新垣結衣", guy); Lovers ayame = new Lovers("剛力彩芽", guy); Lovers keiko = new Lovers("北川景子", guy); CompletableFuture<Void> lovers1 = CompletableFuture.runAsync(() -> maki.play(), executor); CompletableFuture<Void> lovers2 = CompletableFuture.runAsync(() -> yui.play(), executor); CompletableFuture<Void> lovers3 = CompletableFuture.runAsync(() -> ayame.play(), executor); CompletableFuture<Void> lovers4 = CompletableFuture.runAsync(() -> keiko.play(), executor); CompletableFuture<Void> allTask = CompletableFuture.allOf(lovers1, lovers2, lovers3, lovers4); allTask.join(); if (allTask.isDone()) { executor.shutdownNow(); System.out.println("(○・ω・)ノ-------- Are you happy? --------"); } } } |
17 行目で Semaphore を生成しています。
コンストラクタの引数はパーミット数と公平性を設定します。
今回のプログラムではパーミット数はリソース数と同じ 2 となっています。
公平性は API ドキュメントを見ると今回のプログラムの場合は優しい私はパフォーマンスより平等性を重視して true と設定しました。
ちなみに API ドキュメントによると
このクラスのコンストラクタは、オプションで公平性パラメータを受け入れます。
falseに設定すると、このクラスはスレッドがパーミットを取得する順序について保証しません。
特に、バージ(barging)が許可されています。
つまり、acquire()を呼び出すスレッドに、待機していたスレッドより先にパーミットを割り当てることができます。
論理的には、新しいスレッドが、待機中のスレッドのキューの先頭に配置されます。
公平性がtrueに設定されると、セマフォは、acquireメソッドのいずれかを呼び出すスレッドが、これらのメソッドの呼出しが処理された順序(先入れ先出し、FIFO)でパーミットを取得するように選択されることを保証します。
FIFO順序付けは、必然的にこれらのメソッド内の特定の内部実行ポイントに適用されます。
そのため、あるスレッドが別のスレッドより前にacquireを呼び出しても、そのスレッドよりあとに順序付けポイントに到達する可能性があります。
また、メソッドからの復帰時も同様です。また、時間指定のないtryAcquireメソッドは公平性の設定に従いませんが、利用可能なパーミットをすべて取得することにも注意してください。
となっています。
実は今回のプログラムではこの公平性の設定の違いというのは解りにくいのですが
食事する哲学者の問題(Dining Philosophers Problem)を Semaphore を使った簡易的なプログラムを組んでみるとよく解ります。
次に Semaphore からパーミットを取得するために acquire() メソッドを使います。
このプログラムでは 23 行目で使っています。
public void acquire() throws InterruptedException は API ドキュメントによると次のようになっています。
このセマフォからパーミットを取得します。パーミットが利用可能になるか、またはスレッドが割り込みされるまでブロックします。
パーミットが利用可能な場合はパーミットを取得してすぐに復帰するため、利用可能なパーミットの数は1つずつ減ります。
パーミットが利用可能でない場合、現在のスレッドはスレッドのスケジューリングに関して無効になり、次の2つのいずれかが起きるまで待機します。
•ほかのスレッドがこのセマフォに対してrelease()メソッドを呼び出し、現在のスレッドが次にパーミットを割り当てられるスレッドになる。
•ほかのスレッドが現在のスレッドに割り込みを行う。
現在のスレッドで、
•このメソッドへのエントリ上で設定された割込みステータスが保持されるか、
•パーミットの待機中に割り込みが発生した場合、
InterruptedExceptionがスローされ、現在のスレッドの割込みステータスがクリアされます。例外:InterruptedException – 現在のスレッドで割込みが発生した場合
これとは別に引数で取得するパーミット数を設定できるメソッドもあります。
次は取得したパーミットを解放する release() メソッドを調べてみます。
33 行目で使っています。
デートが終了したらパーミットを保持する必要はないので解放します。
public void release() は API ドキュメントによると次のように書かれています。
パーミットを解放し、セマフォに戻します。
パーミットを解放すると、利用可能なパーミットの数が1つずつ増えます。
いくつかのスレッドがパーミットを取得しようと試みている場合は、その中の1つのスレッドが選択され、解放されたばかりのパーミットが与えられます。
そのスレッドは、スレッドのスケジューリングに関して(ふたたび)有効になります。
パーミットを解放するスレッドは、acquire()の呼出しでそのパーミットを取得している必要はありません。
セマフォの適切な使用法は、アプリケーションでのプログラミング規約で確立されます。
たったこれだけでリソースにアクセスできる並行スレッド数を制限できてしまうんですねぇ・・・
このプログラムの実行結果は次のようになります。
待っているのは、堀北真希
待っているのは、北川景子
待っているのは、新垣結衣
待っているのは、剛力彩芽
今からデートに行くのは、北川景子
今からデートに行くのは、堀北真希
北川景子 は ゆっち とデートに行きました。
堀北真希 は 恭平 とデートに行きました。
北川景子 は ゆっち とデートから帰ってきました。
今からデートに行くのは、新垣結衣
新垣結衣 は ゆっち とデートに行きました。
新垣結衣 は ゆっち とデートから帰ってきました。
今からデートに行くのは、剛力彩芽
剛力彩芽 は ゆっち とデートに行きました。
剛力彩芽 は ゆっち とデートから帰ってきました。
堀北真希 は 恭平 とデートから帰ってきました。
(○・ω・)ノ——– Are you happy? ——–
さて、疑い深い私は NetBeans のプロファイラでスレッドの状態を確認してみました。( 注意:先ほどの実行結果とはことなります。)
期待通りの動きをしてくれてます。(^_^) NetBeans のプロファイア手軽に使えて便利だね。
Semaphore 使える良い娘じゃないか!
もし、あなたが 恭平さん に素敵な女性とデートさせずに全ての女性を独り占めしたいとしたらこのプログラムをどのように変更したらいいでしょうか?
答えは簡単ですね!
私はヘルプを頼んだ 恭平さん に悪いのでプログラムの変更はいたしません。
どうしてもというかたはご自由に。(^_^;)
ちなみに今回のプログラムでは
CompletableFuture<Void> lovers1 = CompletableFuture.runAsync(() -> maki.play(), executor);
として ExecutorService を使いましたが
CompletableFuture<Void> lovers1 = CompletableFuture.runAsync(() -> maki.play());
として Fork/Join Framework を使うのもありです。
こどもの日の休日スペシャルはこれでお終いです。
食事する哲学者の問題(Dining Philosophers Problem)に続く かも?
TAGS: Java,NetBeans |
2015年5月5日2:18 PM |
Java
【初学者向け】JJUG ナイトセミナ 「Javaのプログラムはどうやって動いているの?」
https://www.youtube.com/watch?t=23&v=QNJBcrSayME
これを見てずっと前にさっぱり理解できなかったパズルがあって今なら少しだけなら解るような気がするかもしれないと思い試してみた。
元ネタは JAVA PUZZLERS の優れたパズラー パズル44:授業をサボる(Cutting Class)です。
このパズルを元に試してみたのですが時既に遅し・・・
Java SE 8 では既にパズルにはならなくなっていました。(Java SE 6 から)
なので、J2SE5.0 を使うことにしました。(ヲヒ
今回このパズルを試すのに組んだコードはこれです。
Love クラスはコンパイル時には存在していてちゃんとコンパイルされるものとします。
ただし、このプログラムの実行時には Love クラスは消えていることとします。
さて、どういった実行結果になるか想像できますか?
Lost Love が表示されるように思われますが残念な結果になります。
衝撃の事実をご覧ください。
なんでこうなるの?
バイトコードを覗いてみます。
バイトコードの怪しそうなところを見ていきましょう。
11: astore_1 とmain()メソッドのコードにあるのが問題の原因です。
これは catch ブロックでキャッチされたキャッチパラメータ e を VM 変数 1 に保存しています。
既に 7: astore_1 と Love クラスのローカル変数 love が VM 変数1に保存されています。
VM 変数1に上述の二つがマッピングされています。
このプログラムを実行すると JVM が起動、そしてクラスのロード、リンク、初期化、main 実行となります。
このプログラムはリンクのベリフィケーションで例外が発生してしまっています。
VM 変数1にマッピングされている二つのクラスをベリファイアはマージしようとする時にベリファイアは Love クラスのスーパークラスを決定するために Love クラスをロードしようとします。
しかし、 Love クラスは消えているのでロードに失敗してしまって NoClassDefFoundError がスローされます。
悲しいことにこの例外はベリフィケーション中にスローされていますのでクラスの初期化される前、main実行開始前ということでこのような残念なことになってしまいます。
本当に VM 変数1へのマッピングだけが問題かどうか確かめてみます。
プログラムを次のように変更します。
Love クラスのローカル変数の宣言を try – catch ブロックの外に出します。
プログラムを実行させて確認します。
期待通りの動作となって Lost Love と出力されています。
キャッチパラメータ e も VM 変数 2 にマッピングされています。
11: astore_2
これでリンクのベリフィケーションも解決されて初期化、main実行で catch ブロックが実行されました。
このエントリーの冒頭にも記述しましたが現在の Java ではこのような問題は発生しません。
おそらくリンクの処理が賢くなってしまったんでしょう。
ついでだからプログラムのコードを戻して Java SE 6 で確認しておきます。
問題ないですね。
VM 変数1に Love クラスのローカル変数 love と catch ブロックでキャッチされたキャッチパラメータ e がマッピングされてもちゃんとベリフィケーションされるようになったようです。
JVM のことは詳しく解らないので間違っているかもしれませんがこうやってバイトコードを覗いてみるのもおもしろいですね。
例外テーブルの動作も表示されているし、BASIC のような goto 文まで。
詳しくは元ネタの本を読んでください。(^_^;)
今日の教訓:「Love が消えてしまうと悲しい!」
違う!
今日の教訓:「古い Java より新しい Java で幸せに!」
TAGS: Java |
2015年5月1日5:41 AM |
Java
【初学者向け】JJUG ナイトセミナ 「Javaのプログラムはどうやって動いているの?」
https://www.youtube.com/watch?t=23&v=QNJBcrSayME
これを実際に試してみた。
javap を使ってバイトコードの逆アセンブルをする。
javap –p –c クラス名
とすると–p オプションで private メソッドも対象になる。-c オプションを付けるとバイトコードも出力される。
やってみた。
add() メソッドは動画で詳しく説明されていたので良く解る。
これでお終いではちょっとさみしいので Java 8 から導入されたインタフェースのデフォルトメソッドを使うようにプログラムを変更してみる。
Java 7 まではインタフェースにメソッドの実装は許されてなかった。
Java 8 からは default メソッドとして可能となりました。
いったいどうなっているのでしょうか?
javap で覗いてみることにします。今度は –v オプションを使ってバイトコードと定数プールも出力させてみます。
この結果から解るようにインタフェース側にメソッドの実装がされています。
おもしろいですね!
しかし、部分的にバイトコードを知ることができたのですがまだまだ解らないことだらけなので次回開催を楽しみに待ってます。
TAGS: Java |
2015年4月25日2:13 PM |
Java
【初学者向け】JJUG ナイトセミナ 「Javaのプログラムはどうやって動いているの?」が昨晩開催されていました。
私は仕事の都合上、生配信を見ることはできませんでした。
うれしいことに YouTube に動画がアップされてましたので見させていただきました。
https://www.youtube.com/watch?t=23&v=QNJBcrSayME
今までネット上とか雑誌で仕組みが解説された記事を読んだことはあるのですがあまり良く理解できませんでした。
今回は初学者向けと言うことで非常に優しく解説され、動画ということもあり内容的にも面白く楽しめたのが良かったです。
JVM がスタックマシンであって実際にどのように動いているか簡単な足し算のメソッドを例にとって解説されていました。
これだけ見てるとシンプルな仕組みなんだなって思ってしまいます。
実際はもっと複雑な仕組みの上で動作しているのだろうけど JVM が身近になったような気にさせられます。
【初学者向け】としてこの続きが開催され、今回のように動画がアップされることを期待します。
さて、このセミナの中でクイズが出題されました。
このクイズは #てらだよしおがんばれ と言うハッシュタグで有名な Java エバンジェリストの寺田佳央さんが作られたものを改変したものらしいです。
このクイズ正解者は 0 でした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
package anniversary; import java.time.Year; public class Anniversary { public static Anniversary ANNIVERSARY = new Anniversary(); private static int BIRTH_YEAR = Year.of(1995).getValue(); private static int THIS_YEAR = Year.now().getValue(); private final int age; private Anniversary(){ age = THIS_YEAR - BIRTH_YEAR; } public int getAge(){ return age; } public static void main(String... args) { System.out.println("The " + ANNIVERSARY.getAge() + "th Anniversarry!"); } } |
実行結果は次のようになります。
The 0th Anniversarry!
この問題を出題する前に static の初期化の説明がされていました。
にもかかわらず全員不正解という結果に櫻庭さんが動揺している様子がなんともいえなかった。
そういう私も問題を見たときに「・・・んん?」ってなりました。
だって普通なら変数(定数)宣言の前にそれらを使うインスタンス生成(コンストラクタ呼び出し)のコードなんて見ないもんね。
この問題を解くにあたって素直に The 20th Anniversarry! は無いとする。
残りみっつ。
The 2015th Anniversarry! は四択にするために苦し紛れに選択肢にあげたもの。
残りふたつ。
Exception だとすると何処で?
出るようなところは見当たらない。
よって、The 0th Anniversarry! が答えとなる。
何故か?
それはさっき説明にあった static の性質を利用したものだと推測される。
このプログラムで鍵となるのは static くらいしか見当たらないしね。
ちなみに、はじめ寺田佳央さんも間違えたらしいです。
これ、最初にクイズだといって出題されていたから解るけど何気に読んで実行したら「なんでやねん!」ってなってしまう自分の姿が目に浮かびます。
static なフィールドにはたとえ final であっても null とか 0 なんかで初期化されてしまうので注意が必要です。
解答の説明でこの問題を解決するにはインスタンス生成の(コンストラクタ呼び出し)コードを変数(定数)宣言の後にしてしまうのが解決方法になります。
ならば、私は無理矢理違う必要の無い方法で。(^_^;)
定数と言えば enum ですよね。
enum が使えるようになってからは
public static final int HOGE = 1963;
なんて書くのはダサいとか言い出していた人もいました。
私はそこまで思わなくて選択肢が一つ増えたなぁってくらいです。
で、さっきのクイズを enum を使うとこうなるのかな?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
package anniversary; import java.time.Year; public class Anniversary { public static Anniversary ANNIVERSARY = new Anniversary(); private enum YEAR{ BIRTH_YEAR (Year.of(1995).getValue()), THIS_YEAR (Year.now().getValue()); private final int value; private YEAR(int value) { this.value = value; } public int getValue() { return value; } } private final int age; private Anniversary(){ age = YEAR.THIS_YEAR.getValue() - YEAR.BIRTH_YEAR.getValue(); } public int getAge(){ return age; } public static void main(String... args) { System.out.println("The " + ANNIVERSARY.getAge() + "th Anniversarry!"); } } |
実行結果は次のようになります。
The 20th Anniversarry!
初期化の値って SJC-P ネタであったような・・・
今日の教訓 static は final であっても要注意!
TAGS: Java |
2015年4月25日7:57 AM |
Java
プログラマーの日って知っていますか?
コンピュータのデータを扱う単位である8ビットに256通りの表現があることから、
1月1日から256日目にあたる9月13日(閏年の場合は9月12日)と定められたロシアの公式な祝日となっています。
ロシアのプログラマーと言えば topcoder で活躍している優秀な人が多くいることを何処かで見た覚えがあります。
おそらくロシアではプログラマーは尊敬され、羨望の職業なのかもしれません。
日本ではこんな洒落た祝日は無いのが残念です。
では、プログラマーの日が日曜日だったら日本のプログラマーもゆっくり過ごせるはずなのでその日がいつなのか調べてみることにします。
Java SE8 から新しく追加された Date and Time API を使うことができます。
新しい物好きの私はこれを使ってプログラマーの日が日曜である一番近い日を表示させるプログラムを組んでみました。
実はこのプログラムは Java Magazine の fix this に出題されたものを参考に(まるっといただいて)してます。
なので最新の Java Magazine の fix this を楽しみにしている人はこれ以上読み進めないでください。
Java Magazine の fix this と同じように TemporalAdjuster インタフェースを利用するプログラムを組んでみました。
「見せて貰おうか。連邦軍のモビルスーツの性能とやらを!」
あっ、違った(^_^;)
「見せて貰おうか。Java8 の Date and Time API の性能とやらを!」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
package jp.yucchi.programmers.day; import java.time.DayOfWeek; import java.time.LocalDate; import java.time.Year; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import static java.time.temporal.ChronoUnit.DAYS; import java.time.temporal.TemporalAdjuster; /** * * @author Yucchi */ public class ProgrammersDay { public static void main(String[] args) { LocalDate today = LocalDate.now(); System.out.println("今日は、" + today.format(DateTimeFormatter.ofPattern("yyyy年 M月d日 E曜日"))); System.out.println("(○・ω・)ノ-------------end-------------"); TemporalAdjuster programmersDayAduster = temporal -> { if (DAYS.between(LocalDate.of(today.getYear(), 1, 1), temporal) >= 256) { temporal = temporal.plus(1, ChronoUnit.YEARS); } temporal = temporal.with(ChronoField.DAY_OF_YEAR, 256); while (temporal.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue()) { if (Year.isLeap(temporal.get(ChronoField.YEAR))) { temporal = temporal.plus(366, ChronoUnit.DAYS); } else { temporal = temporal.plus(365, ChronoUnit.DAYS); } } return temporal; }; // // こちらでもOK! // LocalDate programmersDay = (LocalDate) programmersDayAduster.adjustInto(today); LocalDate programmersDay = today.with(programmersDayAduster); System.out.println("プログラマーの日が日曜日である一番近い日は、" + programmersDay.format(DateTimeFormatter.ofPattern("yyyy年 M月d日 E曜日"))); System.out.println("(○・ω・)ノ-------------end-------------"); } } |
このプログラムはまず public static LocalDate now() メソッドを使ってシステムクロックとデフォルトのタイムゾーンを使用した現在の日付オブジェクトの生成します。
そして public String format(DateTimeFormatter formatter) メソッドで指定されたフォーマッタを使用してこの日付を書式設定します。
public static DateTimeFormatter ofPattern(String pattern) メソッドでフォーマットパターンを設定します。
これでフォーマッタで指定されたパターンで現在の日付が表示されます。
ここからはプログラマーの日が日曜である一番近い日を求める処理をみていきましょう。
TemporalAdjuster インタフェースってどんな働きをするのでしょうか?
API ドキュメントには
時間的オブジェクトを調整するための方針です。
アジャスタは、時間的オブジェクトを変更するための主要なツールです。それらは、戦略デザイン・パターンのように、調整のプロセスを外部化して異なるアプローチを可能にするために存在します。例として、週末を避けて日付を設定するアジャスタや、日付を月の最後の日に設定するアジャスタなどがあります。
TemporalAdjusterには、2つの同等な使用方法があります。1つ目は、このインタフェース上でメソッドを直接呼び出す方法です。2つ目は、Temporal.with(TemporalAdjuster)を使用する方法です。
// these two lines are equivalent, but the second approach is recommended
temporal = thisAdjuster.adjustInto(temporal);
temporal = temporal.with(thisAdjuster);
コード内での読みやすさが大幅に向上するため、2つ目のアプローチ(with(TemporalAdjuster))を使用することをお薦めします。
TemporalAdjustersクラスには、staticメソッドとして使用できるアジャスタの標準セットが含まれています。これらの機能を次に示します。
•月の最初または最後の日を見つける
•翌月の最初の日を見つける
•その年の最初または最後の日を見つける
•翌年の最初の日を見つける
•月内の最初または最後の曜日(「6月の最初の水曜日」など)を見つける
•次または前の曜日(「次の木曜日」など)を見つける
実装要件:このインタフェースは実装が可変であることを制限しませんが、不変にすることを強くお薦めします。
と記載されています。
なんだかいろいろ出来そうなことが書いてあります。
それではまず、現在の日時がプログラマーの日を過ぎていたらラムダ式で渡された LocalDate オブジェクトの temporal を1年進めます。
public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) メソッドを使って元日から現在日時が 256日以上か判定しています。
もし、256日以上過ぎていたら Temporal plus(long amountToAdd, TemporalUnit unit) メソッドで temporal を1年進めます。
このメソッドは、このオブジェクトと同じ型のオブジェクトに指定された期間を追加したものを返します。
TemporalAdjuster programmersDayAduster = temporal -> {
if (DAYS.between(LocalDate.of(today.getYear(), 1, 1), temporal) >= 256) {
temporal = temporal.plus(1, ChronoUnit.YEARS);
}
次に temporal の日時を Temporal with(TemporalField field, long newValue) メソッドを使って指定されたフィールドを変更して、このオブジェクトと同じ型のオブジェクトを返します。
temporal = temporal.with(ChronoField.DAY_OF_YEAR, 256);
つまり、LocalDate オブジェクト temporal を元日から 256 日目のプログラマーの日を生成して置き換えたということです。
次は temporal オブジェクトの曜日を取得してそれが指定された曜日(日曜日)であるまで年を進めて処理を繰り返します。
ただし、うるう年の場合もあるのでそれも処理を分けます。
指定された曜日であれば temporal オブジェクトを返します。
while (temporal.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue()) {
if (Year.isLeap(temporal.get(ChronoField.YEAR))) {
temporal = temporal.plus(366, ChronoUnit.DAYS);
} else {
temporal = temporal.plus(365, ChronoUnit.DAYS);
}
}
while ループの判定(temporal オブジェクトの曜日を取得してそれが指定された曜日(日曜日)) かどうかは
default int get(TemporalField field) メソッドで指定したフィールドの値を int として取得します。
そして、public int getValue() メソッドで曜日の int
値を取得し、比較判定をします。
temporal オブジェクトを次の年のプログラマーの日に進めるのにうるう年かどうかの判定を行います。
if (Year.isLeap(temporal.get(ChronoField.YEAR))) {
temporal = temporal.plus(366, ChronoUnit.DAYS);
} else {
temporal = temporal.plus(365, ChronoUnit.DAYS);
}
public static boolean isLeap(long year) メソッドで判定します。
このメソッドは、ISO先発グレゴリオ暦暦体系のルールに従って、年がうるう年であるかどうかをチェックします。
うるう年だった場合は 1年と 1日 (366 日) 進めます。
うるう年でなければ 1 年 (365 日) 進めます。
最後に計算されたプログラマーの日を LocalDate オブジェクトとして取得して表示します。
これには二つの等価な方法があります。
一つ目はコメントアウトされている方法として
処理が全て終わって指定の曜日のプログラマーの日が計算できたなら Temporal adjustInto(Temporal temporal) メソッドに
調整する時間的オブジェクト today を渡して、オブジェクトの調整済のコピーを返します。
LocalDate programmersDay = (LocalDate) programmersDayAduster.adjustInto(today);
あとは最初と同じように programmersDay をフォーマッタで指定されたパターンで表示するだけです。
二つ目の方法は、public LocalDate with(TemporalAdjuster adjuster) を使用する方法です。
このメソッドは日付の調整済のコピーを返します。
これは、日付を調整して、この日付に基づくLocalDate
を返します。調整は、指定されたアジャスタ戦略オブジェクト ( TemporalAdjuster programmersDayAduster )を使用して行われます。
ちなみに主要な日付/時間クラス(MonthやMonthDayなど)は、TemporalAdjusterインタフェースを実装しています。
そしてアジャスタは、さまざまな長さの月やうるう年などの特別なケースの処理を担当しています。
LocalDate programmersDay = today.with(programmersDayAduster);
あとは最初と同じように programmersDay をフォーマッタで指定されたパターンで表示するだけです。
TemporalAdjuster インタフェースの API ドキュメントでは、コード内での読みやすさが大幅に向上するため、こちらをお勧めしていました。
以上で簡単にプログラムの解説を記述してみました。
なお、間違い等あるかもしれませんのであしからず(^_^;)
プログラムの実行結果は次のようになります。
今日は、2015年 4月13日 月曜日
(○・ω・)ノ————-end————-
プログラマーの日が日曜日である一番近い日は、2015年 9月13日 日曜日
(○・ω・)ノ————-end————-
使い勝手の良くない Calendar クラスと違って実に良くできています。
また、Date and Time API は ISO 8601 に準拠していて、イミュータブルでスレッドセーフとなっています。
Date and Time API はイミュータブルであるが故にオブジェクトを生成、置き換えなどのメソッドが用意されています。
つまり、セッターメソッドは無いということです。
「ザクとは違うのだよ。ザクとは・・・」
違った(^_^;)
「Calendar とは違うのだよ。Calendar とは・・・」
Date and Time API 使ったら Calendar クラスなんて使えなくなるよ。
さて、先ほどのプログラムですが本当にちゃんと動いて正しい値が出力されているのか確かめてみます。
プログラマーの日を10年間表示させてみます。
Stream API を利用してプログラムを組んでみました。
先ほどのプログラムに下記コードを追加します。
|
System.out.println("プログラマーの日を10年間表示させる。"); IntStream.range(today.getYear(), today.getYear() + 10) .mapToObj(year -> Year.of(year)) .map(day -> day.atDay(256)) .forEach(pDay -> System.out.println(pDay.format(DateTimeFormatter.ofPattern("yyyy年 M月d日 E曜日")))); System.out.println("(○・ω・)ノ-------------end-------------"); |
このプログラムの実行結果は次のようになります。
今日は、2015年 4月13日 月曜日
(○・ω・)ノ————-end————-
プログラマーの日が日曜日である一番近い日は、2015年 9月13日 日曜日
(○・ω・)ノ————-end————-
プログラマーの日を10年間表示させる。
2015年 9月13日 日曜日
2016年 9月12日 月曜日
2017年 9月13日 水曜日
2018年 9月13日 木曜日
2019年 9月13日 金曜日
2020年 9月12日 土曜日
2021年 9月13日 月曜日
2022年 9月13日 火曜日
2023年 9月13日 水曜日
2024年 9月12日 木曜日
(○・ω・)ノ————-end————-
追加したプログラムが正しく動いていれば TemporalAdjuster インタフェースを利用した最初のプログラムは正しく動作していると思われます。
ちょっと待てよ!
これ Stream API でシンプルに組めるんじゃないか?
プログラマーの日の設定を土曜日にして組んで追加してみました。
|
int startYear; if (DAYS.between(LocalDate.of(today.getYear(), 1, 1), today) >= 256) { startYear = today.getYear() + 1; } else { startYear = today.getYear(); } Stream.iterate(startYear, i -> i + 1) .map(year -> Year.of(year)) .map(day -> day.atDay(256)) .filter(day -> day.getDayOfWeek() == DayOfWeek.SATURDAY) .findFirst().ifPresent(pDay -> System.out.println("プログラマーの日が土曜日である一番近い日は、" + pDay.format(DateTimeFormatter.ofPattern("yyyy年 M月d日 E曜日")))); System.out.println("(○・ω・)ノ-------------end-------------"); |
このプログラムの実行結果は次のようになります。
今日は、2015年 4月13日 月曜日
(○・ω・)ノ————-end————-
プログラマーの日が日曜日である一番近い日は、2015年 9月13日 日曜日
(○・ω・)ノ————-end————-
プログラマーの日を10年間表示させる。
2015年 9月13日 日曜日
2016年 9月12日 月曜日
2017年 9月13日 水曜日
2018年 9月13日 木曜日
2019年 9月13日 金曜日
2020年 9月12日 土曜日
2021年 9月13日 月曜日
2022年 9月13日 火曜日
2023年 9月13日 水曜日
2024年 9月12日 木曜日
(○・ω・)ノ————-end————-
プログラマーの日が土曜日である一番近い日は、2020年 9月12日 土曜日
(○・ω・)ノ————-end————-
Stream API のほうがシンプルで解りやすく綺麗に思える。(>_<。)
「認めたくないものだな。自分自身の、若さゆえの過ちというものを。」
違う!(^_^;)
「認めたくないものだな。自分自身の、未熟さゆえの過ちというものを。」
今回、Date and Time API を使って遊んだプログラムは下記 GitHub にあります。
https://github.com/Yucchi-1995/Programmers-Day
Java 楽しいね(^_^)
追記
Stream API を使って求めるプログラムを修正
プログラマーの日を過ぎていたときは次の年から計算するようにしました。
TAGS: Java |
2015年4月13日8:48 PM |
« 古い記事
新しい記事 »