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

FitGen.exeを日本語Windowsで実行したとき発生するJavaのエンコーディングに関するエラーを回避する

TL;DR

環境変数の JAVA_TOOL_OPTIONS-Dfile.encoding=UTF-8 を設定する。

環境変数_JAVA_TOOL_OPTIONS.PNG

詳細

GARMIN FIT SDKでカスタムメッセージ等を定義した場合、FitGen.exeを実行しFIT SDKを生成する必要がある。
https://developer.garmin.com/fit/cookbook/fitgen/

しかし、最近Garmin Developersで配布されているFIT SDKのEventMesg.javaのドキュメントコメントに謎の文字化けが混入しておりそのままFitGen.exeを実行すると

> .\FitGen.exe -o test -java
FIT Code Generator - Protocol 2.00  Profile 21.40Release
SDK Version: production/akw/21.40.00-0-g813c158
Parsing existing config.csv...
Writing new config.csv...
Generating Java code...
Building Java tools...0%
com\garmin\fit\EventMesg.java:679: エラー: この文字は、エンコーディングMS932にマップできません
     * Comment: Theテつ?firstテつ?byteテつ?isテつ?theテつ?radar_threat_level_max, theテつ?secondテつ?byteテつ?isテつ?theテつ?radar_threat_count, and theテつ?lastテつ?16テつ?bitsテつ?areテつ?reservedテつ?forテつ?futureテつ?useテつ?andテつ?shouldテつ?beテつ?setテつ?toテつ?FFFF.
                     ^
以下略

というエラーが発生し、ビルドに失敗する。

この文字は、エンコーディングMS932にマップできません というエラーはjavacにUTF-8等適切なエンコーディングを渡してやることで回避することが可能。以下のようなオプションを渡すことで回避できる。

javac -encoding UTF-8 EventMesg.java

しかしFitGen.exeの場合、javac呼び出し部分はFitGen.exe内部で行われているので直接javacにオプションを渡すことができない。

そこでFitGen.exeを実行するWindowsのユーザー環境変数にJAVA_TOOL_OPTIONSを追加し、javacに渡したいオプションを指定することでエラーを回避することができる。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/troubleshoot/envvars002.html
https://qiita.com/n_slender/items/6c566bb345e844ba8127

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

FitGen.exeを日本語Windowsで実行したとき発生するJavaのエンコーディングに関するエラーを回避する

TL;DR

環境変数の JAVA_TOOL_OPTIONS-Dfile.encoding=UTF-8 を設定する。

環境変数_JAVA_TOOL_OPTIONS.PNG

詳細

GARMIN FIT SDKでカスタムメッセージ等を定義した場合、FitGen.exeを実行しFIT SDKを生成する必要がある。
https://developer.garmin.com/fit/cookbook/fitgen/

しかし、最近Garmin Developersで配布されているFIT SDKのEventMesg.javaのドキュメントコメントに謎の文字化けが混入しておりそのままFitGen.exeを実行すると

> .\FitGen.exe -o test -java
FIT Code Generator - Protocol 2.00  Profile 21.40Release
SDK Version: production/akw/21.40.00-0-g813c158
Parsing existing config.csv...
Writing new config.csv...
Generating Java code...
Building Java tools...0%
com\garmin\fit\EventMesg.java:679: エラー: この文字は、エンコーディングMS932にマップできません
     * Comment: Theテつ?firstテつ?byteテつ?isテつ?theテつ?radar_threat_level_max, theテつ?secondテつ?byteテつ?isテつ?theテつ?radar_threat_count, and theテつ?lastテつ?16テつ?bitsテつ?areテつ?reservedテつ?forテつ?futureテつ?useテつ?andテつ?shouldテつ?beテつ?setテつ?toテつ?FFFF.
                     ^
以下略

というエラーが発生し、ビルドに失敗する。

この文字は、エンコーディングMS932にマップできません というエラーはjavacにUTF-8等適切なエンコーディングを渡してやることで回避することが可能。以下のようなオプションを渡すことで回避できる。

javac -encoding UTF-8 EventMesg.java

しかしFitGen.exeの場合、javac呼び出し部分はFitGen.exe内部で行われているので直接javacにオプションを渡すことができない。

そこでFitGen.exeを実行するWindowsのユーザー環境変数にJAVA_TOOL_OPTIONSを追加し、javacに渡したいオプションを指定することでエラーを回避することができる。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/troubleshoot/envvars002.html
https://qiita.com/n_slender/items/6c566bb345e844ba8127

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

Selenium

Selenium

UIベースでの単体テストの方法

メインソース

