20210512のJavaに関する記事は10件です。

JBoss EAP XP( Eclipse MicroProfile準拠の製品 ) 覚え書き

モチベーション 掲題の製品名を伺う機会あったが、さっぱり存じなかったので 自分向けに備忘するもの。 JBoss EAP XP とは JBoss EAPに追加インストールできる、Eclipse MicroProfile仕様のImplementationモジュール。 公式HPにてjarファイル形式で絶賛、配布中。 正式名称はJBoss Enterprise Application Platform expansion packとのこと。 公式ガイド辿り方 JBoss EAPの公式HPにアクセス 左メニューのProduct Versionから、JBoss EAP製品バージョンを選択。 *:2021年5月時点:JBoss EAP v7.3でしか利用不可の様子 左メニューのCategoryから、JBoss Enterprise Application Platform expansion pack (EAP XP)で絞り込み。 画面中央あたりJBoss EAP XP x.y.z での Eclipse MicroProfile の使用が公式ガイド。 *:2021年5月時点:JBoss EAP XP 2.0.0 が最新。 JBoss EAP XP自体のライフサイクル 公式のLife Cycle and Update Policiesから辿れる。 具体的にはこちらのページ。 2021年5月時点では以下。 1.x 系: Full Supportは2021年1月11日に終了済。 Maintenance supportも2021年4月11日に終了済。 2.x 系: Full SupportはEAP XP 3.0リリースを以って終了予定。 Maintenance supportは同3.0リリースから+3ヶ月の猶予が有る予定。(= 1.x 系のスパンを踏襲) その他:互換あるJBoss EAPバージョン(x.y.z の、zの部分) ガイドの中(1.2. JBoss EAP XP のインストール)に明記あり。 JBoss EAP XP 2.0の場合はこちら。 以下、引用。 JBoss EAP XP 2.0.0 を JBoss EAP 7.3.0 にインストールする場合は、最初に JBoss EAP 7.3.4 GA パッチを適用する必要があります。 参考リンク JBoss EAPの公式HP Eclipse MicroProfile Configについて
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クラス継承時の処理の順番について

