きり丸の技術日記

技術検証したり、資格等をここに残していきます。

Javaで比較するときはObjects.equalsを使うのがオススメ

小ネタ。正直、記述量が増えるので好き嫌いで言うと嫌いですが、NullPointerException(以後、NPE)が発生しなくなるので安全に処理したい時はオススメです。

環境

  • Java
    • 15

出現時期

  • java.util.Objects
    • Java1.7

Java7の時代からあります。分かりづらいですが、Objectではないです。Objectsです。

使い方

Objects.equals(Object a, Object b);

どの辺が便利か

安全に比較することができます。特にプリミティブ型のラッパークラス(Integer, Boolean)等はnullになりうります。もし、nullと比較してしまうと、NPEが発生してしまいます。

@Test
void test_01() {
  boolean param1 = true;
  Boolean param2 = null;
  try {
    var actual = param1 == param2;
    Assert.fail();
  } catch (NullPointerException e) {
  } catch (Exception e) {
    Assert.fail();
  }
}

Objects.equalsを使用すると、NPEが発生せずにfalseが返却されます。

@Test
void test_02() {
  boolean param1 = true;
  Boolean param2 = null;
  assertThat(
      Objects.equals(param1, param2)
  ).isEqualTo(false);
}

なお、当然ですがObjects.equalsに渡す前にNPEが発生する場合は、NPEになります。

static class Nested {
  public Boolean bool;
  public Nested(Boolean bool){
    this.bool = bool;
  }
}

@Test
void test_05() {
  boolean param1 = true;
  Nested param2 = null;
  try {
    Objects.equals(param1, param2.bool);
    Assert.fail();
  } catch (NullPointerException e) {
  } catch (Exception e) {
    Assert.fail();
  }
}

ソースコード

終わりに

Java7が出た時期は2011年7月ですので、もうこちらのクラスを知っている人は多いと思います。

地味ですが、NPEを回避できますのでぜひ使ってみて下さい。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

f:id:nainaistar:20210620141146p:plain

LombokでToString.IncludeとExclude、callSuperで便利にログ出力する

小ネタ。JavaにはLombokという便利なライブラリがあります。その便利なライブラリの中にあるToString、その中でもIncludeExclude, callSuperを駆使すると便利だったので、ぜひ覚えてください。

この記事では、ToStringInclude, Exclude, callSuperの3つを紹介します。

環境

  • Java
    • 15
  • lombok
    • 1.18.20

ユースケース

アプリケーション起動時に、どういう設定ファイルの値を読み込んでいるかをログに出力して確認したい。

※ アプリケーション起動時に処理させたい時にはPostConstructを使用すると良いです。PostConstructの紹介記事

書くこと

  • ToStirngの機能
    • Include
    • Exclude
    • callSuper

書かないこと

  • ToStringのその他の機能

普段私が使っておらずメリットがわからないので、特に紹介しません。ある程度Lombokも歴史があるので、他の方が纏めてくださっている記事はあるはずです。

今回使用するファイル

他システムに連携するための設定を抽象クラスExternalPropertiesにもち、抽象クラスを継承したFacebookPropertiesTwitterPropertiesを検証に使用する。

@Getter
@Setter
@Slf4j
@ToString
public abstract class ExternalProperties {
  private String host;
  private String protocol;
  private String port;
  private String endpoint;
  private String timeout;

  @ToString.Exclude // ログに出さない
  private String basicUser;
  @ToString.Exclude // ログに出さない
  private String basicPassword;

  @ToString.Include(name = "rootUrl") // デフォルトはメソッド名になる
  public URI getUri() {
    return URI.create(protocol + "://" + host + ":" + port + "/" + endpoint);
  }

  @PostConstruct
  public void display() {
    log.info(this.toString());
  }
}

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "external.facebook")
@ToString(callSuper = true)
public class FacebookProperties extends ExternalProperties {
}

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "external.twitter")
@ToString(callSuper = true)
public class TwitterProperties extends ExternalProperties {
}

ToStringについて

クラスに@ToStringと付与することで、toString()の結果がハッシュ値ではなくインスタンスの所有する変数を出力するようになります。基本的にはToStringを単体で使用することは少なく、@Valueもしくは@Data@ToStringを包括しているので意識せずに使用していると思われます。

