20201126のJavaに関する記事は15件です。

【Java】BigDecimalの四捨五入や切り捨て・切り上げの方法

プログラミング勉強日記

2020年11月26日
BigDecimalクラスを使うことで四捨五入や切り捨・切り上げはできるけど、これによって誤差(丸め誤差)が生じてしまうので、その解決方法も一緒に紹介する。

BigDecimalクラスとは

 小数点以下の値に誤差が出ないよう正確に計算するために使う。小数点以下の四捨五入や切り捨て・切り上げといった処理を行うことができる。BigDecimalクラスを使用するためにはjava.math.BigDecimalパッケージをインポートする必要がある。
 BigDecimalクラスのsetScale()メソッドを使用することで、任意の桁数で切り上げ処理を行うことができ、基本的な書き方は下記のようになる。

構文
setScale(桁数,BigDecimal.ROUND_UP).doubleValue()

誤差を発生しないために

 BigDecimalクラスのコンストラクタの引数には浮動小数点型と文字列型どちらも指定することができるが、正確な値を扱うためには文字列型で定義する。引数に浮動小数点型を使用すると誤差が発生する可能性があるからだ。

サンプルコード
import java.math.BigDecimal;

public class Main {

    public static void main(String[] args) {
        // 引数にdouble型(浮動小数点型)で指定
        BigDecimal bd1 = new BigDecimal(0.6); 
        // 引数にString型(文字列型)で指定
        BigDecimal bd2 = new BigDecimal("0.6"); 

        System.out.println(bd1);
        System.out.println(bd2);
    }

}
実行結果
0.59999999999999997779553950749686919152736663818359375
0.6

どうしても最初にdouble型で定義したい場合は数値をString型に定義しなおす必要がある。

サンプルコード
import java.math.BigDecimal;

public class Main {
   public static void main(String[] args) {
      double number = 0.5925;
      // 数値を文字列に変換する
      BigDecimal num = new BigDecimal(String.valueOf(number));

      //小数第一位で切り上げ
      double num0 = num.setScale(0,BigDecimal.ROUND_UP).doubleValue();

      //小数第二位で切り上げ
      double num1 = num.setScale(1,BigDecimal.ROUND_UP).doubleValue();

      //小数第三位で切り上げ
      double num2 = num.setScale(2,BigDecimal.ROUND_UP).doubleValue();

      System.out.println("小数第一位で切り上げ:" + num0);
      System.out.println("小数第二位で切り上げ:" + num1);
      System.out.println("小数第三位で切り上げ:" + num2);
   }
}
実行結果
小数第一位で切り上げ:1.0
小数第二位で切り上げ:0.6
小数第三位で切り上げ:0.6

参考文献

Java 切り上げ
【解決Java】BigDecimalで四捨五入や切り捨ての丸め誤差を解決

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

【Android】JavaでSHA256を計算するアプリを作ってみた

雰囲気

なんか、
Screenshot_20201126-222656.png

こんな感じで動く。
Screenshot_20201126-222702.png

環境

Windows10
AndroidStudio 3.5.0.0
Android SDK 26.1.1
(Pixel OS10で動作確認)

MainActivity.java

package com.weare2434.sha256;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.app.AlertDialog;
import java.security.MessageDigest;

public class MainActivity extends AppCompatActivity {
    private EditText editText;
    private Button button;
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        editText = (EditText) findViewById(R.id.editText);
        button = (Button) findViewById(R.id.button);
        textView = (TextView) findViewById(R.id.textView);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String text = editText.getText().toString();
                if (!text.equals("")){
                    textView.setText(text);
                    editText.setText("");

                    //SHA256
                    byte[] cipher_byte;
                    try {
                        MessageDigest md = MessageDigest.getInstance("SHA-256");
                        md.update(text.getBytes());
                        cipher_byte = md.digest();
                        StringBuilder sb = new StringBuilder(2 * cipher_byte.length);
                        for(byte b: cipher_byte) {
                            sb.append(String.format("%02x", b&0xff) );
                        }
                        text = String.valueOf(sb);

                        //クリップボードにテキストを保存
                        ClipboardManager clipboard = (ClipboardManager)
                                getSystemService(Context.CLIPBOARD_SERVICE);
                        clipboard.setPrimaryClip(ClipData.newPlainText("", text));

                        Toast.makeText(view.getContext(), text, Toast.LENGTH_SHORT).show();

                        new AlertDialog.Builder(view.getContext())
                                .setTitle("")
                                .setMessage("ハッシュ値:\n" + text + "\n計算成功!クリップボードにコピーしました。")
                                .setPositiveButton("close", null)
                                .show();
                        text = "計算成功!";
                    } catch (Exception e) {
                        text = "計算失敗!";
                        Toast.makeText(view.getContext(), text, Toast.LENGTH_SHORT).show();

                        new AlertDialog.Builder(view.getContext())
                                .setTitle("")
                                .setMessage(text)
                                .setPositiveButton("close", null)
                                .show();
                    }
                    textView.setText(text);
                }
                else {
                    Toast.makeText(view.getContext(), "入力してください", Toast.LENGTH_SHORT).show();
                    textView.setText("No Input");
                }
            }
        });

        //textViewコピー有効
        textView.setTextIsSelectable(true); // Text Selection をenableにする
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            textView.setCustomSelectionActionModeCallback(new ActionMode.Callback2() {
                @Override
                public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                    return true;
                }

                @Override
                public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                    return false;
                }

                @Override
                public boolean onActionItemClicked(ActionMode mode, MenuItem item) {

                    switch (item.getItemId()) {
                        case android.R.id.copy:
                            int min = 0;
                            int max = textView.getText().length();
                            if (textView.isFocused()) {
                                final int selStart = textView.getSelectionStart();
                                final int selEnd = textView.getSelectionEnd();

                                min = Math.max(0, Math.min(selStart, selEnd));
                                max = Math.max(0, Math.max(selStart, selEnd));
                            }

                            final CharSequence selectedText = textView.getText()
                                    .subSequence(min, max);
                            String text = selectedText.toString();

                            ClipboardManager clipboard = (ClipboardManager)
                                    getSystemService(Context.CLIPBOARD_SERVICE);
                            clipboard.setPrimaryClip(ClipData.newPlainText("", text));

                            // ActionModeの終了
                            mode.finish();
                            return true;
                        case android.R.id.cut:
                            return true;
                        case android.R.id.paste:
                            return true;

                        default:
                            break;
                    }
                    return false;
                }

                @Override
                public void onDestroyActionMode(ActionMode mode) {
                }
            });
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.weare2434.sha256.MainActivity">


    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="156dp"
        android:ems="13"
        android:inputType="textMultiLine"
        android:hint="Please Input Text"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="90dp"
        android:text="No Input" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:text="calc HASH"
        android:id="@+id/button"/>
</RelativeLayout>

素晴らしすぎるコピペ元様(順不同、多謝)

JavaでSHA256を計算する - Java入門

【Android】クリップボードにコピーする - Qiita

[Android] EditText をコードで記述する - nyan のアプリ開発

【Android】EditTextでの文字の複数行入力とInputTypeの種類

GitHub

https://github.com/weare2434/sha256

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

Java SE8 Silverに合格(2020年10月)したので、まとめてみます。

スペック

  • 非ITエンジニア
  • Javaの経験は、ProgateとPaizaで触っただけ
    (他言語の勉強の時、課金していたのでもったいないお化け状態で触りました)
  • 他のプログラミング言語の取得資格
    • Ruby Silver
    • Python 3 エンジニア認定基礎試験

Java SE8 Silverを受験した理由

国家資格(ITパスポート)を取得したので、ミーハー気分で国際資格を取得したいと思ったからです。
それと、自分の中でJava Silverというのは、初心者が取得するプログラミング言語の中でも難しいイメージがあったので挑戦したかったからです。

学習教材

スッキリわかるJava入門紫本白本も教材として考えていましたが、二週間ちょっとしか時間がなかったので、問題集は黒本一択にしました。

勉強方法

Javaの経験はProgateとPaizaだけで、うろ覚えだったので「2週間でJava SE Bronzeの基礎が学べる本」を一読。お作法的なのを思い出すために読みました。
Progateの無料部分だけ少し触ったりもしました。

黒本の総仕上げ問題以外を解く。
わからなければ悩むのではなく、さっさと答えを見てコードを書いて確認しました。
各章毎に「なんとなく理解した」ではなく、6〜7割程度なら「ちゃんと理解出来た」と思えるまで、なぜそうなるのかを調べたり、コードの一部を変更したりして、仕組みの理解や知識の補完を行いました。

総仕上げ問題を解く。
これも同様に6〜7割程度なら「ちゃんと理解出来た」と思えるまで詰めました。

後は、理解の乏しいところを重点的に取り組んで理解を深めました。


上記のように書くと全く有意義な情報ではないのですが、試験問題が「黒本と同じ問題や類似した問題が多い」という記事をよく目にしたので、「黒本の内容が理解出来るようになる」のを目標に勉強すれば合格出来ると思います。
ちなみに正解率85%でした。

オブジェクト指向言語であるRubyをある程度勉強していたので理解しやすいところもありましたが、もし自分がプログラミングをしていなかったらそうとう難しいと感じていただろうなと思いました。

試験申し込みについて

色々な記事で目にしましたが、本当にややこしいです。
参考にした記事が比較的新しかったにも関わらず、サイトの仕様が変わったのか「アカウントの作成」ボタンがなかったりとか・・・。

ポイントは下記2つです

  • Oracleアカウントの作成(合否の確認のため)
  • ピアソンVUEでアカウントの作成(Java Silverの試験の申し込みのため)

受験チケットはOracleで購入出来るのですが、楽天で購入した方が安かったです。
そして試験の申し込みは「1Z0-815-JPN」にすること。(「1Z0-815」というのがあるので注意してください)

