- 投稿日:2020-10-23T17:19:12+09:00
期間の重複チェック(LocalTime, LocalDate, LocalDateTime)
毎回ロジックを考えている気がするのでメモしておく。
- 期間の終了がnull (つまり開始からこの先ずっと対象期間) というパターンもよくあるので、それにも対応。
- 「10:00 ~ 11:00」と「11:00 ~ 12:00」のような、期間1の終了と期間2の開始がぴったり一致している時も重複あり判定にしている。このパターンを重複なし判定にする場合は、
!start2.isAfter(end1)
をstart2.isBefore(end1)
に、!end2.isBefore(start1)
をend2.isAfter(start1)
にする。/** * 期間の重複チェック(LocalTime版) * 単一のポイントで期間が重なったときも重複したとみなす。 * e.g.) * 期間1 => 10:00 ~ 11:00 * 期間2 => 11:00 ~ 12:00 * 結果 => 重複あり(true) * * @param start1 期間1の開始 * @param end1 期間1の終了 * @param start2 期間2の開始 * @param end2 期間2の終了 * @return 重複してたらtrue */ def isOverlapped(start1: LocalTime, end1: LocalTime, start2: LocalTime, end2: LocalTime): Boolean = !start2.isAfter(end1) && !end2.isBefore(start1) /** * 期間の重複チェック(LocalTime版, 期間終了Option可) * 単一のポイントで期間が重なったときも重複したとみなす。 * e.g.) * 期間1 => 10:00 ~ 11:00 * 期間2 => 11:00 ~ 12:00 * 結果 => 重複あり(true) * * @param start1 期間1の開始 * @param end1Opt 期間1の終了 (Noneなら開始以降ずっと対象期間) * @param start2 期間2の開始 * @param end2Opt 期間2の終了 (Noneなら開始以降ずっと対象期間) * @return 重複してたらtrue */ def isOverlapped(start1: LocalTime, end1Opt: Option[LocalTime], start2: LocalTime, end2Opt: Option[LocalTime]): Boolean = end1Opt.fold(true)(!start2.isAfter(_)) && end2Opt.fold(true)(!_.isBefore(start1)) /** * 期間の重複チェック(LocalDate版) * 単一のポイントで期間が重なったときも重複したとみなす。 * e.g.) * 期間1 => 2020-10-01 ~ 2020-10-10 * 期間2 => 2020-10-10 ~ 2020-10-20 * 結果 => 重複あり(true) * * @param start1 期間1の開始 * @param end1 期間1の終了 * @param start2 期間2の開始 * @param end2 期間2の終了 * @return 重複してたらtrue */ def isOverlapped(start1: LocalDate, end1: LocalDate, start2: LocalDate, end2: LocalDate): Boolean = !start2.isAfter(end1) && !end2.isBefore(start1) /** * 期間の重複チェック(LocalDate版, 期間終了Option可) * 単一のポイントで期間が重なったときも重複したとみなす。 * e.g.) * 期間1 => 2020-10-01 ~ 2020-10-10 * 期間2 => 2020-10-10 ~ 2020-10-20 * 結果 => 重複あり(true) * * @param start1 期間1の開始 * @param end1Opt 期間1の終了 (Noneなら開始以降ずっと対象期間) * @param start2 期間2の開始 * @param end2Opt 期間2の終了 (Noneなら開始以降ずっと対象期間) * @return 重複してたらtrue */ def isOverlapped(start1: LocalDate, end1Opt: Option[LocalDate], start2: LocalDate, end2Opt: Option[LocalDate]): Boolean = end1Opt.fold(true)(!start2.isAfter(_)) && end2Opt.fold(true)(!_.isBefore(start1)) /** * 期間の重複チェック(LocalDateTime版) * 単一のポイントで期間が重なったときも重複したとみなす。 * e.g.) * 期間1 => 2020-10-01 10:00:00 ~ 2020-10-10 20:00:00 * 期間2 => 2020-10-10 20:00:00 ~ 2020-10-20 20:00:00 * 結果 => 重複あり(true) * * @param start1 期間1の開始 * @param end1 期間1の終了 * @param start2 期間2の開始 * @param end2 期間2の終了 * @return 重複してたらtrue */ def isOverlapped(start1: LocalDateTime, end1: LocalDateTime, start2: LocalDateTime, end2: LocalDateTime): Boolean = !start2.isAfter(end1) && !end2.isBefore(start1) /** * 期間の重複チェック(LocalDateTime版, 期間終了Option可) * 単一のポイントで期間が重なったときも重複したとみなす。 * e.g.) * 期間1 => 2020-10-01 10:00:00 ~ 2020-10-10 20:00:00 * 期間2 => 2020-10-10 20:00:00 ~ 2020-10-20 20:00:00 * 結果 => 重複あり(true) * * @param start1 期間1の開始 * @param end1Opt 期間1の終了 (Noneなら開始以降ずっと対象期間) * @param start2 期間2の開始 * @param end2Opt 期間2の終了 (Noneなら開始以降ずっと対象期間) * @return 重複してたらtrue */ def isOverlapped(start1: LocalDateTime, end1Opt: Option[LocalDateTime], start2: LocalDateTime, end2Opt: Option[LocalDateTime]): Boolean = end1Opt.fold(true)(!start2.isAfter(_)) && end2Opt.fold(true)(!_.isBefore(start1))
- 投稿日:2020-10-23T17:15:07+09:00
Javaで、前後の連続する改行やタブを取り除く
追記
1文字だとマッチしないことに気づき
"^[\t\n]*([^\t\n]+(.|\\s)*[^\t\n]+)[\t\n]*$"
から
"^[\t\n]*([^\t\n]*(.|\\s)*[^\t\n]+)[\t\n]*$"
に修正した。
最初のプラスがアスタリスクになった。
大嫌いな正規表現。
前後以外の改行やタブはそのままにするとこがポイント。
めちゃくちゃハマったので記事にしときます。public static String trimNewline(String str) { String regex = "^[\t\n]*([^\t\n]*(.|\\s)*[^\t\n]+)[\t\n]*$"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()) { return m.group(1); } else { // 「空文字」や「タブや改行だけで構成された文字列」はマッチせずここにくる return ""; } }Javaの正規表現を調べると
「.」は任意の文字にマッチ
とだけ書いてあるものばかりですが、実は、
改行以外の任意の文字列
だったので、めちゃくちゃハマりました。System.out.println(trimNewline("\t\n\t\nhoge\t\n\t\n")); // hoge System.out.println(trimNewline("\t\n\t\nhoge")); // hoge System.out.println(trimNewline("hoge\t\n\t\n")); // hogeまた、途中に改行やタブが入った、
"\t\n\n\n\t\tho\t\t\nhoge\nge\t\n\t\n"
こちらも、
無事に、前後のみ取り除かれました。
- 投稿日:2020-10-23T16:56:11+09:00
【Java】joinメソッドの使い方
プログラミング勉強日記
2020年10月23日
Javaで文字列を連結するときに便利なjoinメソッドを使ったので、まとめる。joinメソッドとは
指定された区切り文字で、文字列を連結するためのメソッド。第一引数を区切り文字(デリミタ)として、第二引数を接頭辞として以降指定された接頭辞の終わりまでの文字列が区切られる。
デリミタは,
(カンマ)、.
(ピリオド)、:
(コロン)、を指定することができる。例public class Main { public static void main(String[] args) throws Exception { String str = String.join(",", "apple", "orange", "melon"); System.out.println(str); } }実行結果apple,orange,melon
- 投稿日:2020-10-23T16:39:17+09:00
if文の中括弧の省略(Javaシルバー)
if文の中括弧の省略
Javaの制御文(if、for、while等)の中括弧は省略しても実行される場合がある。
{}が省略されている場合は、true時の処理対象が1行のみである。以下の例①のコードでは、条件式(i < 3)の結果がtrueの場合、実行される文は処理1の文のみである。
処理2の文は条件式に関わらず必ず実行される。例①if(i < 3) System.out.println("処理1"); System.out.println("処理2");つまり、例①は以下の例②のコードと同じ動きになる。
例②if(i < 3){ System.out.println("処理1"); } System.out.println("処理2");今回、Javaの資格試験にでる可能性があったためメモした。
しかし、文法的には問題ないが可読性と保守性は下がるため、基本的には省略せず、例②のようなブロック{}の形にする。※コメントで教えていただいたので追記する。ifやwhileなどは元々「直後」の文のみ対象となる。複数の文を対象にするための方法として、中括弧{}を使っている。
- 投稿日:2020-10-23T16:28:39+09:00
Javaで、前後の連続する改行を取り除く
苦手な正規表現で苦労したので。
public static String trimNewline(String str) { String regex = "^[\t\n\r]*(.*[^\t\n\r])[\t\n\r]*$"; Pattern p = Pattern.compile(regex); Matcher m = p.matcher(str); if (m.find()) { return m.group(1); } else { // ここには、空文字しかいかない return str; } }
(.*[^\t\n\r])
ではなく、(.+)
としてしまうと、
hoge\t\t
がhoge\t
+\t
と解釈されて、
hoge\t
が返ってきてしまうのに時間食いました。
- 投稿日:2020-10-23T16:07:29+09:00
MyBatisでMapperに引数を経由せずにオブジェクトを渡す方法
記事投稿時点で見つけきらなかったので調べてみました。
やりたかったこと
- フィーチャートグルの状態で発行するSQLを一部変更したい
- でも、フィーチャートグルを使うかどうかはマッパー内部に閉じたい(引数にしたくない)
結論だけみたい人へ
サンプルはこちらにあります。
このソースコードの66行目のaddParameterメソッドを書き換えて渡したいオブジェクトを指定すれば引数で渡したのと同様の方法でアクセスできるようになります。コードの説明
全体像
まずは該当のソースコードを以下に示します。
ソースコード
CustomSqlSessionConfig.javapackage mybatis_implicit_parameter.config; import lombok.experimental.Delegate; import org.apache.ibatis.binding.MapperMethod; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.lang.reflect.Proxy; import java.util.Map; @Configuration public class CustomSqlSessionConfig { @Bean public SqlSessionTemplate sqlSession(SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplateExt(sqlSessionFactory); } static class SqlSessionTemplateExt extends SqlSessionTemplate { public SqlSessionTemplateExt(SqlSessionFactory sqlSessionFactory) { super(sqlSessionFactory); } Object invokeParentMethod(Method method, Object[] args) throws Throwable { return makeParentMethodHandle(method).bindTo(this).invokeWithArguments(args); } MethodHandle makeParentMethodHandle(Method method) throws Exception { return MethodHandles.lookup().findSpecial(SqlSessionTemplate.class, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), SqlSessionTemplateExt.class); } @Delegate final SqlSession proxy = (SqlSession)Proxy.newProxyInstance(SqlSessionTemplateExt.class.getClassLoader(), new Class[]{SqlSession.class}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(hasParameter(method)) { args[1] = addParameter(args[1]); } return SqlSessionTemplateExt.this.invokeParentMethod(method, args); } boolean hasParameter(Method method) { Parameter[] params = method.getParameters(); return (params.length >= 2 && params[1].getType().equals(Object.class)); } Object addParameter(Object parameter) { parameter = (parameter == null) ? new MapperMethod.ParamMap() : parameter; if (parameter instanceof Map) { return addParameter((Map) parameter); } else { throw new RuntimeException("想定してないパターンここに来るなら教えて〜:" + parameter.getClass().getName()); } } Object addParameter(Map map) { //暗黙のパラメーターはここでセットする map.put("insertValue", "foo"); map.put("updateValue", "bar"); return map; } }); } }やってることをかいつまんで説明すると、、、
MyBatisは SqlSessionクラス のinsert/select/update/deleteという名の第1引数にString、第2引数にObjectを持つメソッドを経由してMapperの内容を解析してSQLを発行します。
CustomSqlSessionConfigでは、SQLSessionTemplate
を拡張してそれぞれのメソッドをオーバーライドして引数経由で渡すのが不適切だと思われるオブジェクトを第2引数のパラメーター(実体はMap)に追加して実行するようにしています。こだわりポイント:lombokの@Delegateとダイナミックプロキシを組み合わせる
このコードは自己満足的なコードで本当はもっとシンプルに実現できます。
上に書いたことを愚直に実装しようとすればこのようなコードではなくても、↓のように対象のメソッドをオーバーライドすれば実現できます。
愚直なコード
CustomSqlSessionConfig.java(省略) @Override public int insert(String statement,Object parameter) { return super.insert(statement, addParameter(parameter)); } (省略)でも、同じことを何度も書くのが嫌だってのでちょっと拘ってみました。
ダイナミックプロキシは、Javaの古いバージョンから実装されてる機能で、引数で指定されたinterface(群)のメソッドを持つオブジェクトを動的に作成し、指定した
InvocationHandler
に処理を集約することができる機能です。一方lombokの@Delegateはアノテートされたフィールドの全てのpublicなメソッドをそのフィールドを保持しているクラスのメソッドとして展開するアノテーションです(細かく調整もできます)。ぱっとイメージが付きにくいかも知れませが、このサイトを見ていただけるとイメージがつき易いかと思います。
この2つを組み合わせると、自身のクラスの任意のメソッド(今回はinsertだったりselectだったり)をひとまとめに処理する事が出来るんです。
ちょっと黒魔術的なコードにはなりがちですが、好きな人は溜まらなく好きなはず!知らんけどwこの方法の欠点は、InvocationHandlerから親クラスの同一メソッドを呼び出す(super.xxx的な呼び出し)のが困難になる点です。InvocationHandlerはMethodオブジェクトを引数として受け取るので、
method.invoke(...)
と出来ると良いのですが、@Delegateと組み合わせると、StackOverflowを引き起こしてしまいます。これは、親クラスを指定して同一のメソッドシグネチャを持つMethodオブジェクトを取得してきても同じ結果になります。これは、Method#invokeのAPIリファレンスに書かれてるとおりメソッド呼び出しをinvokeVirtualで実現しているためです。If the underlying method is an instance method, it is invoked using dynamic method lookup as documented in The Java Language Specification, section 15.12.4.4; in particular, overriding based on the runtime type of the target object may occur.
(基礎となるメソッドがインスタンス・メソッドの場合、Java言語仕様セクションに記載されている動的メソッド参照を使用して起動され、特に、ターゲット・オブジェクトの実行時のタイプに基づいてオーバーライドされます。)super.xxxをリフレクションで実現するためには
MethodHandles.lookup().findSpecial(SqlSessionTemplate.class, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), SqlSessionTemplateExt.class);子クラス内で、
findSpecial
で親クラスのメソッドのMethodHandle
を取得する必要があります。
MethodHandleはこれまでのJavaのリフレクションと異なり、どのクラスで実効しているかで結果が異なります。InvocationHandler
実装クラス内でこの処理を実行してもエラーが発生します。あやしいポイント
- パラメーターを追加するメソッドかどうかの判定は若干怪しい。
- パラメーターオブジェクトの型がMap以外のケースがあると動かない。Map以外のケース(nullの場合がある事は確認済み)が来ると動作しません。Map以外が来るかはちゃんと追い切れてません。
動作確認済みライブラリバージョン
ライブラリ名 バージョン Spring-Boot 2.3.4.RELEASE mybatis-spring-boot-starter 2.1.3 lombok 5.1.0 サンプルコードについて
サンプルコードは好きに使っていただいて構いませんが、その結果においてはいかなる責任も負いませーん。
- 投稿日:2020-10-23T13:32:07+09:00
【備忘録】業務用Andoridアプリ開発への挑戦
前提情報
挨拶
乱筆乱文にて失礼いたします
業務改善のためAndroidアプリの開発に挑戦することに
開発手順を検討しつつ進める所存
都合により細部をぼかすことがあるはご容赦いただきたし
内容は随時追加予定選定経緯
- 紙に記入→PCへ入力の業務が非常に多く
データ化されていない情報も多い- PC台数が少なく調べものもままならない
- ネットワーク環境が弱いためオフラインでも使える
→Androidでアプリ開発が最適では
投稿者スペック
- システム管理者3年目(2020/10現在)
- Android使用経験なし
- Java経験なし
環境確認
- Windows10 Pro + Java7
- Android Studio 4.0
- サーバー + SQL Server + Tomcat7
- Androidタブレット(Android 9 Pie)
開発手順
要件定義
- ターゲット補足
- 理想形の構築
- 現状の確認
- すり合わせ
Androidアプリ
- プロジェクト作成(Android Studio)
- 基本情報設定(gradle,manifests)
- GUI作成(activity_.xml)
- SQLite関連
- http通信関連(okhttp3)
WebAPI
- Tomcat7関連
- http通信関連
- 帳票印刷
- 投稿日:2020-10-23T01:38:16+09:00
備忘録No.2「ArrayListとHashSetで検索履歴を作る」[Java]
はじめに
paizaのスキルチェック入門編で苦戦したので記録。
やりたい事
1.任意の文字数の文字列を複数入力し、リストに持たせる。
2.その際、入力した文字列がすでにリストに存在していたら、以前の文字列を削除し、新しく追加する。
3.一番新しく入力されたものから順に上から出力する。コード1
Scanner sc = new Scanner(System.in); List<String> words = new ArrayList<>(); while(sc.hasNext()) { String s = sc.nextLine(); if(words.contains(s)) {//すでにリストにあるか調べ、存在していたら削除する words.remove(s); } words.add(0, s);//入力された文字列をリストの先頭に加える } for(String word : words) { System.out.println(word); }コード1の問題点
・ArrayListは、remove()やadd()で値を増減させると、格納されている全ての値をずらさなければいけない為、処理に時間がかかる。
・ArrayListのcontains()も、リスト内の全ての値を検索する為、格納されている値が多いほど、処理時間が長くなる。解決策
・文字列の検索にHashSetを使う。→ 要素をハッシュ値に変換して検索に使うことができ、素早く値を発見できる。
コード2
入力された文字列は全てリストに追加し、HashSetを使って、重複しているものは表示しないようにする。(実際に削除するのではなく、無かったことにする)
Scanner sc = new Scanner(System.in); List<String> words = new ArrayList<>(); while(sc.hasNext()) { String s = sc.nextLine(); words.add(s); } Collections.reverse(words);//新しく入力されたものから出力する為にリストを反転 Set<String> usedWords = new HashSet<>();//リスト内の文字列の重複の判定に使うHashSet for(String word : words) { if(!usedWords.contains(word)){//すでにusedWordに追加されているか(重複しているか)を判定し、していなかったら表示 System.out.println(word); } usedWords.add(word); }