Java汎用メソッド基礎

汎用

nullチェック

Objects.nonNull(obj);
StringUtils.isEmpty(str);

Listの空チェック

以下の2つはlist自体がnullであった場合にぬるぽとなる。

list.size() == 0;
list.isEmpty();

org.springframework.util.CollectionUtilsのisEmptyを使用すれば、list自体がnullの場合も、booleanで値をしっかり返却してくれる。

CollectionUtils.isEmpty(list);

型チェック

.getClass().getTypeName()

文字列の比較

StringUtils.equals(str1, str2)  //左側にnullにならない値を設定する。importはlang3

intをStringに変換

Integer.toString(intValue);

StringをList<String>に変換

String str = "sample";
Arrays.asList(str);

構文基礎

forEach

list.forEach(s-> System.out.println(s))

Mapの場合

cookieParamMap.forEach((key, value)
  System.out.println("key: + key + ", value: + value)
);

SonarQubeでコード解析をしている場合、ラムダ式を使用すると、SonarQube上で以下のようなバグとして検知される場合がある。よほど可読性やプログラミング効率に対する貢献がなければ、ラムダ式は使用しなくて良い。

A "NullPointerException" could be thrown; "get()" can return null.
Result of 'get()' is dereferenced.

Switch, Case文

var signal  // signalには、blue, yellow, redのいずれかが入る
switch (signal) {
  case "blue":
    console.log("青です");
    break;
  case "yellow":
    console.log("黄です");
    break;
  default:
    console.log("赤です");
}

三項演算子

簡単なif文を1行で記述する。

構文
条件式 ? trueの場合の処理 : falseの場合の処理

例
int win = "777";
String result = win == "777" ? "当たり" : "はずれ";
system.out.println(result); // "当たり"

3点リーダ

以下のように型+3点リーダの形式で、引数が与えられることがある。

void fund(String... args) {
}

これは、可変長引数と呼ばれるもので、引数の数が不定のメソッドを定義したい場合に使用する。

void countMemberFunc(String className, String… members) {
  system.out.println(className + count(members) + "名");
}
countMemberFunc("3年A組", "田中", "佐藤");  // 3年A組2名
countMemberFunc("3年B組", "鈴木", "山田", "小林");  // 3年A組3名

String… は、String[] args と同義。固定長配列のため、引数として渡された時点で長さが確定し、値の追加はできない

コロンが2つつくやつ

String::trim

メソッド参照というもの。ラムダ式で使用できる記法。例えば、以下の文字列を区切りで取り出すために、;のあとの空白を取り除きたい場合

var value = "paramA; paramB"
List<String> list = Arrays.asList(value.split(";")).stream()
  .map(s->s.trim())

これを、Stringのtrimメソッドを通す、という意味で、メソッド参照の記法が使える。

List<String> list = Arrays.asList(value.split(";")).stream()
  .map(String::trim)

可読性が低いので、おすすめはしない。

Appendix

乱数生成

UUIDで作成する

UUID.randomUUID().toString();

単純な数値で作成する場合

// 5桁の乱数を生成する例
Random random = new Random();
int num = 10000 + random.nextInt(90000);

SonarQubeなどでソースコードチェックを行っている場合、Randomは信頼されていないクラスとして検知される。 ダミーやスタブ実装時であれば問題ないが、本番で使用する場合はUUIDを使用する。

もしくはSucureRandomを使用する?

上位桁の0埋め

SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); // SHA1PRNGはSHA1を使用したPRNG(乱数生成アルゴリズム)
random.nextInt(10000);  // 10000以下の乱数を生成
int randomNumStr = String.format("%04d", randomNum);  // 上位桁0埋め

ランダムなバイト値(1~9とa~f を使用した文字列)で作成

乱数生成の理想は、1~9とランダムなアルファベットを想像すると思うが、コンピューターの世界では2進数や16進数といったフォーマットしか無いため、a~zという範囲指定はアナログで実装する必要がある。

そのため、SecureRandomというライブラリを使用して実装できるこの方法が好まれる。

byte[] randomByte = new byte[length]; // length分のbyteを生成
SecureRandom random= SecureRandom.getInstance("SHA1PRNG"); // SecureRandom を使用するためのインスタンスを生成 (おまじないでOK
random.nextBytes(randomByte); // randomByteに、 長さが指定したlengthの、ランダムなbyte配列を格納…①
String randomStr = HexencodeHexString(randomByte); // byteを16進数で取得…②
_randomStr = randomStr.substring(randomStr.length() / 2); //…③
  • ①1バイトは8ビットで、 10進数では-128~128の値
  • ②1バイトは、16進数では0~ffで表現される ※上記では0埋めされ、 00~ffで出力される
  • ③各バイトが00~ffに変換され、 桁数がlengthの2倍になるため、半分削除

値を指定桁でマスキング

認証情報や個人情報など、 マスキングしてログ出力したい。

以下は末尾4桁を残してxでマスキングする例。Stringのメソッド substring() でマスキング桁数を削除し、 apache.commons.lang3.StringUtilsのleftPad() で、削除した分『x』で桁埋めする。

leftPad()の第2引数は、xの数ではなく、桁埋めした後の桁数 (xの桁数+マスキングしない文字列の桁数)、つまりもともとの文字列の桁数を指定する点に注意する。

int replaceLength = personalInfo.length() - 4;  // マスキングする桁数
if (replaceLength > 0) {
  String replaceStr StringUtils.leftPad(personalInfo.substring(replaceLength), personalInfo.length(), "x");
}