試験受付時の注意事項

顔写真を撮られます。
受付に行く前にトイレ等で身だしなみを整えるのをお勧めします。

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

JPA Thymeleaf でstatus=500エラーが出た時にやったことのメモ

環境

  • windows10
  • Eclipse
  • spring boot
  • Thymeleafテンプレートエンジン

表示されたエラー

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Thu Nov 26 21:25:14 JST 2020
There was an unexpected error (type=Internal Server Error, status=500).
An error happened during template parsing (template: "class path resource [templates/todoList.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/todoList.html]")
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:241)
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parseStandalone(AbstractMarkupTemplateParser.java:100)
    at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:666)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098)
    at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072)
    at org.thymeleaf.spring5.view.ThymeleafView.renderFragment(ThymeleafView.java:362)
    at org.thymeleaf.spring5.view.ThymeleafView.render(ThymeleafView.java:189)
    at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1394)
    at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1139)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1078)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:961)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:626)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "todo.emergency == 1 ? '★★★':'★'" (template: "todoList" - line 22, col 8)
    at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
    at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
    at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
    ... 48 more
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "todo.emergency == 1 ? '★★★':'★'" (template: "todoList" - line 22, col 8)
    at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:290)
    at org.thymeleaf.standard.expression.VariableExpression.executeVariableExpression(VariableExpression.java:166)
    at org.thymeleaf.standard.expression.SimpleExpression.executeSimple(SimpleExpression.java:66)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:109)
    at org.thymeleaf.standard.expression.Expression.execute(Expression.java:138)
    at org.thymeleaf.standard.processor.AbstractStandardExpressionAttributeTagProcessor.doProcess(AbstractStandardExpressionAttributeTagProcessor.java:144)
    at org.thymeleaf.processor.element.AbstractAttributeTagProcessor.doProcess(AbstractAttributeTagProcessor.java:74)
    at org.thymeleaf.processor.element.AbstractElementTagProcessor.process(AbstractElementTagProcessor.java:95)
    at org.thymeleaf.util.ProcessorConfigurationUtils$ElementTagProcessorWrapper.process(ProcessorConfigurationUtils.java:633)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleOpenElement(ProcessorTemplateHandler.java:1314)
    at org.thymeleaf.engine.OpenElementTag.beHandled(OpenElementTag.java:205)
    at org.thymeleaf.engine.Model.process(Model.java:282)
    at org.thymeleaf.engine.Model.process(Model.java:290)
    at org.thymeleaf.engine.IteratedGatheringModelProcessable.processIterationModel(IteratedGatheringModelProcessable.java:367)
    at org.thymeleaf.engine.IteratedGatheringModelProcessable.process(IteratedGatheringModelProcessable.java:221)
    at org.thymeleaf.engine.ProcessorTemplateHandler.handleCloseElement(ProcessorTemplateHandler.java:1640)
    at org.thymeleaf.engine.TemplateHandlerAdapterMarkupHandler.handleCloseElementEnd(TemplateHandlerAdapterMarkupHandler.java:388)
    at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler$InlineMarkupAdapterPreProcessorHandler.handleCloseElementEnd(InlinedOutputExpressionMarkupHandler.java:322)
    at org.thymeleaf.standard.inline.OutputExpressionInlinePreProcessorHandler.handleCloseElementEnd(OutputExpressionInlinePreProcessorHandler.java:220)
    at org.thymeleaf.templateparser.markup.InlinedOutputExpressionMarkupHandler.handleCloseElementEnd(InlinedOutputExpressionMarkupHandler.java:164)
    at org.attoparser.HtmlElement.handleCloseElementEnd(HtmlElement.java:169)
    at org.attoparser.HtmlMarkupHandler.handleCloseElementEnd(HtmlMarkupHandler.java:412)
    at org.attoparser.MarkupEventProcessorHandler.handleCloseElementEnd(MarkupEventProcessorHandler.java:473)
    at org.attoparser.ParsingElementMarkupUtil.parseCloseElement(ParsingElementMarkupUtil.java:201)
    at org.attoparser.MarkupParser.parseBuffer(MarkupParser.java:725)
    at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:301)
    ... 50 more
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'emergency' cannot be found on object of type 'com.example.todolist.entity.Todo' - maybe not public or not valid?
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:217)
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:104)
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.access$000(PropertyOrFieldReference.java:51)
    at org.springframework.expression.spel.ast.PropertyOrFieldReference$AccessorLValue.getValue(PropertyOrFieldReference.java:406)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:92)
    at org.springframework.expression.spel.ast.OpEQ.getValueInternal(OpEQ.java:42)
    at org.springframework.expression.spel.ast.OpEQ.getValueInternal(OpEQ.java:32)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:188)
    at org.springframework.expression.spel.ast.Ternary.getValueInternal(Ternary.java:53)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:112)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:337)
    at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:263)
    ... 75 more

もはや「何だこれ・・・」って言って途方にくれた。

参考にしたページ

https://teratail.com/questions/158177

たいていの場合、エラーが検出されたHTMLの行番号、列番号も表示されます。

ということらしいですが、上のエラーは何行あんねんって感じなので、とりあえず一番上を見ると

An error happened during template parsing (template: "class path resource [templates/todoList.html]")

って何行目なんて書いてないよね?!ってなってずっと悩んでた

ページ内検索をまずやるべきだった(;_;)

ctrl + Fでエラーページで検索をかけてみました。

lineで検索!!

ちゃんとありました!!

Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "todo.emergency == 1 ? '★★★':'★'" (template: "todoList" - line 22, col 8)

上記がヒットしました。

とりあえず書籍通りに入力できているかは確認してみたけど、できてるはずなんだけどなんで???

コメントアウトしよう!!

とりあえずline22とあるので、該当の行をコメントアウトしてページを読み直したら表示された。。

解決したわけではないけど、、、

とりあえずどこが悪いのかははっきりしたので、どこを検証すべきかがはっきりすることで前に勧めます。

エラー解決の有効手段のまとめ

  • ページ内検索でline
  • 該当行をコメントアウト

エラーが出てどうしたら良いかわからず露頭に迷っている方の参考になればと思いますm(__)m

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

BigDecimal の stripTrailingZeros 使ってる人は気をつけて

はじめに

お仕事で BigDecimal の stripTrailingZeros というメソッドを使っていて悲しくなったので記事にしました :cry:

stripTrailingZeros って何?

BigDecimal のメソッドで、末尾のゼロが削除された新しい BigDecimal を返します。

簡単にいうとこんな感じ

0.00 であれば 0
0.10 であれば 0.1

何があったのか

動作確認をしていると、 0.00% と表示されてしまうことがありました。

「あれ?ちゃんとエミュレーターでも動き確認してたのになぜ??」

調べてみると stripTrailingZeros が正常に動いてなさそう、、!

なぜ〜〜〜〜 :rolling_eyes:

原因

なんと、JDK7 だと正常に動作しないとのこと。。。

そりゃないよ。。

なので一部の Android 端末だと正常に動作しません :cry:

Google IssueTracker にあげられてました

こんな感じになります。正常に動作するものとしないものが混じってるのがほんと厄介で気付きにくいです。。

fun printStripTrailingZeros(text: String) {
  println(BigDecimal(text).stripTrailingZeros())
}

printStripTrailingZeros("1.0")
printStripTrailingZeros("0.0")
printStripTrailingZeros("0.00")
printStripTrailingZeros("0.10")

// result
// 1
// 0.0
// 0.00
// 0.1

対処方法

画面に表示する際に compareTo で比較して、BigDecimal.ZERO と等しければ 0 を表示するようにしました。
正規表現でも可能ですが、ひとまずはこれで良さそうです(めんどくさいだけ笑)

おわりに

他にも何か気をつけた方がいい点あれば教えていただけたら幸いです :hand_splayed:

みなさんも本当にお気をつけください!!

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

【Java】パッケージメモ ( 名前空間の中にあるクラスをまとめる )

パッケージ

パッケージについて学んだことのメモです。
Eclipse上でパッケージの作成をする際は過去に書いた記事があります。
【Eclipse】Javaのパッケージの作成方法

パッケージとは

パッケージとは「 ~~.class ファイル 」の集まりでを示す言葉で、「 大量のクラスを整理するための仕組み 」です。

パッケージの概念

デフォルトパッケージ

Javaはその仕様上、クラス「 ~~.class ファイル 」ファイルを必ずどこかのパッケージに収める必要があります。
その為、パッケージを明示していない(いままでのような)クラス「 ~~.class ファイル 」ファイルは
「デフォルトパッケージ」という形で取り扱っています。

名前空間

名前空間 」 とは「 対象のものを指定した名称で確実に識別・特定するめの概念 」です。

つまりは、「どんなクラスか?」みたいな、形容詞的なものです。

パッケージ

上記の 「 名前空間 」はパッケージと言う概念そのもので、
パッケージとは、名前空間の中にあるクラスをまとめる仕組みです。

身近な例えでいうと 自分のパソコンやスマホの中にある大量の画像ファイルの中から、目当ての画像ファイルを探すとなると非常に大変で時間がかかります。同じ「画像ファイル」でも

  • 家族の写真
  • 仕事の写真
  • ペットの写真
  • 旅行の写真
  • 料理の写真

といった、同じカテゴリーのなかでクラスがいっぱいあったとしても、特定するのが容易になります。

パッケージ化のメリット

パッケージを使う目的は

  • 名前空間を提供し、名前の衝突を避ける
  • アクセス制御機能を提供する
  • クラスの分類を可能にする

クラスの分類を可能にする

大量に増えた 「 ~~.class ファイル 」を目的や機能ごとに整理しておくと、目的ごとに使いやすくなり、クラスの管理も容易になります。

