- 投稿日:2020-10-10T23:14:50+09:00
難読化されたメソッド名を特定する
Androidアプリを難読化して動かすと、例外が発生することがあります。
スタックトレースを見ても難読化により、どのメソッドで例外が発生したのかわかりません。
そんなとき、例外発生したメソッドを特定する方法です。難読化
Androidでは、セキュリティやサイズ縮小を目的に以下の設定を行うことがあります。
build.gradleminifyEnabled true shrinkResources trueこの設定で動かしてみると、例外が発生することがあります。
難読化で例外発生して困ること
以下の実装があるとします。
Calc.javapackage com.example.myapplication; public class Calc { public int divide(int param1, int param2) { return param1 / param2; } }MainActivity.javapackage com.example.myapplication; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Calc calc = new Calc(); int result = calc.divide(10, 0); System.out.println(result); } }これを実行すると、ゼロ除算で例外が発生します。
スタックトレースCaused by: java.lang.ArithmeticException: divide by zero at b.a.a.a.a(:5) at com.example.myapplication.MainActivity.onCreate(:13) at android.app.Activity.performCreate(Activity.java:7009) at android.app.Activity.performCreate(Activity.java:7000) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2731)難読化により、例外発生したメソッド名が
b.a.a.a.a
となり、どこで例外発生したかわかりません。難読化された文字列からメソッドを特定する
難読化前後のマッピングを見れるファイルがあります。
Androidのプロジェクトフォルダに移動し、検索欄で「mapping」と検索してみてください。
検索結果に「mapping.txt」が表示されます。このファイルを開きます。
例外発生したメソッド名
b.a.a.a.a
は、パッケージ名.クラス名.メソッド名で表現されています。
パッケージ名とクラス名(b.a.a.a
)でmapping.txtを検索します。以下該当箇所がヒットします。mapping.txtcom.example.myapplication.Calc -> b.a.a.a: 3:3:void <init>() -> <init> 5:5:int divide(int,int) -> aマッピングを見ると、例外発生したメソッド名(
a
)がdivide
であることがわかります。
- 投稿日:2020-10-10T22:23:40+09:00
SpringBoot入門
【業務で使用することになり勉強を始めました。全くの素人であるし間違っていることが多いです。随時更新。】
その1「Springbootでindex.html表示」
・MVCモデルを使っている。
・STSで実施。
・パッケージエクスプローラのspringスタータープロジェクト作成。
・依存関係でSpringBootDevToolsとspringwebとThymeleafを選択
・src/main/javaにパッケージが作られデフォルトでapplication.javaが作られる。
application.javaにはmainが入っているので、実行はここから
・controllerはパッケージに置く
・viewはsrc/main/resources/templateに置く。まずはcontroller
こいつはコントローラだよってことで@Controllerを書く
コンテキストルート(http://localhost:0808/) に接続したいというリクエスト対して @RequestMappingというアノテーションを使用して応える。@Controller public class ClassName { @RequestMapping(value = "/") }解説:
@controllerでコントローラだよ
@RequestMapping(value = "スラッシュはコンテキストルートを表す")@RequestMappingは、valueのパスとリクエストが一致した場合にお仕事を始める。
お仕事とはメソッドを動かすこと。
今のままだとメソッドがないので
メソッド追加!!@Controller public class ClassName { @RequestMapping(value = "/") public String respons() { return "index.html"; } }解説:
@RequestMapping(value = "/")の後のrespons(名前はなんでもいい)メソッドが動くpublic String void respons() { return "index.html"; }returnをindex.htmlとすることで、ブラウザにindex.htmlが表示される。
もちろん、ここをmain.htmlなんかにすればtemplate配下のmain.htmlが表示される。ちなみにindex.htmlは省略できる。
public String respons() { return "index"; }その2「Tymeleafを使ってみる」
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1 th:text="${res}"></h1> </body> </html>解説:
htmlタグはコピペしました。
これを記述することでh1タグにあるthオプションを使えるようになる。
"${res}"の中身がブラウザで表示される
「res」の中身はjavaで書く次はjava
package com.samle.spring; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class Sample { @RequestMapping(value="/") public ModelAndView sample() { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", "hello world"); return mod; } }その1と違うところはModelAndViewという奴
sampleメソッドではこのmodを返すことになる。つまり、modに詰めた値がブラウザに表示される。
まずどこにmodを返すのか?
返す場所を記述する
今回はsample.htmlなのでmod.setViewName("sample");次は二つのことを同時におこなう
1.まだmodの中は空なので値を詰める。
2.詰められた値をsample.htmlのどこに表示するのかを決めるmod.addObject("res", "hello world");引数1個目はの"res"はhtmlであった、"$res"
2個目は表示する文字が入る。上手くいけばHelloWorldが表示されるはず
その3「リクエストパラメータを使って動的な表示」
次はsubmitを使って表示を変える。
そろそろ複雑になってくるので、順を追って見ていきます。1.URL欄ににコンテキストルートを入力。
sample.javaでリクエストを受け取る
@Controller public class Sample { @RequestMapping(value="/") public String index() { return "sample"; }その1と同じ方法でsample.htmlを呼び出す
2.htmlが表示
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form action="/req"> <p>姓</p><input type="text" name="firstName"><br> <p>名前</p><input type="text" name="lastName"> <button type="submit">送信</button> </form> <h1 th:text="${res}"></h1> </body> </html>この段階ではth:text="${res}"の中身がないので表示はされません。
3.textに文字を入れて、submitする
formタグのactionが/reqとなっているのでjavaの方で設定して、入力を受け取る
4.submitされた値を受け取る
@RequestMapping(value="/req") public ModelAndView respons(@RequestParam("firstName") String first, @RequestParam("lastName") String last) { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", "フルネーム" + first + " " + last + "さん"); return mod; }受け取る時は@RequestMapping(value="/req)とすることで受け取ることができます。
あとはその2でやった内容と同じです。
見事フルネームを出すことができれば成功!
package com.samle.spring; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @Controller public class Sample { @RequestMapping(value="/") public String index() { return "sample"; } @RequestMapping(value="/req") public ModelAndView respons(@RequestParam("firstName") String first, @RequestParam("lastName") String last) { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", "フルネーム" + first + " " + last + "さん"); return mod; } }<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form action="/req"> <p>姓</p><input type="text" name="firstName"><br> <p>名前</p><input type="text" name="lastName"> <button type="submit">送信</button> </form> <h1 th:text="${res}"></h1> </body> </html>sampleクラス内に@RequesrMappingを2つ書くことで出し分け可能。
その4「javaの引数をひとつに」
このままだと、inputの数が30あったら引数も30になってしまう。
そんな時に使うのは@ModelAttribute
以下のように使うpackage com.samle.spring; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class Sample { @RequestMapping(value="/") public String index() { return "sample"; } /* こっから新しい内容 */ @RequestMapping(value="/req") public ModelAndView respons(@ModelAttribute Param p) { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", p); return mod; } public static class Param{ private String firstName; private String lastName; public String getFirstName(){ return firstName; } public String getLastName(){ return lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } } }解説:
@RequestMapping(value="/req") public ModelAndView respons(@ModelAttribute Param p) { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", p); return mod; }modelAttributeを引数の中に書くと、modelAttribute後の名前に等しいオブジェクトをNewする。
上記のコードであれば、paramクラスのpインスタンスがnewされるのと同時に
リクエストパレメータの値をsetする
あとはpインスタンスをmodに詰める中身を呼び出すには、"${res.name}"と書けばいい
次はhtml<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form action="/req"> <p>姓</p><input type="text" name="firstName"><br> <p>名前</p><input type="text" name="lastName"> <button type="submit">送信</button> </form> <div th:if="${res}"> <h1 th:text=" 'あなたは' + ${res.firstName} + ' ' + ${res.lastName} + 'ですね' "></h1> </div> </body> </html>その3との違いはここ
<div th:if="${res}"> <h1 th:text=" 'あなたは' + ${res.firstName} + ' ' + ${res.lastName} + 'ですね' "></h1> </div>th:ifはresの中身がnullならfalseとなってdivタグの中は読まれない。
なぜこうするのかというと
初めにコンテキストルートで表示される時,${res.firstName}の中身が空なので
エラーになってしまうから。
${res.firstName}を読ませないためにifで囲う必要がある
- 投稿日:2020-10-10T22:23:40+09:00
SpringBootを0〜100までやってみる。
【業務で使用することになり勉強を始めました。全くの素人であるし間違っていることが多いです。随時更新。】
その1「Springbootでindex.html表示」
・MVCモデルを使っている。
・STSで実施。
・パッケージエクスプローラのspringスタータープロジェクト作成。
・依存関係でSpringBootDevToolsとspringwebとThymeleafを選択
・src/main/javaにパッケージが作られデフォルトでapplication.javaが作られる。
application.javaにはmainが入っているので、実行はここから
・controllerはパッケージに置く
・viewはsrc/main/resources/templateに置く。まずはcontroller
こいつはコントローラだよってことで@Controllerを書く
コンテキストルート(http://localhost:0808/) に接続したいというリクエスト対して @RequestMappingというアノテーションを使用して応える。@Controller public class ClassName { @RequestMapping(value = "/") }解説:
@controllerでコントローラだよ
@RequestMapping(value = "スラッシュはコンテキストルートを表す")@RequestMappingは、valueのパスとリクエストが一致した場合にお仕事を始める。
お仕事とはメソッドを動かすこと。
今のままだとメソッドがないので
メソッド追加!!@Controller public class ClassName { @RequestMapping(value = "/") public String respons() { return "index.html"; } }解説:
@RequestMapping(value = "/")の後のrespons(名前はなんでもいい)メソッドが動くpublic String void respons() { return "index.html"; }returnをindex.htmlとすることで、ブラウザにindex.htmlが表示される。
もちろん、ここをmain.htmlなんかにすればtemplate配下のmain.htmlが表示される。ちなみにindex.htmlは省略できる。
public String respons() { return "index"; }その2「Tymeleafを使ってみる」
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1 th:text="${res}"></h1> </body> </html>解説:
htmlタグはコピペしました。
これを記述することでh1タグにあるthオプションを使えるようになる。
"${res}"の中身がブラウザで表示される
「res」の中身はjavaで書く次はjava
package com.samle.spring; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class Sample { @RequestMapping(value="/") public ModelAndView sample() { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", "hello world"); return mod; } }その1と違うところはModelAndViewという奴
sampleメソッドではこのmodを返すことになる。つまり、modに詰めた値がブラウザに表示される。
まずどこにmodを返すのか?
返す場所を記述する
今回はsample.htmlなのでmod.setViewName("sample");次は二つのことを同時におこなう
1.まだmodの中は空なので値を詰める。
2.詰められた値をsample.htmlのどこに表示するのかを決めるmod.addObject("res", "hello world");引数1個目はの"res"はhtmlであった、"$res"
2個目は表示する文字が入る。上手くいけばHelloWorldが表示されるはず
その3「リクエストパラメータを使って動的な表示」
次はsubmitを使って表示を変える。
そろそろ複雑になってくるので、順を追って見ていきます。1.URL欄ににコンテキストルートを入力。
sample.javaでリクエストを受け取る
@Controller public class Sample { @RequestMapping(value="/") public String index() { return "sample"; }その1と同じ方法でsample.htmlを呼び出す
2.htmlが表示
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form action="/req"> <p>姓</p><input type="text" name="firstName"><br> <p>名前</p><input type="text" name="lastName"> <button type="submit">送信</button> </form> <h1 th:text="${res}"></h1> </body> </html>この段階ではth:text="${res}"の中身がないので表示はされません。
3.textに文字を入れて、submitする
formタグのactionが/reqとなっているのでjavaの方で設定して、入力を受け取る
4.submitされた値を受け取る
@RequestMapping(value="/req") public ModelAndView respons(@RequestParam("firstName") String first, @RequestParam("lastName") String last) { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", "フルネーム" + first + " " + last + "さん"); return mod; }受け取る時は@RequestMapping(value="/req)とすることで受け取ることができます。
あとはその2でやった内容と同じです。
見事フルネームを出すことができれば成功!
package com.samle.spring; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @Controller public class Sample { @RequestMapping(value="/") public String index() { return "sample"; } @RequestMapping(value="/req") public ModelAndView respons(@RequestParam("firstName") String first, @RequestParam("lastName") String last) { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", "フルネーム" + first + " " + last + "さん"); return mod; } }<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form action="/req"> <p>姓</p><input type="text" name="firstName"><br> <p>名前</p><input type="text" name="lastName"> <button type="submit">送信</button> </form> <h1 th:text="${res}"></h1> </body> </html>sampleクラス内に@RequesrMappingを2つ書くことで出し分け可能。
その4「javaの引数をひとつに」
このままだと、inputの数が30あったら引数も30になってしまう。
そんな時に使うのは@ModelAttribute
以下のように使うpackage com.samle.spring; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class Sample { @RequestMapping(value="/") public String index() { return "sample"; } /* こっから新しい内容 */ @RequestMapping(value="/req") public ModelAndView respons(@ModelAttribute Param p) { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", p); return mod; } public static class Param{ private String firstName; private String lastName; public String getFirstName(){ return firstName; } public String getLastName(){ return lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } } }解説:
@RequestMapping(value="/req") public ModelAndView respons(@ModelAttribute Param p) { ModelAndView mod = new ModelAndView(); mod.setViewName("sample"); mod.addObject("res", p); return mod; }modelAttributeを引数の中に書くと、modelAttribute後の名前に等しいオブジェクトをNewする。
上記のコードであれば、paramクラスのpインスタンスがnewされるのと同時に
リクエストパレメータの値をsetする
あとはpインスタンスをmodに詰める中身を呼び出すには、"${res.name}"と書けばいい
次はhtml<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form action="/req"> <p>姓</p><input type="text" name="firstName"><br> <p>名前</p><input type="text" name="lastName"> <button type="submit">送信</button> </form> <div th:if="${res}"> <h1 th:text=" 'あなたは' + ${res.firstName} + ' ' + ${res.lastName} + 'ですね' "></h1> </div> </body> </html>その3との違いはここ
<div th:if="${res}"> <h1 th:text=" 'あなたは' + ${res.firstName} + ' ' + ${res.lastName} + 'ですね' "></h1> </div>th:ifはresの中身がnullならfalseとなってdivタグの中は読まれない。
なぜこうするのかというと
初めにコンテキストルートで表示される時${res.firstName}の中身が空なので
エラーになってしまうから。
${res.firstName}を読ませないためにifで囲う必要がある
- 投稿日:2020-10-10T21:24:45+09:00
文字列を一文字表示[メモ書き]
1文字ずつ表示させる。
demojava/demo8/Demo8.javapackage demojava.demo8; public class Demo8 { public static void main(String[] args) { String s = "TENET"; for(int i = 0 ; i < s.length();i++){ System.out.println( (i + 1) + "文字目 = " + s.charAt(i)); } for(int i = ( s.length() - 1); i >=0 ;i--){ System.out.println( (i + 1) + "文字目 = " + s.charAt(i)); } } }
- 投稿日:2020-10-10T21:24:45+09:00
文字列を一文字ずつ表示[メモ書き]
1文字ずつ表示させる。
demojava/demo8/Demo8.javapackage demojava.demo8; public class Demo8 { public static void main(String[] args) { String s = "TENET"; for(int i = 0 ; i < s.length();i++){ System.out.println( (i + 1) + "文字目 = " + s.charAt(i)); } for(int i = ( s.length() - 1); i >=0 ;i--){ System.out.println( (i + 1) + "文字目 = " + s.charAt(i)); } } }
- 投稿日:2020-10-10T20:55:22+09:00
java
随時追記
BufferReeader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = br.readLine()) String[] str = line.split(" "); // スペースで分割されるキャスト
int i = Integer.parseInt(str); //string->int
- 投稿日:2020-10-10T20:55:22+09:00
javaのメモ
随時追記
BufferReeader
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String line = br.readLine()) // スペースで分割される String[] str = line.split(" ");キャスト
//string->int int i = Integer.parseInt(str);Scanner
Scanner scanner = new Scanner(System.in);AtCoderでTLEにならないために
・breakは使わない
・ScannerよりBufferReader
・オーダがO(N²)を越えると確実にアウトっぽい
- 投稿日:2020-10-10T19:20:37+09:00
java浮動小数点の取り扱い参考書を読みながら
0.7 + 0.1 = 0.8だけどコンピューターに計算させると誤差が出る話。
どうして出るかは割愛(下記参照)。
https://www.php.net/manual/ja/language.types.float.php参考書ではBigDecimalを使用して対処している。
demojava/demo5/Demo5.javapackage demojava.demo5; import java.math.BigDecimal; public class Demo5 { public static void main(String [] args) { double test1 = 0.0; test1 = 0.7 + 0.1; System.out.println("test1 = " + test1); test2(); } public static void test2() { BigDecimal val1 = new BigDecimal("0.7"); BigDecimal val2 = new BigDecimal("0.1"); BigDecimal val = val1.add(val2); System.out.println("test2 = " + val); } }実行結果
- 投稿日:2020-10-10T19:20:37+09:00
java浮動小数点の取り扱い参考書を読みながら[メモ書き]
0.7 + 0.1 = 0.8だけどコンピューターに計算させると誤差が出る話。
どうして出るかは割愛(下記参照)。
https://www.php.net/manual/ja/language.types.float.php参考書ではBigDecimalを使用して対処している。
demojava/demo5/Demo5.javapackage demojava.demo5; import java.math.BigDecimal; public class Demo5 { public static void main(String [] args) { double test1 = 0.0; test1 = 0.7 + 0.1; System.out.println("test1 = " + test1); test2(); } public static void test2() { BigDecimal val1 = new BigDecimal("0.7"); BigDecimal val2 = new BigDecimal("0.1"); BigDecimal val = val1.add(val2); System.out.println("test2 = " + val); } }実行結果
- 投稿日:2020-10-10T18:33:13+09:00
Android Studioをつかって実行可能なjarをつくる
はじめに
Android Studioをつかって実行可能なjarをつくる方法を説明します。
Version
OSのバージョンは次の通りです。
Microsoft Windows 10 Home 10.0.18363 N/A ビルド 18363Android Studioのバージョンは次の通りです。
Android Studio 4.0 Build #AI-193.6911.18.40.6514223, built on May 21, 2020 Runtime version: 1.8.0_242-release-1644-b01 amd64 VM: OpenJDK 64-Bit Server VM by JetBrains s.r.ojavaのバージョンは次の通りです。
java -version java version "1.8.0_251" Java(TM) SE Runtime Environment (build 1.8.0_251-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)プロジェクトをつくる
次の手順でプロジェクトをつくります。
Android Studioを起動します。
Start a new Android Studio projectを選択します。
No Activityのプロジェクトを作成します。
Nameを"My Application"(default)とします。Languageを"Kotlin"とします。jarのモジュールをつくる
次の手順でプロジェクト内にjarのモジュールを追加します。
File - New - New Module ... を選択します。
Java or Kotlin Libraryを選択します。
Library nameを"lib"(default)とします。Class nameを"MyClass"とします。fun main
MyClass.kt を開きます。内容を次の処理に置き換えます。
MyClass.ktpackage com.example.lib fun main(args: Array<String>) {}ここまで実行した状態の画面は次の通りです。
build.gradle
libのbuild.gradleに次を追加します。
build.gradlejar { manifest { attributes("Main-Class": "com.example.lib.MyClassKt") } from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } } }jar taskを追加する
次の手順でjar taskを追加します。
View - Tool Windows - Gradle を選択します。Windowが表示されます。
My Application - Tasks - build - jar をダブルクリックします。ビルド開始します。ここまで実行した状態の画面は次の通りです。
次のパスにjarができます。
MyApplication\lib\build\libs\lib.jar
実行
command prompt を開きます。lib.jarがあるパスに移動します。
次のコマンドを実行します。
エラーなく実行できれば期待通り実行可能なjarができていることがわかります。java -jar .\lib.jar
- 投稿日:2020-10-10T18:05:14+09:00
業務用のシステムにおいてクラス設計(分割)をどのように考えるか(1)
まだ模索中のテーマですが、一旦の方針が固まって成果も得られてきたので書き下します。
知っている人には「当たり前だろ」となるので、記事をスキップしてください。若手〜中堅向けの内容です。
議論により、さらに磨いていけたらと思います。前提
- Javaアプリ(C#でもいい。オブジェクト指向の言語なら)
- BtoC、BtoBに関わらず、業務系のシステムを対象
- WebAPIなども含みます。 要はJavaバッチなどは対象外。
- プロジェクトチームのプログラミングスキルはそこまで高くない。
- 多重下請けの弊害で、コストは一人前なのにスキルの伴っていないSE(最早コーダー)が多数いるようなチームを想定していただければ。
考慮すべきポイント
- ビジネスの変化に(要件の変更・追加・削除)に柔軟に対応する構成にしたい。
- 今後も継続的に改修されていくが、メンバーが入れ替わる可能性が高いため、可読性を大事にしたい。
- プロジェクトメンバーのスキルに期待できないため、あまり難しい理論とかは入れたくない。(DDDとかは嫌だ)
こんなところでしょうか。要は、「簡単に拡張できるコードを簡単なコードで書きたい」ということですね。
結論
簡単に結論を書きます。
クラスの分割を役割に応じて縦(手続き的)と横(オブジェクト指向的)に分割して構成します。
大した話ではなくて、SpringBootでいうController/Service/Respositoryをどう分けましょうかというお話です。考え方1:業務は固有・仕組みは共通という前提
単純な話でして、システム開発は大抵の場合は顧客の業務をどうにかするためのものです。効率化であったり、ビジネス創出であったりですね。
で、この時に重要なのは、いろんなシステムをみた時に、共通する部分と共通しない部分はどこかということです。
それは見出しの通りで、顧客の業務そのものは共通することはありません。しかし、それを実現する仕組みはかなりの確率で共通しています。業務は固有
例えば、銀行の国際送金業務とコンビニの発注業務のシステムを考えた時、その業務の手順に共通はどれだけあるでしょうか?0じゃないでしょうか?このように、業態が異なればその業務は全く別のものになります。また、同じ国際送金業務でも、銀行がやる場合と証券会社がやる場合では違うでしょう。同じ銀行同士でもUFJさんと住友さんでは違う(業務が同じであれば、同じシステムを入れれば低コストで実現できる)でしょう。
つまり、基本的に業務部分は使い回すことはできないのです。仕組みは共通
全く異なる業務システムでも、その中で使う仕組みは大抵同じです。どういうことかというと、データを格納したければLDAPやSQLを使うし、電文を送信したければHTTPやSOAPなどを使用します。
つまり、DBに格納するであったり、リクエストをどこかに送信するなんていう仕組みの部分は共通(というか使い回し)が効くということがわかると思います。ある業界に絞っていえば、規格に則っているなんて場合もあるでしょう。例えば、国際送金にはスウィフト電文を使うなどが決まっていますので、国際送金業務という範囲では、やはり仕組みは共通と言えそうです。考え方2:業務に起因する重複コードは受け入れる
たまに、重複コードは一切許さず2回以上登場するコードはメソッド化して呼び出せなんていう過激派がいますが、(ここまでいかずともですが・・・)こんな考え方でクラスやメソッドを作っていくと、役割に応じた切り分けが不可能となり、ネーミング的にも機能分割的にも混沌としたコードが出来上がります。
また、最初は美しくExtendsなどで分割できていたとしても、改修を続けるうちに(最悪の例では次の改修時に)継承したクラスのごく一部だけ変えたいなんて言うケースはザラにあります。
こんなクラスを最初に作って美しく分割していたとしても、次の改修で一部異なるだけの処理のために、AbstractClass2を作って2重継承させます。地獄の始まりですね。
私は4重継承までみたことがあります。従来では重複のないコードが美しいとされていましたが、スキルの低いメンバーの入れ替わりが多い案件だと複雑なコードは却ってお荷物になります。そのコードを理解できない、メンテナンスできないという状態に陥るからです。
ではどうするか。業務と仕組みに分けて考えて、業務部分はそれぞれのクラスで実装し、仕組み部分は共通化します。
例え、多少の重複コードが発生しようが業務部分は個別に実装するのです。多少どころか大部分が共通でも改修時に楽になります。
こんな感じでmethod1は業務クラスで実装します。method1の内容は重複しているコードが書かれるかもしれませんが、業務が異なるものとして重複を受け入れましょう。そうすると、最初は同じだった業務を後から片方だけ変更したいと言うような要件が出ても最低限の変更をすることで修正ができます。
method2については、共通の仕組みとしてAbstractClassに配置しています。ちょっと長くなってきたので、続きは次の記事にしましょう。
- 投稿日:2020-10-10T16:23:21+09:00
【Java Silver】(例外処理)try-catch-finally文とtry-with-resource文について
try-catch-finally文とtry-with-resource文
どちらも例外処理を記述するための構文。
※例外処理とは
Javaの例外には、Error、Exception(検査例外)、RuntimeException(非検査例外)がある。
・Errorはプログラムでは対処できない問題(※コンパイルエラーとは異なり、あくまで例外)
・Exception(検査例外)は、例外発生時の処理を記述しないとコンパイルエラーが発生する問題
・RuntimeException(非検査例外)は、例外発生時の処理が任意の例外。
Exceptionのサブクラスなので、先にExceptionをキャッチするcatchブロックを書いたあとで、RuntimeExceptionをキャッチするcatchブロックを書くと、到達不能コードとなりコンパイルエラーが発生するメソッドに例外を無視させる・メソッドから例外を発生させる
・throws : メソッド内で該当の例外が発生した場合、自信のメソッド内でcatchするのではなく、呼ばれる側に例外を投げる処理。検査例外をスローするメソッドは、throwsでスローする可能性を宣言しなくてはならない。
例)
public test() throws IndexOutOfBounsException { //処理 }→処理の過程でIndexOutOfBounsException が発生しても、testメソッドは無視。testメソッドを実行した側にIndexOutOfBoundsExceptionが発生する。
・throw : メソッドから例外を発生させる
例)public test(){ throw new IndexOutOfWxception(); }→testメソッドを実行すると、testメソッドにIndexOutOfWxception例外が発生する
try-catch-finally文
・tryブロック:例外が発生する可能性がある処理
・catchブロック:例外が発生した時の処理
・finallyブロック:どのような場合でも一番最後に実行する処理【try-catch-finally文の例】
※return処理は、finally文の実行後に行われる
public class Main { public static void main(String[] args){ System.out.println(test(null)); //finallyブロックの処理 //引数がnullです //と表示される System.out.println(test(10)); //引数の値は10 //finallyブロックの処理 //引数は有効でした //と表示される } public static String test(Object obj){ try{ System.out.println("引数の値は" + obj.toString()); }catch(NullPointerException e){ return "引数がnullです"; }finally{ System.out.println("finallyブロックの処理"); } return "引数は有効でした"; } }try-with-resource文
例外が発生した時に、自動的にリソースのcloseメソッドを呼び出して、リソースを解放する構文
・処理の実行順
①closeメソッドでリソースを解放する
②catchブロックの例外処理を行う
③finallyブロックの処理を行う※例外処理よりも先にリソースの解放を行う
【try-with-resource文の例】
(Java Silver黒本より)public class Main { public static void main(String[] args){ try(Sample s = new Sample()){ throw new Exception(); //Exceptionを発生させる }catch(Exception e){ System.out.println("A"); }finally{ System.out.println("B"); } } } class Sample implements AutoCloseable{ public void close() throws Exception{ System.out.println("C"); //closeメソッド実行時にはCと表示される } }↓
「C A B」と表示される。
closeメソッド→catchブロック→fianllyブロックの順に実行されるため参考)
公式ドキュメント・AutoCloseableインターフェース閉じられるまで、リソース(ファイルやソケット・ハンドルなど)を保持できるオブジェクト。
AutoCloseableオブジェクトのclose()メソッドは、リソース指定ヘッダーでそのオブジェクトが宣言されているtry-with-resourcesブロックの終了時に自動的に呼び出されます。
この構築によって即時解放が確保され、それ以外の場合に発生する可能性のあるリソース不足例外およびエラーを回避できます。
- 投稿日:2020-10-10T15:19:15+09:00
【Java Silver】ラムダ式に関するポイントまとめ
ラムダ式とは
実装が必要なメソッドを1つだけ持つインターフェース型変数に、実行したいコードを代入する仕組み。
関数型インターフェースとは
関数型インターフェースは、実装が必要なメソッドを1つだけ持つインターフェースのこと。
入力に対して出力が1つに決まるから「関数型」と呼ばれる。関数型インターフェースの種類
インターフェースの種類 実装が必要なメソッド 特徴 Consumer<T> void accept(T) 引数を受け取って処理する。結果を戻さない、引数の消費者 Supplier<T> T get() 何も受け取らずに結果だけを戻す供給者 Predicate<T> boolean test(T) 引数を受け取って、それを評価する断定者 Function<T, R> R apply(T) 引数を受け取って、指定された型(R)の結果を戻す処理者 ※<T>とは:TはTypeの略。何型でも良い、ということを汎用化して表している。
関数型インターフェースの例
・java.util.ListインターフェースのforEachメソッドはConsumer型のラムダ式を受け取れる
参考)
公式ドキュメント・インターフェースList<E>
公式ドキュメント・forEach例)
public static void main(String[] args){ List<Integer> list = List.of(1,2,3); list.forEach((x) ->{System.out.println(x)}); //引数を受け取って処理し値はreturnしない }・java.lang.StringクラスのtoUpperCaseメソッドはFunction型のラムダ式を受け取れる
例)
public static void main(String[] args){ Function<String, String> func = (x) -> {return x.toUpperCase();}; //引数を受け取って、指定された型の結果を戻す System.out.println(func.apply("hello world")); }ラムダ式の書き方
<基本>
関数型インターフェースの型 変数名 = (引数) -> { //処理 return };
Function<String, String> func = (x) -> { return x.toUpperCase(); };<引数が1つの時、()の省略>
関数型インターフェースの型 変数名 = 引数 -> { //処理 return };
Function<String, String> func = x -> { return x.toUpperCase(); };<処理が1つの時、{}の省略>
関数型インターフェースの型 変数名 = 引数-> //処理;
※return文を書けない!!list.forEach( x -> System.out.println(x) );ラムダ式の注意点
【ラムダ式が持つ仮引数は、定数か実質的に定数】
・コンパイルエラー例(Java Silver黒本より)
void sample(){ int i = 0; Supplier<Integer> foo = () -> i; i++; //iは実質的に定数である必要 System.out.println(foo.get()); }【引数のデータ型に注意】
ラムダ式の変数宣言はデータ型の指定を省略できる。
代入しようとしている関数型インターフェースの型から引数の型を推論できるため。ラムダ式の変数を引数を、型を省略して書いたとしても、それは新しく定義された変数ということになる
→ラムダ式のスコープ内にある、既存の変数と重複しないか要確認!!・コンパイルエラー例
final String val = "Hello World"; Consumer<String> func = (val) -> System.out.println(val); //ラムダ式のvalと、ローカル変数のvalで名前の重複がおこりコンパイルエラー【スコープに注意】
ラムダ式のスコープは、ラムダ式が書かれているブロック全体におよぶ・コンパイルエラー例
public class Main{ public static void main(String[] args){ Srting val = "A"; //ラムダ式のスコープ内にあるローカル変数val Function f = (val) -> { //ラムダ式の変数val System.out.println(val); }; } }今後の課題
正直なところラムダ式についてまだまだ全然理解できていない・・・。
ラムダ式の仕組みについて、下記のサイトを理解したいと思う。また、実際にどんな時にラムダ式を使うのかいまいち理解できていない。
ラムダ式を受け取ることができるメソッドは何なのか?
再利用しない簡単な処理を記述する際にはいつでも利用できるのか?
関数型インターフェースのメソッドでないと使えないのか?関数型インターフェースのメソッドであるとどう見分けるのか?などなど、理解できていないことがたくさんある。
- 投稿日:2020-10-10T14:54:32+09:00
Reladomo の MT Loader(Multi-Threaded Matcher Loader)を使ってみる
はじめに
以前書いた記事 の続きで、今回は Reladomo の MT Loader を使ってみたお話です。
MT Loader(Multi-Threaded Matcher Loader)とは?
Multi-Threaded matcher Loader (MT Loader) は、別のソース(file, feed, other DB, etc.)からの変更を出力先のDBにマージするための機能です。ドキュメントを読んだ感じだと、Reladomo で扱うデータ量が多い場合に MT Loader が推奨されることが多いです。
MT Loader の動作イメージ
- Input と Output(Database) の2つのデータセットのどちらにも存在するデータを検出する
- どちらにも存在するデータはすべて
UPDATE
する(ただし、データに変更があった場合)- Input にはあるが Output(Database) にはないデータセットは
INSERT
する- Output(Database) にあるが Input にないものはすべてクローズする(実装により、
DELETE
するか有効期間を期限切れにするか)MT Loader のアーキテクチャ
読み取り、比較、書き込みに複数のスレッドを使用して、IOを分散できる仕組みになっています。MT Loader の特徴
- File-to-Database である必要はない。 Database-to-Database または Memory-to-Database などに簡単に変更できる。
- MatcherThread または SingleQueueExecutor のサブクラス化により、さまざまな要件に合わせたチューニングが可能。
- 書き込みは1つのテーブルへの書き込みのみ。複数テーブルへの書き込みも可能だが、トランザクションの保証はない。 大量データを扱うユースケースで強みを発揮するものなので、トランザクション管理が重要なケースでは使えない。(使い所には注意が必要)
- 設計で冪等性を担保することができる。
MT Loader を使ってみる
Spring Boot と組み合わせて API を書くことが多いので、その組み合わせで書いてみます。
実際のコードは https://github.com/amtkxa/spring-boot-reladomo-mt-loader にあります。テストコードを書く
事前準備
テストコード実行前にテスト用のDBにあらかじめテストデータの読み込みが完了している状態を作りたいと思います。テストデータの読み込みには
MithraTestResource.addTestDataToDatabase
を使うことを想定しているので、以下のようなファイルを用意します。customer_data.txtclass com.amtkxa.domain.entity.Customer customerId, name, country, businessDateFrom, businessDateTo, processingDateFrom, processingDateTo 1,"Liam","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000" 2,"Emma","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000" 3,"Noah","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000" 4,"Olivia","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000" 5,"William","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000" 6,"Ava","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000" 7,"James","USA","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000","2019-12-01 00:00:00.000","9999-12-01 23:59:00.000"H2 Database に事前にテストデータを取り込むための abstract なクラスも用意しました。
public abstract class AbstractReladomoTest { private static Logger log = LoggerFactory.getLogger(AbstractReladomoTest.class); private MithraTestResource mithraTestResource; protected abstract String[] getTestDataFilenames(); protected String getMithraConfigXmlFilename() { return "reladomo/config/TestReladomoRuntimeConfiguration.xml"; } @BeforeEach public void setUp() { log.info("Setting up reladomo on h2"); this.mithraTestResource = new MithraTestResource(this.getMithraConfigXmlFilename()); ConnectionManagerForTests connectionManager = ConnectionManagerForTests.getInstanceForDbName("testdb"); this.mithraTestResource.createSingleDatabase(connectionManager); for (String filename : this.getTestDataFilenames()) { this.mithraTestResource.addTestDataToDatabase(filename, connectionManager); } this.mithraTestResource.setUp(); } @AfterEach public void tearDown() { this.mithraTestResource.tearDown(); } }MT Loader をテストコードで動かす
MT Loader に以下のような入力データを与えて DB 更新をしてみます。
- customerId: 6 のユーザの country を更新
- 新しいユーザを1人分追加
- それ以外のユーザは入力データに存在しない(該当ユーザの有効期限が期限切れに更新されることを期待)
実際に書いたテストコードを以下のようになりました。
public class SingleQueueExecutorParallelLoadTest extends AbstractReladomoTest { private static int NUMBER_OF_THREADS = 2; private static int BATCH_SIZE = 5; private static int INSERT_THREADS = 3; @Override public String[] getTestDataFilenames() { return new String[] { "testdata/customer_data.txt" }; } private List<Customer> getInputData() { Timestamp businessDate = DateUtil.parse("2019-12-05 00:00:00"); CustomerList customerList = new CustomerList(); customerList.add(new Customer(6, "Ava", "JPN", businessDate)); customerList.add(new Customer(8, "Arthur", "USA", businessDate)); return customerList; } private CustomerList getDbRecords() { return CustomerFinder.findMany( CustomerFinder.all() .and(CustomerFinder.businessDate().equalsEdgePoint()) .and(CustomerFinder.processingDate().equalsInfinity()) ); } @Test public void testLoadDataParallel() { try { QueueExecutor queueExecutor = new SingleQueueExecutor( NUMBER_OF_THREADS, CustomerFinder.customerId().ascendingOrderBy(), BATCH_SIZE, CustomerFinder.getFinderInstance(), INSERT_THREADS ); MatcherThread<Customer> matcherThread = new MatcherThread<>( queueExecutor, new Extractor[] { CustomerFinder.customerId() } ); matcherThread.start(); // Database data load: Parallel DbLoadThread dbLoadThread = new DbLoadThread(getDbRecords(), null, matcherThread); dbLoadThread.start(); // Input data load: Parallel PlainInputThread inputThread = new PlainInputThread(new InputDataLoader(), matcherThread); inputThread.run(); matcherThread.waitTillDone(); // Assert checkResult(queueExecutor); } catch (Exception e) { throw new ReladomoMTLoaderException("Failed to load data. " + e.getMessage(), e.getCause()); } } private void checkResult(QueueExecutor queueExecutor) { // Whatever is in Output Set but not in Input Set will be closed out (terminated). CustomerList customerList = getDbRecords(); assertEquals(2, customerList.count()); // Whatever is in the intersection, will be updated (but only if something changed) Customer customer = CustomerFinder.findOne( CustomerFinder.customerId().eq(6) .and(CustomerFinder.businessDate().equalsEdgePoint()) .and(CustomerFinder.processingDate().equalsInfinity()) ); assertAll("Check updated customer data", () -> assertEquals("Ava", customer.getName()), () -> assertEquals("JPN", customer.getCountry()) // Updated from USD to JPN ); // Whatever in in Input Set but not in Output Set will be inserted Customer customer8 = CustomerFinder.findOne( CustomerFinder.customerId().eq(8) .and(CustomerFinder.businessDate().equalsEdgePoint()) .and(CustomerFinder.processingDate().equalsInfinity()) ); assertAll("Check inserted customer data", () -> assertEquals("Arthur", customer8.getName()), () -> assertEquals("USA", customer8.getCountry()) ); assertAll("Check the count of inserts, updates, terminates", () -> assertEquals(1, queueExecutor.getTotalInserts()), () -> assertEquals(1, queueExecutor.getTotalUpdates()), () -> assertEquals(6, queueExecutor.getTotalTerminates()) ); } private class InputDataLoader implements InputLoader { private boolean firstTime = true; @Override public List<? extends MithraTransactionalObject> getNextParsedObjectList() { return getInputData(); } @Override public boolean isFileParsingComplete() { if (firstTime) { firstTime = false; return false; } else { return true; } } } }MT Loader による更新処理が動く前は、DB は以下の状態になっています。
-- select * from customer where business_date_to = '9999-12-01 23:59:00.000' and processing_date_to = '9999-12-01 23:59:00.000'; +-------------+---------+---------+-------------------------+-------------------------+-------------------------+-------------------------+ | customer_id | name | country | business_date_from | business_date_to | processing_date_from | processing_date_to | +-------------+---------+---------+-------------------------+-------------------------+-------------------------+-------------------------+ | 1 | Liam | USA | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | | 2 | Emma | USA | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | | 3 | Noah | USA | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | | 4 | Olivia | USA | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | | 5 | William | USA | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | | 6 | Ava | USA | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | | 7 | James | USA | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | 2019-12-01 00:00:00.000 | 9999-12-01 23:59:00.000 | +-------------+---------+---------+-------------------------+-------------------------+-------------------------+-------------------------+MT Loader による更新処理が動いた後は、DB は以下の状態になり、期待通りの結果になりました。
-- select * from customer where business_date_to = '9999-12-01 23:59:00.000' and processing_date_to = '9999-12-01 23:59:00.000'; +-------------+--------+---------+-------------------------+-------------------------+-------------------------+-------------------------+ | customer_id | name | country | business_date_from | business_date_to | processing_date_from | processing_date_to | +-------------+--------+---------+-------------------------+-------------------------+-------------------------+-------------------------+ | 6 | Ava | JPN | 2020-10-10 14:24:52.387 | 9999-12-01 23:59:00.000 | 2020-10-10 14:24:53.250 | 9999-12-01 23:59:00.000 | | 8 | Arthur | USA | 2020-10-10 14:24:52.387 | 9999-12-01 23:59:00.000 | 2020-10-10 14:24:53.250 | 9999-12-01 23:59:00.000 | +-------------+--------+---------+-------------------------+-------------------------+-------------------------+-------------------------+さいごに
Reladomo はお仕事でよく利用していて、個人的に好きな技術のひとつなんですが、理解して使えるようになるところに到達するまでのハードルが少し高い印象があります。
例えば....... 比較的よく利用されるであろう Spring Boot に組み込んで動かすサンプルなどが kata になくて、使ってみようとすると DBConnectionManager ってどうやって組み込めばいいんだろ...とか考える必要があるので、恥ずかしながら、そもそも動くレベルのものを作るのにも結構苦労した思い出があります。
Reladomo 自体はとてもいい技術なのに、それついて書かれている記事ってあまり見つからないな......と感じていて、少しもったいないような気持ちになったので、今回は自分で調べたことをまとめて共有してみることにしました。
参考にしたもの
- 投稿日:2020-10-10T14:34:33+09:00
vscodeのjavaデバッグ時にパラメーターを渡す。[メモ書き]
vscodeのjavaデバッグ時にパラメーターを渡す。[メモ書き]
java言語に限らずだけども、vscodeのデバッグ設定ファイルに下記の項目を追加する(※args)。launch.json{ "type": "java", "name": "Debug (Launch) - Current File", "request": "launch", "mainClass": "${file}", "args": [ "あいうえお", "カキクケコ" ] },下記で試す
demojava/demo4/Parameter.javapackage demojava.demo4; public class Parameter { public static void main(String[] args) { System.out.println(args[0] + "::" + args[1] ); } }
- 投稿日:2020-10-10T13:48:34+09:00
javaを使用してスクレイピングを試す[メモ書き]
試したこと
Yahoo NewsのIT記事を取得。
メモ書き
jdk14.0.1
jsoup-1.13.1.jar
vscodeの環境設定が出来ていなかったので最初、jsoupが読み込まれず、エラー。
下記のファイルに設定を行って対応。settings.json"java.project.referencedLibraries": [ "lib/**/*.jar", "C:\\パス\\jsoup-1.13.1.jar" ],demojava/demo3/Web.javapackage demojava.demo3; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import java.io.IOException; public class Web { public static void main(String[] args) throws IOException { Document document = Jsoup.connect("https://news.yahoo.co.jp/topics/it").get(); Elements courses = document.select(".newsFeed_item_link"); for (Element course : courses) { System.out.println(course.attr("href")+ " [[::]] " + course.text()); } } }取得結果
- 投稿日:2020-10-10T13:35:58+09:00
【Java Silver】アクセス修飾子のポイントまとめ
アクセス修飾子
アクセス修飾子 アクセス可能 クラス図での記号 public どこからでも + protected 同一パッケージのクラスとサブクラス # 同一パッケージのクラス ~ private 現在のクラス - 許可されていない箇所からのアクセスはコンパイルエラーとなる
アクセス修飾子を考慮する必要があるもの
・クラス(別パッケージからアクセス可能か判断するため)
・メソッド
・コレクション(配列、ArrayList)
・フィールドアクセス修飾子の注意点
インターフェースとアクセス修飾子
インターフェースは継承される前提のものなので、インターフェース自体は暗黙的にpublicとみなされる
→ インターフェースに定義できるメソッド
・抽象メソッドの場合、暗黙的にpublic、abstractであると解釈される(public、abstractは省略可)
・実装を持つ場合、staticメソッドまたはdefaltメソッドである必要がある(アクセス修飾子に制限無し。privateも可)→ インターフェースに定義できるフィールド
・static変数(publicのみ)
・定数(publicのみ)
・インスタンス変数を定義することはできない【インターフェースを実装したクラスの具象メソッド】
インターフェースに定義された抽象メソッドのアクセス修飾子と、インターフェースを実装たクラスにある、抽象メソッドを実装した具象メソッドは同じアクセス修飾子である必要がある。
例) ※コンパイルエラー発生
interface A(){ void doSomething(); //インダーフェースの抽象メソッド。暗黙的にpublicと解釈される } B implements A(){ protected void doSomething(){ //コンパイルエラー。アクセス修飾子はインターフェースの抽象メソッドと同じ「public」である必要 //処理 } }抽象クラスとアクセス修飾子
抽象クラスは継承される前提のものなので、抽象クラス(abstractで修飾)は暗黙的にpublicとみなされる
→ 抽象クラスに定義できるメソッド
・抽象メソッドの場合、abstractでの修飾が必要。抽象メソッドはオーバーライドされる前提のメソッドなので、アクセス修飾子はprivate以外を設定できる
・具象メソッドの場合、アクセス修飾子は自由に設定できる→ 抽象クラスに定義できるフィールド
・設定に制限無し※メソッドをオーバーライドするときのアクセス修飾子は、元のメソッドと同じか制限を緩くする必要がある
例)
interface A { void doSomething(); } abstract class B implements A { public abstract void doSomething(); //インターフェースを抽象メソッドで実装可能。アクセス修飾子はインターフェースのメソッドと同じ } class C extends B { public void doSomething(){ //抽象メソッドをオーバーライド。 System.out.println("何かする"); }; } public class Main { public static void main(String[] args) { C example = new C(); example.doSomething(); } }
- 投稿日:2020-10-10T13:30:37+09:00
【JMH】JMH Gradle PluginでFAILURE: Build failed with an exception. A failure occurred while executing me.champeau.gradle.IsolatedRunnerと言われた場合の対処【Gradle】
JMH Gradle Plugin
(me.champeau.gradle.jmh
)で、以下のようなエラーが出る状況への対処法です。> Task :jmh FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':jmh'. > A failure occurred while executing me.champeau.gradle.IsolatedRunner > Error during execution of benchmarks自分の場合、ベンチマーク関連のディレクトリ構成が原因でした。
プラグインのREADMEに記載されている通り、
JMH Gradle Plugin
ではsrc/jmh
配下にベンチマークが有ることを想定しています。
このため、どこにベンチマークが有るかを設定するか、以下のような構成でsrc/jmh
配下にベンチマークを配置することで上手くいきました。
ここで、ベンチマーク本体は
build.gradle
のgroupId
に合わせたパッケージに配置する必要が有る点にも注意が必要です。おまけ
利用していた
build.gradle.kts
です。build.gradle.ktsplugins { kotlin("jvm") version "1.4.10" id("me.champeau.gradle.jmh") version "0.5.2" } group = "com.wrongwrong" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { implementation(kotlin("stdlib")) implementation(group = "org.openjdk.jmh", name = "jmh-core", version = "1.25.2") }
- 投稿日:2020-10-10T13:26:59+09:00
【JMH】JMH Gradle PluginでExecution failed for task ':jmhRunBytecodeGenerator'. java.lang.NullPointerException...となって実行が失敗する場合の対処【Gradle】
JMH Gradle Plugin
(me.champeau.gradle.jmh
)で、以下のようなスタックトレースが出て実行が失敗する場合への対処法です。FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':jmhRunBytecodeGenerator'. > A failure occurred while executing me.champeau.gradle.JmhBytecodeGeneratorRunnable > Generation of JMH bytecode failed with 1 errors: - Annotation generator had thrown the exception. java.lang.NullPointerException ...自分は以下の
build.gradle.kts
でプロジェクトをやっていましたが、group = "com.wrongwrong"
としているのに、ベンチマークがcom.wrongwrong
パッケージ配下に無かったことが原因でした。build.gradle.ktsplugins { kotlin("jvm") version "1.4.10" id("me.champeau.gradle.jmh") version "0.5.2" } group = "com.wrongwrong" version = "1.0-SNAPSHOT" repositories { mavenCentral() } dependencies { implementation(kotlin("stdlib")) implementation(group = "org.openjdk.jmh", name = "jmh-core", version = "1.25.2") }上手くいっていなかった時の配置は以下の通りでした。
以下のように配置することで上手くいきました。
以下のように、子パッケージに配置した場合も上手くいきます。
- 投稿日:2020-10-10T12:22:41+09:00
【Java Silver】配列の生成方法
配列の特徴
- 配列はnullを許容する
- 配列の要素数は、初期化された時に決まり、後から変更できない(要素の追加・削除不可。上書きは可能)
- 配列はオブジェクトクラスのサブクラスなので、オブジェクト型とみなすことができる
例)
example(Object[] val); //引数にオブジェクト型の配列を受けとる example(Object val) //引数に配列を受け取ることができる配列の生成方法
【注意するポイント】
- 変数宣言時に大かっこ[]があるか → []無し、または[]以外のカッコを使っていたらコンパイルエラー
- 変数宣言時に要素数を指定していないか → 変数宣言時に要素数を指定していたらコンパイルエラー
- インスタンスの生成時に要素数を指定しているか
- インスタンスの生成でnewを記述しているか
※) new省略し、初期化子( {} )だけで配列を作ることは可能!!
- 初期化子で指定した要素の数だけ、自動的にメモリ領域を確保する。
- 初期化子が使えるのは、変数宣言と同時の時のみ
- 変数宣言と初期化を別にするとコンパイルエラー
- 配列の初期化子は、左辺のデータ型から配列のデータ型を推論する【配列の初期化例】
int a [][] = {{1,2},{3,4}}; //newを使わず初期化子だけで配列を生成 int b [] = {}; //newを使わず初期化子だけで配列を生成。配列の中身は空だがエラーではない int[][]c = new int [][]{}; //newと初期化子、両方使うなら[][]の中は空である必要がある int[] d; d = new int[]{2,3}; //配列を格納する変数を作成後、インスタンスを生成して変数に代入 int [] e = new int[3]; //要素数3のインスタンス領域を確保【コンパイルエラーが発生する初期化例】
int[] a; a = int[2]; //インスタンス生成時にnewを記述しておらず、インスタンスを生成できていない int array = new int[2]; //配列型を示す[]が無い int array[2]; //変数宣言時に要素数を指定している。 int array1 = new int[2]; array1 = {1,2}; //初期化子が使えるのは変数宣言と同時の時だけ int[] array2= new int[3]{}; //無名配列を作るときは[]の中に要素数を記述しない【ポイント】
配列はインスタンスなので、生成しない限り使えない
配列を表す変数は、配列インスタンスへの参照を保持するための器であり、変数内に配列が作られるわけでは無い。配列インスタンスの方に、扱う要素数を記述する必要がある。
- 投稿日:2020-10-10T11:42:41+09:00
【Android / Java】表示Fragmentによりイベントを切り替える
はじめに
Android Studio(java)でアプリを開発していて、「表示しているフラグメントに応じて端末の戻るボタンを押した時の処理を切り替える」という実装をしたい場面があった。
そのときに学んだ内容を投稿します。学んだ内容
コードの一部を記載
ActivityからFragmentを表示
このアクティビティxmlファイルにフラグメントを表示させる
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <FrameLayout android:id="@+id/fl_activity_main" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>フラグメントの表示処理はこのように行う
MainActivity.java// 指定したフラグメントを表示するメソッド。引数のfragmentに表示したいフラグメントのインスタンスを渡す。 public void showFragment(Fragment fragment, FragmentManager fragmentManager) { fragmentManager .beginTransaction() .replace(R.id.fl_activity_main, fragment) .addToBackStack(null) .commit(); }表示しているFragmentを取得してイベント(処理)を行う
今回は例として、端末の戻るボタンを押したときに表示されているフラグメントが
FooFragment
またはBarFragment
であれば、アクティビティを終了させる、という実装にしている。MainActivity.java// onBackPressed()は端末の戻るボタンを押した時に呼ばれるメソッド @Override public void onBackPressed() { // ここで現在表示しているフラグメントを取得 // findFragmentById()の引数にはフラグメント表示のreplace()で表示先のレイアウト部品のid(コンテナ)を入れる。 Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fl_activity_main); // FooFragmentまたはBarFragmentを表示しているときにはアクティビティを終了(finish) if (fragment instanceof FooFragment || fragment instanceof BarFragment) { finish(); } super.onBackPressed(); }最後に
どのように表示しているフラグメントを判断させるか、意外と簡単に実装ができた。
今後も学習した内容を積極的にアウトプットしていきます。参考資料
ありがとうございました!!
- 投稿日:2020-10-10T11:32:09+09:00
Doma入門 - Criteria API チートシート
前提
Domaのバージョンは2.43.0です。
Criteria APIの概要についてはDoma入門を参照してください。
利用しているJavaのバージョンは8です。エンティティクラス
Employee
とDepartment
が定義済みとします。また、下記の変数が定義されているものとします。
Entityql entityql = new Entityql(config); Nativesql nativeSql = new NativeSql(config); Employee_ e = new Employee_(); Department_ d = new Department_();例示されるSQLは実際に生成されるものと異なる場合があります。
EntityqlとNativeSqlの使い分けの原則
Entityqlを使うとき
- 関連エンティティを取得したい
- 追加時に主キーを自動生成したい
- 更新や削除で楽観的排他制御をしたい
- 更新系処理でバッチ処理したい
- 更新系処理を
EntityListener
でフックしたい- 下記に述べるようなNativeSqlを使う理由が特にない
NativeSqlを使うとき
- Stream検索したい
- Collect検索したい
- 集約関数を使って集計したい(HAVINGやGROUP BYを使う必要がある)
- UNIONやUNION ALLをしたい
- 任意のカラムをタプルクラス(
Tuple2
など)で取得したい- 別テーブルの検索結果を使って追加したい
- 任意の条件を指定して更新や削除をしたい
- 主キーのないテーブルを扱いたい
- 検索でエンティティを取得する際、主キーが重複するエンティティを許容したい
検索
全件検索
List<Employee> list = entityql.from(e).fetch(); // select * from employee t0_1件検索
存在しなかったらnullを返す。
Employee employee = entityql.from(e).where(c -> c.eq(e.id, 1)).fetchOne(); // select * from employee t0_ where t0_.id = ?存在しなかったら
Optional.empty()
を返す。Optional<Employee> employee = entityql.from(e).where(c -> c.eq(e.id, 1)).fetchOptional(); // select * from employee t0_ where t0_.id = ?Stream検索
メモリを圧迫せずに大量データを1件づつ処理する。
String names = nativeSql.from(e).mapStream(stream -> stream.map(Employee::getName).collect(Collectors.joining(",")) ); // select * from employee t0_Collect検索
Stream検索のショートカット。
Map<Integer, List<Employee>> map = nativeSql.from(e).collect(Collectors.groupingBy(Employee::getDepartmentId)); // select * from employee t0_上記は以下のコードと同等。
Map<Integer, List<Employee>> map = nativeSql.from(e).mapStream(stream -> stream.collect(Collectors.groupingBy(Employee::getDepartmentId)) ); // select * from employee t0_射影
結果をタプルクラスとして返す。
List<Tuple2<String, Integer>> list = nativeSql.from(e).select(e.name, e.age).fetch(); // select t0_.name, t0_.age from employee t0_結果をエンティティクラスとして返す。主キーはSELECT句に必ず含まれエンティティにもセットされる。
List<Employee> list = entityql.from(e).selectTo(e, e.name, e.age).fetch(); // select t0_.id, t0_.name, t0_.age from employee t0_ソート
List<Employee> list = entityql.from(e).orderBy(c -> { c.asc(e.name); c.desc(e.age); }).fetch(); // select * from employee t0_ order by t0_.name asc, t0_.age desc重複行の除外
List<String> list = nativeSql.from(e).distinct().select(e.name).fetch(); // select distinct t0_.name from employee t0_Limit/Offset
List<Employee> list = entityql.from(e).limit(10).offset(3).fetch(); // select * from employee t0_ limit 10 offset 3悲観的ロック
List<Employee> list = entityql.from(e).forUpdate().fetch(); // select * from employee t0_ for update集約
集約関数としては、
org.seasar.doma.jdbc.criteria.expression.Expressions
に定義された、avg
、count
、countDistinct
、max
、min
、sum
が使える。Integer integer = nativeSql.from(e).select(Expressions.sum(e.age)).fetchOne(); // select sum(t0_.age) from employee t0_グループ単位の集計
List<Tuple2<Integer, Long>> list = nativeSql.from(e).groupBy(e.departmentId).select(e.departmentId, Expressions.count()).fetch(); // select t0_.department_id, count(*) from employee t0_ group by t0_.department_id
groupBy
メソッドを呼び出さない場合、select
メソッドに指定したプロパティからGROUP BY句に必要なカラムを推測し自動で付与する。したがって、次のコードは上記と同等のSQLを生成する。List<Tuple2<Integer, Long>> list = nativeSql.from(e).select(e.departmentId, Expressions.count()).fetch(); // select t0_.department_id, count(*) from employee t0_ group by t0_.department_idグループ単位の集計結果に対する絞り込み
// 従業員数が3人より多い部署について、部署ごとの従業員数を求める List<Tuple2<Long, String>> list = nativeSql .from(e) .innerJoin(d, on -> on.eq(e.departmentId, d.id)) .having(c -> c.gt(Expressions.count(), 3L)) .select(Expressions.count(), d.name) .fetch(); // select count(*), t1_.name from employee t0_ inner join department t1_ on (t0_.department_id = t1_.id) group by t1_.name having count(*) > 3結合
内部結合
内部結合のみを行う。
List<Employee> list = entityql.from(e).innerJoin(d, on -> on.eq(e.departmentId, d.id)).fetch(); // select t0_.* from employee t0_ inner join department t1_ on (t0_.department_id = t1_.id)内部結合し関連エンティティも取得する。
List<Employee> list = entityql.from(e).innerJoin(d, on -> on.eq(e.departmentId, d.id)).associate(e, d, (employee, department) { employee.setDepartment(department); department.getEmployees().add(employee); }).fetch(); // select * from employee t0_ inner join department t1_ on (t0_.department_id = t1_.id)外部結合
外部結合のみを行う。
List<Employee> list = entityql.from(e).leftJoin(d, on -> on.eq(e.departmentId, d.id)).fetch(); // select t0_.* from employee t0_ left outer join department t1_ on (t0_.department_id = t1_.id)外部結合し関連エンティティも取得する。
List<Employee> list = entityql.from(e). leftJoin(d, on -> on.eq(e.departmentId, d.id)).associate(e, d, (employee, department) { employee.setDepartment(department); department.getEmployees().add(employee); }).fetch(); // select * from employee t0_ left outer join department t1_ on (t0_.department_id = t1_.id)自己結合
同じメタモデルの異なるインスタンスを使えば同じテーブル同士で結合(自己結合)できる。
Employee_ m = new Employee(); List<Employee> list = entityql.from(e).leftJoin(m, on -> on.eq(e.managerId, m.id)).fetch(); // select t0_.* from employee t0_ left outer join employee t1_ on (t0_.manager_id = t1_.id)関連エンティティの取得もできる。
Employee_ m = new Employee(); List<Employee> list = entityql.from(e).leftJoin(m, on -> on.eq(e.managerId, m.id)).associate(e, m, (employee, manager) { employee.setManager(manager); }).fetch(); // select * from employee t0_ left outer join employee t1_ on (t0_.manager_id = t1_.id)UNION
List<Tuple2<Integer, String>> list = nativeSql .from(e) .select(e.id, e.name) .union(nativeSql.from(d).select(d.id, d.name)) .fetch(); // select t0_.id, t0_.name from employee t0_ union select t0_.id, t0_.name from department t0_ソートをするには対象のカラムをindexで指定する。indexは1から始まる。
List<Tuple2<Integer, String>> list = nativeSql .from(e) .select(e.id, e.name) .union(nativeSql.from(d).select(d.id, d.name)) .orderBy(c -> c.asc(2)) .fetch(); // (select t0_.id, t0_.name from employee t0_) union (select t0_.id, t0_.name from department t0_) order by 2 ascUNION ALLもできる。
List<Tuple2<Integer, String>> list = nativeSql .from(e) .select(e.id, e.name) .unionAll(nativeSql.from(d).select(d.id, d.name)) .fetch(); // select t0_.id, t0_.name from employee t0_ union all select t0_.id, t0_.name from department t0_追加
1件追加
Employee employee = ...; entityql.insert(e, employee).execute(); // insert into employee (id, name, age, version) values (?, ?, ?, ?)バッチ追加
List<Employee> employees = ...; entityql.insert(e, employees).execute(); // insert into employee (id, name, age, version) values (?, ?, ?, ?)検索結果を追加
同じデータ構造を持つ別テーブルに複数件を追加。
Department_ da = new Department_("DEPARTMENT_ARCHIVE"); nativeSql.insert(da).select(c -> c.from(d).where(cc -> cc.in(d.departmentId, Arrays.asList(1, 2)))).execute(); // insert into department_archive (id, name, version) select t0_.id, t0_.name, t0_.version from department t0_ where t0_.id in (1, 2)更新
1件更新
Employee employee = ...; entityql.update(e, employee).execute(); // update employee set name = ?, age = ?, version = ? + 1 where id = ? and version = ?バッチ更新
List<Employee> employees = ...; entityql.update(e, employees).execute(); // update employee set name = ?, age = ?, version = ? + 1 where id = ? and version = ?特定条件に合致する複数件を更新
nativeSql .update(e) .set(c -> c.value(e.departmentId, 3)) .where( c -> { c.(e.managerId, 3); c.lt(e.age, 30); }) .execute(); // update employee t0_ set department_id = ? where t0_.manager_id = ? and t0_.age < ?SQL上の演算結果で更新
nativeSql .update(e) .set(c -> { c.value(e.name, concat("[", concat(e.name, "]"))); c.value(e.age, Expressions.add(e.age, 1)); }) .where(c -> c.eq(e.id, 1)) .execute(); // update employee t0_ set name = concat(?, concat(t0_.name, ?)), age = (t0_.age + ?) where t0_.id = ?削除
1件削除
Employee employee = ...; entityql.delete(e, employee).execute(); // delete from employee where id = ? and version = ?バッチ削除
List<Employee> employees = ...; entityql.delete(e, employees).execute(); // delete from employee where id = ? and version = ?特定条件に合致する複数件を削除
nativeSql.delete(e).where(c -> c.ge(e.age, 50)).execute(); // delete from employee t0_ where t0_.age >= ?WHERE句に指定できる検索条件
比較演算
=
entityql.from(e).where(c -> c.eq(e.age, 20)).fetch(); // select * from employee t0_ where t0_.age = ?<>
entityql.from(e).where(c -> c.ne(e.age, 20)).fetch(); // select * from employee t0_ where t0_.age <> ?>
entityql.from(e).where(c -> c.gt(e.age, 20)).fetch(); // select * from employee t0_ where t0_.age > ?>=
entityql.from(e).where(c -> c.ge(e.age, 20)).fetch(); // select * from employee t0_ where t0_.age >= ?<
entityql.from(e).where(c -> c.lt(e.age, 20)).fetch(); // select * from employee t0_ where t0_.age < ?<=
entityql.from(e).where(c -> c.le(e.age, 20)).fetch(); // select * from employee t0_ where t0_.age <= ?IS NULL
entityql.from(e).where(c -> c.isNull(e.age)).fetch(); // select * from employee t0_ where t0_.age is nullIS NOT NULL
entityql.from(e).where(c -> c.isNotNull(e.age)).fetch(); // select * from employee t0_ where t0_.age is not null= または IS NULL
age
がnullでなければ = を生成。entityql.from(e).where(c -> c.eqOrIsNull(e.age, age)).fetch(); // select * from employee t0_ where t0_.age = ?
age
がnullならば IS NULLを生成。entityql.from(e).where(c -> c.eqOrIsNull(e.age, age)).fetch(); // select * from employee t0_ where t0_.age is null<> または IS NOT NULL
age
がnullでなければ <> を生成。entityql.from(e).where(c -> c.neOrIsNotNull(e.age, age)).fetch(); // select * from employee t0_ where t0_.age <> ?
age
がnullならば IS NOT NULLを生成。entityql.from(e).where(c -> c.neOrIsNotNull(e.age, age)).fetch(); // select * from employee t0_ where t0_.age is not nullLIKE
何の加工もしないLIKE述語。
entityql.from(e).where(c -> c.like(e.name, "A%")).fetch(); // select * from employee t0_ where t0_.name like ? // select * from employee t0_ where t0_.name like 'A%' (バインドされた値つきSQL)前方一致のためのLIKE述語。ワイルドカードはエスケープされる。
entityql.from(e).where(c -> c.like(e.name, "A%", LikeOption.prefix())).fetch(); // select * from employee t0_ where t0_.name like ? escape '$' // select * from employee t0_ where t0_.name like 'A$%%' escape '$' (バインドされた値つきSQL)中間一致のためのLIKE述語。ワイルドカードはエスケープされる。
entityql.from(e).where(c -> c.like(e.name, "A%", LikeOption.infix())).fetch(); // select * from employee t0_ where t0_.name like ? escape '$' // select * from employee t0_ where t0_.name like '%A$%%' escape '$' (バインドされた値つきSQL)後方一致のためのLIKE述語。ワイルドカードはエスケープされる。
entityql.from(e).where(c -> c.like(e.name, "A%", LikeOption.suffix())).fetch(); // select * from employee t0_ where t0_.name like ? escape '$' // select * from employee t0_ where t0_.name like '%A$%' escape '$' (バインドされた値つきSQL)NOT LIKE
何の加工もしないNOT LIKE述語。
entityql.from(e).where(c -> c.notLike(e.name, "A%")).fetch(); // select * from employee t0_ where t0_.name not like ? // select * from employee t0_ where t0_.name not like 'A%' (バインドされた値つきSQL)前方一致のためのNOT LIKE述語。ワイルドカードはエスケープされる。
entityql.from(e).where(c -> c.notLike(e.name, "A%", LikeOption.prefix())).fetch(); // select * from employee t0_ where t0_.name not like ? escape '$' // select * from employee t0_ where t0_.name not like 'A$%%' escape '$' (バインドされた値つきSQL)中間一致のためのNOT LIKE述語。ワイルドカードはエスケープされる。
entityql.from(e).where(c -> c.notLike(e.name, "A%", LikeOption.infix())).fetch(); // select * from employee t0_ where t0_.name not like ? escape '$' // select * from employee t0_ where t0_.name not like '%A$%%' escape '$' (バインドされた値つきSQL)後方一致のためのNOT LIKE述語。ワイルドカードはエスケープされる。
entityql.from(e).where(c -> c.notLike(e.name, "A%", LikeOption.suffix())).fetch(); // select * from employee t0_ where t0_.name not like ? escape '$' // select * from employee t0_ where t0_.name not like '%A$%' escape '$' (バインドされた値つきSQL)BETWEEN
entityql.from(e).where(c -> c.between(e.age, 20, 30)).fetch(); // select * from employee t0_ where t0_.age between ? and ?IN
シンプルなIN述語。
entityql.from(e).where(c -> c.in(e.age, Arrays.asList(10, 20))).fetch(); // select * from employee t0_ where t0_.age in (?, ?)タプルを使ったIN述語。
entityql.from(e).where(c -> c.in(new Tuple2(e.age, e.salary), Arrays.asList(new Tuple2(10, 1000), new Tuple2(20, 2000)))).fetch(); // select * from employee t0_ where (t0_.age, t0_.salary) in ((?, ?), (?, ?))サブクエリを使ったIN述語。
entityql.from(e).where(c -> c.in(e.departmentId, c.from(d).select(d.id))).fetch(); // select * from employee t0_ where t0_.department_id in (select t1_.id from department t1_)NOT IN
シンプルなNOT IN述語。
entityql.from(e).where(c -> c.notIn(e.age, Arrays.asList(10, 20))).fetch(); // select * from employee t0_ where t0_.age not in (?, ?)タプルを使ったNOT IN述語。
entityql.from(e).where(c -> c.notIn(new Tuple2(e.age, e.salary), Arrays.asList(new Tuple2(10, 1000), new Tuple2(20, 2000)))).fetch(); // select * from employee t0_ where (t0_.age, t0_.salary) not in ((?, ?), (?, ?))サブクエリを使ったNOT IN述語。
entityql.from(e).where(c -> c.notIn(e.departmentId, c.from(d).select(d.id))).fetch(); // select * from employee t0_ where t0_.department_id not in (select t1_.id from department t1_)EXISTS
entityql.from(e).where(c -> c.exists(c.from(d).where(c2 -> c2.eq(e.departmentId, d.id))).fetch(); // select * from employee t0_ where exists (select * from department t1_ where t0_.deparment_id = t1_.id)論理演算
AND
entityql.from(e).where(c -> { c.eq(e.age, 20); c.ge(e.salary, 100000); c.lt(e.salary, 200000); }).fetch(); // select * from employee t0_ where t0_.age = ? and t0_.salary >= ? and t0_.salary < ?OR
entityql.from(e).where(c -> { c.eq(e.age, 20); c.or(() -> { c.ge(e.salary, 100000); c.lt(e.salary, 200000); }); }).fetch(); // select * from employee t0_ where t0_.age = ? or (t0_.salary >= ? and t0_.salary < ?)NOT
entityql.from(e).where(c -> { c.eq(e.age, 20); c.not(() -> { c.ge(e.salary, 100000); c.lt(e.salary, 200000); }); }).fetch(); // select * from employee t0_ where t0_.age = ? and not (t0_.salary >= ? and t0_.salary < ?)カラムに関する式
リテラル
バインド変数を使わず、そのまま値をSQLに埋め込む。
org.seasar.doma.jdbc.criteria.expression.Expressions
のlitera
メソッドが受け入れる型のみをサポートしている。List<Employee> list = entityql.from(e).where(c -> c.eq(e.id, Expressions.literal(10))).fetch(); // select * from employee t0_ where t0_.id = 10算術演算
算術演算には、
org.seasar.doma.jdbc.criteria.expression.Expressions
に定義されたadd
、sub
、mul
、div
、mod
などが使える。List<String> list = nativeSql.from(e).select(Expressions.add(e.age, 10)).fetch(); // select (t0_.age + ?) from employee t0_文字列関数
文字列関数には、
org.seasar.doma.jdbc.criteria.expression.Expressions
に定義されたconcat
、lower
、upper
、trim
、ltrim
、rtrim
などが使える。List<String> list = nativeSql.from(e).select(Expressions.lower(e.employeeName)).fetch(); // select lower(t0_.name) from employee t0_CASE式
List<Tuple2<String, String>> list = nativeSql .from(e) .select( e.name, Expressions.when( c -> { c.lt(e.age, Expressions.literal(10), Expressions.literal("A")); c.lt(e.age, Expressions.literal(20), Expressions.literal("B")); c.lt(e.age, Expressions.literal(30), Expressions.literal("C")); }, Expressions.literal("D"))) .fetch(); // select t0_.name, case when t0_.age < 10 then 'A' when t0_.age < 20 then 'B' when t0_.age < 30 then 'C' else 'D' end from EMPLOYEE t0_
- 投稿日:2020-10-10T11:10:57+09:00
【Java Silver】Javaの初期化について
インスタンスの初期化
- コンストラクタで初期化処理を行う
- インスタンス変数は、インスタンス作成時に初期化される
※定数(final)もコンストラクタで初期化できる
クラスに定義された、初期化されていない定数は、コンストラクタで初期化できる例)
public class Main{ public static void main(String[] args){ Sample a = new Sample(5); System.out.println(a.num); //5と表示される } } class Sample{ final int num; //この時点で定数フィールドは初期化されていない Sample(int num){ //int num はインスタンス変数と同名のローカル変数 this.num = num; //コンストラクタで定数を初期化 } }コンストラクタの共通処理を初期化する
- { } という初期化子(インスタンスイニシャライザ)を利用する
- コンストラクタがオーバーロードされて複数ある場合、共通処理をインスタンスイニシャライザで先に在以降できる
- インスタンスイニシャライザは、インスタンス化される直前に実行される
例)
public class Main{ public static void main(String[] args){ Sample a = new Sample(5); //「インスタンスイニシャライザを実行」 System.out.println(a.num); //5 Sample b = new Sample(); //「インスタンスイニシャライザを実行」 System.out.println(b.num); //10 } } class Sample{ final int num; //この時点で定数フィールドは初期化されていない { System.out.println("インスタンスイニシャライザを実行"); } Sample(){ this.num = 10; } Sample(int num){ this.num = num; //コンストラクタで定数を初期化 } }staticフィールドを初期化する
staticフィールドはインスタンスを生成しなくても利用できる。
そのため、コンストラクタで初期化できない→static初期化子(イニシャライザ)で初期化する必要がある
例)
public class Main{ public static void main(String[] args){ System.out.println(Sample.num); //100と出力 } } class Sample{ static final int num; //この時点でstaticな定数は初期化されていない static { num = 100; //staticな定数の初期化 } }初期化子が動作する順番
イニシャライザ→インスタンスイニシャライザ→コンストラクタ
例)
public class Main{ public static void main(String[] args){ User a = new User(); //「イニシャライザの実行 インスタンスイニシャライザの実行 コンストラクタの実行」と出力 } } class User{ private static int count; static{ User.count = 0; //static変数を初期化 System.out.println("イニシャライザの実行"); } { System.out.println("インスタンスイニシャライザの実行"); } User(){ System.out.println("コンストラクタの実行"); } }
- 投稿日:2020-10-10T11:10:57+09:00
【Java Silver】初期化について
インスタンスの初期化
- コンストラクタで初期化処理を行う
- インスタンス変数は、インスタンス作成時に初期化される
※定数(final)もコンストラクタで初期化できる
クラスに定義された、初期化されていない定数は、コンストラクタで初期化できる例)
public class Main{ public static void main(String[] args){ Sample a = new Sample(5); System.out.println(a.num); //5と表示される } } class Sample{ final int num; //この時点で定数フィールドは初期化されていない Sample(int num){ //int num はインスタンス変数と同名のローカル変数 this.num = num; //コンストラクタで定数を初期化 } }コンストラクタの共通処理を初期化する
- { } という初期化子(インスタンスイニシャライザ)を利用する
- コンストラクタがオーバーロードされて複数ある場合、共通処理をインスタンスイニシャライザで先に在以降できる
- インスタンスイニシャライザは、インスタンス化される直前に実行される
例)
public class Main{ public static void main(String[] args){ Sample a = new Sample(5); //「インスタンスイニシャライザを実行」 System.out.println(a.num); //5 Sample b = new Sample(); //「インスタンスイニシャライザを実行」 System.out.println(b.num); //10 } } class Sample{ final int num; //この時点で定数フィールドは初期化されていない { System.out.println("インスタンスイニシャライザを実行"); } Sample(){ this.num = 10; } Sample(int num){ this.num = num; //コンストラクタで定数を初期化 } }staticフィールドを初期化する
staticフィールドはインスタンスを生成しなくても利用できる。
そのため、コンストラクタで初期化できない→static初期化子(イニシャライザ)で初期化する必要がある
例)
public class Main{ public static void main(String[] args){ System.out.println(Sample.num); //100と出力 } } class Sample{ static final int num; //この時点でstaticな定数は初期化されていない static { num = 100; //staticな定数の初期化 } }初期化子が動作する順番
イニシャライザ→インスタンスイニシャライザ→コンストラクタ
例)
public class Main{ public static void main(String[] args){ User a = new User(); //「イニシャライザの実行 インスタンスイニシャライザの実行 コンストラクタの実行」と出力 } } class User{ private static int count; static{ User.count = 0; //static変数を初期化 System.out.println("イニシャライザの実行"); } { System.out.println("インスタンスイニシャライザの実行"); } User(){ System.out.println("コンストラクタの実行"); } }
- 投稿日:2020-10-10T11:01:57+09:00
JUnit5メモ
- 投稿日:2020-10-10T11:01:57+09:00
【個人用】JUnit5備忘録メモ(作業中)
- 投稿日:2020-10-10T09:38:29+09:00
#1【初心者】知識0からeclipseでWebアプリ(Webサイト)を作る.「 Webアプリを作る為の環境を構築しよう」
はじめに
友人とWebサイトを1から作りたいという話となり,右も左もわからない状態からeclipseを使ってとりあえず触りながらのメモ書きをしていく.(調べても知ってて当然だよねって感じで細かい設定とか書いてないサイトが多い...)
環境一覧
プログラミングソフト:Eclipse 2020
JAVA SE 14
Tomcat 8.5.88
- 環境作成編
#1 Webアプリを作る為の環境を構築しよう(この記事)2020 10月更新
#2 GitHubとEclipseを繋げて動的Webアプリを共同開発する
デザインを考えよう編
フロントエンド作成編
バックエンド作成編
実際にサービスを動かしてみよう編
今回の記事では,
1.Eclipseをインストールする.
2.Javaのインストール
3.パースペクティブの設定
4.プロジェクトの作成
5.サーブレットの作成
6.サーブレットの実行という流れ.
単語メモ
パースペクティブ:各々のアプリを開発するのに最適な画面設定
サーブレット:Webサーバ上(バックエンド)で動くプログラム,今回の記事で作成するのはデバック用のローカルサーバ.1.Eclipse をインストールする.
Eclipseには日本語化などの面倒な設定を行わずにそのまま使い始めることができる「Pleiades All in One」というエディションが用意されている.
ダウンロードサイトより,Eclips 2020をダウンロードする.様々なバージョンが表示されるが,今回使用するのは Windows10 64bit の Java Full Edituonをダウンロードする.
勝手にダウンロードが始まる.
始まらなければ青く表示されたURLを押せばダウンロードが始まる.
ダウンロードした.zipファイルを解凍する.
Windowsの標準解凍ソフトだと上手く解凍できないらしいので7-Zipを使って解凍する.解凍するとpleiadesというフォルダが現れるので,Windows(C:)フォルダに直接置いておく(場所は自由だが C: の直下が分かりやすい).
Windows(C:)直下に移動したら,C:\pleiades\eclipseフォルダ内にあるeclipse.exeを右クリックしてショートカットを作成して,デスクトップなどの分かりやすい場所に置き直しておく.(アプリとしてインストールしないためショートカットから起動する)
初回起動ではワークスペース(データの保存場所)をどこにするか聞かれるので,ドキュメントのフォルダ内にeclipseというフォルダを新規作成して,参照からフォルダを変更しておく.(デフォルトでも問題ないが分かりやすい場所に移動しておく)
変更したら起動(L)を押して画面が表示されればインストール完了.
2.Java SE をインストールする.
今回は JAVA SEの14を使用する.
トップページから14を選択するか,JAVA SE 14 ダウンロードサイトから Windows x64 Installer をダウンロードする(なぜかChromeだと動かない時があるのでIEを使ったほうがいい).特に設定などはないのでそのままインストールするだけ.
3.パースペクティブの設定
右上のJavaアイコンの横にある①+マーク(パースペクティブ)を押すとリストが出てくるので②Java EEを選択する.
Java EEを開くとサーバー管理などのサポートがあるサーブレットパースペクティブ(画面構成)となる.
サーバータブなどはWebアプリを開発する際に必須なのでしっかりとJava EEに変更してから開発を開始する.4.プロジェクトの新規作成
ファイル→新規→その他を選択
ウィザードからWebフォルダ内にある①「動的Webプロジェクト」を選び,②次へを押す.
設定画面が出てくるのでプロジェクト名を好きな名前で設定し,ターゲットランタイムをTomcat8 (Java8)に変更する.
変更すると動的モジュールバージョンと構成がターゲットランタイムに合わせて自動的に変更される.変更できたら次へを押す.ここはデフォルトで大丈夫なので次へを押す.
web.xmlデプロイメント記述子の作成にチェックを入れ,完了を押すとプロジェクトが作成される.
これでサーブレット作成する準備が完了.
5.サーブレットの作成
サーブレットを作成するためにJavaリソース→srcを右クリックしてサーブレットを選択する.
とりあえず名前を適当に①sample_main ②sample と設定して完了を押す.
無事に基本的なコードが記述されたサーブレットが作成されました.
Webサーバのタブを開いて,「使用可能なサーバがありません.このリンクをクリックして新規サーバを作成してください.」という部分をクリックする.
Apacheフォルダ内から「Tomcat v8.5」サーバを選択して次へを押す.
サーバに追加したいリソースを①選択して,②追加を押す.
右側にファイルが移動しているのを確認したら完了を押す.
サーバーが作成されるので,右クリックして公開を押す.
次にもう一度右クリックして再開を押す.
問題がなければサーバの右側にあるステータスが[起動済み,同期済み]となる.
次にchromeを開いて,URLに以下を入力.
http://localhost:8080/[プロジェクト名]/[クラス名]今回手順通りに作っていれば,
http://localhost:8080/test/sampleこんな画面が表示されれば問題なく動作している.
試しに
ここにある
("Served at: ")を
("tesutesu")に変えてみる(日本語で打つと文字化けするのでアルファベットで入力する).
しっかり更新されてますね.
とりあえずこれで開発環境の設定は完了,とりあえず次回はGit hubで共有してみる.
次→[]
- 投稿日:2020-10-10T09:38:29+09:00
#1【初心者】知識0からEclipseでWebアプリ(Webサイト)を作る.「 Webアプリを作る為の環境を構築しよう」
はじめに
友人とWebサイトを1から作りたいという話となり,右も左もわからない状態からeclipseを使ってとりあえず触りながらのメモ書きをしていく.(調べても知ってて当然だよねって感じで細かい設定とか書いてないサイトが多い...)
環境一覧
プログラミングソフト:Eclipse 2020
JAVA SE 14
Tomcat 8.5.88
- 環境作成編
#1 Webアプリを作る為の環境を構築しよう(この記事)2020 10月更新
#2 GitHubとEclipseを繋げて動的Webアプリを共同開発する 明日更新
デザインを考えよう編
フロントエンド作成編
バックエンド作成編
実際にサービスを動かしてみよう編
今回の記事では,
1.Eclipseをインストールする.
2.Javaのインストール
3.パースペクティブの設定
4.プロジェクトの作成
5.サーブレットの作成
6.サーブレットの実行という流れ.
単語メモ
パースペクティブ:各々のアプリを開発するのに最適な画面設定
サーブレット:Webサーバ上(バックエンド)で動くプログラム,今回の記事で作成するのはデバック用のローカルサーバ.1.Eclipse をインストールする.
Eclipseには日本語化などの面倒な設定を行わずにそのまま使い始めることができる「Pleiades All in One」というエディションが用意されている.
ダウンロードサイトより,Eclips 2020をダウンロードする.様々なバージョンが表示されるが,今回使用するのは Windows10 64bit の Java Full Edituonをダウンロードする.
勝手にダウンロードが始まる.
始まらなければ青く表示されたURLを押せばダウンロードが始まる.
ダウンロードした.zipファイルを解凍する.
Windowsの標準解凍ソフトだと上手く解凍できないらしいので7-Zipを使って解凍する.解凍するとpleiadesというフォルダが現れるので,Windows(C:)フォルダに直接置いておく(場所は自由だが C: の直下が分かりやすい).
Windows(C:)直下に移動したら,C:\pleiades\eclipseフォルダ内にあるeclipse.exeを右クリックしてショートカットを作成して,デスクトップなどの分かりやすい場所に置き直しておく.(アプリとしてインストールしないためショートカットから起動する)
初回起動ではワークスペース(データの保存場所)をどこにするか聞かれるので,ドキュメントのフォルダ内にeclipseというフォルダを新規作成して,参照からフォルダを変更しておく.(デフォルトでも問題ないが分かりやすい場所に移動しておく)
変更したら起動(L)を押して画面が表示されればインストール完了.
2.Java SE をインストールする.
今回は JAVA SEの14を使用する.
トップページから14を選択するか,JAVA SE 14 ダウンロードサイトから Windows x64 Installer をダウンロードする(なぜかChromeだと動かない時があるのでIEを使ったほうがいい).特に設定などはないのでそのままインストールするだけ.
3.パースペクティブの設定
右上のJavaアイコンの横にある①+マーク(パースペクティブ)を押すとリストが出てくるので②Java EEを選択する.
Java EEを開くとサーバー管理などのサポートがあるサーブレットパースペクティブ(画面構成)となる.
サーバータブなどはWebアプリを開発する際に必須なのでしっかりとJava EEに変更してから開発を開始する.4.プロジェクトの新規作成
ファイル→新規→その他を選択
ウィザードからWebフォルダ内にある①「動的Webプロジェクト」を選び,②次へを押す.
設定画面が出てくるのでプロジェクト名を好きな名前で設定し,ターゲットランタイムをTomcat8 (Java8)に変更する.
変更すると動的モジュールバージョンと構成がターゲットランタイムに合わせて自動的に変更される.変更できたら次へを押す.ここはデフォルトで大丈夫なので次へを押す.
web.xmlデプロイメント記述子の作成にチェックを入れ,完了を押すとプロジェクトが作成される.
これでサーブレット作成する準備が完了.
5.サーブレットの作成
サーブレットを作成するためにJavaリソース→srcを右クリックしてサーブレットを選択する.
とりあえず名前を適当に①sample_main ②sample と設定して完了を押す.
無事に基本的なコードが記述されたサーブレットが作成されました.
Webサーバのタブを開いて,「使用可能なサーバがありません.このリンクをクリックして新規サーバを作成してください.」という部分をクリックする.
Apacheフォルダ内から「Tomcat v8.5」サーバを選択して次へを押す.
サーバに追加したいリソースを①選択して,②追加を押す.
右側にファイルが移動しているのを確認したら完了を押す.
サーバーが作成されるので,右クリックして公開を押す.
次にもう一度右クリックして再開を押す.
問題がなければサーバの右側にあるステータスが[起動済み,同期済み]となる.
次にchromeを開いて,URLに以下を入力.
http://localhost:8080/[プロジェクト名]/[クラス名]今回手順通りに作っていれば,
http://localhost:8080/test/sampleこんな画面が表示されれば問題なく動作している.
試しに
ここにある
("Served at: ")を
("tesutesu")に変えてみる(日本語で打つと文字化けするのでアルファベットで入力する).
しっかり更新されてますね.
とりあえずこれで開発環境の設定は完了,次回はGit hubで共有してみる.
次→[]
- 投稿日:2020-10-10T05:05:23+09:00
java初心者のつまづき1
パッケージ名にjavaと付けるとエラーになる。
当然と言えば当然なのかも。
下記を参照。
https://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=35660&forum=12誤
java\demo1\Hello.javapackage java.demo1; public class Hello { public static void main(String[] args) { System.out.println("Hello"); } }正
demojava\demo1\Hello.javapackage demojava.demo1; public class Hello { public static void main(String[] args) { System.out.println("Hello"); } }
- 投稿日:2020-10-10T04:27:36+09:00
「脱Java」という言葉があるらしい。
電子入札コアシステム(新方式:脱Java)への移行について
http://www.cals.jacic.or.jp/coreconso/inadvance/data/20191122_oshirase.html脱Javaは、日本だけですか?
英語があれば教えて下さい。