0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Java】結局 `import static` はどの場面で使うのか

0
Posted at

はじめに

Javaを書いていると、たまに次のようなコードを見かけることがあります。

import static java.lang.Math.max;
import static java.lang.Math.min;

また、JUnitのテストコードでは以下のような書き方もよく見ます。

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

これは import static と呼ばれる構文です。

通常の import はクラスを省略して書くために使いますが、import staticstaticメソッドやstaticフィールドをクラス名なしで使えるようにする仕組み です。

ただ、実務でJavaを書いていると、

結局、import static っていつ使うのが正解なの?

と迷うことがあります。

この記事では、import static の基本から、使ってよい場面・避けた方がよい場面まで整理します。

通常のimportとの違い

まず、通常の import から確認します。

import java.util.List;

このように書くと、コード内で java.util.List とフルパスで書かなくても、単に List と書けるようになります。

List<String> names = List.of("Alice", "Bob");

一方、import static はクラスではなく、staticメンバー を直接importします。

例えば、Math.max() を使う場合、通常は次のように書きます。

int result = Math.max(10, 20);

これを import static すると、次のように書けます。

import static java.lang.Math.max;

int result = max(10, 20);

Math. を省略できるようになります。

import staticがよく使われる場面

1.テストコード

import static がもっとも自然に使われるのは、JUnitなどのテストコードです。

例えば、JUnitでは次のようなコードをよく書きます。

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

class CalculatorTest {

    @Test
    void add_2つの数値を足し算できる() {
        Calculator calculator = new Calculator();

        int result = calculator.add(1, 2);

        assertEquals(3, result);
    }

    @Test
    void divide_0で割ると例外が発生する() {
        Calculator calculator = new Calculator();

        assertThrows(ArithmeticException.class, () -> {
            calculator.divide(10, 0);
        });
    }
}

もし import static を使わない場合、次のようになります。

Assertions.assertEquals(3, result);
Assertions.assertThrows(ArithmeticException.class, () -> {
    calculator.divide(10, 0);
});

もちろんこれでも動きます。

しかし、テストコードでは assertEqualsassertThrows のようなアサーションメソッドは頻繁に使います。

そのため、毎回 Assertions. と書くよりも、import static しておいた方が読みやすくなる場合が多いです。

テストコードでは読みやすさが上がりやすい

テストコードでは、主役は

  • 何を準備したか
  • 何を実行したか
  • 何を検証したか

です。
そのため、次のように書けると意図が見やすくなります。

assertEquals(3, result);
assertThrows(IllegalArgumentException.class, () -> service.execute(null));

Assertions.assertEquals と書くよりも、検証内容に集中しやすくなります。

そのため、JUnitのアサーションでは import static はかなり一般的です。

2.Mockitoなどのテスト補助ライブラリ

Mockitoでも import static はよく使われます。

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;

例えば次のようなテストコードです。

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.Test;

class UserServiceTest {

    @Test
    void findName_ユーザー名を取得できる() {
        UserRepository repository = mock(UserRepository.class);
        when(repository.findNameById(1L)).thenReturn("Taro");

        UserService service = new UserService(repository);

        String result = service.findName(1L);

        assertEquals("Taro", result);
        verify(repository).findNameById(1L);
    }
}

Mockitoの mockwhenverify はテストコードの中で頻繁に使われます。

そのため、以下のように書くよりも、

Mockito.when(repository.findNameById(1L)).thenReturn("Taro");
Mockito.verify(repository).findNameById(1L);

import static を使った方が、テストの流れを読みやすいことがあります。

when(repository.findNameById(1L)).thenReturn("Taro");
verify(repository).findNameById(1L);

特にMockitoは、英語の文章に近い形でテストを書けるように設計されているため、import static と相性がよいです。

3. Math系のメソッドを頻繁に使う場合

Math.maxMath.min などを頻繁に使う場合も、import static が使われることがあります。

import static java.lang.Math.max;
import static java.lang.Math.min;

int value = max(0, min(score, 100));

通常の書き方では次のようになります。

int value = Math.max(0, Math.min(score, 100));

どちらが読みやすいかはケースバイケースです。

単発で使う程度なら、Math.max と書いた方が分かりやすいです。

int result = Math.max(a, b);

一方、数式的な処理が多いクラスでは、import static によってコードがすっきりすることがあります。

double distance = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));

ただし、sqrtpow がどこから来たメソッドなのか分かりにくくなる可能性もあるため、チームの方針に合わせるのがよいです。

import staticを避けた方がよい場面

1. 業務コードで意味が曖昧になる場合

業務コードでは、import static によって可読性が下がることがあります。

例えば次のようなコードです。