名前空間を提供し、名前の衝突を避ける

数多くクラスを作ると クラス名が重複することがあります
その場合、 クラス名は同名でもパッケージ名で区別することが可能 です。

パッケージが違うと、同じクラス名のクラスでも別クラスとなるため、コンパイラやJVMは名前が違っても、意図したクラスを判断してつかってくれる。

またこれにより、クラス(.class)ファイルは「パッケージ名+ファイル名」のように
名前空間(Namespace)が与えられて、名前の一意性も高まります。

パッケージの指定方法

結論このように書きます。
以下のコードを、パッケージに含めるソースの先頭部分に記述します。

package パッケージ名; 

※ import がある場合でも、パッケージ宣言はソースコードの先頭行に記述すること!

package jp.co.xxx; //importより先に記述する。
import example.hogehoge;

public class Hello(){
  System.out.println("hello world!");
}

パッケージ内のクラス、インターフェースの呼び出し

パッケージ内インターフェースの呼び出しはこちら

パッケージ名.インタフェース名

パッケージ内クラスの呼び出しはこちら

パッケージ名.クラス名
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】インターフェースについて( 変数とメソッドの型のみを定義したもの )

インターフェース

インターフェースはオブジェクト指向プログラミングに関わる重要な考え方らしいので、ちゃんと習得しておきたいところです。

インターフェースとは

Javaで使われるインターフェースとは、

クラス に含まれる メソッド の具体的な処理内容を記述せず、変数とメソッドの型のみを定義したもの 」です。

「処理の実装されていない」というのは、 { }(ブロック) 内の処理が未実装 ということです。

Javaでは、以下のような不便があります。

ですが、インタフェースをつかうことで

  • 多重継承が可能になる。
  • 配下メソッドが全部抽象 メソッド である クラス になる。
  • それぞれの メソッド は意味的に異なるインターフェースに振り分ける
  • 派生 クラス 側で必要な メソッド のみ選択できる

インターフェースの定義

インターフェースでは

  • フィールド:定数のみ宣言できる
  • メソッド:抽象メソッドのみ定義できる(暗黙的にpublic abstractで修飾される) と決まっている。
interface インターフェース名{
}

サンプルコードはこちらです。

public interface インターフェース名 {
    // 処理が実装されていないメソッド
    public void sample();
}

インターフェースの実装

「インターフェースの実装」とは、、、

  • 定義済みインターフェースを継承してクラスを定義すること
  • インターフェース実装したクラスを実装クラスという
class クラス名 implements インターフェース名 {
}

Javaのインターフェースの特徴

インターフェースの特徴として以下が挙げられる

インターフェースは インスタンス変数を持つことが出来ない

具象メソッド(抽象メソッドではないメソッド)を実装することができない

インターフェース はインスタンスフィールドを持たない

インターフェースでは、フィールド を宣言しようとするとpublic static finalのように定数を宣言したものとして扱われる。

インターフェース は具象メソッドを実装することが出来ない

抽象メソッドしか定義することが出来ず、インターフェース は実装された場合、 インスタンス が生成された場合には抽象メソッドは必ずオーバーライド される必要がある。

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

【Java】static修飾子 ( 動的メソッド / 静的メソッド )

static修飾子

static修飾子とは

インスタンス化しなくても、クラスから直接メソッドや変数にアクセスすることができるようにする修飾子」です。

用語

クラスフィールド / クラスメソッド ( staticあり )

主に、mainメソッドなどがそうですが

  • staticな メソッドを「 クラスメソッド / 静的メソッド
  • staticな 変数 を「クラスフィールド / 静的変数

とよびます。

※ クラスメソッド から 下記の「インスタンスメソッド」や「インスタンスフィールド」にアクセスはできません。

インスタンスフィールド / インスタンスメソッド ( staticなし )

  • static をつけない メソッドを「 インスタンスメソッド / 動的メソッド
  • static をつけない 変数 を「*インスタンスフィールド / 動的変数 *

※こちらはインスタンス化が必要ですが、「インスタンスクラスからクラスメソッド、クラスフィールドにアクセスすることは可能」です。

static変数

変数に static修飾子をつけると、その変数が含まれるクラスインスタンス化せずに変数にアクセスできるようになる。
static変数は「**そのクラス から生成されたすべての インスタンス で共有される値」なので、グローバル変数のように使うことができます。

static変数 の宣言と呼び出し方

static変数 の宣言の仕方はこちらです。

アクセス修飾子 static 型名 変数名

宣言した static変数, の呼び出し方はこちらです。

クラス名.変数名

※ ちなみに、クラスフィールド・メソッド を インスタンス生成した上で呼び出すと、「作らなくてもいいよ」的なワーニングが表示されます。

staticメソッド

staticメソッド とは

そのクラスでどんなにインスタンスの生成をしても、「唯一の メソッド」であることを指します。

staticメソッド の使い方

staticメソッドは、「new演算子を使ってインスタンスを生成することなく呼び出すことができる」メソッドです。

クラスフィールドと、インスタンスフィールドの違いで

  • インスタンスフィールド : インスタンスごとに値を保存できる。
  • クラスフィールド : インスタンスを複数生成しても値が共有される

ただ一つのクラスフィールドにアクセスするようになっています。

インスタンスを生成しなくてもメソッドを呼び出すことが可能なため、「 コードを短く書ける 」というメリットがあります。

staticメソッドの注意点

  • staticメソッド内で扱う変数は、全てクラス変数かローカル変数でなければならない
  • staticメソッドは、サブクラスに継承されない
  • staticメソッドは オーバーライドすることができない

参考文献・記事

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

【Java】static修飾子 ( 静的メソッド / 静的変数 )

static修飾子

static修飾子とは

インスタンス化しなくても、クラスから直接メソッドや変数にアクセスすることができるようにする修飾子」です。

用語

クラスフィールド / クラスメソッド ( staticあり )

主に、mainメソッドなどがそうですが

  • staticな メソッドを「 クラスメソッド / 静的メソッド
  • staticな 変数 を「クラスフィールド / クラス変数 / 静的変数

とよびます。

※ クラスメソッド から 下記の「インスタンスメソッド」や「インスタンスフィールド」にアクセスはできません。

インスタンスフィールド / インスタンスメソッド ( staticなし )

  • static をつけない メソッドを「 インスタンスメソッド / 動的メソッド
  • static をつけない 変数 を「 インスタンスフィールド / 動的変数

※こちらはインスタンス化が必要ですが、「インスタンスクラスからクラスメソッド、クラスフィールドにアクセスすることは可能」です。

static変数

変数に static修飾子をつけると、その変数が含まれるクラスインスタンス化せずに変数にアクセスできるようになる。
static変数は「そのクラス から生成されたすべての インスタンス で共有される値」なので、グローバル変数のように使うことができます。

static変数 の宣言と呼び出し方

static変数 の宣言の仕方はこちらです。

アクセス修飾子 static 型名 変数名

宣言した static変数, の呼び出し方はこちらです。

クラス名.変数名

※ ちなみに、クラスフィールド・メソッド を インスタンス生成した上で呼び出すと、「作らなくてもいいよ」的なワーニングが表示されます。

staticメソッド

staticメソッド とは

そのクラスでどんなにインスタンスの生成をしても、「唯一の メソッド」であることを指します。

staticメソッド の使い方

staticメソッドは、「new演算子を使ってインスタンスを生成することなく呼び出すことができる」メソッドです。

クラスフィールドと、インスタンスフィールドの違いで

  • インスタンスフィールド : インスタンスごとに値を保存できる。
  • クラスフィールド : インスタンスを複数生成しても値が共有される

ただ一つのクラスフィールドにアクセスするようになっています。

インスタンスを生成しなくてもメソッドを呼び出すことが可能なため、「 コードを短く書ける 」というメリットがあります。

staticメソッドの注意点

  • staticメソッド内で扱う変数は、全てクラス変数かローカル変数でなければならない
  • staticメソッドは、サブクラスに継承されない
  • staticメソッドは オーバーライドすることができない

参考文献・記事

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

第五回 Javaのラムダ式とは

はじめに

本記事では Java8 から追加された機能、ラムダ式について解説していきます。


概要

ラムダ式とは

Javaのラムダ式(lambda expression)とは、関数型インターフェイスを実装したクラスのインスタンスを、ごく短いコーディング量で簡単に作れる文法のことです。

ラムダ式はクラス定義とインスタンス生成をお手軽に実現する文法

Javaのラムダ式は、 関数型インターフェイス を実装したクラスのインスタンスを簡単に作るための文法です。
言い換えればクラスの宣言とインスタンスの生成を同時に行う文法です。

以下のような、何かのインターフェイスを実装したクラスがあり、そのクラスのインスタンスを生成して使いたいとします。
※ここの例では インターフェイス( java.util.Comparator )を使います。

ComparatorImpl.java
import java.util.Comparator;

class ComparatorImpl implements Comparator<String> {
  public int compare(String s1, String s2) {
    return s1.compareTo(s2);
  }
}
Sample1.java
import java.util.Comparator;

class Sample1 {
  public static void main(String[] args) {
    Comparator<String> comparator = new ComparatorImpl();
    System.out.println(comparator.compare("ABC", "DEF")); // => -3
  }
}

これを以下のように1行で書けます。ラムダ式は「 (s1, s2) -> s1.compareTo(s2) 」の部分です。
最初のプログラムと同じようにインターフェイスが持つメソッド compare が呼べていますね。

Sample1.java
import java.util.Comparator;

class Sample1 {
  public static void main(String[] args) {
    Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
    System.out.println(comparator.compare("ABC", "DEF")); // ⇒ -3
  }
}

