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 |
Java
Java8 がリリースされてから1年以上経過しているので Java プログラマの皆様はガシガシとその恩恵を受けた素晴らしいコードを書いていらっしゃるでしょう。
私は未熟者なのでボチボチとチマチマ楽しんでいます。(^_^;)
そこで問題です。
このプログラムを実行すると何が表示されますか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
package jp.yucchi.hellorepeat; import java.util.Arrays; import java.util.List; public class HelloRepeat { public static void main(String[] args) { List<String> list = Arrays.asList("Hello", "World!"); for (int i = 0; i < list.size(); i++) { Runnable r = () -> System.out.println(list.get(i)); r.run(); } } } |
1. Hello
World!
2. World!
Hello
3. コンパイルエラー
4.実行時エラー
すいません。
簡単すぎますね。
答えは 3番のコンパイルエラーです。
ラムダ式の中から外部の変数を参照する場合、その変数はfinalもしくは実質的finalでなければならない。
このルールに違反しているからです。
どうすればこのプログラムを正しく動かせるでしょうか?
それも簡単なので答えを載せておきますね!
このプログラムは for ループ文を使っているのでまずは、拡張 for ループ文で修正してみましょう。
拡張 for ループ文は少しばかりの制約はあるけど要素を先頭から順に取得するような処理には便利に使えます。
for ループ文ではインデックス取得の為に実質的ファイナルでない変数にアクセスしてコンパイルエラーになりましたが拡張 for ループ文だとそのような心配はありません。
|
for (String str : list) { Runnable r = () -> System.out.println(str); r.run(); } |
あっけなく、簡単に修正できました。それでは、default void forEach(Consumer<? super T> action) を使って修正してみます。
|
list.forEach(e -> { Runnable r = () -> System.out.println(e); r.run(); } ); |
こちらの方法が Java8 っぽいので個人的には好きです。
でも内部的には、デフォルト実装の動作は次のようになっているそうです。
for (T t : this)
action.accept(t);
つまり、こういうことなのかな?
|
for (String str : list) { new Consumer<String>() { @Override public void accept(String e) { Runnable r = new Runnable() { @Override public void run() { System.out.println(e); } }; r.run(); } }.accept(str); } |
以上のことを考慮してラムダ式使う場合は、forEach(Consumer<? super T> action) を使うのが良さそうですね。
これで平凡なパズルはお終いです。
TAGS: Java |
2015年4月1日4:54 PM |