20210605のJavaに関する記事は6件です。

マルチスレッド処理でDBアクセスのレイテンシを隠ぺいする話

大量の書き込みトラフィックを処理しなくてはならないようなWebサービス(例えばスマホゲーム向けAPIサーバー等)だとDB(RDBMSやNoSQL)を水平分割していることも多いと思います。 例えばユーザーIDで水平分割されているシステムであるユーザーのフレンド50人を一覧表示する場合、ユーザーの情報を50人分取得する必要がありますが全ユーザーが同じDBに収容されているわけではないためサブクエリやIN句で一気に取ってくることができません。 ではどのようにして取得するでしょうか? 1. シーケンシャルに一人ずつ取ってくる? 例えば50人のユーザーIDリストがあったとして、一人ひとり収容されているDBのコネクションを取得してSQLを発行してデータを取得するのはどうでしょうか 以下のようなコードになりそうです(実際に動くコードではありません) public List<UserInfo> getUserInfo(List<UserId> friendUserIdList) { var builder = ImmutableList.<UserInfo>builder(); for (UserId friendUserId : friendUserIdList) { // friendUserIdに対応したDBコネクションを取得 try (DbConnection con = userDbTransactionManager.startSessionByUserId(friendUserId)) { UserInfo userInfo = userInfoRepository.selectByUserId(con, frinedUserId); // DBからUserInfoオブジェクトを取得 builder.add(userInfo); } } return builder.build(); } 実際に動くコードを組んでやってみるとわかると思いますが、これは非常に遅いです。 50回分アプリケーションサーバーとDB間を通信が行き来してSQLの実行時間もその分累積されるためこのメソッドの実行時間は非常に長くなってしまいます。 よく考えてみると(考えなくても?)これは典型的なN+1ですよね・・・ 2. 同じシャード(DB)に格納されているユーザーをまとめて取得する? 前項と同じように50人のユーザーIDリストを渡されたらまずリストをシャードごとに分割して同じシャードのユーザーをまとめて取得するようにします。 public List<UserInfo> getUserInfo(List<UserId> friendUserIdList) { // ユーザーIDリストをシャード(DB)ごとのリストに分割する Map<ShardId, List<UserId>> userIdMap = shardManager.groupByShardId(friendUserIdList); var builder = ImmutableList.<UserInfo>builder(); for (Entry<ShardId, List<UserId>> entry : userIdMap.entrySet()) { final ShardId shardId = entry.getKey(); // ShardId(DBのシャードを表すID)に対応したDBコネクションを取得 try (DbConnection con = userDbTransactionManager.startSessionByShardId(shardId)) { // ユーザーIDリストからIN句やサブクエリを用いて効率よくUserInfoオブジェクトを取得してくる List<UserInfo> userInfoList = userInfoRepository.selectByUserIdList(con, entry.getValue()); builder.addAll(userInfoList); } } return builder.build(); } これはちょっと速くなったんじゃないでしょうか? ユーザーがシャード毎にまとまった分サーバーとDBの往復が減ってメソッドの実行時間が短縮されるはずです。 でもシャード数が増えると同じシャードに入っている確率が下がる(※)ので一人ずつの場合に近い状態になってしまいます・・・ ※50人がバラバラのシャードに分散されている可能性が高くなるのでuserIdMap.entrySet().size()が50に近づいてしまう 3. 2をマルチスレッドで実行してみる さてここからが本題です上記2のfor文の中身をマルチスレッドで並列実行することで複数のサーバーとDBの往復通信時間とSQL実行時間を隠ぺいします。 public List<UserInfo> getUserInfo(List<UserId> friendUserIdList) { // ユーザーIDリストをシャード(DB)ごとのリストに分割する Map<ShardId, List<UserId>> userIdMap = shardManager.groupByShardId(friendUserIdList); CompletableFuture<List<UserInfo>>[] cfs = new CompletableFuture[userIdMap.entrySet().size()]; int index = 0; for (Entry<ShardId, List<UserId>> entry : userIdMap.entrySet()) { final ShardId shardId = entry.getKey(); CompletableFuture<List<UserInfo> cf = CompletableFuture.supplyAsync(() -> { // ShardId(DBのシャードを表すID)に対応したDBコネクションを取得 try (DbConnection con = userDbTransactionManager.startSessionByShardId(shardId)) { // ユーザーIDリストからIN句やサブクエリを用いて効率よくUserInfoオブジェクトを取得してくる List<UserInfo> userInfoList = userInfoRepository.selectByUserIdList(con, entry.getValue()); return userInfoList; } }, executor); // デフォルトだとForkJoinPoolが使われるので独自のExecutorを利用する(後述) cfs[index] = cf; index++; } CompletableFuture.allOf(cfs).join(); var builder = ImmutableList.<UserInfo>builder(); for (CompletableFuture<List<UserInfo>> cf : cfs) { builder.addAll(cf.get()); } return builder.build(); } これでかなり高速になったはずです。 ちょっと複雑なように見えますが2のfor文の中身をJava8から導入されたCompletableFutureに関数として渡すことでマルチスレッド化しているだけです。(戻り値をまとめるために後ろにちょっとコードがついてますが・・・) Appendix CompletableFuture#supplyAsyncの第二引数にExecutorを指定している理由 Java8のマルチスレッド処理フレームワークは裏でExecutorというスレッドを制御するクラスからスレッドを取得して実行しています デフォルトだとForkJoinPoolというJavaを実行しているCPUのコア数に合わせたスレッド数に制御してくれるクラスを利用します。 一見それでよいように思えてしまいますが、DBアクセス中はサーバー側のCPUはただDBからの戻りを待っているだけなので別の処理ができます。なのでCPUコア数にとらわれずもっと多数のスレッドで処理させた方が効率が上がります。 簡単にExecutor(もしくはExecutorを実装したExecutorService)を作るのであればjava.util.concurrent.Executorsというユーティリティクラスがあるので、例えばnewFixedThreadPool​(int nThreads)というメソッドを使えばnThredsで指定したスレッド数のExecutorが簡単に作れます。(ただし実際のプロダクトにマッチするかはケースバイケース) 実際のプロダクトで使う際は負荷や用途に合わせてExecutor(ExecutorService)を自作するのもよいでしょう。 キャッシュすればいいのでは? 確かにUserInfoをRedisのような高速なKVSにキャッシュすることで高速化することも可能でしょう。 ただ、今回の例ではユーザーのフレンド一覧ということになっていますキャッシュはある程度参照の局所性がある場合は効果的ですが、ユーザーのフレンド一覧に参照の局所性があるでしょうか? フレンドは参照の局所性がないデータなので、キャッシュミスをしてDBに取りに行く頻度が高くなります。なのでDBに取りに行った場合でも高速にレスポンスが返せるように設計しておくことが重要と考えます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Effective Java(第3版) 項目11 equalsをオーバーライドするときは、常にhashCodeをオーバーライドする

equalsに続き、hashCodeをオーバーライドする場合に守るべき内容について、かなり詳細に記載しています。 記載内容 hashCodeの一般契約 まず、equalsをオーバーライドしているすべてのクラスで、hashCodeをオーバーライドしなければならない。 そうしないと、一般契約を破ることになる。 (一般契約についての説明はObject.hashCode()の通りなのでここでは割愛) equalsをオーバーライドして、hashCodeをオーバーライドしていない場合、以下の一般契約を破ることになる。 equals(Object)メソッドに従って2つのオブジェクトが等しい場合は、 2つの各オブジェクトに対するhashCodeメソッドの呼出しによって同じ整数の結果が生成される必要があります。 このようなオブジェクトを、HashMapのキーに使用すると、意図せぬ動作になることがある。 実装方法 基本的なhashCodeの実装は以下の様になる。 (PhoneNumberはshortのareaCode、prefix、lineNumをメンバーに持つ) PhoneNumber.java @Override public boolean hashCode() { int result = Short.hashCode(areaCode); result = 31 * result + Short.hashCode(prefix); result = 31 * result + Short.hashCode(lineNum); return result; } パフォーマンスが重要でない状況であれば、以下の実装でも良い。 PhoneNumber.java @Override public boolean hashCode() { return Objects.hash(lineNum, prefix, areaCode); } ただし、この書き方では、可変長引数のための配列生成、基本データ型が含まれる場合はボクシングとアンボクシングが発生するため、 パフォーマンスは良くない。 もし、クラスが不変で、ハッシュコードを計算するコストが高い場合、以下の様にキャッシュすることが出来る。 (スレッドセーフであるように実装する必要が有る) private int hashCode; @Override public boolean hashCode() { int result = hashcode; if (result == 0) { result = Short.hashCode(areaCode); result = 31 * result + Short.hashCode(prefix); result = 31 * result + Short.hashCode(lineNum); hashCode = result; } return result; } 上記のコードは、ハッシュコードの計算結果とhashCodeフィールドの初期値(上記の場合0)が一致する可能性が低いことが前提になっている。 注意点 ハッシュコードの計算に使用するフィールド パフォーマンスを向上するために、ハッシュコードの計算から意味のあるフィールドを除外してはならない。 その結果、hashcodeメソッドは速くなるかもしれないが、ハッシュ値の品質がハッシュテーブルで使用できないほど 下がるかもしれない。 例として、Java2より前のStringのhashcodeは、先頭16文字だけを1文字おきに使用してハッシュ値を計算していたため、 URLの様に階層化された大きな文字列をキーにした場合、ハッシュテーブルの検索速度がO(n^2)になっていた hashCodeが返す値をドキュメント化しない hashCodekが返す値の詳細な仕様に関するドキュメントを提供するべきではない。 記載した場合、クライアントはその値に依存した処理を記載する可能性がある。 ドキュメントを提供しなければ、値の変更に対する柔軟性を得ることが出来る。 考察 equalsに続いて長い項目ですが、こちらも記載されている内容はhashCodeに関する基本的な内容であり、 特に一般契約については理解しておく必要が有ります。 実装方法による性能の差異 3つの実装方針が記載されていますが、特に気になるのは、 自分で計算するものと、Objects.hashを使用したものとの性能差だと思います。 そこで、以下のクラスA、BのhashCodeをそれぞれの実装方法で実装し、A.hashCodeの速度を比較してみました。 class A { int n; String s; B object; } class B { int n; String s; } 結果は、4倍程度の性能差はありましたが、 100万回実行して14ミリ秒と50ミリ秒という数値(CPUは3.5GHz)でしたので、微妙な感はあります。 実処理に組み込んだ場合にGCにどの程度影響するのか等も考慮すると、 それなりに影響するのかもしれませんが、コードの読みやすさとの天秤を考えると悩ましいです。 このレベルの性能差が気になるのであれば、3番目のキャッシュする方式の方が良さそうに思います。 (同じオブジェクトに繰り返しhashCodeを呼ぶ場合に限りますが) 実装方針 equalsでも触れましたが、IntelliJ、Eclipse等のIDEで、equalsメソッドとhashcodeメソッドをセットで生成してくれますので、 これに従うのが良いと思いますし、 もし、何らかの理由で特殊な比較を行う場合は、その旨をコメントで記載するべきです。 やはりRecordの正式導入が待ち遠しいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Framework の RestTemplate で JSON を Object 型またはその配列で取得する

概要 Spring Framework の RestTemplate で JSON を取得し、Object 型または Object 型の配列で取得する 取得したオブジェクトが実際にはどの型にマッピングされているか検証する 検証環境: Spring Boot 2.5.0 + Java 11 (AdoptOpenJDK-11.0.11+9) ソースコード ここではコントローラークラスのみ示す。 package org.example; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class MyController { // 指定された URL から JSON を取得し Object 型で受け取ってそのまま返す @GetMapping("/object-proxy") public Object objectProxy(@RequestParam String url) { RestTemplate rest = new RestTemplate(); RequestEntity<Void> req = RequestEntity.get(url).build(); ResponseEntity<Object> res = rest.exchange(req, Object.class); Object body = res.getBody(); System.out.println("objectProxy"); System.out.println("body: " + body.getClass()); // 実際のクラスを確認する return body; } // 指定された URL から JSON を取得し Object 型の配列で受け取ってそのまま返す @GetMapping("/array-proxy") public Object[] arrayProxy(@RequestParam String url) { RestTemplate rest = new RestTemplate(); RequestEntity<Void> req = RequestEntity.get(url).build(); ResponseEntity<Object[]> res = rest.exchange(req, Object[].class); Object[] body = res.getBody(); System.out.println("arrayProxy"); System.out.println("body: " + body.getClass()); // 実際のクラスを確認する for (Object obj : body) { System.out.println("obj: " + obj.getClass()); // 実際のクラスを確認する } return body; } // JSON オブジェクトを返す (Content-Type は指定せず) @GetMapping("/object") public String getJsonObject() { return "{\"name\": \"Alice\"}"; } // JSON 配列を返す (Content-Type は指定せず) @GetMapping("/array") public String getJsonArray() { return "[{\"name\": \"Alice\"},{\"name\": \"Bob\"}]"; } } 検証 curl コマンドでエンドポイントにアクセスして検証する。 JSON オブジェクトを返すエンドポイント JSON オブジェクトを取得。 Content-Type を指定していないため text/plain になっている。 $ curl --include http://localhost:8080/object HTTP/1.1 200 Content-Type: text/plain;charset=UTF-8 Content-Length: 17 Date: Sat, 05 Jun 2021 08:00:05 GMT {"name": "Alice"} JSON 配列を返すエンドポイント JSON 配列を取得。 Content-Type を指定していないため text/plain になっている。 $ curl --include http://localhost:8080/array HTTP/1.1 200 Content-Type: text/plain;charset=UTF-8 Content-Length: 35 Date: Sat, 05 Jun 2021 08:00:09 GMT [{"name": "Alice"},{"name": "Bob"}] JSON オブジェクトを取得し Java の Object 型で受け取ってそのまま返す JSON オブジェクトを返すエンドポイントから JSON を取得し、Java の Object 型で受け取ってそのまま返している。 $ curl --include http://localhost:8080/object-proxy --get --data-urlencode "url=http://localhost:8080/object" HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Sat, 05 Jun 2021 08:00:15 GMT {"name":"Alice"} Spring Boot 側のログを見る。 Object 型で取得した JSON オブジェクトが、実際には LinkedHashMap になっていることがわかる。 objectProxy body: class java.util.LinkedHashMap JSON 配列を取得し Java の Object 型の配列で受け取ってそのまま返す JSON 配列を返すエンドポイントから JSON を取得し、Java の Object 型の配列で受け取ってそのまま返している。 $ curl --include http://localhost:8080/array-proxy --get --data-urlencode "url=http://localhost:8080/array" HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Sat, 05 Jun 2021 08:00:21 GMT [{"name":"Alice"},{"name":"Bob"}] Spring Boot 側のログを見る。 Object 型の配列で取得した JSON 配列が、LinkedHashMap を要素に持つ Object 側の配列になっていることがわかる。 arrayProxy body: class [Ljava.lang.Object; obj: class java.util.LinkedHashMap obj: class java.util.LinkedHashMap JSON 配列を取得し Java の Object 型で受け取ってそのまま返す JSON 配列を返すエンドポイントから JSON を取得し、Java の Object 型で受け取ってそのまま返している。 $ curl --include http://localhost:8080/object-proxy --get --data-urlencode "url=http://localhost:8080/array" HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Sat, 05 Jun 2021 08:00:27 GMT [{"name":"Alice"},{"name":"Bob"}] Spring Boot 側のログを見る。 Object 型で取得したオブジェクトが、実際には ArrayList になっていることがわかる。 objectProxy body: class java.util.ArrayList 参考資料 RestTemplate (Spring Framework 5.3.7 API) - Javadoc curl - How To Use
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PreparedStatementでin述語を使いたい場合の解決策(雑ですみません)

(in述語って言葉を初めて知ったが、、、) daoクラスを書いているときに、 select fizz, buzz from HOGE where fizz in (?,?,,,) のようなSQLを投げたいと思った。 しかし、SQLInjectionの観点から、 String sql ="select fizz, buzz from HOGE where fizz in ({1})"; StringBuilder sb = new StringBuilder(); for(String fizz : fizzList ){ sb.append(fizz+","); } String tmpBindParam = sb.toString(); String bindParam tmpBindParam.sbString(0,tmpBindParam.length()-1) sql = sql.replace("{1}",bindParam); PreparedStatement ps = conn.prepareStatement(sql); rs = ps.executeQuery(); ... のようなことは危険である。 間違っているので実際には書いてないが、 要するにPreparedStatement#setStringなどでバインド変数を使わない手法。 そこで、Bestかわかからないがこうやったらできるだろうと思った。 StringBuilder sb = new StringBuilder(); for(String fizz : fizzList ){ sb.append("?,"); } String placeHolder = sb.deleteCharAt( sb.length() -1 ).toString(); String sql ="select fizz, buzz from HOGE where fizz in (" +placeHolder+ ")"; PreparedStatement ps = conn.prepareStatement(sql); int i = 1; for(String fizz : fizzList ){ ps.setString(i++, fizz); } rs = ps.executeQuery(); こんな感じでやったら、一応バインド変数を用いて、 同じ数の ? に動的に値を渡すことが可能である。 ただ本当にこれがベストなのかわからない。 もしもっと効率的な書き方があれば教えてほしい。 Java8で書けるもっといい方法があれば。。とくに。。。知りたい。 (あんま調べても出てこなかったのよね)。。 ちなみにin述語って言葉はSQLアンチパターンで本にありました。↓↓ SQLアンチパターン/BillKarwin/和田卓人/和田省二【3000円以上送料無料】 //追記 先輩がこのサイト教えてくれました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JUnit でテスト用のデータを使用する(@Sqlアノテーション )

概要 実行時点によって、データが変わってしまうので、 ユニットテストは、テスト用のデータで実行したいという場合、 @Sqlアノテーション を使用する。 1. テスト用のsqlを作成 src/main/resources 配下に、以下のようなsqlを作成する。 sample.sql INSERT INTO sample (id, name) VALUES(20, 'サンプル'); 2.テストを作成 test.java // @Sql()の中には`src/main/resources`以下のパスを書く @Test @Sql({"/newsRoutingTest.sql"}) public void sampleTest(){ // テスト内容 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Silver SE11 対策ノート

このノートの使い方 対象:Java Silver SE11受験予定の方 Java Silverに合格したので、勉強の過程でまとめたノートを公開します。 「黒本」に沿った構成にしています。覚える必要がある箇所やトリッキーなJavaのルールをまとめてあります。 第1章 簡単なJavaプログラムの作成 ◆javaコマンド ・書き方:java 完全修飾クラス名 [引数、引数、・・・] ※完全修飾名とは、「java.lang.String」のような正式名のこと。「String」クラスは、単純名と呼ぶ。 ・引数は、スペースで区切って列挙する。 ・¥"=「”」と1引数として出力される。 ・コマンドの種類 ①実行コマンド:java ファイル名(.javaなどの拡張子は不要)/java ファイル名.java(拡張子をつけることでコンパイルが不要になる) ②コンパイルコマンド:javac ファイル名.java(拡張子必要) ③モジュールコマンド:jmod ・javaコマンド実行時の動作:  ①JVMを起動  ②指定されたクラスをクラスパスから探し出してロードする  ③String型オブジェクトを作成し、起動パラメータ(引数)を格納する  ④起動パラメータ(引数)を保持したString配列型オブジェクトへの参照を引数に渡してメソッドを実行 ◆整数リテラル ・整数リテラルの種類:整数値(int型)、文字(char型)、浮動小数点数(double型)、真偽値(boolean型)の4つ。 ・リテラルを他のデータ型であると明示したい時は、long型は「L」「l」、float型は「F」「f」といった接尾辞を値の後ろにつける。 ・各進数の接頭辞 ①2進数:「0b」を接頭辞としてつける。例えば10進数の「63」は「0b0111111」と表現。 ②8進数:「0」を接頭辞としてつける。例えば10進数の「63」は「077」と表現。 ③16進数:「0x」を接頭辞としてつける。例えば10進数の「63」は「0x3F」と表現。 ・「_」アンダースコア:桁数の多い数値リテラルを見やすくする。リテラルの先頭&末尾と記号の前後には記述できない(記号例:「.」「L」「F」「0b」「0x」 )。それ以外であれば出現する場所、回数は自由。 ◆識別子 ・命名規則:  ①予約語(abstract, if, intなど)を識別子として使えない  ②記号は「_」と通貨記号$のみ使える  ③数字から始められない 第2章 Javaの基本データ型と文字列操作 ◆型変換 ・型変換には2種類ある。 ①暗黙の型変換:キャスト式が必要ではない。小さいデータ型を、大きいデータ型に入れる。 ②明示的な型変換:キャスト式が必要。大きいデータ型を、小さいデータ型に入れる。 ・型の大きさ順 (小) byte→short→int→long→float→double (大) (覚え方:BTS、アイラブフロートダブル!) char型=int,long,float,doubleに自動変換される。 (覚え方:あちゃ~、アイラブフロートダブル!) ◆var ・ローカル変数のみに使える。 ・使えない:フィールドの宣言、引数の型宣言、ラムダ式、配列の初期化式、null ◆Stringクラス ・Stringは不変(immutable)なオブジェクト。文字列を変更するには、新しくインスタンスを作る。 【Stringクラスの主なメソッド】 ■indexOfメソッド:検索したが一致しない場合、-1を返す。引数と一致した場合、その開始位置を返す。 ■replaceメソッド:先頭から最後まで一致するものを全て置き換えていく。 ■replaceAllメソッド:置換した結果の文字列を持った新しいStringインスタンスを作り、そのインスタンスへの参照を戻す。 ■lengthメソッド:文字数(半角、全角関係なく) ■concatメソッド:文字を連結させる。 第3章 演算子と判定構造 ◆equalsメソッド ・Objectクラスに属する、オーバーライドして同値性を確認するメソッド。オーバーライドせず使用した場合は、同値性ではなく同一性を確認する。 Objectクラスのequalsメソッドの定義 public boolean equals(Object obj){ return(this == obj); } ・同値性(オーバーライド時):異なる参照を持つインスタンスだが、持っている値が同じである性質のこと。 ・同一性(オーバーロード時):異なる参照を持っているかを確認する。異なる場合、falseを返す。 ・引数にnullが入った時は、必ずfalseを返す。 ◆internメソッド ・文字列がコンスタントプールにあるか確認し、すでにあればその文字列を返す(再利用)。なければその文字列を追加する。 ◆if文 ・{}が省略できる。省略した場合、最初の一文のみがif文内の処理とみなされる。 {}を省略 if(a<10) //{ system.out.print("true") //←この文だけif文の処理とみなされる //} system.out.print("false") ・条件文に合致し処理内容が実行されたら、その後の条件文は実行されない。 ◆switch文 ・case値のルール  ①switch文の引数にある値と、同じ型/互換性がある型であること。  ②定数(final宣言された変数)か、リテラルであるか  ③nullでないこと。 ケース値のルールを確認する final int NUM = 0; int num = 0; switch(num){ case "10": System.out.print("A"); //引数numはint型であるが、 break; //"10"はString型なのでコンパイルエラー case num: System.out.print("B"); //numはfinal宣言されてないのでコンパイルエラー break; } ・defaultは、どれにも当てはまらないときに実行される。 ・nullを引数に渡すと、NullPointerExceptionがスローされる。 第4章 制御構造 ◆while文 ・{}を省略する時の注意点:do{処理}の処理は、一文しか書けない。2つ以上書くとコンパイルエラーになる。 {}省略時は、do{}内の処理文は1文まで int cnt = 0; do system.out.println("A"); system.out.println("B"); //中かっこを省略し2つの分を記述しているのでコンパイルエラー while (cnt++<5); ◆for文 ・初期化子と更新文は複数記述できる。 ・初期化子は同じ型でないといけない。2つ目の初期化子からは、型名を書かなくて良い。 ・条件式は1つしか記述できない。複数記述するとコンパイルエラー。 条件式は1つまで for(int i=0, j=2; i<10; i++, j++){ //処理 } ◆条件式を複数記述したいときは[[論理演算子]]を使う for(int i=0, j=2; i<10 && j<10; i++, j++){ //処理 } ・更新文は繰り返し処理が終わったときに実行される。 ・for文内で宣言した変数は、for文内のみで有効。 ・continue以降に何か処理を書くと、到達不可能コードとしてコンパイルエラーが出る。 第5章 配列の操作 ◆配列 配列 宣言の仕方 //データ型[] 配列変数名 int[] num; //または //データ型 配列変数名[] int num[]; 配列 サイズを指定して配列を作成 //配列変数名 = new データ型[要素数] int[] num = new int[5]; //または int[] num; num = new int[5]; 配列 初期化 //データ型 配列変数名 = {配列に入れる値} int[] num = { 1, 2, 3, 4, 5}; ・配列のインスタンスを生成すると、値が入ってなくても自動的に0, nullなど型に合わせてデフォルト値で初期化される。(int型→0 Integer型→null double型→0.0 boolean→false String型→¥u0000) ・次のようにも記述可能。int[] e[] ・「int[ ] a = new int[0] 」のように、要素数が0の場合、ハッシュコードが表示される。 ・継承関係:スーパークラス型の配列型変数で、サブクラスのインスタンスの集合を扱える。 配列における継承関係 public interface A{} public class B implements A{} public class Main{ public static void main(String[] args){ A[] array = new B[]{new B(), new B()}; } } ・無名配列:大かっこ[]の中に数字は入れられない。 無名配列の宣言 //[]の中に数字は入れない。 int[] array = new int[] {} //誤った記述例 int[] array = new int[5] {} 第6章 インスタンスとメソッド ◆Staticなフィールド ・staticで修飾されたフィールドやメソッドは、static領域と呼ばれる領域に配置される。 ・staticなフィールドへのアクセス ①「クラス名.フィールド名」 (例)Sample.num = 10; ②通常通りにインスタンス生成、「参照.フィールド名」でアクセス。 staticなフィールドへのアクセス② Sample s = new Sample(); s.num = 10; ・クラス単位で管理される。=クラスから作られた全てのインスタンスで共有される。 ・staticなフィールドは、インスタンスを生成しなくても使える。 ・staticなメソッドは、staticなメンバ(staticで修飾されたフィールドやメソッド)にしかアクセスできない。(反対に、staticでないメソッドから、staticなメンバにはアクセス可能) ◆ローカル変数 ・初期化されていないローカル変数を参照すると、コンパイルエラー。 ・finalをつけて宣言できる。 ◆this ・ローカル変数(メソッド内で定義した変数)より、フィールド変数(メソッド外で定義するクラス変数)を優先したい時に用いる。 ◆インスタンス ・ダブルクオテーションで文字リテラルを囲うことでも、インスタンスの生成ができる。(例:String="sample";) ◆メソッドの呼び出し ・戻り値がvoid(つまり、何も値を戻さない)の場合、戻り値を受け取るような代入式を記述するとコンパイルエラー。 例 class Sample{ public void hello(){ system.out.println("hello"); } } public class Main{ public static void main(String[] args){ Sample s = new Sample(); String a = s.hello(); //戻り値がvoid型のメソッドを代入して、 //sで値を受け取ろうとしているのでコンパイルエラー s.hello(); //ただメソッドを呼び出すだけで何も受け取らないのでエラーにならない } } ◆return文 ・機能①:呼び出し元のメソッドに値を戻す。 ・機能②:呼び出しもとに制御を戻す。メソッドの処理を強制終了し、呼出し元のメソッドに戻る。 ・return文の後ろの処理は実行不可能。return文の後ろに何らかの処理をするコードを記述すると、コンパイルエラー。 ◆オーバーロード ・オーバーロードのルール:シグニチャ(引数の数や型、順番)が異なること。アクセス修飾子の違いは関係ない。 ・戻り値型だけが異なるメソッドは、オーバーロードとみなされず、同じメソッドが重複して存在するとして、コンパイルエラーになる。 <正しくオーバーロードされていない例> int calc(double a, int b){処理内容}をオーバーロードしたい。 →double calc(double a, int b){処理内容}:戻り値が異なるだけで、オーバーロードされていない。 ◆コンストラクタ ・別のコンストラクタを呼び出すときは、thisを使う。最初に記述しないとコンパイルエラー。 ・コンストラクタは、インスタンス生成時にしか呼び出せない。 ・voidなど戻り値型がついていたら、コンストラクタではない。 ・extends等で継承しても、サブクラスには引き継がれない。 ・コンストラクタを修飾するアクセス修飾子に制限はない。 ・コンストラクタチェイン:サブクラスをコンパイルすると、サブクラスのコンストラクタにはスーパークラスのコンストラクタを呼び出すsuper();が追加される。 コンストラクタチェイン(例) public class B extends A{ B(){ this(4); System.out.print("3"); } B(int b){ super(); //←コンパイルして追加されたスーパークラスAの呼び出し System.out.print(b); } ◆final ・finalで修飾された変数は定数であり、変更できない。 ・変更するためには、コンストラクタで値を初期化する必要がある。 コンストラクタで定数を初期化する public class Sample{ private final int num; //定数の宣言 pibliv Sample() {} //コンストラクタ内で定数を初期化していないのでコンパイルエラー public Sample(int num){ this.num = num; //コンストラクタ内で定数を初期化している } } ◆可変長引数 ・自由に数を変更できる引数のこと。 ・引数の型の直後にピリオド3つを付けて宣言する。 void sample(int... num){ //do something } ・注意点:  ①異なる型はまとめられない。  ②可変長引数以外の型も引数に含める場合、可変長引数は最後に書く。 第7章 クラスの継承、インターフェース、抽象クラス ◆インターフェース 【基本的なルール】 ・インターフェースを継承した具象クラスは、抽象メソッドを具象メソッドに書き換えなければいけない。 ・インターフェースで実装する抽象メソッドは、自動的にアクセス修飾子がpublicになる。 ・インスタンス化できない ・インターフェースを継承したクラスでは、メソッドをオーバーライドするときにpublic以外のアクセス修飾子へ変更できない。>>p.522 ・定数フィールド(static, final)しか定義できない。 【その他のルール】 ・defaultメソッド:defaultメソッドを使うと、メソッドの処理内容が書ける。インターフェースを継承したクラスは、オーバーライトせずにメソッドを使える。 ・抽象メソッドをオーバーライドした抽象メソッドでは、戻り値の型を変更できる。(この場合の型は、スーパーメソッドで指定されている型のサブクラス型でなければならない。=「共変戻り値」)>>p.523 ・デフォルトメソッドとstaticメソッドとして実装を定義できる。 ◆抽象クラス ・抽象メソッドと具象メソッドが書ける。 ・抽象メソッドには、必ずabstractをつけ、中身を書いてはいけない。 ・抽象メソッドはオーバーライドされる前提なので、private以外のアクセス修飾子であれば自由に設定できる。 ・抽象クラスを継承したサブクラスでは、抽象メソッドをオーバーライドしなければならない。 ・インスタンス化できない。 ・抽象クラスを継承した抽象クラスを定義できる。 ◆オーバーライド ・オーバーライドのルール:  ■シグネチャ(メソッド名、引数の型、引数の数、順番)が同じであること。  ■戻り値型は同じ型か、サブクラス型であること。  ■アクセス修飾子は同じか、より緩くするここと。  ■throwsで宣言する例外は、スローする例外と同じ型かサブクラス型であること。 ◆ポリモーフィズム ・同じ名前のメソッドなのにあるクラスでの振る舞いと別のクラスの振る舞いで違う動きをする 第9章 API ◆Map ・キーとバリューの組み合わせで管理するコレクション。表のようなイメージ。 ・keyメソッド:キーを取り出す。 ・valueメソッド:バリューを取り出す。 ◆ArrayList ・スレッドセーフではない ◆compareメソッド ・2つの配列を辞書順に並べた時の、並び順を比較する。 ・2つの配列が等しい場合:0を返す。 ・第1引数が第2引数よりも辞書順で先の場合:-1を返す。 ・第1引数が第2引数よりも辞書順で後の場合:1を返す。 ◆Mathクラス ・Math.pow:累乗した値を取得する。例)Math.pow(2,3); → 2の3乗=8 ・Math.sqrt:引数の平方根をdouble型で取得する。例)Math.sqrt(16); →4.0(double型なので小数点まで表示される) ・Math.round:引数を小数点第一位で四捨五入し、整数にする。 ◆stringBuilderクラス ・デフォルトで16文字分のバッファを余分に持っている。  例:"abcde"という文字列を、stringBuilderクラスのcapacity()メソッドでキャパを調べると、16+5=21になる。 第10章 例外処理 ◆例外処理 try-catch文の書き方 try { 例外が発生する可能性のある処理 } catch (例外の型 引数) { 例外が発生した場合の処理(例外が発生しなければ行われない処理) } finally { 例外の有無に関わらず、最後に必ず実行される処理 } ・例外処理が実行される順番:①closeメソッド → ②catchブロック → ③finallyブロック ・catch文を複数書くときの注意:先にサブクラスの例外を書く。スーパークラスを先に書くと、コンパイルエラー。 ・throw:意図的に例外を発生させたいときに、処理内容に書く。 ・throws:検査例外をスローするメソッドに書かなければいけない。mainメソッドでcatchする場合は、このthrowsを用いて宣言するか、Exceptionをcatchの引数に(catch(Exception))記入しなければならない。 ・複数のtry-catchがネストしている場合、スローされた例外を受け取るのは、その例外に対応した最も近いcatchブロック。 ・tryブロックで宣言した変数の有効範囲は、tryブロック内のみ。 ・非検査例外:RuntimeExceptionとそのサブクラス。throwsで宣言しなくて良い。 ・return文が最後にあったら、finally→catchの順に出力される。 ・戻り値が入る箱は1つしかないので、最後にreturn文で返された戻り値が、最終的な値になる。 ◆主な例外の種類 ・NullPointerException:配列に値が入ってない時に呼び出そうとするとスローされる。system.out.print()で表示される分にはスローされない。 ・IndexOutOfBoundsException:ArrayIndexOutOfBoundsExceptionやStringIndexOutOfBoundsExceptionのスーパークラス。 ・ClassCastException:あるオブジェクトを継承関係にないクラスにキャストしようとしたことを示すためにスローされる。 ・ConcurrentModificationException:オブジェクトの並行変更を検出したメソッドによって、そのような変更が許可されていない場合にスローされる。例えば、あるスレッドで配列の値を順に取り出しているときに、もう1つのスレッドで配列の値を変更しようとしたときなど。 ・UnsupportedOperationException:変更不可なコレクションに、変更が行われた場合。(list.ofで作られた配列が変更されたときなど) 第11章 モジュールシステム 【コマンドの種類】 java --describe-module jmod describe:モジュールの設定情報を調べる jdeps --list-deps java --show-module-resolution:クラスやモジュールの依存関係を調べる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む