Javaのラムダ式は、本質的にはこれだけです。
匿名クラスを定義し、そのクラスのインスタンスを生成するものです。
書き方のお作法や制限事項が少々ありますが、それは二の次です。


本来は複数行で行うクラスの定義が、なぜたった1行にできるのか。その理由はぱっと見では分からないです。
以降はその仕組みをしっかりと説明していきます。


関数型インターフェイスは、抽象メソッドを1つだけ持つ

前の説明では 関数型インターフェイス という用語をいきなり使いました。
関数型インターフェイス(functional interface) とは 抽象メソッドを1つだけ持つインターフェイス のことです。

ラムダ式を学ぶ上で絶対に忘れてはならないのは、この「1つだけ」というところ。この特徴がラムダ式の理解には必須、かつラムダ式とは不可分です。その理由は後述します。

関数型インターフェイス としては、例えば標準APIでは以下のものがあります。
また、抽象メソッドが1つだけあるインターフェイスに過ぎませんから、自分で作ることも可能です。

java.lang.Comparable
java.lang.Runnable
java.util.Comparator
java.util.concurrent.Callable
java.util.function.Consumer
java.util.function.Function
java.util.function.Predicate
java.util.function.Supplier
...など

関数型インターフェイスの例

それぞれ抽象メソッドは一つだけです。( runcompare )

java.lang.Runnable
package java.lang;

@FunctionalInterface
public interface Runnable {
  public abstract void run(); // ←これがRunnableの抽象メソッド
}
java.util.Comparator
package java.util;

import文は省略

@FunctionalInterface
public interface Comparator<T> {
  int compare(T o1, T o2); // ←これがComparatorの抽象メソッド

  以下default/staticメソッドの宣言がずっと続く
}

クラスがラムダ式になるまで

普通のクラスがラムダ式になる過程を一つずつ追っていきます。


通常クラスの場合

まずインターフェイスを実装した通常クラスから始めます。
Comparator インターフェイスを実装した通常クラス( ComparatorImpl )と
実行クラス( Sample1 )を作成します。

ComparatorImpl.java
import java.util.Comparator;

class ComparatorImpl implements Comparator<String> {
  public int compare(String s1, String s2) {
    return s1.compareTo(s2);
  }
}
Sample1.java
import java.util.Comparator;

class Sample1 {
  public static void main(String[] args) {
    Comparator<String> comparator = new ComparatorImpl();
    System.out.println(comparator.compare("ABC", "DEF"));  // => -3
  }
}

解説

インターフェイスである Comparator 、その実装クラスである ComparatorImpl 、そして実行クラスの Sample1 が登場します。
特段難しい点はありませんが、ここで実装と呼び出しを行っている compare メソッドが他で再利用する予定がなく、その場限りのメソッドであるとします。
そのような場合にわざわざ Comparator インターフェイスの実装クラスを定義し、利用するという2段階を行うことは非常に手間です。


問題点

処理自体は大した処理でもないのでコード自体は短く済みますが、似たような実装を行うメソッドを呼び出したいという場合には、実装したいメソッドの数だけ実装クラスを定義する必要があります。
これが同じクラスファイル内であれば問題は小さいように感じますが、別のクラスファイルなどに分かれている場合などには管理することも手間になります。


匿名クラスの場合

このような手間を解決する時に利用できるのが匿名クラスを用いた実装です。

Sample1.java
import java.util.Comparator;

class Sample1 {
  public static void main(String[] args) {
    Comparator<String> comparator = new Comparator<String>() {
      public int compare(String s1, String s2) {
        return s1.compareTo(s2);
      }
    };
    System.out.println(comparator.compare("ABC", "DEF")); // ⇒ -3
  }
}

クラスの定義とインスタンス生成(new)が1行になり、クラス名も消えました。
この匿名クラスのクラス名はJavaコンパイラが勝手に命名します。

これがラムダ式のベースになるものです。つまりラムダ式は特殊な書き方で匿名クラス(のようなもの)を作る文法だとも言えます。


ラムダ式の場合

最後にラムダ式を用いた実装です。
匿名クラスから更に new Comparator<String>() {}public int compare を省略してラムダ式となります。

Sample1.java
import java.util.Comparator;

class Sample1 {
  public static void main(String[] args) {
    Comparator<String> comparator = (String s1, String s2) -> { return s1.compareTo(s2); };
    System.out.println(comparator.compare("ABC", "DEF")); // ⇒ -3
  }
}

comparator 変数には Comparator<String> を実装した匿名クラスのインスタンスが代入されます。
つまり、ラムダ式とはインターフェースを実装したインスタンスを生成する式 といえます。


さらに省略したラムダ式

引数の型や処理が1文なら return{} も省略可能です。

Sample1.java
import java.util.Comparator;

class Sample1 {
  public static void main(String[] args) {
    Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
    System.out.println(comparator.compare("ABC", "DEF")); // ⇒ -3
  }
}

ラムダ式は関数型インターフェイスがあってこそ

この例で重要な役割を果たしたのは、代入演算子の左辺の変数の型 Comparator<String> です。
この型が関数型インターフェイスの決まりごとを守っているのでラムダ式が成り立ちます。

関数型インターフェイスには1つしか抽象メソッドがないので、戻り値・引数の型と順番を関数型インターフェイスの型からJavaコンパイラが推測できます。この仕組みを 型推論 といいます。


ラムダ式がコンパイルエラーになる理由

ラムダ式の裏側では、クラスの変形手順の一番最初に出てきた、関数型インターフェイスを実装した匿名クラスをJavaが自動で作り、そのクラスのインスタンスを新しく生成して戻しています。

ですから、今なら以下のラムダ式がコンパイルエラーになる理由が分かるでしょう。
なぜなら、ラムダ式の対象となる関数型インターフェイスが何か、Javaコンパイラが分からないからですね。

Sample1.java
class ComparatorImplTest {
  public static void main(String[] args) {
    (s1, s2) -> s1.compareTo(s2);
  }
}

ラムダ式の約束事

ここまでの内容で、ラムダ式とは「関数型インターフェイスを実装したクラスのインスタンスを簡単に作るための文法」だということがお分かりいただけたでしょうか。
ここからは、ラムダ式を使う上で覚えておくべきいろいろな仕様について、ざっと説明します。


ラムダ式の基本形

ラムダ式の基本形は「①引数の宣言部 -> ②抽象メソッドの本体部」です。ここで注目すべきは -> (アロー演算子)です。Javaで -> が出たなら、それはラムダ式です。

①引数の宣言部と②抽象メソッドの本体部には、以下のパターンがあります。Javaのラムダ式は、この①と②のどれかの組み合わせで作られているのです。


引数の宣言部

パターン 引数部の書き方
抽象メソッドに引数がない場合 ()
抽象メソッドの引数が1つだけの場合 (引数の型 引数の変数名)
(引数の変数名)
引数の変数名
抽象メソッドの引数が2つ以上の場合 (引数の型1 引数の変数名1, 引数の型2 引数の変数名2, …)
(引数の変数名1, 引数の変数名2, …)

どのパターンでも、必須なのは引数の変数名です。これはラムダ式を理解する上では大事なことなので、覚えておきましょう。

そして、引数の型が省略されても、その変数の型が何かはJavaコンパイラは分かっています。ですから、メソッド本体部ではその変数の型が持つフィールドやメソッドを全部使えます。

ちなみに、引数の型が書かれているなら、finalなどの修飾子や注釈(アノテーション)をつけることもできます。逆に、変数名のみだと修飾子・アノテーションは書けません。


抽象メソッドの本体部

抽象メソッドの戻り値の型 抽象メソッドの処理内容 本体部の書き方
void 処理が1行 処理
{処理;}
処理が2行以上 {処理1行目; 処理2行目; …}
void以外 処理が1行 戻り値を戻す処理
{return 戻り値を戻す処理;}
処理が2行以上 {処理1行目; 処理2行目; …; return 処理結果;}

ラムダ式ならではのことは、{}return を省略できる場合があることです。先述した、抽象メソッドを実装する上で必要最小限なことは何だろう…の視点があれば、納得できるかと思います。

{} があるならメソッドの中身は複数行で書けますし、1行だけでもOKです。普通のメソッドと同じく、改行やインデントは自由です。
{} がないなら1行しか書けませんが、最後の ; はいりません。

抽象メソッドの戻り値の型が void ではなく、さらにメソッド本体部に {} がないなら、その文や式の評価結果が戻り値の型と合っていればOKです。これはプログラムでの「評価」の仕組みが分かっていないと、ピンと来ないかもしれませんね。

return については、戻り値の型が void でも途中で return はできますし、void ではないならどこかで必ず戻り値の型を return しなければなりません。これらも普通のメソッドと同じルールです。


引数とメソッド本体の組み合わせ例

以下の例では、すべて同じことをしています。
特に注目してもらいたいのは、引数部がどう省略できるかと、メソッド本体の {}return がどういう場合に省略できるかです。


引数なし、戻り値の型がvoid

Runnable runnable0 = new Runnable() {
  public void run() {
    System.out.println("ABC");
  }
};
Runnable runnable1 = () -> System.out.println("ABC");
Runnable runnable2 = () -> {System.out.println("ABC");};

引数なし、戻り値の型がString

Callable<String> callable0 = new Callable<String>() {
  public String call() {
    return "ABC";
  }
};
Callable<String> callable1 = () -> "ABC";
Callable<String> callable2 = () -> {return "ABC";};

引数1つ、戻り値の型がboolean

Predicate<String> predicate0 = new Predicate<String>() {
  public boolean test(String s) {
    return "ABC".equals(s);
  }
};
Predicate<String> predicate1 = (String s) -> "ABC".equals(s);
Predicate<String> predicate2 = (s) -> "ABC".equals(s);
Predicate<String> predicate3 = s -> "ABC".equals(s);