package G_T.OfficeSystem.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
public class SeleniumTest {
    public static void main(String[] args) {
        SeleniumTest doTest = new SeleniumTest();
        doTest.threadTest();
    }
    void threadTest() {
        ExecutorService pool = Executors.newFixedThreadPool(8);
        RunImpl runImpl = new RunImpl();
        for (int i = 0; i < 3; i++) {
            pool.submit(runImpl);
        }
        pool.shutdown();
    }
    class RunImpl implements Runnable {
        @Override
        public void run() {
            WebDriver driver = new ChromeDriver();
            driver.get("http://localhost:8080/OfficeSystem_Hibernate/Login");
            WebElement userId = driver.findElement(By.id("userId"));
            WebElement email = driver.findElement(By.id("email"));
            WebElement password = driver.findElement(By.id("password"));
            userId.sendKeys("hoang");
            email.sendKeys("hoang1@gmail.com");
            password.sendKeys("1");
            driver.findElement(By.cssSelector(".BUTTON")).click();
            driver.get("http://localhost:8080/OfficeSystem_Hibernate/Chat");
//          driver.close();
        }
    }
}

pom

<dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.9.1</version>
</dependency>

ほかに必要なもの

driveとしてchromedriver.exeが必要
driver.get("http://localhost:8080/OfficeSystem_Hibernate/Login");