import static com.example.constant.Status.ACTIVE;
import static com.example.constant.Type.NORMAL;
import static com.example.constant.Role.ADMIN;

if (ACTIVE.equals(user.getStatus()) && NORMAL.equals(user.getType())) {
    // 処理
}

一見すっきりしていますが、ACTIVENORMAL が何を表す値なのか分かりにくくなっています。

特に業務システムでは、

  • ユーザー状態
  • 案件状態
  • 契約状態
  • 請求状態
  • 支払状態

など、似たような定数が増えがちです。

このような場合は、次のようにクラス名を残した方が読みやすくなります。

if (UserStatus.ACTIVE.equals(user.getStatus())
        && ContractType.NORMAL.equals(user.getType())) {
    // 処理
}

コードは少し長くなりますが、意味は明確になります。

import static はコードを短くするための機能ではありますが、短ければ読みやすいとは限りません。

2. どこから来たメソッドか分からなくなる場合

例えば、次のようなコードがあるとします。

validate(user);
convert(user);
execute(user);

これらが同じクラス内のメソッドなのか、親クラスのメソッドなのか、static importされたメソッドなのか分かりにくいです。

特に、業務ロジック内で独自のユーティリティメソッドをstatic importすると、コードの出どころが追いにくくなります。

import static com.example.util.UserValidator.validate;
import static com.example.util.UserConverter.convert;
validate(user);
UserDto dto = convert(user);

一見すっきりしていますが、読み手は

この validate はどこで定義されているのか?

と気になるかもしれません。

この場合は、次のようにクラス名を付けた方が分かりやすいことがあります。

UserValidator.validate(user);
UserDto dto = UserConverter.convert(user);

static importは、使いすぎると「コードの出どころ」を隠してしまいます。

3. ワイルドカードのstatic import

次のような書き方もできます。

import static java.lang.Math.*;

これにより、Math クラスのstaticメンバーをすべてクラス名なしで使えます。

double value = sqrt(pow(x, 2) + pow(y, 2));

ただし、ワイルドカードのstatic importは、基本的には慎重に使った方がよいです。

理由は、何がimportされているのか分かりにくくなるからです。

import staticを使う判断基準

個人的には、import static を使うかどうかは次の基準で考えるとよいと思います。

使ってよい場面

以下のような場面では、import static は有効です。

  • JUnitの assertEqualsassertThrows など
  • AssertJの assertThat
  • Mockitoの mockwhenverify
  • Hamcrestの iscontainsString など
  • Math.maxMath.min などを同じクラスで頻繁に使う場合
  • enumや定数をDSL的に読みやすく使いたい場合
  • ライブラリ側がstatic import前提で設計されている場合

特にテストコードでは、import static はかなり相性がよいです。

テストコードは、処理の詳細よりも

assertThat(result).isEqualTo(expected);

のように、期待値の確認が自然に読めることが重要だからです。

避けた方がよい場面

一方で、次のような場合は避けた方がよいです。

  • 業務ロジック内で意味が曖昧になる場合
  • 独自ユーティリティメソッドを大量にstatic importする場合
  • 定数名だけでは何の値か判断できない場合
  • 複数のクラスから似たような定数をstatic importする場合
  • ワイルドカードで大量にstatic importする場合
  • チーム内で読み方が統一されていない場合

特に、業務コードでは「短さ」よりも「意味の明確さ」を優先した方がよいです。

import staticは「省略」ではなく「読みやすさ」のために使う

import static は、クラス名を省略できる便利な機能です。

しかし、単にコードを短くするためだけに使うと、かえって読みづらくなることがあります。

例えば、以下のようなコードは短いですが、意味が曖昧です。

if (ACTIVE.equals(status)) {
    // 処理
}

一方、次のコードは少し長いですが、意味は明確です。

if (UserStatus.ACTIVE.equals(status)) {
    // 処理
}

つまり、import static を使うべきかどうかは、

クラス名を省略した方が、コードの意図が読みやすくなるか?

で判断するとよいです。

まとめ

import static は、staticメソッドやstaticフィールドをクラス名なしで使えるようにするJavaの機能です。

便利な機能ですが、何でもstatic importすればよいわけではありません。

判断基準はシンプルです。

クラス名を省略することで、コードの意図が読みやすくなるなら使う。
逆に、出どころや意味が分かりにくくなるなら使わない。

特におすすめなのは、テストコードでの利用です。

JUnit、AssertJ、Mockitoなどは import static と相性がよく、テストコードを自然に読みやすくできます。

一方で、業務コードでは使いすぎに注意が必要です。

定数やユーティリティメソッドをstatic importしすぎると、コードの意味が曖昧になり、保守性が下がることがあります。

最終的には、import static は「コードを短くするため」ではなく、コードの意図を読みやすくするため に使うのがよいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?