- 投稿日:2020-04-21T23:48:10+09:00
Javaソースコードの基本構造
- 投稿日:2020-04-21T21:56:58+09:00
【初心者でもできる‼】Javadocの書き方
1.事前知識
または
事前知識として、上記リンクの内容が必要です。
2.Javadocとは
Javadoc
とは、サン・マイクロシステムズが開発したコンピュータソフトで、JavaのソースコードからHTML形式のAPI仕様書
を生成するものである。- Javadocコメントは
/**
と*/
の間にコメントを記述
する。3.Javadocの書き方とドキュメント作成
Sample.java/** * @author 作成者名 * @version x.x.x */ /** * Javadocの解説用メインクラス */ public class Sample { /** * mainメソッド * @param args 使用しない */ public static void main(String[] args) { // 文字列の表示 System.out.println("JavaDocサンプル"); } }
- 上記の文をコピーして、文字コードは
S-JIS
を指定し、ファイル名をSample.java
で保存し、コマンドプロンプトでjavadoc -d html Sample.java
を実行します。↓↓$ javadoc -d html Sample.java
- 結果は以下のようになります。
- ブラウザが起動し、
Sample.html
が表示されます。
- 画像のように表示されれば成功です。
4.関連
- 投稿日:2020-04-21T21:37:57+09:00
基本情報処理技術者試験の勉強について
就職活動でSier に内定をもらって、配属アンケートでの希望通りにSEになった。
親会社の意向で基本情報処理技術者試験を受験することが義務となった。
「合格」ではなく「受験」が義務らしい。でももともと取る気だったからちょうどいい。
勉強の方針や日記、現状などについて書いていきたいと思う。
[目標]
・基本情報処理技術者試験の令和2年秋試験に合格する
→「午前問題」:60点以上
→「午後問題」:60点以上[現状]
・「基本情報技術者試験ドットコム」(https://www.fe-siken.com/) の過去問道場
→「午前問題」の得点率
→ランダムに80問解いたとき:75〜80%程度
→別ページの模擬試験や過去問を解いたとき:65%程度→「午後問題」の得点率
→問1 :情報セキュリティ :60%(必須問題)
→問2 :ソフトウェア :30%
→問3 :データベース :ー
→問4 :ネットワーク :50%
→問5 :ソフトウェア設計 :40%
→問6 :プロジェクトマネジメント :ー
→問7 :経営戦略・企業と法務 :50%
→問8 :データ構造及びアルゴリズム:50%(必須問題)
→問9 :C言語 :ー
→問10:COBOL :ー
→問11:java :40%
→問12:アセンブラ :ー
→問13:表計算 :ー[やるべきこと]
・データベースの問題を解いてみる
・プロジェクトマネジメントの問題を解いてみる
・問9〜13はjava を解く
・経営戦略・企業と法務と解く
・ソフトウェアを解く言語は令和2年春からpython が登場したらしいが、コロナウイルスの影響で中止になった。過去問が少なすぎるのでpython はやめておこう。以前、独学を進めたjava で行こうと思う。
データベースの問題はSQL のコツを掴んでしまえば得点が上がりそう。
プロジェクトマネジメントもデータベース同様、解いたことがないのでやってみよう。得意になるかもしれない。
経営戦略・企業と法務は、用語とその計算式さえ覚えてしまえば解いていけると思う。
ソフトウェアは、スレッドを用いた並列処理の問題なので、実際にプログラムで書いて勉強してみればつかみやすいと思う。
当面のやることは、このへんかなあ。でも申込み(7月中旬)もまだまだ先なんだよなあ。
- 投稿日:2020-04-21T20:45:09+09:00
【わかりやすく解説‼】Javaのインスタンスの使い方
1.事前知識
または
事前知識として、上記リンクの内容が必要です。
2.インスタンスとは
- クラスをもとに生成された実体(オブジェクト)を
インスタンス
という。- クラスをもとにオブジェクトを生成することを
インスタンス化
という。インスタンス
は、他クラスの変数やメソッドを呼び出す(使用する)際にクラスからnew演算子
を使用することで生成できる。- インスタンスの生成時は
コンストラクタ
が呼び出される。3.コンストラクタとは
コンストラクタ
とは、クラスのインスタンス生成時に実行されるメソッドで、主にそのクラスのメンバ変数を初期化するときに使用される。- コンストラクタの記述がない場合は
デフォルトコンストラクタ
が呼び出される。- デフォルトコンストラクタとは、
引数なしで中身が空(処理がない)
のコンストラクタである。4.基本的な書き方
基本的な書き方public class メインクラス名{ public static void main(String[] args) { // インスタンスの生成 クラス名 変数名 = new クラス名(); } } class クラス名{ // コンストラクタ(インスタンス生成時に実行される) クラス名(){ 初期化処理など } }
- 基本的なインスタンスの生成は上記のように記述します。
5.記述例
Test.java// インスタンスとコンストラクタのテストクラス public class Test { // mainメソッド public static void main(String[] args) { // インスタンス(オブジェクト)を生成(コンストラクタ呼び出し) Hello hello = new Hello(); } } // インスタンスのテストクラス class Hello { // コンストラクタ Hello() { //Helloの表示 System.out.print("Hello"); } }6.特殊な記述例
Test1.java// コンストラクタ内でのインスタンス生成 public class Test1 { // mainメソッド public static void main(String[] args) { // インスタンス(オブジェクト)を生成(コンストラクタ呼び出し) new Hello1(); } } // コンストラクタのテストクラス class Hello1 { // コンストラクタ Hello1() { // インスタンス(オブジェクト)を生成 InstanceHello ih = new InstanceHello(); // インスタンスのメソッドの呼出し ih.showHello(); } } // インスタンスのテストクラス class InstanceHello { // Hello表示メソッド void showHello(){ System.out.print("Hello"); } }Test2.java// 変数に格納しないインスタンス public class Test2 { // mainメソッド public static void main(String[] args) { // インスタンス(オブジェクト)を生成 & メソッド呼び出し new Hello2().showHello(); } } // インスタンスのテストクラス class Hello2 { // Hello表示メソッド void showHello(){ // Helloの表示 System.out.print("Hello"); } }7.関連
- 投稿日:2020-04-21T17:09:51+09:00
bucket4j を用いた Rate-Limiting
https://github.com/vladimir-bukhtoyarov/bucket4j
bucket4j は Java 向けの Rate-Limiting ライブラリとしては著名なものになり、さまざまな OSS、ライブラリでも用いられている。
内部的にはトラフィックシェーピングなどで広く用いられる Token Bucket と呼ばれるアルゴリズムを用いている。https://en.wikipedia.org/wiki/Token_bucket
Spring Boot Integration
3rd Party 製ではあるが Spring Boot Starter についても提供されている。
https://github.com/MarcGiffing/bucket4j-spring-boot-starter
この記事では bucket4j-spring-boot-starter をベースに簡単なアプリケーションを動作させ、実際にどのように動作するか検証を行った。
bucket4j-spring-boot-starter-examples
https://github.com/MarcGiffing/bucket4j-spring-boot-starter-examples
bucket4j-spring-boot-starter の作者が、動作確認用の examples プロジェクトをいくつか公開しているため、この記事ではこちらをベースに動作を試みる。
いくつかある中で、 jCache(EHCache)を状態管理に用いた bucket4j-spring-boot-starter-example-ehcache が最も単体で動作をさせやすいので、こちらを用いる。
Configuration
上記の examples ファイルを checkout し、いくつか設定を変更する。
なお、今回は Java 11 上で動作をさせることを前提とする。pom.xml
オリジナルの bucket4j-spring-boot-starter から以下について手を加えた
bucket4j-spring-boot-starter 最新版の使用
<dependency> <groupId>com.giffing.bucket4j.spring.boot.starter</groupId> <artifactId>bucket4j-spring-boot-starter</artifactId> <version>0.2.0</version> </dependency>JAXB 対応
Java9以降では標準対応をしていないのでライブラリを追加
<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.0</version> </dependency>javax.activation 対応
Java9以降ではjavax.activationが標準対応していないのでライブラリを追加
<dependency> <groupId>com.sun.activation</groupId> <artifactId>javax.activation</artifactId> <version>1.2.0</version> </dependency>application.yml
filter-key-type の定義を削除
最新版では deprecated 扱いになっているため。以下の定義を削除。
設定パラメータの微調整。
対象となる URL などを変更した。
その結果、設定ファイルは以下のような形となる。management: endpoints: web: exposure: include: "*" prometheus: enabled: true spring: cache: jcache: config: classpath:ehcache.xml bucket4j: enabled: true filters: - cache-name: buckets filter-method: servlet filter-order: -10 url: /login metrics: tags: - key: USERNAME expression: "@securityService.username() != null ? @securityService.username() : 'anonym'" - key: URL expression: getRequestURI() rate-limits: - bandwidths: - capacity: 5 time: 1 unit: minutes
- Rete-Limiting : 有効
- 対象URL:/login
- tags : username, url (tagの単位で rate-limiting の計算を行う)
- 制限 : username x url ごとに1分あたり5回のリクエストを許可
- stateの管理 : jcache(ehcache)
Running
実際に curl コマンドで Rate-Limiting 対象の URL Endpoint にリクエストを送り、挙動を確認する。
同一ユーザーで1分間に5回以上リクエストをすると、429 が返却される。通常時(200)
< HTTP/1.1 200 < X-Rate-Limit-Remaining: 0 < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Cache-Control: no-cache, no-store, max-age=0, must-revalidate < Pragma: no-cache < Expires: 0 < X-Frame-Options: DENY (snip.)Rate-Limit 時 (429)
< HTTP/1.1 429 < X-Rate-Limit-Retry-After-Seconds: 60 < X-Content-Type-Options: nosniff < X-XSS-Protection: 1; mode=block < Cache-Control: no-cache, no-store, max-age=0, must-revalidate < Pragma: no-cacheo < Expires: 0- < X-Frame-Options: DENY (snip.)Monitoring
Spring Actuator 経由で設定状況、ならびに Rate-Limiting 処理のステータスについて確認をすることができる。
/actuator/bucket4j
{ "servlet": [ { "cacheName": "buckets", "filterMethod": "SERVLET", "strategy": "FIRST", "url": "/login", "filterOrder": -10, "rateLimits": [ { "filterKeyType": null, "executeCondition": null, "skipCondition": null, "expression": "1", "bandwidths": [ { "capacity": 5, "time": 1, "unit": "MINUTES", "fixedRefillInterval": 0, "fixedRefillIntervalUnit": "MINUTES" } ] } ], "metrics": { "enabled": true, "types": [ "CONSUMED_COUNTER", "REJECTED_COUNTER" ], "tags": [ { "key": "USERNAME", "expression": "@securityService.username() != null ? @securityService.username() : 'anonym'", "types": [ "CONSUMED_COUNTER", "REJECTED_COUNTER" ] }, { "key": "URL", "expression": "getRequestURI()", "types": [ "CONSUMED_COUNTER", "REJECTED_COUNTER" ] } ] }, "httpResponseBody": "{ \"message\": \"Too many requests!\" }" } ] }/actuator/prometheus
# HELP bucket4j_summary_consumed_total # TYPE bucket4j_summary_consumed_total counter bucket4j_summary_consumed_total{URL="/login",USERNAME="anonym",name="buckets",} 87.0 bucket4j_summary_consumed_total{URL="/login",USERNAME="admin",name="buckets",} 1.0 cache_removals{cache="buckets",cacheManager="cacheManager",name="buckets",} 0.0 cache_puts_total{cache="buckets",cacheManager="cacheManager",name="buckets",} 88.0 cache_evictions_total{cache="buckets",cacheManager="cacheManager",name="buckets",} 0.0 # HELP bucket4j_summary_rejected_total # TYPE bucket4j_summary_rejected_total counter bucket4j_summary_rejected_total{URL="/login",USERNAME="anonym",name="buckets",} 796.0 bucket4j_summary_rejected_total{URL="/login",USERNAME="admin",name="buckets",} 7.0 cache_gets_total{cache="buckets",cacheManager="cacheManager",name="buckets",result="miss",} 2.0 cache_gets_total{cache="buckets",cacheManager="cacheManager",name="buckets",result="hit",} 890.0 http_server_requests_seconds_count{exception="None",method="GET",status="200",uri="/actuator/bucket4j",} 1.0 http_server_requests_seconds_sum{exception="None",method="GET",status="200",uri="/actuator/bucket4j",} 0.049276574 http_server_requests_seconds_max{exception="None",method="GET",status="200",uri="/actuator/bucket4j",} 0.049276574Conclusion
bucket4j ならびに bucket4j-spring-boot-starter を用いると、かなり簡単に既存の Spring Boot アプリケーションに Rate-Limiting の機能を追加することができる。
本格的に Rate-Limiting の処理をシステム全体に適用する場合は、Ambassador Pattern や Side-Car Pattern を用いて API Gateway のミドルウェアを導入した方が望ましいと思います。
特定のアプリケーションの処理に個別に適用する程度であれば、bucket4j などを用いて簡易的に導入するというのも、一つの選択肢になると思います。
- 投稿日:2020-04-21T17:01:41+09:00
ローカルPCでAWS KMSを使った処理をテストする
KMSを使った処理をローカルPCでテストしたときのメモ。
必要なもの
- Java開発環境
- Docker
実行手順
1.local-kmsの準備
AWS SDKから呼び出すlocal-kmsをコンテナで起動する。
今回は暗号化と復号のテストをするので、暗号化で使用するKeyを設定する。
local-kmsのドキュメントそのままに、seed.yamlファイルを作成する。Keys: - Metadata: KeyId: bc436485-5092-42b8-92a3-0aa8b93536dc BackingKeys: - 5cdaead27fe7da2de47945d73cd6d79e36494e73802f3cd3869f1d2cb0b5d7a9 Aliases: - AliasName: alias/testing TargetKeyId: bc436485-5092-42b8-92a3-0aa8b93536dcyamlを作成したら、local-kmsを起動する。
docker run -p 8080:8080 \ --mount type=bind,source="$(pwd)"/init,target=/init \ nsmithuk/local-kms2.AWS SDKの準備
KMSClientインスタンスの生成時にEndpointConfigurationからlocal-kmsにアクセスするように指定する。
リージョンはなんでもいい。// EndpointにローカルKMSを指定.RegionはどこでもOK AwsClientBuilder.EndpointConfiguration endpointConfig = new AwsClientBuilder.EndpointConfiguration("http://localhost:8080/", "ap-northeast-1"); kmsClient = AWSKMSClientBuilder.standard().withEndpointConfiguration(endpointConfig).build();あとは生成したKMSClientから必要なメソッドを呼ぶ。
local-kmsはKMSと同じように暗号化と復号を実行してくれる。カスタマーキーの登録もできる。
AWSにデプロイしたり、KMSのセットアップしなくていいので楽です。ちなみに、呼び出されたlocal-kms側のLogはこんな感じ。アカウント、リージョンのところは適当っぽい。
Encryption called: arn:aws:kms:eu-west-2:111122223333:key/bc436485-5092-42b8-92a3-0aa8b93536dc参考
- 投稿日:2020-04-21T16:21:58+09:00
エラーログを読めるようになるまで
経緯
この前Twitterで、「お前は絶望的にプログラミングに向いてないから諦めて刺身にタンポポ乗せる仕事でもやってろ」という激熱タイトルの記事が回ってきたのですが、これ非常に面白かったのでお勧めです。この中に
エラーログが読めない大学生が多すぎる
という内容がありまして、自分も思い当たる節が多かったので、とりあえずSpigot Pluginに絞ってエラーログの読み方は解説しておこうと思いこの記事を突発的に書いています。エラーログを見てみよう
さて、まずはエラーログがないとお話になりません。解説のために意図的にNullPointerExceptionを発生させるPluginを作ってみました。
QiitaPlugin.javapackage com.github.siloneco.qiita; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.plugin.java.JavaPlugin; public class QiitaPlugin extends JavaPlugin implements Listener { @Override public void onEnable() { Bukkit.getPluginManager().registerEvents(this, this); Bukkit.getLogger().info(getName() + " enabled."); } @Override public void onDisable() { Bukkit.getLogger().info(getName() + " disabled."); } @EventHandler public void onJoin(PlayerJoinEvent e) { Player p = null; p.sendMessage("test"); } }このコードだと、誰かがサーバーに参加したときにnullにsendMessageをしてしまってエラーとなります。
そしてこれが実際に参加したときのエラーログlatest.log[14:23:24 ERROR]: Could not pass event PlayerJoinEvent to QiitaPlugin v1.0.0 org.bukkit.event.EventException: null at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:306) ~[spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at org.bukkit.plugin.RegisteredListener.callEvent(RegisteredListener.java:62) ~[spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at org.bukkit.plugin.SimplePluginManager.fireEvent(SimplePluginManager.java:500) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at org.bukkit.plugin.SimplePluginManager.callEvent(SimplePluginManager.java:485) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.PlayerList.onPlayerJoin(PlayerList.java:346) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.PlayerList.a(PlayerList.java:166) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.LoginListener.b(LoginListener.java:159) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.LoginListener.e(LoginListener.java:57) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.NetworkManager.a(NetworkManager.java:233) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.ServerConnection.c(ServerConnection.java:140) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.MinecraftServer.D(MinecraftServer.java:845) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.DedicatedServer.D(DedicatedServer.java:406) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.MinecraftServer.C(MinecraftServer.java:679) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at net.minecraft.server.v1_12_R1.MinecraftServer.run(MinecraftServer.java:577) [spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] at java.lang.Thread.run(Unknown Source) [?:1.8.0_231] Caused by: java.lang.NullPointerException at com.github.siloneco.qiita.QiitaPlugin.onJoin(QiitaPlugin.java:26) ~[?:?] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_231] at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_231] at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) ~[?:1.8.0_231] at java.lang.reflect.Method.invoke(Unknown Source) ~[?:1.8.0_231] at org.bukkit.plugin.java.JavaPluginLoader$1.execute(JavaPluginLoader.java:302) ~[spigot-1.12.2.jar:git-Spigot-79a30d7-acbc348] ... 14 more無事エラーログが生成されました。
エラーログの読み方
早速このログを読み解いていきましょう。ここでは分かりやすいように色をつけてみました。
実際に見ないといけない箇所は、濃い色がついている部分のみです。これだけ長いエラーログなのに読まなければならないところはこんなに少ないんですね。これを知らない人が多いのでエラーログが読めないという事です。手順はとても簡単ですが、まず自分が今作っているPluginのパッケージ名を覚えておかなければなりません。ここではcom.github.siloneco.qiita
です。1. 自分のパッケージ名が入っている行を探す
上の画像で言うと水色の部分です。自分の今作っているPluginのパッケージ名が入っています。at で始まる行はエラーの発生地点が書いてあるので、自分のPluginが使用しているパッケージ名のみ見ればよいです。基本的にはCaused byの直後にあることが多いです。
2. その行の右側の 括弧 を見る
上の画像で言うと緑色の
(QiitaPlugin.java:26)
の部分です。これはエラーの発生地点が(ファイル名:行数)
の形で記されています。この例の場合、QiitaPlugin.java
ファイルの26行目
でエラーが起こっているという事です。3. エラーの理由を見る
残りの緑色の部分に注目します。一番上の緑色(
Could not pass event PlayerJoinEvent to QiitaPlugin v1.0.0
)はここでは関係ないですが、たまにここにエラー理由が書いてあることがあるので一応緑色にしました。今回は単にQiitaPluginバージョン1.0.0のPlayerJoinEventが正常に処理できなかった
という報告が書いてあります。英語が読めない場合はGoogle翻訳を使ってください。理解できる程度の日本語で返ってきます。
次の緑色(Caused by: java.lang.NullPointerException
)を見てみます。これはもう読めば明らかですね。Caused by ~
は~によって起こる
という意味ですので、今回の場合はjava.lang.NullPointerException によってエラーが起こった
と理解することができます。もっと意訳すると、発生したエラーは java.lang.NullPointerException である
ということです。4. 整理しよう
頭の中で整理できない人は紙に書いてもメモ帳に書いても構いません。1~3で得た情報を整理してみましょう。
QiitaPlugin.javaの26行目でエラーが起こっている。 発生したエラーは java.lang.NullPointerException である。5. 発生したエラーはどういうものなのか調べる
もうすでに知っている場合は必要ありません。 6へ飛んでください。
今回発生したエラーは java.lang.NullPointerException です。これをGoogleにぶち込んで調べてみましょう。おそらく沢山の事例が出てくると思います。出ない場合は検索ワードに「解決方法」などを加えてあげると基本出てきます。それでも出ない場合は「fix」などを追加して海外の記事を読み漁りましょう。
色々な記事を読んでいると、どうやら NullPointerException というのは、nullに ドット(.) を繋げて実行した場合に出るエラー らしいですね。6. 以上を踏まえて、問題個所を見てみよう
では問題のコードを見てみましょう
QiitaPlugin.java-24行目p.sendMessage("test");なるほど、この行の場合ドットを繋げているのは
p
しかありません。ということはp
がnullである可能性が非常に高いですね。確認してみましょうQiitaPlugin.java-23行目-27行目@EventHandler public void onJoin(PlayerJoinEvent e) { Player p = null; p.sendMessage("test"); }やっぱり
p
にnullが代入されていました。e.getPlayer()
に変更して解決ですね。おまけ: エラーを直したら別のエラーが出た
よくある話です。1に戻って新しく出たエラーを解決しましょう。
それでも解決できない!
どうしても解決できないエラーがあるなら人に聞いた方が早いです。コミュニティーで聞くとか自分よりも腕がある人に聞くとか色々方法はあります。その時に気を付けて欲しいのは、
- 環境を明示する
- コードを提示する
- エラーログを提示する
- その他関係がありそうな細かい情報を記載する
これらの事に気を使って質問してください。エラーログだけ見て解決できない複雑なエラーは環境などが絡んでいる可能性が大きいです。例えばJavaのバージョンは何か、サーバーのバージョンは何か、何をしたときにエラーが出るのか、変更前は正常に動いていたのであればどのような変更をしたのかなど、エラーの解決には多くの情報が必要です。
何もしてないのに壊れましたみたいなのは止めてください()最後に
Qiita初投稿だったのですが、これが一人でも多くのPlugin開発者の役に立てれば幸いです。Pluginに関するお題や質問などをお題箱で受け付けていますので、気が向いたらポンポン入れてみてください。それでは。
変更履歴
4/21 - タイトル要らんかった
- 投稿日:2020-04-21T16:12:04+09:00
MyBatisのMapper XMLでJavaの列挙型(Enum)を使う方法
はじめに
MyBatisのMapper XMLにおいて、
ネット上に定数(static final)を参照するコードはあるものの、
列挙型(enum)を参照するコードはあまり見かけなかったので記事にしました。インストール
build.gradlecompile 'org.mybatis:mybatis:3.5.2'列挙型サンプル
SampleEnums.javapackage com.example; public class SampleEnums { public enum Color { RED("1"), GREEN("2"), BLUE("3"); private final String code; private Color(final String code) { this.code = code; } public String getCode() { return this.code; } } }Mapper XMLサンプル(コードを設定)
<select id="findByColorIsBlue" resultType="SampleDto"> SELECT * FROM SAMPLE_TABLE WHERE COLOR = '${@com.example.SampleEnums$Color@BLUE.getCode}' <!-- 生成されるSQL SELECT * FROM SAMPLE_TABLE WHERE COLOR = '2' --> </select>Mapper XMLサンプル2(名称を設定)
<select id="findByColorIsBlue" resultType="SampleDto"> SELECT * FROM SAMPLE_TABLE WHERE COLOR = '${@com.example.SampleEnums$Color@BLUE}' <!-- 生成されるSQL SELECT * FROM SAMPLE_TABLE WHERE COLOR = 'BLUE' --> </select>
- 投稿日:2020-04-21T14:57:21+09:00
同一のRadioGroupに入っているRadioButtonを画面上の自由な位置に配置したい
概要
Androidの標準APIで提供されている
RadioButton
とRadioGroup
。複数の
RadioButton
を1つのRadioGroup
の中に入れ子にすれば、その中で同時に一つだけが選択可能になる、とここまではいいのだが、RadioButton
は自由に配置することが許されない。なんでRadioButtonは自由に動かせない?
ドキュメントでもなんでも見ればわかるのだが、
android.widget.RadioGroup
はandroid.widget.LinearLayout
を継承しているため。LinearLayoutが何だかはみんな知ってるでしょう。
つまり横1列か縦1列にしか並べられないということ。なんでやねん!!
解決策
ということで、作っちゃいました。そういうクラス。
FreeRadioGroup.javaimport android.util.Log; import android.view.View; import android.widget.CompoundButton; import android.widget.RadioButton; import android.widget.RadioGroup; import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; public class FreeRadioGroup { private List<RadioButton> registeredRadioButtons = new ArrayList<>(); public FreeRadioGroup(){ } public void addChild(RadioButton rb){ if(registeredRadioButtons.contains(rb)){ Log.w("FreeRadioGroup", "RadioButton already registered. Abort registering"); }else { registeredRadioButtons.add(rb); rb.setOnCheckedChangeListener(OCL); } } public void removeChild(RadioButton rb){ if(!registeredRadioButtons.contains(rb)){ Log.w("FreeRadioGroup", "RadioButton is not registered. Abort unregistering"); }else { registeredRadioButtons.remove(rb); rb.setOnClickListener(null); } } public int getSelectedButtonIndex(){ for(int i=0,n=registeredRadioButtons.size();i<n;i++){ if(registeredRadioButtons.get(i).isChecked()){ return i; } } return -1; } @Nullable public RadioButton getSelectedButton(){ for(int i=0,n=registeredRadioButtons.size();i<n;i++){ RadioButton r = registeredRadioButtons.get(i); if(r.isChecked()){ return r; } } return null; } private final CompoundButton.OnCheckedChangeListener OCL = new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean b) { if(registeredRadioButtons.contains((RadioButton)compoundButton) && b){ for(RadioButton r : registeredRadioButtons){ if(r.equals((RadioButton)compoundButton)){ continue; } r.setChecked(false); } } } }; }とりあえず丸コピして使っていただいて構いません。
addChild(RadioButton rb)
で登録したいRadioButton
を登録すると、登録したものの中で同時に1つだけが選択される仕組み。逆になんでこれが公式に実装されてないのか疑問に思うレベルで必要だと思うのだが……。
現状の問題点
・ボタンの状態が変わった時に呼ばれるリスナーを他に登録しようとすると上書きになってしまうため使えない
自分が使う分には今のところこれで困ってないので、時間があるときに考えておきます。あるいは何かいい方法があったらコメントで教えて頂ければと。OnClickの方は普通に使えるのでそれだけで済むのであれば問題ないはず。
- 投稿日:2020-04-21T14:34:39+09:00
Java基礎
char型の計算はunicodeになる
https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_0000-0FFFint型の小数は切り捨て
計算後にdouble型に代入するとx.0になるEclipseの標準出力で文字化けした場合
ウインドウ > 設定 > 一般 > ワークスペース
テキスト・ファイル・エンコードをUTF-8にする(ソースコードの文字コード)
デフォルトのutf-8このファイルも1行追加する
eclipse.ini
-Dfile.encoding=utf-8
- 投稿日:2020-04-21T14:07:56+09:00
JUnitのアサーションとMatcher API
概要
JUnitにおいて、値の検証を担う要素である
「アサーション」、「Matcher API」について理解する。アサーションとは
アサーション(Assertion)とは、直訳で断言、断定という意味を持つ。
JUnitは大きく分けると、
- 前提条件、想定結果の定義
- テスト対象の実行
- 想定結果と実行結果の比較検証
上記3つのステップでユニットテストを行う。
JUnitにおけるアサーションとは、
3つ目の「想定結果と実行結果の比較検証」を行うための仕組みである。実際のコードでは、以下のように使用する。
アサーションの例.javaint expected = 1; int actual = calc(); assertThat(actual, is(expected));このように記述することで、テストを
Assert that actual is expected.(実測値は想定値だと断言する。)
のように自然言語(話し言葉の英語)で書くことができる。Matcher APIとは
先ほど、例に挙げたソースコードに登場した
is(expected)
というメソッドは、
Matcher APIのCoreMatcherクラスに実装されているstaticメソッドである。
Matcher APIとは、値の比較方法を提供するアサーションに対し、
値の検証方法を提供する仕組みであり、
以前はHamcrestというJUnitの拡張ライブラリのみに実装されていたが、
現在ではJUnit本体にも組み込まれている。CoreMatchersが提供するMatcher
is 「actual は expected である。」
値が同値であることを検証する。
assertThat(actual, is(expected));not 「actual は expected でない。」
Matcherが返す検証結果を反転させる。is以外のMatcherにも適用可能である。
assertThat(actual, is(not(expected)));nullValue 「actual は null である。」
isメソッドは型パラメータを持つメソッドなのでnull値を渡すことができない。
よって、null検証を行いたい場合はnullValueメソッドを使用する。assertThat(actual, is(nullValue())); /* 以下の書き方も可能だが、 より自然言語に近い書き方をするため上記の書き方をするのが通例 */ assertThat(actual, nullValue());notNullValue 「actual は null でない。」
値がnullでないことを検証する。
assertThat(actual, is(notNullValue())); // 以下と同義 assertThat(actual, is(not(nullValue()));sameInstance 「actual と expected は 同一のインスタンス である。」
値が同値であることを検証する。
isメソッドがequals
による比較なのに対して、sameInstanceは==
による比較を行う。assertThat(actual, is(sameInstance(expected)));instanceOf 「actual は expected と互換性のある値である。」
値が想定する値を継承していることを検証する。
assertThat(actual, is(instanceOf(Serializable.class)));JUnitMatchersが提供するMatcher
hasItem 「actual は expected を含んでいる。」
コレクションクラスのように反復可能な(イテレーターを持っている)実測値に
想定する値が含まれていることを検証する。List<String> actual = getList(); assertThat(actual, hasItem(expected));hasItems 「actual は expected を含んでいる。」
検証する内容はhasItemと同じだが、想定値に可変長引数をとる点が異なる。
順序に関係なく、想定する値すべてが含まれているかを検証するのに有効なMatcher。List<String> actual = getList(); assertThat(actual, hasItem(expected1, expected2));補足:hamcrest-libraryが提供するMatcher
その他、hamcrest-libraryという拡張ライブラリは
コレクション、数値、テキストなど汎用的な検証に使用できるMatcherを提供している。
他にも様々な拡張ライブラリがあるため、次に紹介するカスタムMatcherの導入の前には、
すでに提供されているMatcherがないか探してみるのが吉。カスタムMatcher
Matcherは、独自の検証を行うカスタムMatcherというものを作ることができる。
ここでは、以下の要件を満たすMatcherを独自に実装する。・Dateクラスを比較検証する時、年、月、日までを比較対象とする
・想定値と実測値を以下のフォーマットで出力する
フォーマット:is "想定値(yyyy/mm/dd)" but actual is "実測値(yyyy/mm/dd)"以下は、上記要件を満たしたMatcherの実装とその呼び出し処理。
DateTest.javaimport static jp.sample.matcher.IsDate.*; ~省略~ assertThat(new Date(), is(dateOf(2020, 4, 12)));IsDate.java// 1. Matcherクラスの宣言 public class IsDate extends BaseMatcher<Date> { // 3. 想定値を保持する仕組みの実装 private final int yyyy; private final int mm; private final int dd; Object actual; // 3. 想定値を保持する仕組みの実装 IsDate(int yyyy, int mm, int dd) { this.yyyy = yyyy; this.mm = mm; this.dd = dd; } // 4. 検証処理の実装 @Override public boolean matches(Object actual) { this.actual = actual; if (!(actual instanceof Date)) { return false; } Calendar cal = Calendar.getInstance(); if (yyyy != cal.get(Calendar.YEAR)) { return false; } if (mm != cal.get(Calendar.MONTH)) { return false; } if (dd != cal.get(Calendar.DATE)) { return false; } return true; } // 5. エラーメッセージの実装 @Override public void describeTo(Description desc) { desc.appendValue(String.format("%d/%02d/%02d", yyyy, mm, dd)); if (actual != null) { desc.appendText(" but actual is "); desc.appendValue( new SimpleDateFormat("yyyy/MM/dd").format((Date) actual)); } } // 2. 検証メソッドの実装 public static Matcher<Date> dateOf(int yyyy, int mm, int dd) { return new IsDate(yyyy, mm, dd); } }1. Matcherクラスの宣言
カスタムMatcherの実装クラスとして、org.hamcrest.Matcherインターフェースを
インプリメントするが、Matcherインターフェースの直接のインプリメントは非推奨である。
そこで、Matcherインターフェースをインプリメントしている
org.hamcrest.BaseMatcherを継承する形で実装を行う。BaseMatcherには、実測値を型パラメータとして持たせる。
/* public class [任意のクラス名] extends BaseMatcher<[実測値の型]> { } */ public class IsDate extends BaseMatcher<Date> { }2. 検証メソッドの実装
assertThatメソッドの第2引数に渡すメソッドを実装する。
第2引数には想定値が渡されるため、Matcherクラスのコンストラクタを定義して
Matcherクラスを想定値で初期化する。
想定値を保持する仕組みは「3. 想定値を保持する仕組みの実装」参照。/* public static Matcher<[実測値の型]> [任意のメソッド名]([初期化に必要な値]) { return [Matcherクラスのコンストラクタ]; } */ public static Matcher<Date> dateOf(int yyyy, int mm, int dd) { return new IsDate(yyyy, mm, dd); }3. 想定値を保持する仕組みの実装
値の検証には、実測値と想定値が必要となる。
そこで、Matcherクラスではそれらをメンバとして保持する仕組みを実装する。
※想定値はコンストラクタでセットするが、実測値はassertThatメソッドの中で
matchesメソッドが呼ばれる時に渡されてくるのでコンストラクタではセットしない。// 想定値を保持するメンバ private final int yyyy; private final int mm; private final int dd; // 実測値を保持するメンバ Object actual; // 想定値をセットするコンストラクタ IsDate(int yyyy, int mm, int dd) { this.yyyy = yyyy; this.mm = mm; this.dd = dd; }4. 検証処理の実装
assertThatメソッドが実行されると、処理の過程で
Matcherインターフェースに定義されているmatchesメソッドが呼ばれる。
matchesメソッドで戻り値がfalseだとテスト失敗、trueだとテスト成功と判断される。
実測値を引数に取るため、Matcherクラスの保持している想定値と検証を行い、
想定通りの検証結果ならtrue、異なる場合はfalseを返却するように実装する。@Override public boolean matches(Object actual) { // 渡された実測値をMatcherクラスのメンバとして保持する this.actual = actual; // 型チェック(お約束的に書く) if (!(actual instanceof Date)) { return false; } // 以下は独自実装のため、目的に合わせた実装をする Calendar cal = Calendar.getInstance(); if (yyyy != cal.get(Calendar.YEAR)) { return false; } if (mm != cal.get(Calendar.MONTH)) { return false; } if (dd != cal.get(Calendar.DATE)) { return false; } return true; }5. エラーメッセージの実装
matchesメソッド同様、Matcherインターフェースに定義されているメソッド。
テスト結果が失敗だった時のみ呼ばれ、ここではエラーメッセージの定義を行う。@Override public void describeTo(Description desc) { // 想定値の出力 desc.appendValue(String.format("%d/%02d/%02d", yyyy, mm, dd)); // 実測値の出力 if (actual != null) { desc.appendText(" but actual is "); desc.appendValue( new SimpleDateFormat("yyyy/MM/dd").format((Date) actual)); } }コンソールへの出力結果java.lang.AssertionError: Expected: is "2011/02/10" but actual is "2012/03/08" got: <Thu Mar 08 23:02:49 JST 2012> エラーメッセージの雛形 [エラークラス名] Expected: is [describeToメソッドで定義したメッセージ] got: [実測値のtoString出力結果]appendValueメソッド
// 引数に文字列を渡した場合 "2012/03/08" (ダブルコーテーションで囲まれた値) // 引数にオブジェクトを渡した場合 <Thu Mar 08 23:02:49 JST 2012> (渡したオブジェクトのtoString出力結果)appendTextメソッド
but actual is (ダブルコーテーションで囲まれない)参考文献
この記事は以下の情報を参考にして執筆しました。
- 投稿日:2020-04-21T09:40:47+09:00
Java初心者脱却ブートキャンプ その1 Javaクラスの構成と書き方
新入社員でJavaを勉強し始めたみなさんに対し、
不定期で更新していきたいと思います。今回のテーマは「クラスの書き方」です。
クラスの基本形
まずは、クラスの基本形です。例として「人」を表すクラスを考えてみましょう。
public class Person { /** 氏名 */ private String name; /** 年齢 */ private int age; /** * コンストラクタ * @param name 氏名 * @param age 年齢 */ public Person(String name, int age) { this.name = name; this.age = age; } /** * 年齢の取得 */ public int getAge() { return this.age; } /** * ひとつ歳をとる */ public void countUpAge() { this.age++; } /** * 自己紹介 */ public void introduction() { System.out.println("こんにちは、私は" + this.name + ", " + this.age + "歳です。"); } }クラスを構成する要素
今回紹介したクラスに限らず、
クラスには大きく分けて3つのセクションに分けて記載を行います。public クラス名 { フィールド コンストラクタ メソッド }フィールド
Personクラスでは、以下の部分を指します。
/** 氏名 */ private String name; /** 年齢 */ private int age;フィールドは、そのクラス内ならどこでも使用できる変数です。
つまり、同じクラス内であれば、違うメソッドからでも同じ変数を参照することができるようになります。コンストラクタ
Personクラスでは以下の部分を指します。
/** * コンストラクタ * @param name 氏名 * @param age 年齢 */ public Person(String name, int age) { this.name = name; this.age = age; }ちなみに、このPersonクラスを初期化する場合には、以下のようなコードになります。
Person person = new Person("太郎", 10);このように、コンストラクタは開始時に特定の処理を実行するために使用します。
オブジェクトの初期化を行う際には、必ずコンストラクタが呼ばれますので、
フィールド値の設定などに利用されることが多いです。メソッド
Personクラスでは以下の部分を指します。
/** * 年齢の取得 */ public int getAge() { return this.age; } /** * ひとつ歳をとる */ public void countUpAge() { this.age++; } /** * 自己紹介 */ public void introduction() { System.out.println("こんにちは、私は" + this.name + ", " + this.age + "歳です。"); }メソッドには実行する処理を記載します。
メソッドのキホン
メソッドは、一つの処理をまとめるために利用します。
全ての処理をひとつにまとめることができますが、
手順ごとに別に分けた方が見る人もわかりやすいですよね。メソッドには名前をつけることができますので、
用途や取り扱う対象の変数などによってうまく分けていくことが大事です。基本的な書き方
メソッドの基本形は以下の通りになります。
アクセス修飾子 戻り値の型 メソッド名(引数1, 引数2, ...) { 処理 return 戻り値; }数学の時間に、「関数」という単語を聞いた記憶はありますでしょうか。
f(x) = x + 1これをJavaのメソッドに直すと、以下のような記載になります。
public void func(x) { return x + 1; }どちらも、「引数 x に 1 を加算して返却する」ものです。
実は、「メソッド」と「関数」はとても近い存在で、ほぼ同じものを指します。
例えば、C言語では以下の記載を関数と呼んでいます。int func(x) { return x + 1; }Javaをはじめとするオブジェクト思考の言語では、クラス内に記載された関数の記載をメソッドと呼んでいます。
関数とメソッドの厳密な違いについては、また別な機会に解説したいと思います。
アクセス修飾子
アクセス修飾子とは、そのフィールドやメソッドがどの範囲でアクセスできるかを定義しており、
publicやprivateで指定しています。publicを付与するとクラス外からもアクセス可能となり、
prvateとすると、そのクラス内からしかアクセスできなくなります。戻り値
例として、以下の場合にはint(数値)を戻り値とするgetAgeというメソッドを定義しています。
public int getAge() { return this.age; }この戻り値の型を指定することで、このメソッドの呼び元は、
「このメソッドはint型を返却するんだな」と認識することができます。// このようにpersonの年齢を取得できる int himAge = person.getAge(); // これは型が一致せずエラーになります String himAge = person.getAge();戻り値がない場合
戻り値がない場合には、戻り値の部分に「void」という文字を設定します。
voidは「空・何もない」ということを表します。戻り値にvoidを指定した場合には、何も値を返却しないためreturnの記載が不要となります。
/** * ひとつ歳をとる */ public void countUpAge() { this.age++; // ここにあるべき「return」を省略することができる。 }引数とオーバーロード
さて、今度は「氏名を設定するメソッド」を足してみましょう。
名前には姓と名がありますし、ミドルネームがある場合もあります。
それぞれに対応した氏名設定のメソッドを作ってみましょう。/** * 姓名対応のメソッド * @param lastName 姓 * @param firstName 名 */ public void setName(String lastName, String firstName) { this.name = lastName + " " + firstName; } /** * ミドルネーム対応のメソッド * @param lastName 姓 * @param firstName 名 * @param middleName ミドルネーム */ public void setName(String lastName, String firstName, String middleName) { this.name = firstName + "・" + middleName + "・" + lastName; }さて、これをそれぞれ実行してみましょう。
person.setName("佐藤", "太郎"); // -> 「佐藤 太郎」 person.setName("佐藤", "クリスティーナ", "太郎"); // -> 「太郎・クリスティーナ・佐藤」このように、同じ「setName」という名前のメソッドながら、違う処理を行うことができます。
これを、「メソッドのオーバーロード」と呼びます。
オーバーロードは、同じ目的でありながら複数の引数をもつ場合に活用することができます。
さいごに
「クラスの中に色々とあってよく分からない」という方も結構いらっしゃると思います。
ですが、実は覚えるべきルールはそんなに多くありません。
今回紹介した3つの要素に分けて考えるだけでも、ソースコードがより読みやすくなります。
いきなり全部を読み取ろうとせず、
少しずつ分離して考えていくことが理解への第一歩ではないでしょうか。
- 投稿日:2020-04-21T08:58:40+09:00
eclipse all in one インストール
Eclipseのインストールメモ。
ダウンロード
ここで好きなバージョンを選択(今回はEclipse 2020を想定)
なんやかんや入ってるFull Editionを選択
インストール
pleiades-2020-03-java-win-64bit-jre_20200322.zip
ダウンロードしたzipを解凍していい感じに配置。私のいつもの構成はこんな感じ
D:/ └ pleiades/ ├ eclipse/ ├ java/ ├ tomcat/ └ workspace/
- 解凍したらできるpleiades-2020-03-java-win-64bit-jre_20200322フォルダの中身だけ使う
- C:はすぐいっぱいになるのでD:に入れる(これは端末状況によるかな)
起動
eclipse.exeで起動。
ワークスペースの場所はこだわりがなければこのまま「起動」。