(参考資料 https://www.shookuro.com/entry/2019/11/03/16042)

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

Spring Frameworkが分からない奴はこれを買え!!!!!!

はじめに

皆さんJavaの教本と言えばなんでしょうか?色々あると思いますが一番おすすめできるのは「スッキリわかるJava入門」と多くの人が答えると思います。
じゃあ一通りやった後にJavaのフレームワークを触ってみましょうとなった際や、就職してJavaのシステム開発に携わることになった場合多くの場所でSpringが使われていると思います。
ですがSpringの習得難易度は初学者にとってはそこそこ高くOJTをしながらやっている場合がほとんどかと思います。そんな中Spring入門書籍としては最強の本を発見したので紹介したいです。

Spring Boot 2 入門: 基礎から実演まで

Amazonへのリンクはこちらになります。
この本のいい点を簡単に紹介していきたいと思います。

値段が1000円以下なので技術書としてはかなりお手軽な部類
Hello Worldから簡単なCRUDできるアプリの開発
(CRUDできるアプリはフロント周りもちょっとおしゃれでモチベが上がる)
Thymeleafというテンプレートエンジンでの開発
Kindle本且つテキストなのでコピペもできる(ちょっと工夫はいる)
ログインページの作成
Spring Testの追加(2020/11月追加)

やっていることとしては難しくないことが分かると思います。このレベルのテキストが欲しかった人が多かったのではないでしょうか。
今まで入門書としては「Spring徹底入門」という本があったりしましたが、この本はタイトル詐欺で初心者がこれを買うのはお勧めしません。この本が理解できるなら普通にフリーランスで食っていけるレベルかと…。
それと違い「Spring Boot 2 入門: 基礎から実演まで」は多分業務未経験~業務一年未満を想定して書かれていると思うのでとにかくわかりやすいです。
ただ、Javaだけでなく最低限のSQLとhtmlの知識があったほうがいいと思います。

勉強のフロー

ではどのような順番で初学者は勉強していくとよいのでしょうか?
自分のおすすめする順番を書いておきます。

スッキリわかるJava入門
スッキリわかるJava入門 実践編
これらで基礎固めをします。実践編は半分程度こなせば大丈夫です。

スッキリわかるサーブレット&JSP入門
これでWebアプリの入門をする。しっかり学習してどう動いているか理解する。

Spring Boot 2 入門: 基礎から実演まで
フレームワークを使用した簡単なWebアプリの開発ができるようになる
フレームワークのどこが便利か認識できるようになる

以上となります。SQLなどはこのレベルでは適宜調べるか写経する感じでいいと思います。一度にすべてやっても覚えきれないので。

おわりに

技術書にありがちな「初級者向けの本と上級者向けの本が出てるけど中級者向けの本がない…」というものを埋めてくれる本が出てくれて感動しました。(これは初級向けに近いですが…)自分は「Spring徹底入門」で挫折した身なのでこの本のおかげでもう一回Springを勉強するモチベーションがわいてきました。
これがUdemyの動画だったり紙で出てくれたら最高なのになぁ。

11/17追記
徹底入門のインタビューがあったのでのせておきます。面白かったので共有。
https://codezine.jp/article/detail/9552

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

【Java】FileReaderクラスとBufferedReaderクラスの使い方

プログラミング勉強日記

2020年11月16日
FileReaderクラスとBufferedReaderクラスの使い方を改めてまとめておく。

FileReaderクラスとは

 テキストファイルを読み込むためのAPIで、テキストファイルの内容を元にした処理などを行うために使う。ファイルの文字は、データを文字単位で読み書きできる文字のストリームで読み込まれる。FileReaderクラスは文字を読み込むメソッドとしては、1文字ずつ読み込むreadメソッドしか用意がないので、データをバイト単位で読み書きできるバイトのストリームで読みたい場合はFileInputStreamクラスを使用する。

readメソッドで1文字ずつ読み込む
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        try {
            // Fileクラスのコンストラクタの引数にファイルのパスを指定
            File file = new File("text.txt");

            // ファイルが存在しない場合の例外処理
            // existsメソッドはファイルが存在する場合にはtrue、存在しない場合にはfalseを返す
            if (!file.exists()) {
                System.out.print("ファイルが存在しません");
                return;
            }

            // FileReaderクラスとreadメソッドを使って1文字ずつ読み込み表示する
            FileReader fileReader = new FileReader(file);
            int data;
            // ファイルの終わりになるまで処理を繰り返す
            while ((data = fileReader.read()) != -1) {
                // 読み込んだ文字をchar型にキャストして文字を表示させる 
                System.out.print((char) data);
            }

            // 最後にファイルを閉じてリソースを開放する
            fileReader.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

BufferReaderクラスとは

 FileReaderクラス同様にテキストファイルを読み込むクラスで、FileReaderクラスとは用意されているメソッドが違う。BufferReaderクラスでは1行ずつ読み込むreadlineメソッドが用意されている。
 FileReaderクラスでは1文字ずつ読み込んでいるが、文字数が多い場合に効率が悪くなることがあるので、一定量メモリを決めて、たまったらOSに読み込み命令を出すようにする。これをバッファリングという。

readlineメソッドで1行ずつ読み込む
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        try {
            // Fileクラスのコンストラクタの引数にファイルのパスを指定
            File file = new File("c:SampleTest.txt");

            // ファイルが存在しない場合の例外処理
            // existsメソッドはファイルが存在する場合にはtrue、存在しない場合にはfalseを返す
            if (!file.exists()) {
                System.out.print("ファイルが存在しません");
                return;
            }

            // BufferedReaderクラスのreadLineメソッドを使って1行ずつ読み込み表示する
            FileReader fileReader = new FileReader(file);
            BufferedReader bufferedReader = new BufferedReader(fileReader);
            String data;
            // ファイルの終わりになるまで処理を繰り返す
            while ((data = bufferedReader.readLine()) != null) {
                System.out.println(data);
            }

            // 最後にファイルを閉じてリソースを開放する
            bufferedReader.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

参考文献

クラスBufferedReader
クラスFileReader
【Java】FileReader、BufferedReaderでテキストファイルを読み込む

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

Memento Pattern

特定のクラスの状態を複製するクラスを用意してMementoとする。userは複製を作成したときにMementoをインスタンス化してコレクションに記録し、必要に応じてインスタンスを取り出す
Mementoは複製するメンバを保持する

Design Pattarm MENU

以下のクラス構成で確認します

クラス 説明
Origin.class 複製対象のクラス
Memento.class Originを複製する場合に、MementoにOriginのデータを複製し、Mementoを保存する
MementoList.class コレクション(ListやMapなど)を保持し、Mementoインスタンスを格納する
user(Main.class) 動作を確認する

*user 他の開発者がこのパターンを利用する、という意味合いを含みます

Origin.class
class Origin{
  int  No;

  void setNo(int No){
       this.No=No;
  }

  Memento createMemento(){
       return new Memento(No);
  }
}
Memento.class
class Memento{     
  int No;          // Originと同じfieldを持たせる

  Memento(int No){
      this.No=No;
  }

  int getNo(){
      return No;
  }
}
MementoList.class
class MementoList{
  Map  map = new HashMap();

  void put(int key,Memento mmt){
       map.put(key,mmt);
  }

  Memento getMemento(int key){
       return (Memento) map.get(key);
  }
}
user(Main.class)
public static void main(String[] args){
  Origin or = new Origin();
  MementoList list = new MementoList();

  or.setNo(1);
     list.put(1,or.createMemento());
  or.setNo(2);
     list.put(2,or.createMemento());

  Memento m1 = list.getMemento(1);
  System.out.println(m1.getNo());
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【スタージュンも認めた】グルメスパイザー欲しいけど1103兆3543億円もするのでandroidアプリ作った

お断り

主はandroidアプリ開発、java言語が今回が初めてなのでレイアウトがボロボロだったり所々突っ込みどころがあると思います。
ネタが嫌いな方はブラウザバック推奨します















グルメスパイザーとは

バンダイから発売されてしまった赤い腕型のおもちゃ。正式名称は「トリコ 菓子粉砕機グルメスパイザー」。中にスナック菓子を入れてレバーをシャカシャカ動かすと、内部で菓子が粉砕され、ふりかけとなって出てくる仕組み。ぶっちゃけタカラトミーのおかしなフリカケのパクr。
CMも作られており、トリコが嬉しそうにグルメスパイザーをシャカシャカしている。

以下から引用

ニコニコ大百科

対象読者

・1103兆3543億円もってない人
・手軽に菓子粉砕したい人
・美食家
・スタージュン
・どこでもポン!クラッシュ!クラッシュ!パッパッパ!したい人

動画:説明あり

https://www.nicovideo.jp/watch/sm37827489
https://www.youtube.com/watch?v=CkE88pO2VAI

ダウンロード、インストール

download

野良アプリになるのでgumbyさんの記事参考にしてもらえればインストールできると思います

開発期間とか

企画、要件定義とか => 3秒
環境構築、プログラミング =>6時間くらい
合計6時間3秒です

機能

13973 (3).jpg

PON!! CRASH!! UMASOO!! GOURMETSPYZER!!

ボタンを押下するとトリコが喋ります

スパイスをかける音 

ボタンを押下するとフラッシュライトが光り例のシーンが簡単に再現できます
ボタンを再度押下するとフラッシュライトが消えます

動作確認

huwei p30 lite

ソース

MainActivity.java
import android.media.AudioAttributes;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
import android.content.Context;

public class MainActivity extends AppCompatActivity {

    private SoundPool soundPool;
    private int soundPon, soundCresh,soundUmasooo,soundGourmetSpyzer,UfoSoundEffect;
    private Button button1, button2,button3,button4,button5;
    private CameraManager McameraManager;
    private String McameraID;
    private boolean SW;



    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        McameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

        AudioAttributes audioAttributes = new AudioAttributes.Builder()
                // USAGE_MEDIA
                // USAGE_GAME
                .setUsage(AudioAttributes.USAGE_GAME)
                // CONTENT_TYPE_MUSIC
                // CONTENT_TYPE_SPEECH, etc.
                .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                .build();

        soundPool = new SoundPool.Builder()
                .setAudioAttributes(audioAttributes)
                // ストリーム数に応じて
                .setMaxStreams(5)
                .build();

        // wav をロード
        soundPon = soundPool.load(this, R.raw.pon, 1);

        soundCresh = soundPool.load(this, R.raw.crash, 1);

        soundUmasooo = soundPool.load(this, R.raw.umasooo, 1);

        soundGourmetSpyzer = soundPool.load(this, R.raw.gourmetspyzer, 1);

        UfoSoundEffect = soundPool.load(this, R.raw.ufosoundeffect, 1);

        soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
                Log.d("debug", "sampleId=" + sampleId);
                Log.d("debug", "status=" + status);
            }
        });

        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);
        button3 = findViewById(R.id.button3);
        button4 = findViewById(R.id.button4);
        button5 = findViewById(R.id.button5);

        button1.setOnClickListener(v -> {
            // pon.wav の再生
            // play(ロードしたID, 左音量, 右音量, 優先度, ループ,再生速度)
            soundPool.play(soundPon, 1.0f, 1.0f, 0, 0, 1);

        });

        button2.setOnClickListener(v -> {
            // crash.wav の再生
            soundPool.play(soundCresh, 1.0f, 1.0f, 1, 0, 1);

        });
        button3.setOnClickListener(v -> {
            // Umasooo.wav の再生
            soundPool.play(soundUmasooo, 1.0f, 1.0f, 1, 0, 1);
        });
        button4.setOnClickListener(v -> {
            // gourmetspyzer.wav の再生
            soundPool.play(soundGourmetSpyzer, 1.0f, 1.0f, 1, 0, 1);
        });
        button5.setOnClickListener(v -> {
            soundPool.play(UfoSoundEffect, 1.0f, 1.0f, 1, 0, 1);
                //カメラを取得できなかった時は何もしない
                if (McameraID == null) {
                    return;
                }
                try {
                    if (SW == false) {
                        //SWがfalseならばトーチモードをtrueにしてLDEオン
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            McameraManager.setTorchMode(McameraID, true);
                        }
                    } else {
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                            McameraManager.setTorchMode(McameraID, false);
                        }
                        //SWがtrueならばトーチモードをfalseにしてLDEオフ
                    }

                } catch (CameraAccessException e) {
                    //エラー処理
                    e.printStackTrace();
                }

        });

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            McameraManager.registerTorchCallback(new CameraManager.TorchCallback() {
                //トーチモードが変更された時の処理
                @Override
                public void onTorchModeChanged(String cameraId, boolean enabled) {
                    super.onTorchModeChanged(cameraId, enabled);
                    //カメラIDをセット
                    McameraID = cameraId;
                    //SWに現在の状態をセット
                    SW = enabled;
                }
            }, new Handler());
        }

    }
}


activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginBottom="20dp"
        android:text="pon!!"
        app:backgroundTint="#F44336"
        app:layout_constraintBottom_toBottomOf="@+id/imageView"
        app:layout_constraintEnd_toEndOf="@+id/button5" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginBottom="20dp"
        android:text="crash!!"
        app:backgroundTint="#F44336"
        app:layout_constraintBottom_toBottomOf="@+id/imageView"
        app:layout_constraintStart_toEndOf="@+id/button3" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginLeft="20dp"
        android:layout_marginBottom="20dp"
        android:text="umasoo!!"
        app:backgroundTint="#F44336"
        app:layout_constraintBottom_toBottomOf="@+id/imageView"
        app:layout_constraintStart_toStartOf="parent"
        app:strokeColor="#F44336" />

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginEnd="9dp"
        android:layout_marginRight="9dp"
        android:layout_marginBottom="32dp"
        android:text="GourmetSpyzer!!"
        app:backgroundTint="#F44336"
        app:layout_constraintBottom_toTopOf="@+id/button2"
        app:layout_constraintEnd_toStartOf="@+id/button5"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="4dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/home" />

    <Button
        android:id="@+id/button5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="24dp"
        android:layout_marginRight="24dp"
        android:text="スパイスをかける音"
        app:backgroundTint="#F44336"
        app:layout_constraintBaseline_toBaselineOf="@+id/button4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/button4" />


