20200710のJavaに関する記事は13件です。

例外処理が必須なクラス

チェック例外とは、開くべきはずのファイルがない等、
プログラムの不具合とは関係なく、 状況によっては発生しうる状況とされるもので、例外処理が必須となります。

ClassNotFondException

Excptionクラスのサブクラス。
クラスをロード出来ない場合に発生します。
例外処理は必須です。

IOException

Exceptionクラスのサブクラス。
入出力機能を使用する場合に発生します。
例外処理は必須です。

FileNotFoundException

Exceptionクラスのサブクラス。
ファイルに対する読み書きを行う際、対象のファイルが存在しない場合に発生します。
例外処理は必須です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android studioに関する質問です。

自作のandroidアプリを作成しているのですが、アプリを起動しても繰り返し停止しています、と表示され、うまく動きませんLogcatを確認したのですが、以下の通り表示されるのですが、よくわかりません。どうやって修正すればいいでしょうか。

2020-07-10 22:39:52.674 17730-17730/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.fumemo, PID: 17730
java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.fumemo/com.example.fumemo.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.ApplicationInfo android.content.Context.getApplicationInfo()' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3194)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.ApplicationInfo android.content.Context.getApplicationInfo()' on a null object reference
at android.content.ContextWrapper.getApplicationInfo(ContextWrapper.java:163)
at android.view.ContextThemeWrapper.getTheme(ContextThemeWrapper.java:174)
at android.content.Context.obtainStyledAttributes(Context.java:738)
at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:692)
at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:659)
at androidx.appcompat.app.AppCompatDelegateImpl.findViewById(AppCompatDelegateImpl.java:479)
at androidx.appcompat.app.AppCompatActivity.findViewById(AppCompatActivity.java:214)
at com.example.fumemo.MainActivity.(MainActivity.java:35)
at java.lang.Class.newInstance(Native Method)
at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:95)
at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:41)
at android.app.Instrumentation.newActivity(Instrumentation.java:1250)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3182)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) 
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) 
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) 
at android.os.Handler.dispatchMessage(Handler.java:107) 
at android.os.Looper.loop(Looper.java:214) 
at android.app.ActivityThread.main(ActivityThread.java:7356) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby と Java で解く AtCoder ABC129 D 2次元配列

はじめに

AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。

今回のお題

AtCoder Beginner Contest D - Lamp
Difficulty: 1080

今回のテーマ、2次元配列

やっていることは、単純です。

...#..#. 元の配列
12301201 元の配列を左から右にスキャン
33302201 スキャンした配列を右から左にスキャン

合計2回スキャンして左右方向の光の届く範囲を求めます。
次に、上下に2回スキャンして上下方向の光の届く範囲を求めます。

左右用の配列と上下用の配列の値を合計し、その最大値を求めます。

Java

lamp.java
import java.util.*;

class Main {
    public static void main(String[] args) {
        final Scanner sc = new Scanner(System.in);
        final int H = Integer.parseInt(sc.next());
        final int W = Integer.parseInt(sc.next());
        final char S[][] = new char[H+2][W+2];
        for (int i=1; i<H+1; i++) {
            S[i] =  ("#" + sc.next() + "#").toCharArray();
        }
        sc.close();
        for (int i=0; i<W+2; i++) {
            S[0][i] = '#';
            S[H+1][i] = '#';
        }
        int lr[][] = new int[H+2][W+2];
        int ud[][] = new int[H+2][W+2];

        for (int i=1; i<H+1; i++) {
            int cnt = 0;
            for (int j=0; j<W+1; j++) {
                if (S[i][j]=='.') {
                    cnt++;
                } else {
                    cnt = 0;
                }
                lr[i][j] = cnt;
            }
            for (int j=W; j>0; j--) {
                if (lr[i][j]==0) {
                    cnt = 0;
                } else if (cnt==0) {
                    cnt = lr[i][j];
                } else {
                    lr[i][j] = cnt;
                }
            }
        }
        for (int j=1; j<W+1; j++) {
            int cnt = 0;
            for (int i=0; i<H+1; i++) {
                if (S[i][j]=='.') {
                    cnt++;
                } else {
                    cnt = 0;
                }
                ud[i][j] = cnt;
            }
            for (int i=H; i>0; i--) {
                if (ud[i][j]==0) {
                    cnt = 0;
                } else if (cnt==0) {
                    cnt = ud[i][j];
                } else {
                    ud[i][j] = cnt;
                }
            }
        }

        int ans = 0;
        for (int i=1; i<H+1; i++) {
            for (int j=1; j<W+1; j++) {
                int cnt = lr[i][j] + ud[i][j];
                if (ans<cnt) ans = cnt;
            }
        }
        ans -= 1;
        System.out.println(ans);
    }
}

なんの捻りもないコードです。

スクリプト言語などで競プロをすることについて

スクリプト言語などで競プロをすることについて

スクリプト言語の速度について書かれたブログです。
その中で、スクリプト言語で厳しい例として取り上げられているのが、今回の D - Lampになります。

Ruby