引数2つ、戻り値の型がInteger

Comparator<Integer> comparator0 = new Comparator<Integer>() {
  public int compare(Integer i1, Integer i2) {
    return i1.compareTo(i2);
  }
};
Comparator<Integer> comparator1 = (Integer i1, Integer i2) -> {return i1.compareTo(i2);};
Comparator<Integer> comparator2 = (i1, i2) -> i1.compareTo(i2);
// ↓コンパイルエラー、型を書くなら、すべての引数に書かなければならない
Comparator<Integer> comparator3 = (Ingeter i1, i2) -> i1.compareTo(i2);

引数1つ、戻り値の型がInteger、メソッド本体が複数行

Function<String, Integer> function0 = new Function<String, Integer>() {
  public Integer apply(String s) {
    if ("ABC".equals(s)) {
      return 1;
    } else {
      return 0;
    }
  }
};
Function<String, Integer> function1 = (String s) -> {
  if ("ABC".equals(s)) {
    return 1;
  } else {
    return 0;
  }
};
Function<String, Integer> function2 = (s) -> {
  if ("ABC".equals(s)) {
    return 1;
  } else {
    return 0;
  }
};
Function<String, Integer> function3 = s -> {
  if ("ABC".equals(s)) {
    return 1;
  } else {
    return 0;
  }
};

まとめ

Javaのラムダ式とは、関数型インターフェイスを実装したクラスのインスタンスを、ごく短いコーディング量で簡単に作れる文法

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

【Java・SpringBoot・Thymeleaf】バリデーションチェックの順番を設定(SpringBootアプリケーション実践編7)

ログインをして、ユーザー一覧を表示するアプリケーションを作成し、
Springでの開発について勉強していきます?
前回は入力項目毎にエラーメッセージを表示し、バリデーションのチェックを全て同時に実行しました。
今回はバリデーションチェックの順番を設定します^^

前回の記事?
【Java・SpringBoot・Thymeleaf】エラーメッセージを個別に表示(SpringBootアプリケーション実践編6)

入力チェックのグループを設定

  • 入力チェックに引っかかった場合に処理を途中で止めることができる!
  • バリデーションでグループを作成するためにインターフェースを作成
  • 構成は以下のようになっています
Project Root
└─src
    └─ main
        └─ java  
            └─ com.example.demo
                └─ login
                    └─ controller                ...コントローラクラス用パッケージ
                └─ domain                        ...ビジネスロジック用パッケージ
                    └─ model                     ...Modelクラス用パッケージ
                        └─ LoginForm.java
                        └─ SignupForm.java
                        └─ ValidGroup1.java
                        └─ ValidGroup2.java
                        └─ ValidGroup3.java
                        └─ GroupOrder.java

インターフェースの用意

  • 中身は空のインターフェースを作ります
    • ValidGroup2,3も同様
    • IntelliJはコピーペーストでいい感じに修正してくれます
ValidGroup1.java
package com.example.demo.login.domain.model;

public interface ValidGroup1 {

}

グループ実行順を決めるインターフェースを用意

  • 実行順を設定するインターフェースに@GroupSequenceアノテーションをつけ、パラメータに各グループの.classを指定
  • 指定順にバリデーションが実行される
GroupOrder.java
package com.example.demo.login.domain.model;

import javax.validation.GroupSequence;

@GroupSequence({ ValidGroup1.class, ValidGroup2.class, ValidGroup3.class })
public interface GroupOrder {

}

コントローラークラスを編集

  • @Validatedアノテーションのパラメーターに、実行順序のインターフェースを指定し、バリデーションをグループ実行
  • グループのインターフェースを直接指定することも可能(ValidGroup1.classなど)
    • コードの全文は下記参考
SignupController.java
public String postSignUp(@ModelAttribute @Validated(GroupOrder.class) SignupForm form,
                             BindingResult bindingResult,
                             Model model) {...}

各フィールドをどのグループにするか指定

  • groups属性にインターフェースのクラスを指定し、フィールドとグループの紐付けができる
    • コードの全文は下記参考
SignupForm.java
@NotBlank(groups = ValidGroup1.class)

ユーザー登録画面確認!

  • http://localhost:8080/signup
  • ユーザー登録画面で何も入力せずユーザー登録ボタンを押すと、エラーメッセージが表示される
    • まずは必須入力チェックが行われた
  • 必須入力を入力したら、次に中身のチェックを行う
  • エラーメッセージを順番に実行することができました〜〜o(^^)o

エラー2.png

エラー1.png

(参考)コード全文

SignupController.java
package com.example.demo.login.controller;

import java.util.LinkedHashMap;
import java.util.Map;

import com.example.demo.login.domain.model.GroupOrder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.demo.login.domain.model.SignupForm;

@Controller
public class SignupController {

    //ラジオボタン用変数
    private Map<String, String> radioMarriage;

    //ラジオボタンの初期化メソッド
    private Map<String, String> initRadioMarrige() {
        Map<String, String> radio = new LinkedHashMap<>();
        // 既婚、未婚をMapに格納
        radio.put("既婚", "true");
        radio.put("未婚", "false");
        return radio;
    }

    //ユーザー登録画面のGET用コントローラ
    @GetMapping("/signup")
    public String getSignUp(@ModelAttribute SignupForm form, Model model) {

        // ラジオボタンの初期化メソッド呼び出し
        radioMarriage = initRadioMarrige();
        // ラジオボタン用のMapをModelに登録
        model.addAttribute("radioMarriage", radioMarriage);
        // signup.htmlに画面遷移
        return "login/signup";
    }

    //ユーザー登録画面のPOST用コントローラ
    //バリデーション実装
    @PostMapping("/signup")
    public String postSignUp(@ModelAttribute @Validated(GroupOrder.class) SignupForm form,
                             BindingResult bindingResult,
                             Model model) {

        // 入力チェックに引っかかった場合、ユーザー登録画面に戻る
        if (bindingResult.hasErrors()) {
            // GETリクエスト用のメソッドを呼び出して、ユーザー登録画面に戻る
            return getSignUp(form, model);
        }

        // formの中身をコンソールで確認
        System.out.println(form);
        // login.htmlにリダイレクト
        return "redirect:/login";
    }
}
SignupForm.java
package com.example.demo.login.domain.model;

import java.util.Date;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;

import lombok.Data;

@Data
public class SignupForm {

    //必須入力、メールアドレス形式
    @NotBlank(groups = ValidGroup1.class)
    @Email(groups = ValidGroup2.class)
    private String userId; // ユーザーID

    //必須入力、長さ4から100桁まで、半角英数字のみ
    @NotBlank(groups = ValidGroup1.class)
    @Length(groups = ValidGroup2.class)
    @Pattern(regexp = "^[a-zA-Z0-9]+$", groups = ValidGroup3.class)
    private String password; // パスワード

    //必須入力
    @NotBlank(groups = ValidGroup1.class)
    private String userName; // ユーザー名

    //必須入力
    @NotNull(groups = ValidGroup1.class)
    @DateTimeFormat(pattern = "yyyy/MM/dd")
    private Date birthday; // 誕生日

    //値が20から100まで
    @Min(value = 20, groups = ValidGroup2.class)
    @Max(value = 100, groups = ValidGroup2.class)
    private int age; // 年齢

    //falseのみ可能
    @AssertFalse(groups = ValidGroup2.class)
    private boolean marriage; // 結婚ステータス
}

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

【Java】final修飾子 ( 定数化・継承禁止、オーバーライド禁止 )

final修飾子

final修飾子 は「変数 を 定数 に変えたり、元のクラスやメソッドの機能を変えないための修飾子。」です。

具体的に言うと

といった使い方ができます。

固定にする理由

  • 後から修正がかかった際、一か所を直せば済むから
  • ソースの可読性が高まるから
  • 変更されたくない値を変更されない様にするから

固定にするもの

メソッドのパラメーターや、メソッド のローカル変数 において、
条件分岐ごとの処理による値の書き換えの可能性が無いような場合は、 final で定義するほうがよいです。

具体的に「どんなものを定数にするか」ということですが、例を上げると

  • 固定の文字列
  • ダイアログなどのメッセージ
  • サイトのURL
  • 税率のような基本的に変わらない値

などがパッと思いつきました。

定数を定義する

「変数として扱う必要がない場合は、「 final 」をつける癖をつける。」
ソースを書いていて何度も使うものは変数にしますが、その中でも「固定にするべき」ものは、定数化しましょう。

  • 変数は「値が変わるもの」
  • 定数は「値が固定であるもの」

定数の命名規則 (SNAKE_CASE)

「SNAKE_CASE(スネークケース記法: すべて大文字とアンダースコア)でかくこと。」

定数は 変数特別するため、「 MY_NAME 」のような、すべて大文字で単語の書き方をしてください。
もちろん予約語はつかわないように。
暗黙的に「 public static finalで修飾される 」とセットで覚えておくといいかもしれない。

【定数を定義したクラス】

class ClassFinal {
    // 変数に final を付与して 定数化。(大文字にすること)
    public static final String MESSAGE = "定数です";

}

【定数を上書きしようとするクラス】

class FinalMain {
    public static void main(String[] args) {
        ClassFinal.MESSAGE = "定数を変えようとするとエラーになります";
    }
}

【実行結果】

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    final フィールド ClassFinal.MESSAGE には代入できません

定数を定義する場所

個々のファイルへ定義するより、「CommonConstant.java」みたいな、定数専用のクラスを用意して、まとめて定義することが多い。

クラスにつける

クラスにfinal修飾子を付けると、
そのクラスは継承することが出来なくなり、コンパイルするとエラーとなります。

【finalにした親クラス】