</androidx.constraintlayout.widget.ConstraintLayout>

後書き

週末を返せ

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

Optionalを使ったnull/空文字判定2.0

結局if文使うなら、いっそOptionalは一切使わないほうがいいよなって思ったりした。null/空文字判定をOptionalで進化させたい。

なお、以下断りがなければJava1.8以上で動作する想定です。

きっかけ

最近、文字列の「存在チェック」を行うコードでこんなのを発見して、かなり違和感があった :thinking:

String uri = SystemPropertiesUtil.getPropertyOptional(Constant.URI).orElse("");

if (StringUtils.isEmpty(dkInfoGetApiUrl)) {
// nullならなんかやる
}

DBからAPIのURIを取得してきて、APIコールに使う場面です。 getPropertyOptional はOptionalを返すメソッド。
また、if文に登場する StringUtils#isEmpty はSpring Frameworkが提供しているユーティリティメソッド。

これなら普通にnull/空文字判定をすればよくて、Optionalを使わないほうがむしろシンプルでいい。

この例は少し極端だが、一般的によくある「nullではない∧空文字ではない」をOptionalを利用して使い勝手や安全性を向上させられないか、検討する。

Optionalの性質

Optionalは、Javadocによると

null以外の値が含まれている場合も含まれていない場合もあるコンテナ・オブジェクトです。

とある (Optional (Java Platform SE 8 ))。

Optional#ofNullabele でラップした時点でnull出ない場合はそのまま値を取得できるし、そうでない場合は別途処理をつけたせばよい。つまり、あえて空文字を返す理由もあまりない。

ただし、Stringのインスタンスがnullでないことと空文字でないことの双方を確認する場合、 Optional#ofNullable で初期化すると空文字でないことはチェックから抜けてしまことに留意する。

解決案

一般的に、空文字判定するときは

  • nullチェック
  • 空文字チェック

をまとめてbooleanを返すstaticメソッドで判定するケースが多いと思う。たとえば

StringUtil.java
public static boolean isNullOrEmpty(String string) {
    if (string == null || string.equals("")) return true;

    return false;
}

それを、以下のように代替できないか。
Optionalで期待した文字列が入っていることを確認する場合、よくある StringUtils に以下のようなメソッドを追加してみる。

StringUtil.java
public static Optional<String> ofPresentable(String string) {
    if (string == null || string.equals("")) return Optional.empty();

    return Optional.of(string);
}

2020-11-17:追記
コメントで別案をいただき、よかったので補足。

StringUtil.java
public static Optional<String> ofPresentable(String string) {
    return Optional.ofNullable(string)
            .map(String::trim)
            .filter(s -> !s.isEmpty());
    // Java 11以上ならisBlankが使える
    // return Optional.ofNullable(string).filter(Predicate.not(String::isBlank));
}

戻り値をOptionalでラップすることで値が存在しない可能性を明示できる。そのため、今までなら

SomeObject object = new SomeObject();
if(id == null || id.equals("")) {
    object.setId("default");
} else {
    object.setId(id);
}

としていたところを

final String idPresentable = StringUtil.ofPresentable(id).orElse("default");
SomeObject object = new SomeObject(idPresentable);

とできる。
正直、やっていることに大きな差はないのだが制御構文がなくなることで状態の変更に気を配る負荷を小さくでき、追いかけやすく変更しやすくなる。
また、nullになる可能性を明示できるため、ロジック上nullになることはありえない箇所は任意の実行時例外を投げることで予期しないエラーを防ぐこともできる。

つまり、

final String idPresentable = StringUtil.ofPresentable(id).orElseThrow(new RuntimeException("予期しない入力です"));
SomeObject object = new SomeObject(idPresentable);