ruby.rb
h, w = gets.split.map(&:to_i)
s = Array.new(h + 1).map{Array.new(w + 1, 0)}
1.upto(h) do |i|
  c = 0
  l = 1
  b = gets
  1.upto(w) do |j|
    if b[j - 1] == '.'
      l = j if c == 0
      c += 1
    elsif c > 0
      l.upto(j - 1) do |k|
        s[i][k] = c
      end
      c = 0
    end
    if j == w && c > 0
      l.upto(j) do |k|
        s[i][k] = c
      end
    end
  end
end
ans = 0
1.upto(w) do |j|
  c = 0
  l = 1
  1.upto(h) do |i|
    if (s[i][j] > 0)
      l = i if c == 0
      c += 1
    elsif c > 0
      l.upto(i - 1) do |k|
        ans = s[k][j] + c if ans < s[k][j] + c
      end
      c = 0
    end
    if i == h && c > 0
      l.upto(i) do |k|
        ans = s[k][j] + c if ans < s[k][j] + c
      end
    end
  end
end
puts ans - 1

スキャンの回数を少し工夫したコードで旧環境Ruby (2.3.3)ではTLEでしたが、新環境Ruby (2.7.1)では何とか通る様です。

C++14 Java Ruby 2.3.1 Ruby 2.7.1
旧環境 旧環境 旧環境 新環境
コード長 (Byte) 1420 2044 797 797
実行時間 (ms) 94 465 TLE 1567
メモリ (KB) 35328 108364 36988 47324

どのくらい速くなったか

ケース名 実行時間(旧) 実行時間(新) 新旧比
01.txt 7 59
02.txt 7 63
12.txt 1460 1089 1.34
13.txt 1844 1250 1.48
18.txt 1983 1289 1.54
19.txt 1949 1360 1.43
20.txt 11 59
21.txt 7 63
22.txt 22 70
23.txt 1449 981 1.48
24.txt 9 62
25.txt 10 63

大雑把に、1.4倍速くなっているようです。
rubyの時代が来ましたね。

まとめ

  • ABC 129 D が通った
  • Ruby が速くなった
  • コンパイル系にはかなわないけどね

参照したサイト
スクリプト言語などで競プロをすることについて

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaのequalsで気をつけたい点

その1 nullチェック

public static void main(String[] args) {

    String str1 = null;
    String str2 = "test";

    System.out.println(str1.equals(str2));//実行1
    System.out.println(str2.equals(str1));//実行2
}

実行1は、java.lang.NullPointerExceptionが発生します。
例外発生の原因は、str1にnullを入れてしまい、nullのオブジェクトからメソッドを呼び出してしまったことです。
nullでないstr2からメソッドを呼び出すと、例外が発生しなくなりfalseを返すようになります。

実行2の書き方は、str1がnullであっても例外が発生しないというメリットがありますが、異常系としてnullが入ることを想定せずに実装していた場合、コードミスに気づきにくいという短所があると思います。

nullチェックを意識するのであれば、例外処理で捕まえる方が安全かもしれません。

public static void main(String[] args) {

    String str1 = null;
    String str2 = "test2";

    try {
        System.out.println(str1.equals(str2));
    }catch(NullPointerException ex) {
        System.out.println("例外キャッチ");
    }
}

その2 コンスタントプール

public static void main(String[] args) {

    String str1 = "test1";
    String str2 = "test1";

    System.out.println(str1.equals(str2));//結果1
    System.out.println(str1 == str2);     //結果2
}

結果1はもちろんtrueになりますが、結果2はどうなるでしょうか?
結論、結果2もtrueになります。

これは、コンスタントプールという仕組みがあるためです。
文字リテラルは、プログラム中に頻繁に現れます。しかし、そのたびにStringのインスタンスを生成していては、メモリを大量に消費することになります。

もし、同じ文字列リテラルが再度登場すれば、定数用のメモリ空間にある文字列インスタンスへの参照が「使いまわし」されます。これがコンスタントプールという仕組みです。

このコンスタントプールは、文字列リテラルを使ったときだけ有効です。
new演算子を使って明示的に新しいインスタンスをつくることを記述した場合には、その都度インスタンスがつくられ、それぞれの変数が異なる参照を持ちます。

public static void main(String[] args) {

    String str1 = new String("test");
    String str2 = "test";

    System.out.println(str1.equals(str2));//true
    System.out.println(str1 == str2);     //false
}

実際のプログラムではまずお目にかかりませんが、Java Silverとかでよく出てくるひっかけなので気をつけましょう。

その3 大文字小文字を区別しない判定

equalsIgnoreCaseメソッドを使うと、大文字小文字を区別せずに同値性を判定できます。
ただequalsに比べて出現頻度がかなり低いと思います。

public static void main(String[] args) {

    String str1 = "abc";
    String str2 = "ABC";

    System.out.println(str1.equals(str2));          //false
    System.out.println(str1.equalsIgnoreCase(str2));//true
}

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのTableをJDBCでconnection.getMetaData().getColumns()したときのカラム名リスト

JDBCを使って、テーブルのスキーマ情報を取得する際の完全個人用備忘録。
index順になっています。
そのうち説明も追加します