ラムダ式を元に戻す

ラムダ式はデバッグできない。

なので、もとの式に戻したいことがあるが、元に戻すドキュメントはあまりないのでメモ。

ちなみに、{}をつけるだけ。

// 分解前
http.authorizeHttpRequests(auth -> 
  auth.antMatchers("/asset").anyRequest().authenticated()
);

// 分解後
http.authorizeHttpRequests(auth -> {
  // ここに式を書けるようになる
  auth.antMatchers("/asset").anyRequest().authenticated();
});

オブジェクトのコピー

Javaは基本的に参照渡しであるため、ただ変数に格納しなおしただけではオブジェクトは複製されない。

Object obj = new Object();
Object copyObj = Object();  // copyObjとobjは参照関係であり、片方を修正すればどちらも修正されてしまう

ディープコピー

オブジェクトをまるまる複製したい場合は、org.springframework.utilのSerializationUtils.clone()メソッドを使用する。

copiedHeader = SerializationUtils.clone(header);

比較的処理が重たいので、用途に応じて後述のシャローコピーも検討する。

apacheCommonsにも同名のパッケージ、メソッドが存在するので注意。特にこの2つのパッケージは第一引数と第二引数の仕様が真逆。

第一引数 : コピー元、第二引数: コピー先、となっているSpringの方がわかりやすい。

シャローコピー

コピー先とコピー元の型が異なる場合は、org.springframework.beans/BeanUtilsのcopyPropertiesBeanUtilsメソッドを使用する。

BeanUtils.copyProperties(コピー先OBJ, コピー元OBJ);

プロパティは、参照渡しでコピーされる。

複製というか、マッピングというような意味合いが強い。

MyBatisなどで作られるEntityを、DTOとして作り直す際などに便利。

リクエストレスポンスをJSON形式で出力

APIのログやデバッグでオブジェクトの中身が見たいときに、オブジェクトをJSON形式で出力したい。

com.fasterxml.jackson.databind.ObjectMapperのwriteValueAsString()メソッドを使用する。

sytem.out.println(new ObjectMapper().writeValueAsString(targetObj));

JSON形式の文字列からパラメータを取り出す

String jsonObj = "{\"title\": \"sample\"}"
try {
  JsonNode rootNode = objectMapper.readTree(jsonObj.getResponseBodyAsString());
  JsonNode nodeTitle = rootNode.path("title");
  String title = nodeTitle.asText();
  System.out.println(title);  // sample
} catch (JsonProcessingException el) {
  // TODO 自動生成された catch ブロック
  throw e1;
}

URLからクエリパラメータを取得する

文字列としてURLを扱う際に、そのURLに含まれるパラメータを取得する方法。

String urlString = "https://abc.jp?param1=a&param2=b";
URI uri = URI.create(urlString);
UriComponents locationUriComponents = UriComponentsBuilder.fromUri(uri).build();
MultiValueMap<String, String> queryParams = locationUriComponents.getQueryParams();
queryParams.getFirst("paramKeyName");

バッファーとバイナリ

バッファーとは、値を格納するための固定長のメモリストレージ。

変数と何が違うのかというと、変数は明確な型のある値を格納するもの。

一方のバッファーはバイナリ、つまり、コンピューターしか読めない生のデータを格納する入れ物として使用される。

これを知っていると、ストリームなどを扱う際の理解が早い。

JSONファイルからDTOを作成する

  1. APIのDTOを記載したJSONファイルを用意する(ここではinput.jsonとする)
  2. ファイル名はinput.jsonとする
  3. 以下のコマンドを実行
  4. Output.javaとして、配下のモデルとともに出力される
quicktype input.json --no-maps --no-date-times --acronym-style camel --array-type list -o output.java --just-types

インシデント

@Valueが動作しない

以下のように、@Valueフィールドをコンストラクタで読み込むと、値が設定されていない。

public class SampleService {
  @Value("{app.key}")
  private String appKey;

  public SampleService(String appKey) {
    this.appKey = appKey;
  }

  public sampleMethod() {
    system.out.println(this.appKey); // null
  }
}

Javaアプリケーションが起動すると、コンポーネントスキャンが行われ、@Component系のクラスがDIされると、続いて以下の順で後続のDIが行われる。

  1. コンストラクタインジェクション
  2. フィールドインジェクション
  3. セッターインジェクション

先ほどのソースコードを見ると、フィールド定義の@Valueの方が先に読み込まれそうだが、実際にはコンストラクタが先なので、値が設定されない。

この場合は、コンストラクタで@Valueを設定する。

public class SampleService {

  private String appKey;

  public SampleService(@Value("{app.key}") String appKey) { // コンストラクタで@Valueする
    this.appKey = appKey;
  }

  public sampleMethod() {
    system.out.println(this.appKey); // 取得できる
  }
}

staticフィールドとして@Valueを定義できる?

実装する場合は、@Componentを付与して、読み込まれるタイミングを前倒しする。

物理的には可能だが、本来@Valueはインスタンスフィールドに対し使用するものであり、推奨はされない。

依存関係が特殊になるので、テスト等でも問題が起こる可能性がある。

@Component
public class SampleConst {

  public static String APPKEY;

  public SampleConst(@Value("{app.key}") String appKey) {
    APPKEY = appKey;
  }
}

コメント

タイトルとURLをコピーしました