100%代替できるわけではないし、向いてないユースケースもあるだろう。しかし、いくつかのケースにおいてはむしろこちらのパターンのほうが有効な気がしており、今後是非試してみたいと思っている。

おまけ

今回、強烈な課題を感じたのが文字列の処理だったが、ListやMapについても同じようなメソッドを用意することで要素存在判定、ある場合の処理を簡潔かつ安全にできそうです。

たとえば

ListUtil.java
public static <T> Optional<List<T>> ofPresentable(List<T> list) {
    if (list == null || list.size() == 0) return Optional.empty();

    return Optional.of(list);
}

リッチにするならリストの要素にnullが含まれているかも判定してもよいかもしれません。

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

FitGen.exeを日本語Windowsで実行した時発生するJavaのエンコーディングに関するエラーを回避する

TL;DR

環境変数の JAVA_TOOL_OPTIONS-Dfile.encoding=UTF-8 を設定する。

詳細

GARMIN FIT SDKでカスタムメッセージ等を定義した場合、FitGen.exeを実行しFIT SDKを生成する必要がある。
https://developer.garmin.com/fit/cookbook/fitgen/

しかし、最近Garmin Developersで配布されているFIT SDKのEventMesg.javaのドキュメントコメントに謎の文字化けが混入しておりそのままFitGen.exeを実行すると

> .\FitGen.exe -o test -java
FIT Code Generator - Protocol 2.00  Profile 21.40Release
SDK Version: production/akw/21.40.00-0-g813c158
Parsing existing config.csv...
Writing new config.csv...
Generating Java code...
Building Java tools...0%
com\garmin\fit\EventMesg.java:679: エラー: この文字は、エンコーディングMS932にマップできません
     * Comment: Theテつ?firstテつ?byteテつ?isテつ?theテつ?radar_threat_level_max, theテつ?secondテつ?byteテつ?isテつ?theテつ?radar_threat_count, and theテつ?lastテつ?16テつ?bitsテつ?areテつ?reservedテつ?forテつ?futureテつ?useテつ?andテつ?shouldテつ?beテつ?setテつ?toテつ?FFFF.
                     ^
以下略

というエラーが発生し、ビルドに失敗する。

この文字は、エンコーディングMS932にマップできません というエラーはjavacにUTF-8等適切なエンコーディングを渡してやることで回避することが可能。以下のようなオプションを渡すことで回避できる。

javac -encoding UTF-8 EventMesg.java

しかしFitGen.exeの場合、javac呼び出し部分はFitGen.exe内部で行われているので直接javacにオプションを渡すことができない。

そこでFitGen.exeを実行するWindowsのユーザー環境変数にJAVA_TOOL_OPTIONSを追加し、javacに渡したいオプションを指定することでエラーを回避することができる。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/troubleshoot/envvars002.html
https://qiita.com/n_slender/items/6c566bb345e844ba8127

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

備忘録No.6「文字列として受け取った1桁の和差の式を計算する」[Java]

はじめに

初めてgetNumericValueメソッドを使用したので、記録。

やりたい事

・長さ自由の文字列で計算式を受け取る(使われる数字は全て1桁、演算子は'+'と'-'のみ)
・受け取った文字列を数値にして計算
・計算結果を出力する

(例)
入力:"1+2+3"
出力:6

コード

        Scanner sc = new Scanner(System.in);
        String line = sc.nextLine();
        sc.close();
        char [] c = line.toCharArray();

        int sum = 0;
        boolean add = true;

        for(int i = 0; i < line.length(); i++) {
            if(i % 2 == 0) {//数値と演算子が交互に来るため、偶数は数値の計算,奇数は演算子の入れ替えを行う
                if(add) {
                    int n = c[i];
                    sum += n;
                } else {
                    int n = c[i];
                    sum -= n;
                }
            } else {
                if(c[i] == '+') {
                    add = true;
                } else {
                    add = false;
                }
            }
        }
        System.out.println(sum);

入力:"1+2+3"
出力:150

コード1の問題点

・char型の配列に入れられた数値をint型にした際に、ASCII文字コードで代入されてしまっている。→ 配列に入っている数値の見た目そのままで取り出したい!

改善したコード

                if(add) {
                    int n = Character.getNumericValue(c[i]);
                    sum += n;
                } else {
                    int n = Character.getNumericValue(c[i]);
                    sum -= n;
                }

入力:"1+2+3"
出力:6

まとめ

char型の数値をそのまま使いたい時には、Character型のgetNumericValue()を使う。

追記

省略前
                if(c[i] == '+') {
                    add = true;
                } else {
                    add = false;
                }

上記の部分では、比較演算子はboolean型を返すので、addに直接代入することで、真偽判定ができる。

省略後
                add = c[i] == '+';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】部分文字列を抜き出す(AOJ13 - シャッフル)

文字列から部分文字列を抜き出す

  • Stringクラスのsubstringメソッド
  • 第一引数に抜き出し開始の位置を設定