カラム名 説明 値の例
TABLE_CAT データベース名 database_name
TABLE_SCHEM null
TABLE_NAME テーブル名 table_name
COLUMN_NAME カラム名 id
DATA_TYPE 4
TYPE_NAME INT UNSIGNED
COLUMN_SIZE 10
BUFFER_LENGTH 65535
DECIMAL_DIGITS null
NUM_PREC_RADIX 10
NULLABLE 0
REMARKS カラムについたコメント カラムコメント
COLUMN_DEF null
SQL_DATA_TYPE 0
SQL_DATETIME_SUB 0
CHAR_OCTET_LENGTH null
ORDINAL_POSITION 1
IS_NULLABLE NO
SCOPE_CATALOG null
SCOPE_SCHEMA null
SCOPE_TABLE null
SOURCE_DATA_TYPE null
IS_AUTOINCREMENT YES
IS_GENERATEDCOLUMN NO
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

継承とかについて(JavaSilver)

JavaSilverの取得のためにJavaの基礎で理解が浅い部分をまとめてみています。

継承

継承の概念自体は理解しているつもりです。
継承というより機能を拡張すると表現したほうが個人的には理解しやすいかと思います。
スーパークラスの方が抽象度が高くて機能が少なくて、より具体的な機能が追加されてスーパークラス+α(拡張)になったのがサブクラスというイメージです。

・スーパークラス

SuperA.java
class SuperA {}

・スーパークラスを継承したサブクラス

SubA.java
class SubA extends SuperA {}

できないこと

  • extendsのあとに複数のスーパークラスは指定できない。
SubA.java
class SubA extends SuperA, SuperB {}
  • スーパークラスで定義したprivateなメンバはサブクラスで使用できない。

    スーパークラスで定義したメンバはサブクラスで使用できるがprivateなメンバは同一クラスのからしか使用できないため、サブクラスでも使用できない。

  • メソッドをオーバーライドする場合のアクセス修飾子はスーパークラスで指定している以上の公開範囲でないといけない。

    例えばスーパークラスでpublicを指定しているメソッドをオーバーライドする場合はpublicを指定しないといけない。(公開範囲は狭められない。)

    ・アクセス修飾子の公開範囲

広い 狭い
public protected デフォルト private
どのクラスからでもアクセス可 サブクラス or 同一パッケージ 同一パッケージ 同一クラスのみ
  • final修飾子のついているメンバはオーバーライドできない。

参照型の型変換

スーパークラスとサブクラスでの型変換については以下の2種類があります。
※ついでにインターフェースについても。。。

  • 暗黙型変換
    サブクラス→スーパークラス
    実装クラス→インターフェース

  • キャストによる型変換
    サブクラス←スーパークラス
    実装クラス←インターフェース

つまり変換後に使用できる機能が減らなければ暗黙型変換ができるというイメージですかね?

その他

  • オーバーライド時に呼び出されるメンバ

    サブクラスのオブジェクトをスーパークラス型の変数に代入した場合、インスタンスメソッド以外はスーパークラスのメンバが呼び出される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SpringBoot入門】フォームのバリデーションチェック

目的

Spring Quickstart Guideを取り組み終えた方、SpringBootを学び始めた方、復習をしたい方に向けて、

公式ガイドValidating Form Inputを実際に取り組み学んだことを共有します。

完成形はこちらになります。

名前と年齢を入力するフォームがあり、
スクリーンショット 2020-07-09 14.05.32.png

不正な値が入力したままSubmitボタンが押されるとエラーメッセージを表示して、
スクリーンショット 2020-07-09 14.05.40.png

有効な値が入力されている場合は、別の画面へと遷移出来るように実装していきます。
スクリーンショット 2020-07-09 14.12.54.png

開発環境、これまでのおさらいは以下になります。

開発環境
OS: macOS Mojave バージョン10.14.6
テキストエディタ: Visual Studio Code(以下VSCode)
Java: 11.0.2

QuickstartGuideのおさらいはこちらから
Building a RESTful Web Service編のおさらいはこちらから
Consuming a RESTful Web Service編のおさらいはこちらから
Accessing Data with JPA編のおさらいはこちらから
Handling Form Submission編のおさらいはこちらから

1.SpringBoot projectを始めよう!

まずは、spring initializr にアクセスします。

1.ADD DEPENDENCIESボタンをクリックして、Spring WebThymeleafを追加。
2.Artifact, Nameは、validating-form-inputに変更。
3.Javaを11に変更。

そしてGENERATEボタンをクリックしてZipファイルをダウンロードします。

スクリーンショット 2020-07-09 14.25.27.png

ダウンロードしたZipファイルを展開したら準備完了です。

2.コードを追加しよう!

先ほどのフォルダをVSCodeで開きます。
拡張機能のJava Extension Packのインストールの推奨します。と言われるのでインストールしておきましょう。

スクリーンショット 2020-06-30 10.08.25.png

PersonForm.javaを作成しよう!

src/main/java/com/example/validatingforminput/ にPersonForm.javaファイルを作成します。

スクリーンショット 2020-07-09 14.30.13.png

公式を参考にコードを追加します。

PersonForm.java
package com.example.validatingforminput;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class PersonForm {

  @NotNull
  @Size(min=2, max=30)
  private String name;

  @NotNull
  @Min(18)
  private Integer age;

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }

  public String toString() {
    return "Person(Name: " + this.name + ", Age: " + this.age + ")";
  }
}

追加したコードを深掘りしていきます。

@NotNull@Size