付与していないログ:

FacebookProperties(super=kirimaru.config.FacebookProperties$$EnhancerBySpringCGLIB$$f12ea043@8dfe921)

付与しているログ:

FacebookProperties(super=ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3, rootUrl=http://localhost:10080/facebook))

なお、普段の業務では出てきませんが、競技プログラミング等で循環参照しているデータをデバッグしようとしてログに出力すると、StackOverflowErrorが発生します。注意しましょう。

Include

@ToString.Includeを付与することで、メソッドの結果をtoString()で出力できるようになります。例えば、URIを構築する材料のhostやport等を持った設定ファイルがあったときに、hostがxxx, portがxxxという情報ではなく、そこから構築されるURLを表示したい、といったユースケースで有効活用することができます。

@ToString.Include(name = "rootUrl")
public URI getUri() {
  return URI.create(protocol + "://" + host + ":" + port + "/" + endpoint);
}
FacebookProperties(super=ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3, rootUrl=http://localhost:10080/facebook))

ToString.Includeはrank, nameの変数を所持しています。

  • rank
    • ToStringでの表示順を設定します。デフォルトでは0で、最も後ろに表示されます。この値を100等に設定すると、先頭に表示することもできます。
  • name
    • その名の通り、表示する名前を設定します。デフォルトだとメソッド名が表示されます。

昔はこの設定をofで設定できていたようです。今でもできますが、近いうちにDeperecatedになることがJavaDocに記載されており、ToString.Includeと一緒に扱おうとするとエラーとなります。

java: The old-style 'exclude/of' parameter cannot be used together with the new-style @Include / @Exclude annotations.

Exclude

@ToString.Excludeを変数付与することで、toStringで出力しなくなります。例えば、BASIC認証のID/PASS等は出力したくない、というときに使用できます。

@ToString.Exclude // ログに出さない
private String basicUser;
@ToString.Exclude // ログに出さない
private String basicPassword;

ofと同様に、昔はこの設定をexcludeで設定できていたようです。今でもできますが、近いうちにDeperecatedになることがJavaDocに記載されており、ToString.Excludeと一緒に扱おうとするとエラーとなります。

java: The old-style 'exclude/of' parameter cannot be used together with the new-style @Include / @Exclude annotations.

callSuper

デフォルトではfalseになっています。この設定を使用することで、継承元のToStringの結果を含めることができます。

継承しているファイルでtoString()を使用したい場合は、この設定を含めないと出力元のファイルが何かが分からずに苦労することになります。

callSuper=trueを付与している場合:

TwitterProperties(super=ExternalProperties(host=localhost, protocol=http, port=80, endpoint=twitter, timeout=3, rootUrl=http://localhost:80/twitter))
FacebookProperties(super=ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3, rootUrl=http://localhost:10080/facebook))

callSuper=falseを付与している場合:

ExternalProperties(host=localhost, protocol=http, port=80, endpoint=twitter, timeout=3, rootUrl=http://localhost:80/twitter)
ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3, rootUrl=http://localhost:10080/facebook)

ソースコード

終わりに

正直、Lombokが今後使われるかどうかが分かりません。Lombokはバージョンアップのたびに開発環境の不具合が起きやすいですし、その割にはソースコード的には劇的な変化はないですからね。

Java 16からrecordsという便利な仕組みができたので、Lombokを使用しないようにする、という動きは加速するかと思われます。

個人的にはBuilderクラス等を生成できるので、できればLombokはずっとサポートして欲しいのですが…。

今回学んだことが2021年以降も活かせると嬉しいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

類似記事

f:id:nainaistar:20210607002313p:plain

Javaで起動後DBの値をキャッシュに持つ等をPostConstructで処理させる(SpringBoot)

Javaのオープンチャットにて、「SpringBootの起動時にDBアクセスしてマスタデータを保持することは可能ですか?」といった質問が出てきました。

この記事は、解決方法として挙げられたPostConstructという処理を理解していなかったので、PostConstructを勉強するための記事です。

環境

  • Java
    • 15
  • org.springframework.boot
    • 2.4.5
  • Lombok
    • 1.18.20

※ DIするためにSpringBootを使用していますが、PostConstruct自体はJavaの機能なので、他のフレームワークでも使用できると思います。

ユースケース

  • アプリ起動後にDBのデータをキャッシュに持たせたい
  • アプリ起動時の設定ファイルの設定をログに出力したい
  • DBと設定ファイルが不正な状態の時にアプリ起動を中止させたい

要約

  • DI対象としてインスタンスを生成したタイミングでPostConstructを実行する
  • 単純なインスタンスを作成したタイミングでは実行しない

動作確認

設定ファイルのテスト方法に関しては、こちらの記事を参考にしてください。

プロダクションコード

URLを設定するための抽象クラスExternalPropertiesに、Facebookへの設定を読み込ませる具象クラスFacebookPropertiesを用意する。

@Getter
@Setter
@Slf4j
@ToString
public abstract class ExternalProperties {
  private String host;
  private String protocol;
  private String port;
  private String endpoint;
  private String timeout;

  public URI getUri() {
    return URI.create(protocol + "://" + host + ":" + port + "/" + endpoint);
  }

  @PostConstruct
  public void display() {
    log.info("********************");
    log.info(this.toString());
    log.info("********************");
  }

  @PostConstruct
  public void validate() {
    if (host == null) {
      throw new RuntimeException("設定されていない!");
    }
  }
}

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "external.facebook")
@ToString(callSuper = true)
public class FacebookProperties extends ExternalProperties {
}

テストコード

テストクラスを用意してPostConstructが呼ばれることを確認する。

@SpringJUnitConfig(initializers = ConfigDataApplicationContextInitializer.class)
@EnableConfigurationProperties({FacebookProperties.class})
class FacebookPropertiesTests {
  @Autowired
  FacebookProperties properties;

  @Test
  void AutowiredしないときはPostConstructが呼ばれない() {
    new FacebookProperties();

    // 起動ログを目視で確認する
    // エラーも発生しない
  }
}

EnableConfigurationPropertiesでDI対象としてFacebookPropertiesのインスタンスが生成されるタイミングで実行されました。

PostConstructという名称から、コンストラクタでのインスタンス生成後に呼ばれるかと思っておりましたが、new FacebookProperties()したタイミングでは、呼ばれることはありませんでした。

...ログ省略
14:52:22.700 [main] INFO kirimaru.config.ExternalProperties - ********************
14:52:22.873 [main] INFO kirimaru.config.ExternalProperties - FacebookProperties(super=ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3))
14:52:22.874 [main] INFO kirimaru.config.ExternalProperties - ********************
...ログ省略

備考

この機能を知ったうえでも、PostConstructを使用せずに、mainメソッドに書く方が分かりやすいのではないかと思っていました。

その件についても、オープンチャットに書いたところ以下の回答を得られました。

  • ユースケさんの回答
    • Springでインジェクトされるコンポーネントに依存しない処理ならそれでもいいですね。Springに用意してもらうDataSourceを使ったり、コンポーネントのフィールド変数においたりするにはPostConstruct使うのがいいですね
  • kisさんの回答
    • @PostConstructは必要な初期処理がそれぞれのクラスで書けて、mainで気にする必要が無いところですかねー

f:id:nainaistar:20210605154007p:plain

ソースコード

ExternalProperties.java github.com

テストコード: github.com

終わりに

職場では既にPostConstructを使っていたのですが、その方が既に現場にいないこともあって、なぜ使用しているかを聞くことができませんでした。

PostConstructを使用するとソースコード間の結合度が緩くなりすぎて処理が追えなくなるため、ソースコードの可読性を意識するのであればmain処理に書くべきでは?と思っていたこともあり、勉強していませんでした。

今回の件で、PostConstructを使用するユースケースを勉強することができたので、今後の設計レベルを一段階上げられるように出来そうです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

類似記事

SpringBootでpropertiesやymlの設定ファイルが読み込めることのテストを書く【Java】 nainaistar.hatenablog.com

【2021】SpringBootでpropertiesやymlの設定ファイルが読み込めることのテストを書く nainaistar.hatenablog.com

f:id:nainaistar:20210605153751p:plain