final class ClassFinal {
    String message = "finalクラスです";
    public final void printMessage() {
        System.out.println(this.message);
    }
}

【継承しようとする子クラス】

public class ClassFinalChild extends ClassFinal {
    //処理 ...
}

【呼び出そうとするクラス】

class FinalMain {
    public static void main(String[] args) {
        ClassFinalChild cfc = new ClassFinalChild();
    }

}

【実行結果】

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    型 ClassFinalChild は final クラス ClassFinal をサブクラス化できません

メソッドにつける (オーバーライド)

メソッド につけると、そのメソッドは子クラスでのオーバーライドができなくなります。

【メソッドをfinalにした親クラス】

class ClassFinal {
    String message = "finalクラスです";
    //メソッドに final を付与
    public final void printMessage() {
        System.out.println(this.message);
    }
}

【継承しようとする子クラス】

public class ClassFinalChild extends ClassFinal {
    public void printMessage() {
        System.out.println(this.message + "をオーバーライドします");
    }
}

【呼び出そうとするクラス】

class FinalMain {
    public static void main(String[] args) {
        ClassFinalChild cfc = new ClassFinalChild();
    }

}

【実行結果】

スレッド "main" での例外 java.lang.VerifyError: クラス practice_final_and_statc.ClassFinalChild は、最終メソッド practice_final_and_statc.ClassFinal.printMessage()V をオーバーライドします。

参考文献・記事

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

【Java・SpringBoot・Thymeleaf】エラーメッセージを個別に表示(SpringBootアプリケーション実践編6)

ログインをして、ユーザー一覧を表示するアプリケーションを作成し、
Springでの開発について勉強していきます?
前回の日本語でのエラー表示に引き続きエラーメッセージを個別に表示する実装をします

前回の記事?
【Java・SpringBoot・Thymeleaf】日本語でエラーを表示(SpringBootアプリケーション実践編5)

エラーメッセージを個別に表示

  • htmlファイルを修正して、エラーの場合だけCSSを変更して、テキストボックスを赤くします

エラー用CSSの追加

  • th:classappend属性でCSSのclassを追加する
<div class="form-group" th:classappend="${#fields.hasErrors('userId')} ? 'has-error'">
  • エラー時だけclass属性にhaserrorを追加
  • haserrorはBootstrapのclassで、テキストボックスの枠を赤くする
    • ${#fields.hasErrors('<フィールド名>')}?'haserror'で、trueかfalseかを判定
    • trueの場合のみhaserror classを追加

個別エラーメッセージの表示

  • th:if属性に条件式を設定
    • trueの場合のみ、そのタグが表示される
  • th:if="${#fields.hasErrors('<フィールド名>')}" th:errors="∗{<フィールド名>}"
<span class="text-danger" 
    th:if="${#fields.hasErrors('userId')}" 
    th:errors="*{userId}"> userId error 
</span>
  • 上の例では、th:if属性でフィールドのバリデーションでエラーがあるとtrueを返し、th:errors属性で各フィールドのエラーメッセージを表示する
signup.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"></meta>

    <!-- Bootstrapの設定 -->
    <link th:href="@{/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css}" rel="stylesheet"></link>
    <script th:src="@{/webjars/jquery/1.11.1/jquery.min.js}"></script>
    <script th:src="@{/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js}"></script>

    <title>SignUp</title>
</head>
<body>
<div class="col-sm-5">
    <div class="page-header">
        <h1>ユーザー登録画面</h1>
    </div>
    <form method="post" th:action="@{/signup}" th:object="${signupForm}">
        <table class="table table-bordered table-hover">
            <!-- ユーザーIDの入力エリア -->
            <tr>
                <th class="active col-sm-3">ユーザID</th>
                <td>
                    <!--エラー用CSSの追加-->
                    <div class="form-group"
                         th:classappend="${#fields.hasErrors('userId')} ? 'has-error'">
                        <input type="text" class="form-control"
                               th:field="*{userId}" />
                        <!--個別エラーメッセージ表示-->
                        <span class="text-danger"
                              th:if="${#fields.hasErrors('userId')}"
                              th:errors="*{userId}">
                                userId error
                            </span>
                    </div>
                </td>
            </tr>
            <!-- パスワードの入力エリア -->
            <tr>
                <th class="active">パスワード</th>
                <td>
                    <div class="form-group"
                         th:classappend="${#fields.hasErrors('password')} ? 'has-error'">
                        <input type="text" class="form-control"
                               th:field="*{password}" />
                        <span class="text-danger"
                              th:if="${#fields.hasErrors('password')}"
                              th:errors="*{password}">
                                password error
                            </span>
                    </div>
                </td>
            </tr>
            <!-- ユーザー名の入力エリア -->
            <tr>
                <th class="active">ユーザー名</th>
                <td>
                    <div class="form-group"
                         th:classappend="${#fields.hasErrors('userName')} ? 'has-error'">
                        <input type="text" class="form-control"
                               th:field="*{userName}" />
                        <span class="text-danger"
                              th:if="${#fields.hasErrors('userName')}"
                              th:errors="*{userName}">
                                userName error
                            </span>
                    </div>
                </td>
            </tr>
            <!-- 誕生日の入力エリア -->
            <tr>
                <th class="active">誕生日</th>
                <td>
                    <div class="form-group"
                         th:classappend="${#fields.hasErrors('birthday')} ? 'has-error'">
                        <input type="text" class="form-control" placeholder="yyyy/MM/dd"
                               th:field="*{birthday}"/>
                        <span class="text-danger"
                              th:if="${#fields.hasErrors('birthday')}"
                              th:errors="*{birthday}">
                                birthday error
                            </span>
                    </div>
                </td>
            </tr>
            <!-- 年齢の入力エリア -->
            <tr>
                <th class="active">年齢</th>
                <td>
                    <div class="form-group"
                         th:classappend="${#fields.hasErrors('age')} ? 'has-error'">
                        <input type="text" class="form-control"
                               th:field="*{age}" />
                        <span class="text-danger"
                              th:if="${#fields.hasErrors('age')}"
                              th:errors="*{age}">
                                age error
                            </span>
                    </div>
                </td>
            </tr>
            <!-- 結婚ステータスの入力エリア -->
            <tr>
                <th class="active">結婚</th>
                <td>
                    <div class="form-group">
                        <div th:each="item : ${radioMarriage}">
                            <input type="radio" name="radioMarrige"
                                   th:text="${item.key}"
                                   th:value="${item.value}"
                                   th:field="*{marriage}">
                            </input>
                        </div>
                        <span class="text-danger"
                              th:if="${#fields.hasErrors('marriage')}"
                              th:errors="*{marriage}">
                                marriage error
                            </span>
                    </div>
                </td>
            </tr>
        </table>
        <!-- エラーメッセージの一覧表示 -->
        <ul>
            <li th:each="error : ${#fields.detailedErrors()}">
                <span th:text="${error.message}">Error message</span>
            </li>
        </ul>
        <!-- ユーザー登録ボタン -->
        <button class="btn btn-primary" type="submit">ユーザー登録</button>
    </form>
</div>
</body>
</html>

SpringBootを起動し、ユーザー登録画面を確認!

  • http://localhost:8080/signup
  • ユーザー登録画面で、何も入力せずにユーザー登録ボタンをクリック
  • 入力項目毎にエラーメッセージを表示することができました〜〜〜p(^_^)q
  • このままでは、バリデーションのチェックが全て同時に実行されていて、パスワードに何も入力してないのにエラーメッセージが複数表示されてしまっている。。。
  • 次回はバリデーションチェックの順番を設定します^^

個別エラー.png

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

【Java・SpringBoot・Thymeleaf】日本語でエラーを表示(SpringBootアプリケーション実践編5)

ログインをして、ユーザー一覧を表示するアプリケーションを作成し、
Springでの開発について勉強していきます?
前回のエラーメッセージ実装に引き続きバリデーションエラーメッセージを編集して日本語化します

前回の記事?
【Java・SpringBoot・Thymeleaf】バリデーションエラーメッセージを変更(SpringBootアプリケーション実践編4)

英語のメッセージを編集

  • バリデーションのエラーメッセージを編集するには、messages.propertiesにエラーメッセージを設定
  • エラーメッセージ設定方法
    • ①:<アノテーション名>..<フィールド名>=<エラーメッセージ>
    • ②:<アノテーション名>.<フィールド名>=<エラーメッセージ>
    • ③:<アノテーション名>.<フィールドのデータ型>=<エラーメッセージ>
    • ④:<アノテーション名>=<エラーメッセージ>
    • ⑤:<独自のキー名>=<エラーメッセージ>
      • ⑤:フォームクラスのフィールドと、独自のキーを紐付けなければいけないので、フォームクラスの修正が必要
      • ①~④:Springが自動でフォームクラスとメッセージの紐付けを行ってくれる
  • 以下でそれぞれ使い方を確認します?

パターン①

  • <アノテーション名>.<ModelAttribute名>.<フィールド名>=<エラーメッセージ>

フィールド名の表示

  • エラーメッセージの中にフィールド名を含めたい場合、{0}とメッセージ内に入力することで、フォームクラスのフィールド名を出してくれる
  • フィールド名がuserIdの時は、userIdを入力してくださいと表示されます

メッセージ変更

  • 変更したいときは、<Model.Attribute名>.<フィールド名>=<日本語のフィールド名>で設定できる
    • signupForm.userId=ユーザーIDの場合、エラーメッセージは
    • ユーザーIDを入力してくださいと表示される
    • (パターン③~⑤でも使える)

パラメーターの表示

  • エラーメッセージの中に、パラメーターを表示したいときは
    • ex: パスワードは4桁以上、100桁以下で入力してくださいなど
    • フィールド名{0}以降の数値を指定することで、各パラメーターの値をメッセージに含めることができる
      • Length.signupForm.password={0}は、{2}桁以上、{1}桁以下で入力してください
      • SignupFormのpasswordフィールドに@Length(min=4,max=100)というアノテーションを設定
      • →maxが{1}、minが{2}に入る
  • パラメーターを修正してもメッセージに反映されるので、変更に強くなる!
messages.properties
#ユーザーID
signupForm.userId=ユーザーID
NotBlank.signupForm.userId={0}を入力してください
Email.signupForm.userId={0}にはメールアドレスを入力してください

#パスワード
signupForm.password=パスワード
NotBlank.signupForm.password={0}を入力してください
Length.signupForm.password={0}は、{2}桁以上、{1}桁以下で入力してください
Pattern.signupForm.password={0}は半角英数字で入力してください

#ユーザー名
signupForm.userName=ユーザー名
NotBlank.signupForm.userName={0}を入力してください

#誕生日
signupForm.birthday=誕生日
NotNull.signupForm.birthday={0}を入力してください

#年齢
signupForm.age=年齢
Min.signupForm.age={0}は{1}以上を入力してください
Max.signupForm.age={0}は{1}以下を入力してください

#結婚ステータス
AssertFalse.signupForm.marriage=未婚の方のみ登録できます

パターン②

  • <アノテーションクラス名>.<フィールド名>=<エラーメッセージ>
  • アノテーション名とフィールド名だけを書く
  • 他のフォームクラスで同じフィールド名を使用した場合、同じメッセージが適用される
messages.properties
#ユーザーID
userId=ユーザーID
NotBlank.userId={0}を入力してください(パターン2)
Email.userId={0}にはメールアドレスを入力してください(パターン2)

#パスワード
password=パスワード
NotBlank.password={0}を入力してください(パターン2)
Length.password={0}は、{2}桁以上、{1}桁以下で入力してください(パターン2)
Pattern.password={0}は半角英数字で入力してください(パターン2)

#ユーザー名
userName=ユーザー名
NotBlank.userName={0}を入力してください(パターン2)

#誕生日
birthday=誕生日
NotNull.birthday={0}を入力してください(パターン2)

#年齢
age=年齢
Min.age={0}は{1}以上を入力してください(パターン2)
Max.age={0}は{1}以下を入力してください(パターン2)

#結婚ステータス
AssertFalse.marriage=未婚の方のみ登録できます(パターン2)

パターン③

  • <アノテーション名>.<フィールドのデータ型>=<エラーメッセージ>
  • データ型に対してメッセージが紐付けられます。
messages.properties
#フィールド名
userId=ユーザーID
password=パスワード
userName=ユーザー名
birthday=誕生日
age=年齢

#バリデーションエラーメッセージ
NotBlank.java.lang.String={0}は必須入力です(パターン3)
Email.java.lang.String={0}はメールアドレス形式で入力してください(パターン3)
Length.java.lang.String={0}は、{2}桁以上、{1}桁以下で入力してください(パターン3)
Pattern.java.lang.String={0}は半角英数字で入力してください(パターン3)
NotBlank.java.lang.String={0}は必須入力です(パターン3)
NotNull.java.util.Date={0}を入力してください(パターン3)
Min.int={0}は{1}以上を入力してください(パターン3)
Max.int={0}は{1}以下を入力してください(パターン3)
AssertFalse.boolean=未婚の方のみ登録できます(パターン3)

パターン④

  • <アノテーション名>=<エラーメッセージ>
  • アノテーションに対してメッセージを紐付ける
messages.properties
#フィールド名
userId=ユーザーID
password=パスワード
userName=ユーザー名
birthday=誕生日
age=年齢

#バリデーションエラーメッセージ
NotBlank={0}は必須入力です(パターン4)
Email={0}はメールアドレス形式で入力してください(パターン4)
Length={0}は、{2}桁以上、{1}桁以下で入力してください(パターン4)
Pattern={0}は半角英数字で入力してください(パターン4)
NotBlank={0}は必須入力です(パターン4)
NotNull={0}は必須入力です(パターン4)
Min={0}は{1}以上を入力してください(パターン4)
Max={0}は{1}以下を入力してください(パターン4)
AssertFalse=falseのみ登録できます(パターン4)

パターン⑤

  • <独自のキー名>=<エラーメッセージ>
    • パターン①~④は、messages.properties編集のみでエラーメッセージを編集できたがパターン⑤の場合、コードを修正する
  • 独自のキーを設定する場合、各アノテーションと、独自キーの紐付けが必要になる
  • アノテーションにmessage属性を付けると、message.propertiesの独自キーと紐付けることができる
    • message="{<独自キー名>}"
    • @NotBlank(message = "{require_check}")
    • {}を付けない場合、設定した文字列がエラーメッセージとして表示される
  • 直接エラーメッセージを指定する場合は
@NotBlank(message="パスワードを入力してください")
String password;
SignupForm.java
package com.example.demo.login.domain.model;
import java.util.Date;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;

@Data
public class SignupForm {
    //入力形式、メールアドレス形式
    @NotBlank(message = "{require_check}")
    @Email(message = "{email_check}")
    private String userId; //ユーザーID

    //入力必須、長さ4-100桁まで、半角英数字のみ
    @NotBlank(message = "{require_check}")
    @Length(min= 4 ,max = 100, message = "{length_check}")
    @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "{pattern_check}")
    private String password; //パスワード

    //入力必須
    @NotBlank(message = "{require_check}")
    private String userName; //ユーザー名

    //入力必須
    @NotNull(message = "{require_check}")
    @DateTimeFormat(pattern = "yyyy/MM/dd")
    private Date birthday; //誕生日

    //値は20-100
    @Min(value=20, message = "{min_check}")
    @Max(value=100, message = "{max_check}")
    private int age; //年齢

    //falseのみ可能
    @AssertFalse(message = "{false_check}")
    private boolean marriage; //結婚ステータス

}
  • 独自キーを使う場合はValidationMessages.propertiesというファイルにメッセージを書く必要があるが、以下のmessages.propertiesファイルでも独自キーを使えるように設定する
  • 独自キーを使う場合、デフォルトのままでは日本語を使うと文字化けするため、文字コードの設定も必要