PersonForm.java
@NotNull
@Size(min=2, max=30)
private String name;

String型のname変数を宣言時に2つバリデーションのアノテーションを付与しています。

@NotNullは、nullを許可しません。あくまでnullは許可しないだけで、空文字や空白は許可されるので注意してください。

@Sizeは、()の中で指定したmin以上max以下であるかを検証しています。
今回は、@Size(min=2, max=30)ですので、2文字以上30文字以下ではないとエラーになります。

@Min

PersonForm.java
@NotNull
@Min(18)
private Integer age;

Integer型のage変数を宣言時に2つバリデーションのアノテーションを付与しています。(1つは上述の@NotNull

@Minは、()の中に記述した値より小さいかどうか検証しています。
今回は、@Min(18)ですので、18未満はエラーになります。

③ゲッター/セッター、toStringメソッド

PersonForm.java
public String getName() {
  return this.name;
}

public void setName(String name) {
  this.name = name;
}

public Integer getAge() {
  return age;
}

public void setAge(Integer age) {
  this.age = age;
}

public String toString() {
  return "Person(Name: " + this.name + ", Age: " + this.age + ")";
}

変数name、ageの値を取得、変更するためのゲッター/セッターメソッドを定義しています。
また、name、ageを文字列として表示するためのtoStringメソッドを定義しています。(今回使用していない気がしますが、デバッグ用なのかな?)

pom.xmlにvalidationの依存関係を追加しよう!

公式ガイドには記載がなかったのですが、Spring Boot 2.3以上の時は、
PersonForm.javaimport javax.validation.constraints.〇〇がエラーになります。
(Spring Boot2.3 Relealse Notes)

以下をdependenciesに追加してください。

pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

スクリーンショット_2020-07-09_15_38_58.png

WebController.javaを作成しよう!

src/main/java/com/example/validatingforminput/ にWebController.javaファイルを作成します。

スクリーンショット 2020-07-09 15.41.24.png

公式を参考にコードを追加します。

WebController.java
package com.example.validatingforminput;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Controller
public class WebController implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/results").setViewName("results");
  }

  @GetMapping("/")
  public String showForm(PersonForm personForm) {
    return "form";
  }

  @PostMapping("/")
  public String checkPersonInfo(@Valid PersonForm personForm, BindingResult bindingResult) {

    if (bindingResult.hasErrors()) {
      return "form";
    }

    return "redirect:/results";
  }
}

追加したコードを深掘りしていきます。

①WebMvcConfigurerインターフェースとaddViewControllersメソッド

WebController.java
@Controller
public class WebController implements WebMvcConfigurer {

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/results").setViewName("results");
  }

 // 以下略
}

WebMvcConfigurerインターフェースを実装、addViewControllersメソッドをオーバーライドしています。

http://localhost:8080/resultsというURLの時、results.htmlというテンプレートを参照するように設定していると解釈しています。
URLとテンプレートがマッピングされるようにしているみたいです。results.htmlの実装は後ほど。

②showFormメソッド

WebController.java
@GetMapping("/")
public String showForm(PersonForm personForm) {
  return "form";
}

@GetMappingは、http://localhost:8080/でGETリクエストがあった時、showFormメソッドを呼ぶためのアノテーションです。
引数にPersonFormを受け取っています。メソッドの戻り値である、form.htmlのform属性でPersonFormを関連づける事ができます。
form.htmlの実装は後ほど。

③checkPersonInfoメソッド

WebController.java
@PostMapping("/")
public String checkPersonInfo(@Valid PersonForm personForm, BindingResult bindingResult) {

  if (bindingResult.hasErrors()) {
    return "form";
  }

  return "redirect:/results";
}

@PostMappingは、http://localhost:8080/でPOSTリクエストがあった時、checkPersonInfoメソッドを呼ぶためのアノテーションです。
1つ目の引数@Valid PersonForm personFormは入力されたデータの検証をしています。
2つ目の引数BindingResult bindingResultは、入力されたデータと検証結果(エラーがあるかどうか)を保持するためのアノテーションです。

bindingResult.hasErrors()が書かれているif文では、エラーがあるかどうかを確認しています。
もしエラーがある場合、form.htmlをエラーメッセージと入力されていた値と共に再描画します。

エラーが無ければ、http://localhost:8080/resultsにリダイレクトされます。

form.htmlを作成しよう!

src/main/resources/templates/ にform.htmlファイルを作成します。

スクリーンショット 2020-07-10 10.18.06.png

公式を参考にコードを追加します。

form.html
<html xmlns:th="http://www.thymeleaf.org">
  <body>
      <form action="#" th:action="@{/}" th:object="${personForm}" method="post">
        <table>
          <tr>
            <td>Name:</td>
            <td><input type="text" th:field="*{name}" /></td>
            <td th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Name Error</td>
          </tr>
          <tr>
            <td>Age:</td>
            <td><input type="text" th:field="*{age}" /></td>
            <td th:if="${#fields.hasErrors('age')}" th:errors="*{age}">Age Error</td>
          </tr>
          <tr>
            <td><button type="submit">Submit</button></td>
          </tr>
        </table>
      </form>
  </body>
</html>

nameとageを入力するフォーム画面ですね。

追加したコードのthymeleafの記述について深掘りしていきます。