public class Main {
    public static void main(String[] args) {
    String str = "ねこはなんでこんなにMOFUいんでしょうか";
    //10~14文字目まで抜き出す
    String mofu= str.substring(10,15);
    //山崎を表示
    System.out.println(mofu); //MOFUい
    }
}

シャッフル(ITP1-9)

1つのアルファベットが描かれた n 枚のカードの山をシャッフルします。
1回のシャッフルでは、下から h 枚のカードをまとめて取り出し、それを残ったカードの山の上に積み上げます。
カードの山は以下のように1つの文字列で与えられます。
abcdeefab
最初の文字が一番下にあるカード、最後の文字が一番上にあるカードを示しています。
例えば、これを h が 4 でシャッフルすると、最初の4文字 abcd が、残りの文字 eefab の末尾へ連結されるので以下のようになります:
eefababcd
このシャッフルを何回か繰り返します。
カードの山の最初の並び(文字列)と h の列を読み込み、最後の並び(文字列)を出力するプログラムを作成して下さい。
Input
複数のデータセットが入力として与えられます。各データセットは以下の形式で与えられます:
最初の並びを表す文字列
シャッフル回数 m
h1
h2
.
.
hm
最初の並びを表す文字列が "-" のとき入力の終わりとします。
Output
各データセットに対して、最後の並び(文字列)を1行に出力して下さい。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String a = scanner.next();
            if (a.equals("-")) {
                break;
            }
            int b = scanner.nextInt();
            //b行分の合計値を計算
            int sum = 0;
            for (int i = 0; i < b; i++) {
                sum += scanner.nextInt();
            }
            System.out.println(sum); //7
            System.out.println(a); //vwxyz 5
            //2倍 にする
            String c = a + a; //vwxyzvwxyz
            //System.out.println(c);

            /*文字列aの文字を一文字ずつ、(sum/文字列)のあまり分だけ左に動かしたい
            substringメソッド
            開始位置:(sum/文字列a長さ)のあまり 7/5=1...2   2
            終了位置:(sum/文字列a長さ)のあまり+文字列a長さ 2+5=9
            */
            System.out.println(c.substring(sum % a.length(), sum % a.length() + a.length()));
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】部分文字列/要素を検索(AOJ12 - リング)

AIZU ONLINE JUDGE の教材を使って勉強していきます
行いたい操作や文法をまとめ、
実際にその文法を使ってAOJを解答した例を載せています

indexOfで部分文字列を検索

  • indexOfメソッド
  • 文字列に含まれる部分文字列を検索
    • ヒット場合は、文字列のインデックス番号を返す(先頭0)
  • lastIndexOfメソッドは文字列の後ろから検索
    • 最初に見つかった位置のインデックスを返す
public class Main {
    public static void main(String[] args){

        String str1 = "Penpineappleapplepen";
        String str2 = "pineapple";

        int result = str1.indexOf(str2);

        if (result != -1) {
            System.out.println(str2 + "発見!");
            System.out.println(str2 + "が見つかった位置:" + result);
        } else {
            System.out.println(str2 + "は見つかりませんでした。。。");
        }
    }
}
pineapple発見!
pineappleが見つかった位置:3

indexOfで複数要素を検索

  • 第2引数を指定して複数の要素を検索する
  • indexOfメソッドの第2引数で検索開始位置指定
  • 文字がヒットしたらその次のインデックスを第2引数に指定し、続きから残りの文字列を検索
import java.util.ArrayList;
import java.util.Arrays;
public class Main {
    public static void main(String[] args){

        String str1 = "Penpineappleapplepen";

        int result;
        for (int i = 0; i < str1.length(); i++) {
            result = str1.indexOf("p", i);
            if (result != -1) {
                i = result;
                System.out.println("pを " + result +" 番目に発見した!");
            }
        }
    }
}
pを 3 番目に発見した!
pを 8 番目に発見した!
pを 9 番目に発見した!
pを 13 番目に発見した!
pを 14 番目に発見した!
pを 17 番目に発見した!

リング(ITP1-8)

図のようなリング状の文字列 s の任意の位置から、時計回りに連続した文字をいくつか選んで、文字列 p が作れるかを判定するプログラムを作成してください。
Input
1行目に文字列 s が与えられます。
2行目に文字列 p が与えられます。
Output
p が作れる場合は Yes と、作れない場合は No と1行に出力してください。
Constraints
* 1≤pの長さ≤sの長さ≤100
* 文字列は英小文字からなる
Sample Input 1
vanceknowledgetoad
advance
Sample Output 1
Yes

import java.util.Scanner;

public class Main {
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        String s=scan.next();
        String p=scan.next();
        s+=s;
        if(s.indexOf(p)>=0)System.out.println("Yes");
        else System.out.println("No");
        scan.close();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】文字判定・文字列整形(AOJ11 - 文字のカウント)

アルファベットの小文字かどうか判定