messages.propertiesを編集

  • ユーザー登録用のフォームクラスに@NotNull(message="{require_check}")を追加した
  • message="{<キー名>}"に当たる部分とメッセージを、以下のように設定
    • require_check={0}は必須入力です(パターン5)
messages.properties
#フィールド名
userId=ユーザーID
password=パスワード
userName=ユーザー名
birthday=誕生日
age=年齢

#必須入力チェック
require_check={0}は必須入力です(パターン5)
#メールアドレス形式チェック
email_check={0}はメールアドレス形式で入力してください(パターン5)
#入力文字数チェック
length_check={0}は、{2}桁以上、{1}桁以下の桁数で入力してください(パターン5)
#フォーマットチェック
pattern_check={0}は半角英数字で入力してください(パターン5)
#下限値チェック
min_check={0}は{1}以上を入力してください(パターン5)
#上限値チェック
max_check={0}は{1}以下を入力してください(パターン5)
#ステータスチェック
false_check=falseの場合のみ登録できます(パターン5)

WebConfig.javaを作成

  • フォームクラスとメッセージの紐付けを行う
WebConfig.java
package com.example.demo;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class WebConfig {

    @Bean
    public MessageSource messageSource() {

        ReloadableResourceBundleMessageSource bean = new ReloadableResourceBundleMessageSource();
        //メッセージのプロパティファイル名(デフォルト)を指定します
        //下記ではmessages.propertiesファイルがセットされます
        bean.setBasename("classpath:messages");
        //メッセージプロパティの文字コードを指定します
        bean.setDefaultEncoding("UTF-8");
        return bean;
    }

    @Bean
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
        localValidatorFactoryBean.setValidationMessageSource(messageSource());
        return localValidatorFactoryBean;
    }
}


画面確認!

  • http://localhost:8080/login
  • ユーザー登録画面で、何も入力せずにユーザー登録ボタンをクリック
  • これで、入力チェックのエラーメッセージを日本語化できました〜〜〜
  • エラーメッセージを一覧表示するより、どこが間違っているのかを個別に表示した方が分かりやすいので、次回エラーメッセージを個別に表示する実装をします^^

日本語エラー.png

  • (補足)プロパティファイルにメッセージを定義するのではなく、ソースコードにエラーメッセージを直接指定することもできます
@NotBlank(message="必須入力です")
private String userId;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】this と super 復習(エラー・コーディング規則)

this と super の復習

this と super の使い方を知る

基本的には thissuper を使用する際の軸となる考え方は一緒で、主に下記のような場合に使用します。

クラスインスタンスフィールド変数メソッド の前に付与して

this と super の挙動

こちらのコードをもとに、見ていきます。

【GroupWorker.java】

package practiceThisAndSuper;

//「団員」という概念のクラス
public class GroupWorker {
    /** empId: 団員番号 */
    private int groupId;

    /** name: 氏名 */
    private String name;

    /** コンストラクタ: 引数なし */
    public GroupWorker() {
    }

    public GroupWorker(int groupId, String name) {
        this.groupId = groupId;
        this.name = name;
    }

    // ... フィールド変数のゲッターとセッター(※省きます)

    // Getter Setter -------------------------------

    // Getter
    public int getGroupId() {
        return this.groupId;
    }

    // Setter
    public void setGroupId(int groupId) {
        this.groupId = groupId;
    }

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

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

    // Getter Setter -------------------------------
    /**
     * 団員情報をコンソールへ出力するメソッド
     */
    public void printGroupWorker() {
        System.out.println("団員番号: " + this.groupId);
        System.out.println("氏名  : " + this.name);
    }
}


【RocketGroupWorker.java】