thymeleafとは、springbootで扱う事が出来るテンプレートエンジンです。th:〇〇と記述します。
日本語で書かれたthymeleafチュートリアルもあります!

th:action

formタグのaction属性の内容を置換しています。記述の仕方は、th:action="@{}"です。
method="post"となっているので、Submitボタンが押された時にWebControllerのcheckPersonInfoメソッドが呼ばれます。

th:object

th:objectでオブジェクトを指定しています。これにより、オブジェクト内の変数の参照の仕方がpersonForm.nameではなく、*{name}のような記述方法が可能になります。

th:field

th:objectで指定したオブジェクト内の変数を表示するためにth:field="*{変数名}"と記述します。
今回は、PersonFormクラスの中にname、ageがあるので、th:field="*{name}"th:field="*{age}"となります。
また、th:field="*{変数名}"の中に記述した変数名がinputのid属性とname属性になります。

th:if

th:if=条件と記述します。trueであった場合、そのタグ、子要素が表示されます。
今回の条件は、エラーがあった場合trueになります。

th:errors

th:errors="*{変数名}"と記述します。エラーがあった場合のエラーメッセージが表示されます。
th:ifとth:errorsでエラーの有無を確認して、エラーメッセージ表示する領域を確保している感じですね。

results.htmlを作成しよう!

src/main/resources/templates/ にresults.htmlファイルを作成します。

スクリーンショット 2020-07-10 11.11.36.png

公式を参考にコードを追加します。

results.html
<html>
  <body>
    Congratulations! You are old enough to sign up for this site.
  </body>
</html>

form画面で不正な値が入力されることが無ければこちらの画面に遷移します。

3.実行してみよう!

アプリケーション実行の準備が出来たので確認しましょう。

ターミナルで以下のコマンドを入力してEnterしてください。

ターミナル
$ ./mvnw spring-boot:run

そして、http://localhost:8080/にアクセスすると以下のフォーム画面が表示されるはずです。(form.htmlが表示される)

スクリーンショット 2020-07-09 14.05.32.png

nameに1文字、ageに18未満の数値を入力して、Submitボタンを押すとエラーメッセージが表示されます。

スクリーンショット 2020-07-10 13.38.34.png

スクリーンショット 2020-07-10 13.38.46.png

ageに何も入力せずにSubmitボタンを押してもエラーメッセージが表示されます。

スクリーンショット 2020-07-10 13.41.09.png

nameに2文字以上、ageを18以上の数値を入力して、Submitボタンを押すとresult画面(result.html)が表示されます。

スクリーンショット 2020-07-10 13.45.01.png

スクリーンショット 2020-07-10 13.45.07.png

お疲れ様でした!完成です!

参考サイト

Spring Bootで入力値の検証
ビュー名を返すだけのControllerなら、Controllerは別にいらないらしいよ!
検証とエラーメッセージ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaのラムダ式

ラムダ式とは

Java8で導入された記述方法です。
同じくJava8で導入されたStream API1はラムダ式を使うことが前提とされているため、ラムダ式を学んでおくとメリットがありそうです。

また、ラムダ式を使うと、「匿名クラスを使った関数型インターフェースの記述」を簡潔にできるというメリットもあります。

ラムダ式の書き方

引数が複数ある時

インターフェース名 オブジェクト名 = (引数1, 引数2, ...) -> { return 処理内容 };

コンパイラが型推論してくれるので、引数の型は指定する必要はありません。

引数が1つしかない時

インターフェース名 オブジェクト名 = 引数 -> 処理;

引数が1つしかない時は、returnや引数を囲む()、処理を書く{}等は要りません。

ラムダ式を読む時はシンプルにこの形式を覚えておいて、書く時は引数の数によって記述を気をつければいいと思います。

匿名クラスを使った実装

匿名(無名)クラスとは、インターフェースを実装したローカルクラスの、宣言部分を省略したものです。
ラムダ式を使えば、匿名クラスを使わずに簡潔にわかりやすく書くことができます。

たとえば以下のような匿名クラスがあったとします。

main.java
interface InterfaceTest{
    // 抽象メソッド
    public String name(String name);
}

public class Main {

    public static void main(String[] args) {

        // 匿名クラスを使った書き方
        InterfaceTest greeting = new InterfaceTest() {
            // オーバーライド
            public String name(String name) {
                return "Hello " + name;
            }
        };
        System.out.println(greeting.name("momoji"));
    }

}

上記では、インターフェースのインスタンスを生成しているように見えますが、実際はインターフェースをもった、無名クラスのインスタンスを生成しています。

実行結果↓

Hello momoji

ラムダ式での実装

これをラムダ式で書き換えてみます。

main.java
interface InterfaceTest{
    // 抽象メソッド
    public String name(String name);
}

public class Main {

    public static void main(String[] args) {

        // ラムダ式を使った書き方
        InterfaceTest greeting = (name) -> {
            return "Hello " + name;
        };
        System.out.println(greeting.name("momoji"));
    }

}

{}の中にはgreetingメソッドで行う処理内容が書かれています。
短くなった上、何をしているかが分かりやすくなりました。また、引数の型指定をしなくてよくなりました。

実行結果↓

Hello momoji

forEach文をラムダ式を使ってもっと短く書く