  • ASCIIコード表で’a’から'z'は連続している
    • 'b'の値は'a'の値よりも1大きい
  • c がアルファベットの小文字なら真になるコードを書くには
    c >= 'a' && c <= ‘z’
//c がアルファベットの小文字なら真になるコード
public class Main {
    public static void main(String[] args){
    char c = 'c';

    System.out.printf("確認したい文字cは?  '%s'\n", c);
    /*cがアルファベットの小文字なら真*/
    if (c >= 'a' && c <= 'z')
        System.out.println("c は小文字");
    /*cが英小文字なら真*/
    if (c >= 'a' && c <= 'z')        
        System.out.println("c は小文字");
    else
        System.out.println("c は小文字ではない");
    /*cが数字なら真*/
    if (c >= '0' && c <= '9')       
        System.out.println("c は数字");
    else
        System.out.println("c は数字ではない");
    /*cが'+'か'-'なら真*/
    if (c == '+' || c == '-')     
        System.out.println("c は符号");
    else
        System.out.println("c は符号ではない");
    }
}

確認したい文字cは?  'c'
c は小文字
c は小文字
c は数字ではない
c は符号ではない

ScannerクラスのhasNext関数で繰り返し判定

  • 入力を読み込んでまだ繰り返し続行を判定する
  • 前から順番に値を取得していき、まだ値を取得できる時にはtrueを、これ以上値を取得できない場合にはfalse
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
    String str = scanner.next();
    System.out.println(str);
}

文字列整形

  • formatメソッド:書式指定子を使って値を任意の形式に変換
  • printfメソッド:formatの代わりにそのまま表示。改行文字は付与しない
  • 書式指定子:書式文字列の中に値を埋めこむための記法
  • 書式指定子以外はそもまま出力
  • 書式指定子の例
  • System.out.println(String.format("%sの体重はりんご%d個分です。","アルクマ",70));
    • 1\$,2\$:1,2番目の引数を表す
    • %.2s:文字列の最小幅指定
    • %8s:文字列の幅指定。不足した分は左が空白。負数で右空白
    • %#x: 接頭辞に0/0xをつけて16進数表示
    • %.2e:小数点以下の2桁表示、eで指数表記
    • %tF:日付 %tY-%tm-%td と同じ
    • %td:時刻
import java.util.*;
import java.time.LocalDateTime;
import java.util.Locale;

public class Main {
 public static void main(String[] args) {
    System.out.println(String.format("%sの体重はりんご%d個分です。","アルクマ",70)); 
    System.out.println(String.format("%1$sの体重はりんご%3$d個分です。%1$sは元気です。","アルクマ","キティ",70)); //アルクマの体重はりんご70個分です。アルクマは元気です。
    //文字列の最小幅指定
    System.out.println(String.format("%.2sです。", "アルクマ"));       //アルです。
    //文字列の幅指定
    System.out.println(String.format("%8sです。", "アルクマ"));        //    アルクマです。
    //最大桁数指定
    System.out.println(String.format("10進数 %08d", 12345));          //10進数 00012345
    //16進数
    System.out.println(String.format("16進数 %#x", 10));              //16進数 0xa
    //小数点以下の桁数指定
    System.out.println(String.format("小数点数 %.2f", 123.456));      //小数点数 123.46
    //指数
    System.out.println(String.format("指数/小文字 %.2e", 123.456));  //指数/小文字 1.23e+02
    //日時
    var d = LocalDateTime.now();
    System.out.println(String.format("%tr", d));                      //03:42:23 AM
    System.out.println(String.format("%1$tY年 %1$tm月 %1$td日", d));  //2020年 10月 31日
    //printfメソッドで表示
    System.out.printf("%.2sです。\n", "アルクマ");  
  }
}

文字のカウント(ITP1-8)

与えられた英文に含まれる、各アルファベットの数を数えるプログラムを作成して下さい。 なお、小文字と大文字は区別しません。
Input
複数の行にまたがる1つの英文が与えられます。
Output
与えられた英文に含まれる各アルファベットの数を以下に示す形式で出力して下さい:
a : aの個数
b : bの個数
c : cの個数
.
.
z : zの個数
Constraints
* 英文が含む文字の数 < 1200

import java.util.Scanner;

public class Main {
    public static void main(String[] args){
        Scanner scan=new Scanner(System.in);
        int alf[]=new int[26];
        while(scan.hasNext()){
            String str=scan.next().toLowerCase();
            for(int i=0;i<str.length();i++){
                char c=str.charAt(i);
                if('a'<=c&&c<='z'){
                    alf[str.charAt(i)-'a']++;
                }
            }
        }
        for(int i=0;i<26;i++)System.out.println((char)('a'+i)+" : "+alf[i]);
        scan.close();
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む