package practiceThisAndSuper;

//ロケット団員クラス
class RocketGroupWorker extends GroupWorker {
    /** isExecutive: 幹部フラグ(幹部であるかどうかを true/false で判断するためのフィールド変数) */
    boolean isExecutive;

    public RocketGroupWorker() {
    }

    /**
     * コンストラクタ: 引数あり
     *
     * @param groupId
     * @param name
     */
    public RocketGroupWorker(int groupId, String name) {
        // ①挙動確認: 親クラス(GroupWorker)のコンストラクタを呼び出す
        super(groupId, name);

        // ③挙動確認: thisを指定せずに格納する その1
        boolean isExecutive;
        if (groupId != 10) {
            isExecutive = true;
        } else {
            isExecutive = false;
        }
        System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
    }

    /**
     * コンストラクタ: 引数あり
     *
     * @param groupId
     * @param name
     * @param isExecutive
     */
    public RocketGroupWorker(int groupId, String name, boolean isExecutive) {

        // ①挙動確認: 親クラス(GroupWorker)のコンストラクタを呼び出す
        super(groupId, name);

        // ②挙動確認: 自クラスのフィールド変数(isExecutive)へコンストラクタの引数を格納する
        this.isExecutive = isExecutive;

        // ③挙動確認: thisを指定せずに格納する その2
        isExecutive = true;
        System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");

        // ④挙動確認: thisとsuperで同じメソッドを呼び出す
        this.printGroupWorker();
        super.printGroupWorker();
    }

    // ... フィールド変数のゲッターとセッター(※省きます)
    // Getter
    public boolean getIsExecutive() {
        return this.isExecutive;
    }

    // Setter
    public void setIsExecutive(boolean isExecutive) {
        this.isExecutive = isExecutive;
    }
}

①挙動確認: 親クラス(GroupWorker)のコンストラクタを呼び出す

//①挙動確認: 親クラス(GroupWorker)のコンストラクタを呼び出す
super(groupId, name);

ポピュラーの使用方法で、「特に異なる 初期化処理 などを行う必要がない場合」はこれ。

super の実体は、「GroupWorker.java(親クラス)」
「RocketGroupWorker.java」 の コンストラクタ(二箇所)で呼び出していますが、
継承先で オーバーライド せずに親クラスコンストラクタ を利用しています。

②挙動確認: 自クラスのフィールド変数(isExecutive)へコンストラクタの引数を格納する

// ②挙動確認: 自クラスのフィールド変数(isExecutive)へコンストラクタの引数を格納する
this.isExecutive = isExecutive;

this の基本の使い方「ローカル変数とフィールド変数を明確に区別するため」。

例では、フィールド 変数の isExecutive と 引数の isExecutive を明確に区別した使用法で、this を付与して正しい値の受け渡しを行えています。

③挙動確認: thisを指定せずに格納する

自クラスの フィールド変数 なのか、ローカル変数/引数なのかが分かりづらい

this を付与していないことが原因で ローカル変数/引数の値を変更してしまっています。

この場合、起こりうる結果としては以下になります。

【その1の場合】
フィールド変数へ値を格納することを意図していたとしても、実際に値が格納されるのはローカル変数

        // ③挙動確認: thisを指定せずに格納する その1
        boolean isExecutive;
        if (groupId != 10) {
            isExecutive = true;
        } else {
            isExecutive = false;
        }
        System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");

【その2の場合】

  • フィールド変数へ値を格納することを意図していたとしても、実際に値が格納されるのは引数
  • 仮に②と③の処理順が逆であった場合でも、実際に値が格納されるのは引数になる
// ③挙動確認: thisを指定せずに格納する その1
        boolean isExecutive;
        if (groupId <= 10) {
            isExecutive = true;
        } else {
            isExecutive = false;
        }
        System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");
                 .....
                 .....
                 .....
                 .....

// ③挙動確認: thisを指定せずに格納する その2
        isExecutive = true;
        System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");

何故かというとコンパイル時には以下のような「優先順位が存在するから」です。

「ローカル変数/メソッドの引数」 > 「フィールド変数」

ローカル変数と メソッド引数 が同名の場合 は、コンパイル時に ワーニングが 表示されます ので即時対応が可能なのですが、
フィールド変数 とローカル変数(or メソッド引数)の場合 は、 ワーニングは 表示されません。

// mainメソッドで 定義
RocketGroupWorker be = new RocketGroupWorker(1000, "ムサシ");


// ③挙動確認: thisを指定せずに格納する その1
        boolean isExecutive;
        if (groupId != 10) {
            isExecutive = true;
        } else {
            isExecutive = false;
        }
        System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");

出力結果

幹部ではない

boolean 型の値に関しては、初期値が false となってます。

そのため、この場合は フィールド変数( isExecutive )の値と、 条件分岐後の格納値は一致しているため、
初期化時に期待する結果(フィールド変数の isExecutive が false であること)は変わりません。

isExecutive be = new isExecutive(1, "サカキ");のように コンストラクタ を呼び出した場合は、
条件分岐の結果true が格納されてしまいますので、フィールド変数( isExecutive )は意図した初期化ができません。

上記はフィールド変数に値を格納したいはずなので、

  • this を付与をする
  • 同名のローカル変数は避ける

といったことをやるべきです。

「一時的に同名のローカル変数」を用意したい場合

2パターン挙げます

1. 頭文字に「一時的な」という意味の「Temporary」を使用する

boolean tmpIsExecutive;
        if (groupId != 10) {
            tmpIsExecutive = true;
        } else {
            isExecutive = false;
        }
this.isExecutive = tmpIsExecutive;

2. 一番スッキリ書く方法

一番ベストの書き方はこちら
こちらの書き方がベストですが、最初のうちは 1の if と elseのブロックの考え方を意識すること。

// (groupId != 10)の結果値である true/false が戻り値として格納される
this.isExecutive  = (groupId != 10);

実行結果

...
isExecutive = true;
System.out.println(this.isExecutive ? "幹部です" : "幹部ではない");

出力結果

幹部ではない

この処理において isExecutive = true; が、
this.isExecutive = true; を意図してコーディングしたものである場合は話が変わってきます。

④挙動確認: thisとsuperで同じメソッドを呼び出す

こちらの

  • this.printGroupWorker();
  • super.printGroupWorker();

両方共、親 クラス である GroupWorker の printGroupWorker() を呼び出しています。
言い方を変えればオーバーライドすることにより、上記の thissuper の呼び出し先は変わるということ。

        // ④挙動確認: thisとsuperで同じメソッドを呼び出す
        this.printGroupWorker();
        super.printGroupWorker();

RocketGroupWorker クラスで printGroupWorker() をオーバーライドする

/**
 * 団員情報をコンソールへ出力するメソッド(幹部フラグを含む)
 */
public void printGroupWorker() {
    super.printGroupWorker();
    System.out.println("幹部フラグ: " + this.isExecutive);
}

上記のようにオーバーライドした場合、 **「this は自クラスオーバーライドした printGroupWorker() を参照する。」**

命名規則に関して重要なポイント

「値を受け渡す元と先の 変数に関して、意味的にも値として同じ場合、同様の命名規則」にしてください。

実装時はそのクラスの内容に適した 変数名・メソッドやメソッド名を実装していく訳ですが、要するに「疑問を招くような命名をするな」ということ。

例:「 name 」という変数に「 namae 」という[引数]が格納される

ですが、以下のように変数名と引数名がことなっていても、「同じ値がはいっていますよ」と明示したような引数名なら許容度です。

// parm は parameter(パラメーター)の意味
this.age = parmAge;
this.name = parmName;

ただ結論、this を付与する方向でコーディングしたほうが良い」です。

thisをつけることによって、コード量が大量になって、似たような命名の変数やメソッドを使用するなった場合であっても、目当てのコードが探しやすくなります。

 thisやsuperが許容されないケース

【ケース1】コンストラクタの前に処理が存在する

コンストラクタ内の処理として thissuper を使用する場合は、処理の一番時最初に記述しなければなりません。

    public BlueEmployee(int empId, String name) {
        boolean isExecutive = (groupId == 1);
                this(groupId, name); // ここでエラー
        super(groupId, name); // ここでエラー

この場合、以下のようなエラーが表示されます。

コンストラクター呼び出しは、コンストラクター内の最初のステートメントである必要があります。

このような場合はエラーが吐かれて実行は不可ですので、上記のようなエラーの時はチェックしてみましょう。

【ケース2】static修飾子ついている

static修飾子 が付与されたもの(クラス,変数、定数、メソッド) にはthissuper は付与することができません

サンプルコード

問題ないコードはこちら

public class StaticError {

    public static void main(String[] args) {
        StaticError.callPrintln(); // OK
    }

    private static void callPrintln() {
        System.out.println("解消方法1: 先頭にクラス名を付与する");
        System.out.println("解消方法2: メソッドの定義時にstaticを追加");
    }
}

実行結果

解消方法1: 先頭にクラス名を付与する
解消方法2: メソッドの定義時にstaticを追加

こちらはエラーになるコードです

public class StaticError {

    public static void main(String[] args) {
        this.callPrintln(); // staticメソッド内のためエラーになる
    }

    private static void callPrintln() {
        System.out.println("解消方法1: 先頭にクラス名を付与する");
        System.out.println("解消方法2: メソッドの定義時にstaticを追加");
    }
}

実行結果

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    static コンテキストでは this を使用できません

    at practiceThisAndSuper.StaticError.main(StaticError.java:6)

コード内に解消方法なども書きましたが、

static コンテキストでは this を使用できません

こちらのエラー(ワーニング)が出た際の解消方法は2つあります。

つまりは、staticとなったクラス、変数、定数、メソッドthis ではなく、自クラス名を明記しましょう

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