Javaにはfor-each文と同じ性質を持ったもので、拡張for文というものがあります。配列やリストの全ての要素に指定した処理を行うという性質を持ちます。

拡張for分の書き方

for (データ型 変数名: listや配列){
  処理;
  ...
}

リストの要素を全て順番に出力するには、拡張for文だと以下のように書きます。

main.java
main.java
class Main {
    public static void main (String[] args) {

            List<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");

            // 拡張for文
            for(String l : list) {
                System.out.println(l);
            }

        }
}

上記の例では、
①String型のリスト「list」を定義する
②addメソッドでlistに"a","b","c"の要素をひとつずつ詰めていく
③拡張for文を使ってlistの要素をひとつずつ出力していく。

ということをしています。

実行結果は以下のようになります。

a
b
c

これを、ラムダ式とforEach文を使うともっと短くすることができます。

main.java
class Main {
    public static void main (String[] args) {

            List<String> list = new ArrayList<>();
            list.add("a");
            list.add("b");
            list.add("c");

            // 拡張for文
            // for(String l : list) {
            //  System.out.println(l);
            // }

            // ラムダ式を使ったforEach文
            list.forEach(l -> System.out.println(l));

        }
}

実行結果↓

a
b
c

3行使って書いていたところを、1行に収めることができました。
これを「メソッド参照」を使うともっと短くすることができます。

メソッド参照とは

これもJava8から導入された記法になります。
メソッド参照とは、メソッドの引数としてメソッドを参照できる仕組みのことです。
定義済みのメソッドを引数なしで呼び出すことができます。

メソッド参照は以下のように書きます。

クラス名 :: メソッド名

クラス名の後に「::」、呼び出したいメソッド名を書きます。メソッド名に()は不要です。

さっそくラムダ式と組み合わせてみます。

main.java
// メソッド参照を使ったラムダ式
list.forEach(System.out::println);

実行結果↓

a
b
c

ラムダ式とメソッド参照の合わせ技で、ずいぶん短く書けるようになりました。


  1. 配列やリストなどのコレクションを扱う為のもので、値の集計やデータを使った処理を分かりやすいコードで実装することができるAPI。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBootでapplication.ymlやapplication-[プロファイル名].yml以外のファイル名のプロパティファイルを読みたい

なかなかコレ!というサンプルが見つからなかったのでメモ。

やりたかったこと

  • SpringBoot に application.yml 以外のファイル名の yaml のプロパティファイルを読み込ませる
  • その際、application-test.yml のようにプロファイル名をくっつけたファイルも読み込ませる
  • 読み込んだプロパティは Configuration クラスにマッピングする

マルチプロジェクトの構成で、共通の部品を集めたプロジェクト側にもプロパティファイルを置きたかったが、application.yml だと利用側のプロパティファイルとバッティングするため別名にする必要があった。

実現方法

ポイント

  • Configuration クラスで @PropertySource を使用して読み込むプロパティファイルを指定する
  • @PropertySource は yaml ファイルの読み込みに対応していないので読み込み用のクラスを作成して読み込めるようにする

ソースコード

完全なソースコードはこちら

Configuration クラス

@PropertySourcefactory で Yaml 読み込み用のクラスを指定する。

FooConfig.java
@Configuration
@ConfigurationProperties(prefix = "foo")
@Component
@PropertySource(value = {"classpath:/foo-config.yml",
    "classpath:/foo-config-${spring.profiles.active}.yml"},
    factory = YamlPropertySourceFactory.class)
@Data
public class FooConfig {

    private BarConfig bar;
    private BazConfig baz;

    @Data
    public static class BarConfig {
        private String setting1;
    }

    @Data
    public static class BazConfig {
        private String setting1;
        private String setting2;
    }
}

Factory クラス

どこかから拝借したソース。
Spring のYamlPropertiesFactoryBeanを使用して読み込んだ Yaml を Properties に変換し、PropertySource にして返しているよう。

YamlPropertySourceFactory.java
public class YamlPropertySourceFactory implements PropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource)
        throws IOException {
        Properties propertiesFromYaml = loadYamlIntoProperties(resource);
        String sourceName = name != null ? name : resource.getResource().getFilename();
        return new PropertiesPropertySource(sourceName, propertiesFromYaml);
    }

    private static Properties loadYamlIntoProperties(EncodedResource resource)
        throws FileNotFoundException {
        try {
            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
            factory.setResources(resource.getResource());
            factory.afterPropertiesSet();
            return factory.getObject();
        } catch (IllegalStateException e) {
            Throwable cause = e.getCause();
            if (cause instanceof FileNotFoundException) {
                throw (FileNotFoundException) e.getCause();
            }
            throw e;
        }
    }
}

動作確認

SpringPropertySourceTest.java
@SpringBootTest
@ActiveProfiles("test")
public class SpringPropertySourceTest {

    @Autowired
    FooConfig fooConfig;

    @Test
    public void test() {
        assertThat(fooConfig.getBar().getSetting1()).isEqualTo("barbar1");
        assertThat(fooConfig.getBaz().getSetting2()).isEqualTo("bazbaz2");

    }
}

おまけ

SpringBoot2.3.0 以降だと@PropertySource${spring.profiles.active} を解決できずにエラーが出ます。別記事に書いておきました。(これのせいでめっちゃ時間食った・・・)