クラス継承時の処理の順番について JavaSilverSE11の勉強中、クラスの継承に関する問題の解説を読んで、よく混乱していたので、ここで自分なりにクラス継承時の処理の順番について調べてまとめてみました。 継承とは何か? 継承とは新しく機能を実装するときに、以前実装した機能と共通する機能を引き継ぐことです。 継承はJavaを中心とするオブジェクト指向の基本的な概念となります。 クラスの継承 既存のクラスをもとに、新しい別のクラスを作成し、既存のクラスにある機能を引き継ぐことです。 クラスの継承により、継承先のクラスは同じプログラムを書く手間が省け、新しい機能(差分の機能)を実装するだけで良くなります。 既存のクラス(継承元のクラス)のことをスーパークラス、 新しく作成したクラス(継承先のクラス)のことをサブクラスといいます。 継承の書き方 class サブクラス名 extends スーパークラス名 { } ・継承先のクラス(サブクラス)には「extends」を記述し、「extends」の後ろに継承元のクラス(スーパークラス)を記述する。 ・継承元のクラス(スーパークラス)は一つしか記述できない。 ・継承したクラスをさらに継承できる。 クラス継承時の処理の順番をコードを通して紹介 実際にクラス継承時の処理の順番を、メッセージのやり取りを例にコードを通して紹介します。 以下のコードのコメントに処理の順番を割り振っています。 【スーパークラス】 class SuperSample { public SuperSample() { //[5]SuperSample()メソッドで「1」と出力をし、SuperSampleクラスのSuperSample(String tmp)メソッドに戻る。 System.out.println("1"); } public SuperSample(String tmp) { //[4]thisを使い、SuperSampleクラスのSuperSample()メソッドを呼び出す。 this(); //[6]SuperSample(String tmp)メソッドで「2」と出力をし、SubSampleクラスのSubSample()メソッドに戻る。 System.out.println("tmp"); } } 【サブクラス】 class SubSample extends SuperSample { public SubSample() { //[3]superを使い、SuperSampleクラスのSuperSample(String tmp)メソッドを呼び出す。 super("2"); //[7]SubSample()メソッドで「3」と出力をし、SubSampleクラスのSubSample(String tmp)メソッドに戻る。 System.out.println("3"); } public SubSample(String tmp) { //[2]thisを使い、SubSampleクラスのSubSample()メソッドを呼び出す。 this(); //[5]SuperSample()メソッドで「1」と出力をし、SuperSampleクラスのSuperSample(String tmp)メソッドに戻る。 System.out.println("tmp"); } } 【エントリーポイント】 class Main { public static void main(String[] args) { new SubSample("4"); //[1]エントリーポイントからSubSampleクラスのSubSample(String tmp)メソッドを呼び出す。 } } このコードは以下のように出力します。 1 2 3 4 まずこのコードに登場する「this」や「super」について説明します。 「this」はコンストラクタ内から、オーバーロードされた別のコンストラクタを呼び出します。 「super」はコンストラクタ内から、スーパークラスのコンストラクタを呼び出します。 今回記述したエントリーポイントやSubSampleクラスでは「this」や「super」を記述することで明示的にコンストラクタを呼び出しています。 まとめ 上記のコードの処理順はすごくややこしいと思いますが、以下を把握しておくと処理の順番が把握しやすいと思います。 ・this();でコンストラクタ内から、オーバーロードされた別の引数のないコンストラクタを呼び出す。 ・「super」はコンストラクタ内から、スーパークラスのの引数のないコンストラクタを呼び出す。 参考文献 [https://www.internetacademy.jp/it/programming/java/java-class-inheritance-function.html] [https://itsakura.com/java-inheritance]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ざっくり理解するプログラミング入門基礎編(Java)第1回

この記事は、ざっくり理解するプログラミング入門基礎編(Java)第0回の続きです。 読んでない方はぜひそちらを読んでからこの記事を読んでください。 Javaプログラムの書き方 1  Hello Worldの解説 これは前回の最後で実行してみたコードです。 Main.java public class Main { //外側のブロック(クラス)の始まり public static void main(String[] args){ //内側(メソッド)のブロックの始まり System.out.println("Hello World!"); } //内側のブロックの終わり } //外側のブロックの終わり コメント //の後の文字を見てください。これはコメントと言ってコード内の好きなところにメモのように好きな文字を書くことができます コメントを記述する方法は以下の二つがあります。 //の後にコメントを記述する /*と*/で囲まれた範囲にコメントを記述する この二つの差は//のほうは、//の後の1行しかコメントを書けませんが、/*と*/のほうはこの二つで囲んだ範囲なら何行でもコメントを書けます。 コメントの例 //このタイプは1行しか書けない /* このタイプは間に何行でも書くことができる コメント コメント コメント コメント */ ブロック Javaのコードは{}で囲まれたところが一つの塊になります。 改行があるのでコードを読むのに慣れていないとわかりづらいですが、1行目で開いた{が5行目で閉じられています。 この{}で囲まれた塊をブロックと呼びます。このコードの場合外側のブロックの中にもう1つブロックがあります。 インデント コードの中で右側に少しずれている行がありますよね?これを字下げまたはインデントと呼びます。これは何のためにあるのかというと、どこからどこまでが一つのブロックなのかわかりやすくする目的があります。 ブロックの中にあるものは外にあるものよりも1つ分右にずれます。 また、ブロックの始まりと終わりのインデントが同じことを意識すればブロックがどこで始まってどこで終わっているかを見分けやすくなると思います。 クラスとメソッド 外側のブロックに注目してください。このブロックをクラスと呼びます。ちゃんとした意味があるのですが、今のうちは、とりあえずプログラムを書き始めるためのおまじないくらいに思っておいてください。 クラスの{の前のMainはクラスの名前を表しているのですが、この名前とファイルの名前は同じではなければならないことは覚えておいてください。 次に内側のブロックを見てください。これはメソッドと呼びます。これもクラスと同じで、おまじないのようなものと思っておきましょう。コードの中身は基本的にこの中に書きます。 画面に文字を表示する命令 Main.java public class Main { //外側のブロック(クラス)の始まり public static void main(String[] args){ //内側(メソッド)のブロックの始まり System.out.println("Hello World!"); } //内側のブロックの終わり } //外側のブロックの終わり ではついに、Hello World!と表示する部分です。 もうある程度察しがついているかもしれませんが、3行目のSystem.out.println("Hello World!");のところです。 この文は、"(ダブルクォーテーション)で囲まれた文章を画面に出力するという意味です。 ちなみにこれは「システム アウト プリント ライン」と発音します。 このような文(ここではわかりやすいので命令と呼びます)はこのほかにもたくさんあり、例えばユーザーに文字を入力してもらう命令などがあります。 これらの命令の書式はすべて同じなので覚えておきましょう。 命令名(引数1,引数2,・・・・); 引数(ひきすう)とは、命令に渡すデータのことです。 System.out.println();の場合は引数は表示させたい文字列です。 命令は、メソッドの中にいくつでも書くことができ、命令は上から順番に実行されます。 Main.java public class Main { public static void main(String[] args){ System.out.println("Hello World!"); System.out.println("こんにちは 世界!"); } } 実行結果 Hello World! こんにちは 世界! 例えば上のように書くと、このようにHello World!と表示された後にこんにちは 世界!と表示されます。 練習問題 様々なことを覚えて、混乱してきたと思うのでここで練習問題を解いてみましょう 問1 このプログラムを変更して自分の名前を表示してください。ただし、ファイル名はPractice1_1.javaとします。 問2 問1を変更して3回表示するようにしてください。ただし、ファイル名はPractice1_2.javaとします。 答え Practice1_1.java public class Practice1_1 { public static void main(String[]args){ System.out.println("名前"); } } Practice1_2java public class Practice1_2 { public static void main(String[]args){ System.out.println("名前"); System.out.println("名前"); System.out.println("名前"); } } まとめ コメントには2種類あり、//から始めるものは1行のみで/*と*/で囲むものは何行でも書くことができる。 {}で囲まれたプログラムの塊をブロックと呼ぶ。 基本的に命令はクラスの中にあるメソッドのなかに記述する。 クラスとメソッドはプログラムを始めるためのおまじないのようなもの(と今のうちは思っておいてください)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ざっくり理解するプログラミング入門基礎編(Java) 第0回

 この「ざっくり理解するプログラミング入門」はプログラミング未経験者に向けたプログラミングをイメージ理解し、プログラミングを始める第一歩となるように書いています。プログラミング経験者の方が読むと回りくどくてイライラするかもしれません。 なお、この記事はしっかりとした意味を理解するというよりも、ざっくりとしたイメージでとらえることを目的としたものですので実際の仕組みとは違うことなどもあると思いますのでご了承ください。 プログラムはなぜ動くの?  プログラムは0と1でできているという言葉を聞いたことがある人もいるかもしれませんが、プログラムを書くときプログラマが0と1でコードを書かなきゃいけないなんてことはありません。この0と1でできたプログラムは機械だけが理解できる言葉だと思ってください。これを機械語といいます。 プログラムを動かすには、人間にもわかる言葉でプログラムを書いて、機械語に翻訳して動かします。この人間が書くプログラムのことをソースコードといいますが、このソースコードには言葉にもいろんな種類があるように、様々な言語があります。これをプログラミング言語といいます。言葉にも、英語や日本語、中国語などがあるように、プログラミング言語にもC言語やPython,Javaなどいろんな種類があります。 また、ソースコードを機械語に翻訳するものをコンパイラといいます このコンパイラを使って翻訳して初めてコンピュータは人が書いたプログラムを理解することができます。 実はJava言語ではコンパイラとは少し違うものが使われるのですが、プログラムを書く上でそれを意識することはほとんどないのでこの記事では解説しません。 プログラミング言語 先ほども説明しましたが、プログラミング言語にはたくさんの種類があります。 その中でプログラミング言語は大きく二つに分けられます。 低水準言語と高水準言語です。(高級言語や低級言語とも言います) これは決して、低水準言語が低レベルだとかそういう意味ではありません。低水準言語では作れるけど高水準言語では作れないものもあります。低水準とは、機械語に近いという意味です。逆に高水準とは人間に近いという意味です。代表的なものは、低水準言語ではアセンブラ言語など、高水準言語では、C言語やJava,Python,C#などがあります。 プログラミング言語の向き不向き プログラミング言語には向き不向きがあるので、「自分は〇〇言語が好きだから〇〇言語以外はやらない」というのはよくありません。(プログラミングを始めたばかりの頃の私はJavaが最強のプログラミング言語だと思っていました笑) 例えば、この記事で解説するJavaは事務用のソフトウェア(WordやExcelのようなソフト)を中心にスマホやパソコンなど様々なプラットフォームで動くソフトが作れます。これはすごいことで、ほかの言語では作った環境と違う環境で動かすことはできないことが多いです。 ほかにもC言語は、Windowsをはじめ自分が知る限りではほとんどのOSはC言語で書かれています。 HELLO WORLD! この記事は説明が多かったですが、最後に初めてのプログラムを動かしてみましょう 本来、プログラミングをするにはコンパイラやJavaの場合にはJDKと呼ばれるものが必要なのですが、手軽にプログラムを実行できるサイトがあるので、今回はそちらでプログラムを動かしてみましょう 左上の言語をJavaに設定して以下のコードを入力して、できたら左下の実行ボタンを押してみてください。 大文字と小文字は間違えると動かないので注意してください。 Main.java public class Main { public static void main(String[] args){ System.out.println("Hello World!"); } } どうでしょう、下の出力のところにHello World!と表示されましたか? このプログラムは「Hello World!」と表示するプログラムですが、少し書き換えるだけで表示する文字列を変えることができます。 適当でいいのでプログラムの一部を書き換えて、表示する文字列を変えてみてください。 まとめ プログラムは、プログラマが入力したソースコードがそのまま動いているのではなくコンパイラによって機械語に変換されて動いている。 プログラム言語には高水準言語と低水準言語があり、これは言語のレベルを表すものではない。 次回は今回動かしたプログラムを詳しく解説するところから始めようと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java アルゴリズム修行⑮】マージソート

マージソートとは 等間隔で単純挿入ソートを繰り返すシェルと、枢軸よりも大きいか少ないかで交換を行うクイックソートは 共に隣り合う値以外の交換も発生するので、不安定なアルゴリズムでしたが、 今回は隣り合う値同士の交換のみのマージソートについて学んでいきたいと思います。 マージなので色々と合わせるのかなと思いましたが、実際は 配列を前半部と後半部の2つに分け、
 それぞれをソートしたものをマージする作業を繰り返すことによってソートを行うアルゴリズム というものらしいです。。 全体を2つに分けてそれぞれをソートして、最終的に合成することを繰り返せば良いっぽいですね。 実際にイメージで理解していきましょう。 {4,3,6,7,1,8,6,2}という数列をマージソートしてみます。 分割自体は要素数が1になるまで繰り返すので、それぞれが1桁になるまで分割します。 次に分割した要素を戻していきますが、 ただ戻すだけでは、元の数列に戻すだけなので、ここで並び替えをしながら戻してみましょう。 ① 1要素に分割された値:「4」「3」「5」「7」「1」「8」「6」「2」 ② 並び替えながら戻す:「3,4」「5,7」「1,8」「2,6」 ③ 並び替えながら戻す:「3,4,5,7」「1,2,6,8」 ④ 並び変えながら戻す:「1,2,3,4,5,6,7,8」 こうすることで、最終的に昇順にソートすることができました。 とはいえ、ソートしながら並びを戻すというのをどうコードで表現していくのか という話なので、まずは分割した要素をマージするという部分から考えてみましょう。 マージとは 先ほどのイメージでも、マージ済みの配列を合成する際にも、小さいの方の値を 前方に持ってくるソート作業を同時に行い、合成していたので、 2つのソート済みの配列のマージというのは、 各配列の着目要素の値を比較し、小さい方の値を持つ要素を取り出して、第3の配列に格納する作業 であるということが言えそうです。 ここでいう第3の配列がマージ後のソート済みの配列ということになります。 2つのソート済みの数列があったとして、それぞれの配列を先頭から走査し 小さい方の値の要素を第3の配列に追加していくという流れを実際にコードで再現してみましょう。 MergeArray.java static void merge(int[] a, int na, int[] b, int nb, int[] c) { int pa = 0; int pb = 0; int pc = 0; while (pa < na && pb < nb) // 2つの配列のうち、小さい方の値を配列cに格納 c[pc++] = (a[pa] <= b[pb]) ? a[pa++] : b[pb++]; while (pa < na) { c[pc++] = a[pa++]; // aに残った要素をコピー } while (pb < nb) { c[pc++] = b[pb++];// / bに残った要素をコピー } } aがソート済み配列の1つ目、その要素数がna bがソート済み配列の1つ目、その要素数がnb cがマージ後の配列としたときに aの配列を{2,4,6,8,11,13}
bの配列を{1,2,3,4,9,16,21} として、 2つの数列をマージしてみましょう。 まずは a[0]の2 と b[0]の1 で比較して、 もし、a[0]の方が小さければa[0]の値を、bの方が小さければb[0]の1を配列cに追加します 
ここでは、 b[0]の方が小さいので、1を配列cに追加し、 小さかった方の要素を持つ配列、ここでは配列[b]のカーソルのみ次の要素に移行するという流れになります。 互いの配列のカーソルを移動して、どちらかのカーソルが末尾を超えた場合は while(pa < na && pb < nb)(インデックスが要素数になった場合)は 比較する値がなくなるので、そのまま次のwhile文へ移行します。 cには[1]のみが追加されていて、そのまま続きを見ていくと、 a[0]の2と b[1]の2は同じ値なので、a[0]の2をcに追加します a[1]の4とb[1]の2はb[1]の方が小さいので、b[1]の2をcに追加します a[1]の4とb[2]の3はb[2]の方が小さいので、b[2]の3をcに追加します a[1]の4とb[3]の4は同じ値なので、a[1]の4をcに追加します 
a[2]の6とb[3]の4はb[3]の方が小さいので、b[3]の4をcに追加します 
a[2]の6とb[4]の9はa[2]の方が小さいので、a[2]の6をcに追加します 
a[3]の8とb[4]の9はa[3]の方が小さいので、a[3]の8をcに追加します a[4]の11とb[4]の9はb[4]の方が小さいので、b[4]の9をcに追加します a[4]の11とb[5]の16はa[4]の方が小さいので、a[4]の11をcに追加します a[5]の13とb[5]の16はa[5]の方が小さいので、a[5]の13をcに追加します 

この時点で、(pa(6) < na (6))ではなくなり、aには要素が残っていないことを表すので、 次のwhile文に移行します。 あとは、残った方の要素をインデックスを進めながら格納していくだけです。 while (pa < na)はfalse、(配列aには値が残っていない)while(pb(5) < nb(7))はtrueとなり、 b[5]の16、b[7]の21がcに追加され、{1,2,2,3,4,4,6,8,9,11,13,16,21}となり、マージが完了します。 

3つの繰り返し文が並べられただけの単純なアルゴリズムで実現されるため
、 マージに要する時間計算量はO(n)らしいです。 このソート済配列をマージする方法を応用したのがマージソートということなので、 実際にマージソートをコードに落とし込んでいきます。 コードに落とし込む 例えば、まだマージされていない{5,6,4,8,3,7,9,0,1,5,2,3}という数列で考えてみましょう。 まず、配列を前半部と後半部の2つに分割します。 ここでは要素数は12なので、6個ずつの配列に分割します。 前半部と後半部のそれぞれをソートし、それらを先ほどの方法でマージすれば配列全体がソートできそうです。 {5,6,4,8,3,7}→ ソート後{3,4,5,6,7,8} {9,0,1,5,2,3}→ ソート後{0,1,2,3,5,9} ソート後の配列をマージすると、{0,1,2,3,3,4,5,6,7,8,9} ということになります。 前半部と後半部に分けた配列のソートも全く同じ手続きで行い、 {9,0,1,5,2,3}→{9,0,1}と{5,2,3}としてソートすると {0,1,9}と{2,3,5}となるので、マージすると{0,1,2,3,5,9}となります。 分割後のソートも同じ手続きになるので、整理すると次のようになります。 配列の要素数が2以上であれば、以下の手続きを適用する。 配列の前半部をマージソートによってソート 配列の後半部をマージソートによってソート 配列の前半部と後半部をマージ これを繰り返せばいいので、実際にコードに落としてみましょう。 MergeSort.java //--- マージソート ---// static void mergeSort(int[] a, int n) { buff = new int[n]; // 作業用配列を生成 __mergeSort(a, 0, n - 1); // 配列全体をマージソート buff = null; // 作業用配列を解放 } //--- a[left]~a[right]を再帰的にマージソート ---// static void __mergeSort(int[] a, int left, int right) { if (left < right) { int i; int center = (left + right) / 2; int p = 0; int j = 0; int k = left; __mergeSort(a, left, center); // 前半部をマージソート __mergeSort(a, center + 1, right); // 後半部をマージソート for (i = left; i <= center; i++) { //前半部分をbuffにコピー ① buff[p++] = a[i]; } while (i <= right && j < p) { //後半部分とbuffをマージ ② a[k++] = (buff[j] <= a[i]) ? buff[j++] : a[i++]; } while (j < p) { //buffに残った要素をaにコピー ③ a[k++] = buff[j++]; } } } 実際にマージソートの処理を行っているのは __mergeSortメソッドですが メソッド内では以下のようなことを行っています。 マージ結果を一時的に格納するための作業用配列buffを生成 ソート作業を行うメソッド__mergeSortの呼び出し 作業用配列を解放・破棄 __mergeSortメソッドの引数には、ソート対象の配列aと、 その配列の先頭、末尾インデックスをleftとrightで渡しています。 メソッド全体は if(left < right)で囲っているので、 先頭インデックスよりも、末尾インデックスの方が大きい場合(要素が2以上)の場合のみ処理を行うようにしています。 一番最初に行う処理は、前半部a[left]~a[center]と後半部a[center+1]~a[right]の それぞれに対して、再帰的に __mergeSortを適用することです。 ソート済みになった前半部と後半部のマージは、作業用配列のbuffを使って行います。 for (i = left; i <= center; i++)にて 配列の前半部a[left]~a[center]をbuff[0]~buff[center - left]にコピーします。 for文終了時のpの値は、コピーした要素の個数となり、center - left + 1となります。 while (i <= right && j < p)では、 配列の後半部a[center + 1] ~ a[right]と、 buffにコピーした配列の前半部のp個をマージして、そのマージ後の配列をaに格納しています。 while (j < p)にて、 配列buffに残った未格納部分の要素を配列aにコピーしています。 実際に{6,5,4,3,2,1}という数列で流れを追ってみましょう。 最初に__mergeSort(a,0,6-1)として呼ばれ、 __mergeSort内はif (left < right)で囲まれているので、ここでfalseならば処理自体がスキップされます。 left 0 < right 5 と、rightの方が大きいので そのまま処理に入る
中央値のインデックスを (0+5)/2 で 2として
そのまま 再帰的に前半部のマージソートを呼び出し、繰り返していくと、 __mergeSort(a,0,2) → __mergeSort(a,0,1) → __mergeSort(a,0,0)となり ここで初めて処理がスキップされるので、 __mergeSrot(a,0,1)から見ていきましょう、 ※ a配列自体の変化なく、{6,5,4,3,2,1}のままです。 ■ __mergeSort(a,0,1)の場合 後半部分のマージソートでは、__mergeSort(a,1,1)となりますが、 ここでも、
left 1 < right 1 はfalseなのでこのまま、①(aの前半部分をbuffへコピー)へ移ります。 ①に入る

centerは0で、leftも0なので buff[0] = a[0]の「1」がそのまま代入され、buffは {6,0,0,0,0,0}となります。 そのまま
iはインクリメントされ、 1 <= 0 がfalseなのでこのまま②(buffとaの後半部分とのマージ)に移ります。 while( 1 <= 1 && 0 < 1)となるので、 
もし、buff[0] が、a[1]以下であれば buff[j]を、でなければa[i]を a[0(left)]に代入するというながれ buff[0]の6は a[1]の5より大きいので、、a[1]の5がそのままa[0]に代入されます。 2 <= 1 がfalseなので ここで、aの配列は{5,5,4,3,2,1}となり、③(buffの残り要素をaにコピー)へ移ります。 while(0 < 1) となるので、 a[1] = buff[0] となり、5,6,4,3,2,1, ← となり、 1 < 1 はfalseなので、ここで処理は終わり、 __mergeSort(a,0,2)へと処理が移ります。 ■ __mergeSort(a,0,2)の場合
 center は (0+2)/2となり、後半部分のマージソートは(a,1+1,2)として、 (a,2,2)として呼ばれるが、if文で弾かれるので、(a,0,2)の流れでいくと、まずは①から buff[0] = a[0]が代入され、buffは{5,0,0,0,0,0} 続いて[1] = a[1]となり、 buffは{5,6,0,0,0,0}ここで 2 <= 1 がfalseなので ②に移ります。 while(2 <= 2 && 0 < 2) なのでこのまま buff[0]の「5」はa[2]の「4」よりも大きいので a[0]に a[2]の「4」を代入し、 aの配列は{4,6,4,3,2,1,}となります while(3 <= 2)の時点でfalseなのでこのまま③に移ります。 while(0 < 2)なので、 a[1] = buff[0] を代入 {4,5,4,3,2,1,} a[2] = buff[1]を代入 {4,5,6,3,2,1,} 
となり、2 < 2 がfalseなのでこのまま抜けることになり、 前半部分のマージソートである__mergeSort(a,0,2)が完了します。 ここが終了したら、__mergeSort(a,3,5)へと移り、 今度は同様に後半部分のマージソートへ移るという感じです。 このように、大元の配列をまずは前半部と後半部に分けて 分けた後の配列も同様に前半部と後半部に分けて。。ということを再帰的な呼び出しで実現しています。 なお、離れた要素の交換は行わないので、冒頭の通り、安定なアルゴリズムということになります。 学んだこと 「マージする」というのは、ソート済みの2つの配列をソートしながら合成することを指す。 コード上でのマージは、作業用の配列に元の配列の前半部分代入していき、aの後半と比較しながらマージしていく流れ 再帰的に呼ぶことで、繰り返しを実現できる マージソートにおけるマージをイメージできればあとはそれをひたすら繰り返していく という流れは掴めましたが、実現のために再帰呼び出しが複数使われるとなかなか理解が難しくなりそうですね汗 引き続き頑張っていきます! ※記事内の画像はITをわかりやすく解説から拝借致しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mybatisのtrimで既に存在するWHERE句へ動的にANDやORを追加したい

事象 SELECT * FROM FRUITS WHERE PRICE = 500; こんな感じのSQLに特定条件下でAND()を追加して、果物名(複数可)で絞れるようにしたい 方法 よく見るこいつをもじってAND句に応用する <trim prefix="WHERE" prefixOverrides="AND |OR "> ... </trim> <以下サンプル> appleがtrueだったらappleのデータのみを取ってくる。 bananaもtrueだったらさらにbananaのデータも取ってくる。 ※banana==trueのみの場合、先頭のORは削除される TestMapper.java public interface TestMapper { List<FruitDto> getFruitList(@Param("apple") boolean apple, @Param("banana") boolean banana) { List<FruitDto> getFruitList(); } } TestMapper.xml <select id="getFruitList" resultMap="FruitDto"> SELECT * FROM FRUIT WHERE PRICE = 500 <trim prefix="AND (" prefixOverrides="OR" suffix=")"> <if test="apple == true"> NAME = 'apple' </if> <if test="banana == true"> OR NAME = 'banana' </if> </trim> </select>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

入門Javaのenum

この記事のサンプルコードは、enumの説明に特化しています。それゆえ、一般的には良くないとされるコードも含まれています(金額の計算で BigDecimal ではなく int や double を使っているなど)。 分類などをどう表すか 例えば、架空のECサイトのシステムを考えます。このECサイトの会員にはブロンズ会員・シルバー会員・ゴールド会員の3つのランクがあり、ランクによって割引率などが異なります。これをどうやって表しましょうか? まずは良くない例です。 良くない例 public class Rank { public static final int BRONZE = 1; public static final int SILVER = 2; public static final int GOLD = 3; } ランクを整数の定数で表しています。こうすると、どのような弊害があるでしょうか? 例えば、ランクを引数に取るメソッドがあるとしましょう。 ランクを引数に取るメソッドの定義の例 public class PriceCalculator { public int getDiscountPrice(int price, int rank) { switch (rank) { case Rank.BRONZE: return price; case Rank.SILVER: return (int) (price * 0.9); case Rank.GOLD: return (int) (price * 0.8); default: throw new IllegalArgumentException("Invalid rank"); } } } このメソッドの第2引数は、本来は Rank.BRONZE などを指定してほしいのでしょう。しかし、その旨を丁寧にJavadocに書いたとしても、それを読まないでこんな風に使う人がいるかもしれません。 想定しない呼び出しの例 getDiscountPrice(10000, 1); // 定数を使わずにハードコーディング getDiscountPrice(10000, 0); // 定数に定義されていない値を指定 また、ランクと割引率という非常に関連性の強い値が、別のクラスに記述されているのも気になります。後々で保守が大変そうです。 もし同じ Rank クラス内で割引率を定数化しても、ランクの定数と同じ現象(ハードコーディングなど)は起こりえます。それに、ランクにまつわる割引率以外の値が必要になったとき、更に同じような定数を増やすのは嫌ですよねえ・・・。 良くない例 public class Rank { public static final int BRONZE = 1; public static final int SILVER = 2; public static final int GOLD = 3; // 定数がどんどん増えていく public static final double DISCOUNT_RATE_BRONZE = 0.0; public static final double DISCOUNT_RATE_SILVER = 0.1; public static final double DISCOUNT_RATE_GOLD = 0.2; } enumとは そこで登場するのがenumです。日本語では「列挙型」とも呼ばれます。 enumはこんなのです。 enumの例 public enum Rank { // 定数 BRONZE(0.0), SILVER(0.1), GOLD(0.2); // フィールド private final double discountRate; // コンストラクタ private Rank(double discountRate) { this.discountRate = discountRate; } // メソッド public int getDiscountPrice(int price) { return (int) (price * (1 - discountRate)); } } enumは、次の特徴を持つ特殊なクラスです。 フィールド・メソッドは普通のクラスと同様に定義できる コンストラクタはprivateにしかできない=外部からのインスタンス生成は不可能 アクセス修飾子が無い場合はprivateと解釈される public・protectedを付けるとコンパイルエラー クラス直下に定数を宣言する これらの定数は自クラスのインスタンス 自クラスのpublic static finalなフィールドとなる つまり、BRONZE・SILVER・GOLDはRankクラスのインスタンスであり、かつRankクラスのpublic static finalなフィールドなのです。 ()内に指定された値は、コンストラクタに渡す引数です。つまり今回の場合、コンストラクタを通してdiscountRateフィールドの値となります。 むりやりクラスとして書くと、こんなイメージです。 後述しますが、実際のenumとは異なります。あくまでイメージとして捉えてください。 enumを無理やりクラスで書いた例 public class Rank { // 定数 public static final Rank BRONZE = new Rank(0.0); public static final Rank SILVER = new Rank(0.1); public static final Rank GOLD = new Rank(0.2); // フィールド private final double discountRate; // コンストラクタ private Rank(double discountRate) { this.discountRate = discountRate; } // メソッド public int getDiscountPrice(int price) { return (int) (price * (1 - discountRate)); } } enumの何が嬉しいか 一言で言うと、前述のint定数の問題を解決できます。 ランクを引数に取るメソッドがあった場合、定義されていない値を指定したり、定数を使わずにハードコーディングしたりすることが不可能になります。 // Rankに定義された定数(BRONZE・SILVER・GOLD)以外は引数に指定できない! public void doSomething(Rank rank) { ... } もちろん、nullは引数に指定できてしまいます。これはJavaの文法上、どうにもならないですね・・・? また、ランクと割引率(discountRate)という非常に関連性の強い値を、1つのクラスにまとめることができます。これにより、割引率を使うメソッド(getDiscountPrice())も同クラス内に定義できます。保守が楽チンですね! 更なるenumの活用術については、次の書籍を読むと良いでしょう。 現場で役立つシステム設計の原則 Software Design 2021年3月号の特集「Javaでもう一度学び直すオブジェクト指向プログラミング」 [上級編]各定数でのメソッドオーバーライド 各定数ごとにメソッドの挙動を変えたい場合、各定数ごとにメソッドをオーバーライドすることができます。 このコードはJava Language Specificationから拝借し、少し変更を加えたものです(URL→ https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#d5e15436) enum Operation { PLUS { @Override double eval(double x, double y) { return x + y; } }, MINUS { @Override double eval(double x, double y) { return x - y; } }, TIMES { @Override double eval(double x, double y) { return x * y; } }, DIVIDED_BY { @Override double eval(double x, double y) { return x / y; } }; abstract double eval(double x, double y); } 文法的には無名クラスの書き方です。 [上級編]enumはEnumのサブクラス enumは暗黙的にjava.lang.Enumのサブクラスとして定義されます。ということは、Enumクラスのメソッドを全て使えるということです。 Enumのメソッド一覧 → https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/Enum.html ちなみに、型パラメータEには定義したenum自身が入ります。つまりこんな感じです。 enumをクラスで書いたイメージコード public class Rank extends Enum<Rank> { ... } Enumで定義されたメソッドの中で、特に重要なものは次のとおりです。 メソッド 説明 String name() 定数名を返す String toString() 定数名を返す int ordinal() 定数が宣言された順番を返す(最初の定数が0) また、enum定義時に自動的に作成されるメソッドもあります。 メソッド 説明 static <T extends Enum<T>> T[] values() 全定数の配列を返す(順番は宣言されたとおり) static <T extends Enum<T>> T valueOf(String name) 引数で指定された名前(完全一致)の定数を返す 利用例 Rank[] ranks = Rank.values(); Rank rank = Rank.valueOf("GOLD"); // Rank.GOLDが返る [上級編]EnumSetの利用 java.util.EnumSetクラスは、名前の通りenumのSetです。 例えば、シルバー会員とゴールド会員のみプレゼントを受け取れるとしましょう。これを判定するメソッドはどう実装するでしょうか? 良くない例 public boolean canGetPresent(Rank rank) { return rank == Rank.GOLD || rank == Rank.SILVER; } この規模だと悪くない気もします。しかし、ランクの数が全部で10個になって、プレゼントを受け取れるランクが5個になったらどうでしょうか?書くのも読むのも辛いですね。 そんなときにEnumSetを使います。 EnumSetを使った例 import java.util.EnumSet; public enum Rank { BRONZE(1.0), SILVER(0.9), GOLD(0.8); // フィールド等省略 // EnumSetを利用! private static final EnumSet<Rank> ranksCanGetPresent = EnumSet.of(SILVER, GOLD); public boolean canGetPresent() { // EnumSet#contains()を利用 return ranksCanGetPresent.contains(this); } } 利用側のコード例 Rank rank = ...; if (rank.canGetPresent()) { System.out.println("プレゼントをどうぞ!"); } これなら、ランクの数が増えても簡単ですね!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

陣取りゲーム 【paiza Aランクレベルアップメニュー】

 満を持してのボス戦。  最初はキューを二本化するプランを考えてみたが、一本のキューで交互に入れる方針に変換。  クラスXYにAかBかを持たせて、「値が変わったら手番終了と判断して引数を1増やす」。 「どっちのターンかを配列で持っておいて、引数のカウンターは増やし続け、余りで判定する」という発想は前のランクから。  みんなうまいこと考えるもんだなあ。 Main.java import java.util.Scanner; import java.util.Queue; import java.util.ArrayDeque; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int h = sc.nextInt(); int w = sc.nextInt(); String n = sc.next(); //先行 String[][] map = new String[h][w]; //陣 Queue<XY> q = new ArrayDeque<>(); //キュー。一本化する int ay=0, ax=0, by=0, bx=0; //初期位置のy,x座標 String[] ab = new String[2]; //AかB。先行を先に入れる(先行の引数が偶数) if(n.equals("A")){ ab[0] = "A"; ab[1] = "B"; } else if (n.equals("B")){ ab[0] = "B"; ab[1] = "A"; } int abc = 0; //abのcounterなのでabc for(int i=0; i<h; i++){ //初期化 String temp = sc.next(); for(int j=0; j<w; j++){ map[i][j] = String.valueOf(temp.charAt(j)); if(map[i][j].equals("A")){ //Aのスタート ay = i; ax = j; } if(map[i][j].equals("B")){ //Bのスタート by = i; bx = j; } } } //初期化。先行から。 if(n.equals("A")){ q.add(new XY(ay, ax, ab[abc%2])); abc++; q.add(new XY(by, bx, ab[abc%2])); abc++; } else if(n.equals("B")){ q.add(new XY(by, bx, ab[abc%2])); abc++; q.add(new XY(ay, ax, ab[abc%2])); abc++; } //ここからメイン処理 while(q.size() > 0){ XY xy = q.poll(); int y = xy.y; int x = xy.x; String turn = xy.AB; //ここで引っ張ってきたABが違うなら手番終了と見なす。カウンターに+1 if(!turn.equals(ab[abc%2])){ abc++; } if(y > 0 && map[y-1][x].equals(".")){ //上 map[y-1][x] = turn; //陣地に塗り替える q.add(new XY(y-1, x, ab[abc%2])); //次の手番を入れておく } if(y < h-1 && map[y+1][x].equals(".")){ //下 map[y+1][x] = turn; q.add(new XY(y+1, x, ab[abc%2])); } if(x < w-1 && map[y][x+1].equals(".")){ //右 map[y][x+1] = turn; q.add(new XY(y, x+1, ab[abc%2])); } if(x > 0 && map[y][x-1].equals(".")){ //左 map[y][x-1] = turn; q.add(new XY(y, x-1, ab[abc%2])); } } //出力処理 int asc=0, bsc=0; for(int i=0; i<h; i++){ for(int j=0; j<w; j++){ if(map[i][j].equals("A")){ asc++; } else if(map[i][j].equals("B")){ bsc++; } } } if(asc > bsc){ System.out.println(asc + " " + bsc + "\nA"); } else { System.out.println(asc + " " + bsc + "\nB"); } } } class XY{ int y; int x; String AB; XY(int y, int x, String ab){ this.y = y; this.x = x; AB = ab; } }  こうやって記録して発表することで記憶が定着するのだなあ……。  今後も書き留めていきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java エクセルで交通信号を作成

Spire.XLS for Javaという無料のライブラリを利用して、エクセルで三つ色、また、四つ色のTraffic lights「直訳:交通信号」を作成できます。今日はこの方法を紹介していきます。 下準備 1.E-iceblueの公式サイトからFree Spire. XLS for Java無料版をダウンロードしてください。   2.IDEを起動して新規プロジェクトを作成してから、インストールされたファイルにあった相応しいSpire. XLS.jarを参照に追加してください。   import com.spire.xls.*; import com.spire.xls.core.IConditionalFormat; import com.spire.xls.core.spreadsheet.collections.XlsConditionalFormats; import java.awt.*; public class setTrafficLightsIcons { public static void main(String[] args) { Workbook workbook = new Workbook(); //シートを追加します。 Worksheet sheet = workbook.getWorksheets().get(0); //データを追加します。 sheet.getCellRange("A1").setText("Traffic Lights"); sheet.getCellRange("A2").setNumberValue(0.95); sheet.getCellRange("A2").setNumberFormat("0%"); sheet.getCellRange("A3").setNumberValue(0.5); sheet.getCellRange("A3").setNumberFormat("0%"); sheet.getCellRange("A4").setNumberValue(0.1); sheet.getCellRange("A4").setNumberFormat("0%"); sheet.getCellRange("A5").setNumberValue(0.9); sheet.getCellRange("A5").setNumberFormat("0%"); sheet.getCellRange("A6").setNumberValue(0.7); sheet.getCellRange("A6").setNumberFormat("0%"); sheet.getCellRange("A7").setNumberValue(0.6); sheet.getCellRange("A7").setNumberFormat("0%"); //高さと幅を設定します。 sheet.getAllocatedRange().setRowHeight(20); sheet.getAllocatedRange().setColumnWidth(25); //条件付き書式を設定します。 XlsConditionalFormats conditional = sheet.getConditionalFormats().add(); conditional.addRange(sheet.getAllocatedRange()); IConditionalFormat format1 = conditional.addCondition(); //背景に色を設定します。 format1.setFormatType(ConditionalFormatType.CellValue); format1.setFirstFormula("300"); format1.setOperator(ComparisonOperatorType.Less); format1.setFontColor(Color.black); format1.setBackColor(Color.lightGray); // 条件付き書式で交通信号を設定します。 conditional.addRange(sheet.getAllocatedRange()); IConditionalFormat format = conditional.addCondition(); format.setFormatType(ConditionalFormatType.IconSet); format.getIconSet().setIconSetType(IconSetType.ThreeTrafficLights1); //保存します。 String result = "output/setTrafficLightsIcons_result.xlsx"; workbook.saveToFile(result, ExcelVersion.Version2013); } } 実行結果
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaで安全なスレッドを作りましょう

背景 新幹線の切符は100枚があります。番号は1から100までです。現在、それらの切符を全部売っていくことにしたいです。 通常一つの窓口を設置して、順番に販売していくことはいいですが、乗客は多くなると、窓口のかかりの人は手が回れないですので、もう一つの窓口を設置しました。 そして、現在窓口1と窓口2があります。窓口1と窓口2は一緒に新幹線の切符を販売することになりました。 ここの窓口はスレッドとして理解しても大丈夫です。では、窓口をどうやって設置しますか? スレッドの基本的な使い方 使い方1 Threadを継承したクラスを作成します public class Main { public static void main(String[] args) { Thread thread1 = new MyThread(); Thread thread2 = new MyThread(); thread1.setName("thread 1st"); thread2.setName("thread 2nd"); thread1.start(); thread2.start(); } } class MyThread extends Thread { int ticket = 100; @Override public void run() { super.run(); while (true) { if (ticket <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + +ticket); ticket -= 1; } } } JDKが用意したThreadクラスがあります、Threadを継承したカスタマイズクラスを生成して、runメッソドをオーバーライドします。切符販売処理をrunメセッドの中に書きます。 各スレッドからrunメソッドを実行させるためには、各スレッドのrunメソッドを呼び出します。 JDKのソースコードから確認したら、start()を呼び出すことによって、以下の処理が行われます。 Causes this thread to begin execution 新しいスレッドを立ち上げます the Java Virtual Machine calls the run method of this thread 立ち上げられたスレッドでrunメソッドの中身を実行されます run()だけをよびだすと、runメソッドの中身を実行されます、新しいスレッドが始まらない。mainスレッドでrunの中身が実行されることと同じことになります。 正しい使い方です。 myThread.start() メインスレッドでrunメソッドを実行させることになります。 myThread.run() アンドロイド開発の話 昔アンドロイドの開発をした時に、AsyncTaskを実行しようとして、executeではなく、oInBackgroundを呼び出して、エラーが発生された原因はまさか同じような原因です。 MyAsyncTask.execute(params) -> 正しい使い方です。 MyAsyncTask.oInBackground() main threadでdoInBackgroundコールバック メソッドを実行させます。 使い方2: Runableを実例化したインスタンスを作成して、Threadの構造関数の引数として渡します public class Main { public static void main(String[] args) { Thread thread1 = new Thread(new MyRunable()); Thread thread2 = new Thread(new MyRunable()); thread1.setName("thread 1st"); thread2.setName("thread 2nd"); thread1.start(); thread2.start(); } } class MyRunable implements Runnable { int ticket = 100; @Override public void run() { while (true) { if (ticket <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + +ticket); ticket -= 1; } } } 課題:共通データをアクセスする時 新幹線の切符はそれぞれの販売センター独自で販売するものではなく、すべて販売センターが共通で使うものにんあるべきです そうしないと、重複な切符が販売されてしまいます thread 1st:100 thread 2nd:100 // ここスレッド1とスレッド2が99番目の切符が販売されてしまいました。 thread 1st:99 thread 2nd:99 thread 2nd:98 対策1: Threadを継承したクラスを作成した場合 マルチスレッドから操作するデータをクラスのstatic辺数にします。そうしたら、スレッド達から操作する辺数はそれぞれのスレッドに所属するものではなく、唯一のMyThreadクラスに所属されることになります。 public class Main { public static void main(String[] args) { Thread thread1 = new MyThread(); Thread thread2 = new MyThread(); thread1.setName("thread 1st"); thread2.setName("thread 2nd"); thread1.start(); thread2.start(); } } class MyThread extends Thread{ static int ticket = 100; // 共通変数 @Override public void run() { super.run(); while (true){ if (ticket <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + + ticket); ticket -= 1; } } } 対策2: Runableを実例化したインスタンスを作成した場合 Runable実例化したインスタンス一つだけ生成します。そうしたら、マルチスレッドから操作する辺数は唯一なMyRunableオブジェクトに所属することになります。 public class Main { public static void main(String[] args) { Runnable myRunable = new MyRunable(); Thread thread1 = new Thread(myRunable); Thread thread2 = new Thread(myRunable); thread1.setName("thread 1st"); thread2.setName("thread 2nd"); thread1.start(); thread2.start(); } } class MyRunable implements Runnable { int ticket = 100; // 共通変数 @Override public void run() { while (true) { if (ticket <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + +ticket); ticket -= 1; } } } 安全なマルチスレッド 以上なThreadもしくはRunableの使い方によって、マルチスレッドから共通データを処理することができますが、安全スレッドとは言えません。ではなぜ安全ではないですか 安全スレッドではない理由 thread 2nd:4 thread 1st:3 thread 2nd:2 thread 1st:1 thread 2nd:0 // ここは0が出てきます、安全なスレッドではない証です たとえ新幹線の切符を販売しているときに、0番目の切符を販売できないではないでしょうか? なぜ0が出たか、理由はこちらです。 runメソッドの中に、たくさんのスレッドが同時に入ることができます。 それによって、共通のデータが同時に複数のスレッドに触られ、if文、while文など条件に満たして処理に入りましたけど、処理する時もうはや条件に満たせないことが発生してしまい、不安全です。 安全スレッド対策 新幹線に各車両には一つトイレがあります。一つのトイレは同時にもちろん一人しか利用できないです。一つのトイレは同時にもちろん一人しか利用できないことは安全なスレッドになります。 では、新幹線はどうやって、同時に一人しか利用できないことにさせたかというと、理由は二つがあります。 新幹線の便所使用知らせ灯(Photo by PIXTA) トイレの使用状況を乗客に公開して、利用されるときに、利用しようとする乗客をトイレ外で待たせます。トイレ利用されてないときだけ、利用しようとする乗客にはトイレに入ってもらいます。 図のように、使用状態を知らせ灯によって、乗客はトイレの使用状態に合わせて、トイレに行くタイミングを調整できます。 トイレにドーアにロックを設置します。中にトイレを利用する人にドーア内側からロックをかけてもらいます。そうすると、トイレが利用される時には、いくら外で待っている乗客を入ろうとしても、トイレに入れないです。 https://chikirin.hatenablog.com/entry/20150610 安全なスレッドの使い方 便所使用知らせ灯 + ロックをかけますの役割を果たせるこはsynchronizedです。 synchronized(ロックオブジェクト)を使えば、新幹線のトイレみたいに安全なスレッドを管理できます。 ここで以下のことと認識していただければ、大丈夫です。 synchronized トイレのドーア ロックオブジェクト ドーアのロック(唯一性が必要) ドーアを使います ドーアの使い方1: synchronizedブロック 安全保護したい部分をsynchronizedブロックで囲みます。 public class Main { public static void main(String[] args) { Runnable myRunable = new MyRunable(); Thread thread1 = new Thread(myRunable); Thread thread2 = new Thread(myRunable); thread1.setName("thread 1st"); thread2.setName("thread 2nd"); thread1.start(); thread2.start(); } } class MyRunable implements Runnable { int ticket = 100; private Object lock = new Object(); // ドーアのロックを作成します @Override public void run() { while (true) { synchronized (lock) { // ドーアにロックをかけます if (ticket <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + +ticket); ticket -= 1; } } } } 気をつけないといけないのは、以下の書き方は安全ではないです @Override public void run() { while (true) { synchronized (new Object()) { // <- 新しいロックを作成して、ドーアにかけます。 だめです、間違います、安全なスレッドではないです。 if (ticket <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + +ticket); ticket -= 1; } } } なぜかというと、毎回スレッドははいると、新しいロックオブジェクトを作成することになります。そうしたら、ロックは唯一ではなく、トイレが利用される時に、外で待ちの乗客を止めることができなくなります ドーアの使い方2: synchronizedメソッド synchronizedブロックだと、ロックオブジェクトを用意する必要があります。一方、スレッド安全を確保したいコードを別のメソッドにして、メソッドをsynchronizedにすれば、スレッド安全を確保はできます。ロックオブジェクトも自ら用意しなくてもよいです(内部的に処理されてます)。 class MyRunable implements Runnable { int ticket = 100; @Override public void run() { while (true) { sellTicket(); if (ticket <= 0) { break; } } } synchronized private void sellTicket() { if (ticket <= 0) { return; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + +ticket); ticket -= 1; } } ドーアのロックを使います ロックの作りかたは二つがあります ドーアのロックの使い方1: new Object() JavaのObject型のオブジェクトを入れる必要がありますので、new Object()でいきます。 ドーアのロックの使い方2: this MyRunableを元に作られたオブジェクトは一つしか存在していないし、MyRuableもObject側のサブクラスですので、ロックオブジェクトとして使えます。 public class Main { public static void main(String[] args) { Runnable myRunable = new MyRunable(); Thread thread1 = new Thread(myRunable); Thread thread2 = new Thread(myRunable); thread1.setName("thread 1st"); thread2.setName("thread 2nd"); thread1.start(); thread2.start(); } } class MyRunable implements Runnable { int ticket = 100; @Override public void run() { while (true) { synchronized (this) { if (ticket <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + +ticket); ticket -= 1; } } } } ドーアのロックの使い方3: MyThread.class クラス名.classもObject型のオブジェクトです、そしてMyThreadは唯一なクラスですので、ロックオブジェクトとして使えます。 public class Main { public static void main(String[] args) { Thread thread1 = new MyThread(); Thread thread2 = new MyThread(); thread1.setName("thread 1st"); thread2.setName("thread 2nd"); thread1.start(); thread2.start(); } } class MyThread extends Thread { static int ticket = 100; @Override public void run() { super.run(); while (true) { synchronized (MyThread.class) { if (ticket <= 0) { break; } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + +ticket); ticket -= 1; } } } } 応用 Singleton パターン パターン1 このパタンはなぜかスレッド安全とはいえないかというと、マルチスレッドからgetInstanceアクセスするところで、synchronizedというロックはないから、複数なinstanceが生成されてしまう可能性があります。 class TicketWindowNotSafe{ static private TicketWindowNotSafe instance = null; private static TicketWindowNotSafe getInstance(){ if (instance == null){ return new TicketWindowNotSafe(); } return instance; } } パターン2 synchronizedというロックを使って、あるスレッドはgetInstanceをアクセスするときに、他のスレッドはgetInstanceのそとで待つことになります。getInstanceは同時に一つのスレッドしかアクセスできないため、スレッド安全とは言えます。 しかし、instanceが生成された場合は、synchronizedの中に入ってからinstanceを取り出すことになります。synchronizedの中に入るためには、前のスレッドがgetInstanceから出ることを待つ必要があります。その待ち時間が長いため、全体的に非効率です。 class TicketWindowSafeNotEfficient{ static private TicketWindowSafeNotEfficient instance = null; private static TicketWindowSafeNotEfficient getInstance(){ synchronized (TicketWindowSafeNotEfficient.class){ if (instance != null){ return instance; } return new TicketWindowSafeNotEfficient(); } } } パラーン3 synchronizedの外側で、もしinstanceがすでに生成されましたら、ただちにinstanceを取ります。そのため、instanceがすでに生成された場合、synchronizedに入る必要がなく、待ち時間も必要がなく、全体的に効率よくなります。 class TicketWindowSafeAndEfficient{ static private TicketWindowSafeNotEfficient instance = null; private static TicketWindowSafeNotEfficient getInstance(){ if (instance != null){ return instance; } synchronized (TicketWindowSafeNotEfficient.class){ if (instance != null){ return instance; } return new TicketWindowSafeNotEfficient(); } } } 結論 Javaのsynchronizedはマルチスレッドを安全に守ることができます。大事な知識です。 決して難しいことではないです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む