SpringBoot2.3.0 以降で@PropertySource の value に使用しているプレースホルダの解決に失敗する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsでmvnをするとNB: JAVA_HOME should point to a JDK not a JRE でエラーになる

私の原因

  • 環境変数で M2_HOME ,PATHをユーザー環境変数で設定し、JAVA_HOMEはシステム環境変数で設定していた。
  • また、NB: JAVA_HOME should point to a JDK not a JREのエラーは、パス先がJREだから起こるものではなく、JDKを認識できないときに一般的に表示されるものだった。

解決方法

  • JAVA_HOMEをユーザー環境変数で設定する。 environment_variable.png

動作環境

  • Winodws 10
  • jdk-13.0.1
  • Maven 3.6.3

経緯

  • 普段はMacでAndroid開発をしているが、たまたまWindowsでSpring Bootを触ることになり、Java,MarvenをWindowsにインストールした。
  • 環境変数でM2_HOMEを設定し、Pathも設定した。
  • mvn -vで以下のエラーが起こり、Mavenが実行できない
  • The JAVA_HOME environment variable is not defined correctly 
    This environment variable is needed to run this program 
    NB: JAVA_HOME should point to a JDK not a JRE  
    
  • PCにはすでにJAVA_HOMEが設定されていて、Pathも設定済みだったのだが、、、

参照 :bow:

解決にあたって以下の記事のお世話になりました
- WindowsへのMavenインストール方法
- How do I fix maven error The JAVA_HOME environment variable is not defined correctly?
- MavenのJAVA_HOMEで躓いた時に見るメモ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBoot2.3.0以降で@PropertySourceのvalueに使用しているプレースホルダの解決に失敗する

内容

タイトルそのままです。
SpringBoot2.3.0 以降で@PropertySource の value の指定に classpath:/something-config-${spring.profiles.active}.yml のようにプレースホルダを使っていると、解決できずに例外が発生する。
現在の Boot の最新バージョンは 2.3.1 です。

ソース

SomethingConfig.java
@Configuration
@ConfigurationProperties(prefix = "something")
@Component
@PropertySource(value = {"classpath:/something-config.yml","classpath:/something-config-${spring.profiles.active}.yml"}, factory = YamlPropertySourceFactory.class)
@Data
public class SomethingConfig {

    private String setting1;
    private String setting2;
}

結果

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.profiles.active' in value "classpath:/somethign-config-${spring.profiles.active}.yml"

解決方法

とりあえず 2.2 系を使いました。

issue

https://github.com/spring-projects/spring-boot/issues/21631

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

電卓プログラム

//キーボード入力インポート
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;

//クラス開始
public class Original {

//メインメソッド開始**************************
public static void main(String[] args) throws IOException {


    //キーボード入力準備
    BufferedReader br =
            new BufferedReader (new InputStreamReader(System.in));

    String now = "0";
    String input = "";
    String enzan = "";
    String msg = "数値";
    String data = "";

    //無限ループ
    while(true){
        try {


            //******************************************
            System.out.println("\n"+"***************************");
            System.out.println(" ");
            System.out.println("現在の値: " + now);
            System.out.println("演算子: " + enzan);
            System.out.println("入力された値: " + input);
            System.out.print(msg + "を入力してください>>  ");
            //******************************************

            //dataに入力値を代入する
            data = br.readLine();

            //A-1
            if(hantei(data)){
                BigDecimal bd_data = new BigDecimal(data);//Decimalに変換
                BigDecimal er = new BigDecimal(0);

                bd_data = bd_data.stripTrailingZeros();//ゼロ消す

                //B-1
                if("数値".equals(msg)){
                    now = bd_data.toPlainString();
                    msg = "演算子";

                    //C-1
                }else if("数値又は演算子".equals(msg)){
                    //D-1
                    if("/".equals(enzan) && bd_data.equals(er)){
                        System.out.println("0で割れません");

                    }else{
                        input = bd_data.toPlainString();
                        msg = "演算子";
                    }
                    //C-1
                }else{
                    System.out.println("エラー!演算子を入力してください"+ "\n");
                }


                //A-2
            }else{

                if("+".equals(data) || "-".equals(data) ||
                        "*".equals(data) || "/".equals(data) ||
                        "=".equals(data)){

                    //msgが数値のとき
                    if("数値".equals(msg)){
                        System.out.print("エラー!数値を入力してください");

                        //msgが数値又は演算子のとき
                    }else if("数値又は演算子".equals(msg)){

                        //dataが=だったら
                        if("=".equals(data)){
                            System.out.print("エラー!数値を入力してください");
                        }else{
                            enzan = data;
                            msg = "数値又は演算子";
                        }

                        //msgが演算子のとき
                    }else{

                        //inputが空だったら
                        if("".equals(input)){
                            if("=".equals(data)){
                                System.out.print("エラー!イコール以外を入力してください");
                            }else{
                                enzan = data;
                                msg = "数値又は演算子";
                            }

                            //inputに値が入っていたら
                        }else{

                            BigDecimal bd1 = new BigDecimal(now);
                            BigDecimal bd2 = new BigDecimal(input);

                            //足し算
                            if("+".equals(enzan)){
                                BigDecimal ans1 = bd1.add(bd2);

                                now = ans1.toPlainString();
                                input = "";

                                //引き算
                            }else if("-".equals(enzan)){
                                BigDecimal ans2 = bd1.subtract(bd2);

                                now = ans2.toPlainString();
                                input = "";

                                //掛け算
                            }else if("*".equals(enzan)){
                                BigDecimal ans3 = bd1.multiply(bd2);

                                now = ans3.toPlainString();
                                input = "";

                                //割り算(四捨五入・20桁まで表示)
                            }else{
                                BigDecimal ans4 = bd1.divide(bd2,20,BigDecimal.ROUND_HALF_UP);

                                ans4 = ans4.stripTrailingZeros();
                                now = ans4.toPlainString();
                                input = "";
                            }

                            //dataが=だった場合
                            if("=".equals(data)){
                                enzan = "";
                                //=でない場合
                            }else{
                                enzan = data;
                                msg = "数値又は演算子";
                            }
                        }//inputに値が入っていたら
                    }//msgが演算子のとき

                    //A-3
                }else if("c".equals(data)){
                    now = "0";
                    enzan = "";
                    input = "";
                    msg = "数値";
                    System.out.print("クリアしました");


                    //A-4
                }else if("ce".equals(data)){

                    //B-3
                    if(input != ""){
                        input = "";
                        msg = "数値又は演算子";
                        //C-3
                    }else{
                        if(enzan != ""){
                            enzan = "";
                            msg = "演算子";

                        }else{
                            now = "0";
                            msg = "数値";
                        }
                    }

                    //A-5
                }else if("e".equals(data)){
                    System.out.print("プログラムを終了します");
                    break;
                }else{
                    System.out.print("\n"+"エラー!対応していない入力値");
                }

            }//A-2
        }catch (Exception e) {
            // TODO 自動生成された catch ブロック
            System.out.print("予期せぬエラーの発生");
            e.printStackTrace();
        }
    }//無限ループ
}//メソッド

//判定メソッド**************************************
public static boolean hantei(String data){

    boolean brea = true;

    try{
        Double.parseDouble(data);

    }catch (NumberFormatException e){
        brea = false;
    }
    return  brea;

}

//**************************************************
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

新興のSQLiteクライアント Reasonの開発をしています。

初めまして、hosokawatです。
SQLiteクライアント Reasonの開発をしています。

ダウンロードはこちらから

次の投稿からデバッグ&宣伝を兼ねてReasonを使ったSQLite入門の記事を投稿していくことにしました。
その前置として、Reasonの紹介をします。

Reasonとは?

SQLの作成、検証を効率よく行うことを目的としたSQLiteクライアントです。
ゴテゴテなGUIのツールを使うよりもエディタを使って「手で書く方が早いんだ!!」っていう人をターゲットにしています。

百聞は一見に如かずということでスクリーンショットを見てみてください。

スクリーンショット 2020-07-10 7.04.46.png

推しの機能

  • クエリー自動生成機能
  • テーブル名・カラム名・基本構文の自動補完
  • 複数クエリー一括実行・結果の一括表示
  • 多言語、Macos/Windows対応
  • AceEditorの機能が使える

クエリー自動生成機能

テーブルを選択してのselect,insert,update,delete文作成と、
実行結果のセルから対象選択してのselect,update,deleteするためのクエリー作成機能があります。
スクリーンショット 2020-07-10 7.38.50.png

テーブル名・カラム名・基本構文の自動補完

Reasonではテーブル名・カラム名・基本構文の自動補完をすることができます。
スクリーンショット 2020-07-10 7.13.53.png
よくみてみてください。サブクエリーのカラム名も対応しています。

複数SQL一括実行・結果の一括表示

Reasonでは複数のクエリーを一括で実行してまとめて結果を見ることができます。
スクリーンショット 2020-07-10 7.17.01.png

多言語、Macos/Windows対応

日本語、英語で使うことができます。
そして、Macos/Windowsに対応しています。

AceEditorの機能が使える

エディタのエンジンとしてAceEditorを組み込んでいます。
豊富な機能が搭載されていて、マルチカーソルや高度な検索を使うことができます。

開発環境・言語・フレームワーク

Macbook pro 13インチでEclipseを使って、Java14とJavaFX14使って開発しています。
Java11以降の変化に対応したjavaFX開発のまとまったノウハウがなかなかネットにはなく苦労しました。
今回のプロダクトをリリースするにあたって全ての工程を経験できました。
いずれまとめることができたらなと思っています。

開発期間

githubの草を確認してみたら3月の頭から開発をしていて124日開発しています!
いくつもの難題にぶち当たって刺激的な日々でした。
私の尊敬するエンジニアに言わせればこれは「楽しい困難!」です!

まだまだ実装したい機能はたくさんあるので開発は続けていきます。
別のDBに対応するのか、さらに高度な機能を実装していくのか、は非常悩ましい選択肢です。
当初は4月5月には終わってると思っていたのですが、、しばらくのライフワークになると思います。

まとめ

今回の記事を書きながらReasonを使ってなかなか良くできているなと自分のことながら思いました。
次回からReasonを使ったSQLite入門を始めます。

ダウンロードはこちらから

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む