【Java】* によるパッケージ全体のインポートの使いどころ

Java

Javaの インポート は、アスタリスク( * )を使用することにより、パッケージ全体をまとめて指定することが可能です。

パッケージ全体をインポートしてもサブパッケージは含まれない理由など、パッケージの説明と併せたインポートの概要は、以下ページで整理しています。

* によるパッケージ全体のインポートの詳細もこちらでまとめています。

このページでは、* によるパッケージ全体のインポートについて特に、その使いどころと、一方で使わない方が良いとされることが多い点に関して記載しています。

パッケージ全体のインポートの使いどころ

* を使用したパッケージ全体のインポートは、次の例のように、しばしばテスト用ライブラリで 静的インポート と組み合わせて利用されます。

 //Let's import Mockito statically so that the code looks clearer
 import static org.mockito.Mockito.*;

mockito-core > Class Mockito > 1. Let’s verify some behaviour!

MockMvc を直接使用してリクエストを実行する場合は、次の静的インポートが必要です。

  • MockMvcBuilders.*
  • MockMvcRequestBuilders.*
  • MockMvcResultMatchers.*
  • MockMvcResultHandlers.*

Spring Framework > テスト > MockMvc > 静的インポート

例えば Mockito では、Mockitoクラスに定義された多数のstaticメソッドを中心にテストコードを記述していきますが、単にMockitoクラスをインポートするだけだと、頻繁に使用するstaticメソッドで毎回クラス名を書かなければならなくなってしまいやや冗長です。

import java.util.LinkedList;

import org.mockito.Mockito; // Mockitoクラスをインポート

・・・

var mockedList = Mockito.mock(LinkedList.class);                   // Mockito.mock

Mockito.when(mockedList.get(0)).thenReturn("first");               // Mockito.when
Mockito.when(mockedList.get(1)).thenThrow(new RuntimeException()); // Mockito.when

Mockito.verify(mockedList).get(0);                                 // Mockito.verify

このような場合、静的インポート文 を使用することで、クラス名を付けずにメソッド名だけで記述が可能となります。

import java.util.LinkedList;

import static org.mockito.Mockito.mock;   // Mockito.mock   を静的インポート
import static org.mockito.Mockito.when;   // Mockito.when   を静的インポート
import static org.mockito.Mockito.verify; // Mockito.verify を静的インポート

・・・

var mockedList = mock(LinkedList.class);                   // mock

when(mockedList.get(0)).thenReturn("first");               // when
when(mockedList.get(1)).thenThrow(new RuntimeException()); // when

verify(mockedList).get(0);                                 // verify

静的インポートには、乱用するとどのクラスの物か分かりにくくなってしまうというデメリットもあります。しかしこの例のように、テストコード内で多用されるような場合は、クラス名という余計な接頭辞がなくなることで、文法的に自然言語としても読みやすくなります。

一方でこの方法では、必要なstaticメソッドが増えるたびに静的インポート文を追加する必要が出てきてしまいます。この時、* による全体のインポートが有用です。以下のように Mockito.* とすることで、Mockitoクラスに定義されたstaticフィールドとstaticメソッド全体をまとめて静的インポートし、全てをクラス名なしで使用できるようになります。

import java.util.LinkedList;

import static org.mockito.Mockito.*; // Mockitoクラスの静的メンバをまとめてインポート

・・・

var mockedList = mock(LinkedList.class);                   // mock

when(mockedList.get(0)).thenReturn("first");               // when
when(mockedList.get(1)).thenThrow(new RuntimeException()); // when

verify(mockedList).get(0);                                 // verify

使わない方が良いとされることが多い理由

しかし、以上のような静的インポートと組み合わせた活用法がある一方、意図せずクラス名の衝突を引き起こしてしまうことを回避するため、パッケージ全体のインポートは避けるべきという見方もあります。

例えば、次のように after(long millis) というstaticメソッドを持つ自作クラスを定義していたとします。

package com.example.util;

import java.util.Calendar;

public class CalendarUtils {
    public static boolean after(long millis) {
        var calendar = Calendar.getInstance();
        calendar.setTimeInMillis(millis);
        return Calendar.getInstance().after(calendar);
    }
}

これを、先程の静的インポート Mockito.*; と併せて使おうとすると、次のように after(long) というメソッドが曖昧であるとしてコンパイルエラーになってしまいます。これはMockitoクラスにも、引数がlongである静的afterメソッドが存在しているためです。

import java.util.LinkedList;

import static org.mockito.Mockito.*;            // Mockitoクラスの静的メンバをまとめてインポート
import static com.example.util.CalendarUtils.*; // CalendarUtilsクラスの静的メンバをまとめてインポート

・・・

after(0); // × The method after(long) is ambiguous for the type Main

var mockedList = mock(LinkedList.class);

when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());

verify(mockedList).get(0);

上記の ambiguous(曖昧な)エラーは、シグネチャ (メソッド名および引数の数と型)が同じ場合のみ発生し、例えば次のように引数の型が異なる場合は、同名のメソッドが存在していてもエラーにはなりません。またインスタンスメソッドも呼び出し時に明確であるためエラーは発生しません。

import static org.mockito.Mockito.*;
import static com.example.util.CalendarUtils.*;

・・・

var calendar = Calendar.getInstance();

after(12345678); // org.mockito.Mockito.after(long millis)
after(calendar); // com.example.util.CalendarUtils.after(Calendar calendar)

calendar.after(calendar); // インスタンスメソッドはシグネチャが同じでも問題ない

静的インポートだけでなく、通常のクラスのインポートであっても、パッケージに同名クラスが含まれる場合は同様のエラーが発生します。同名クラスのインポート を行った場合と異なり、collide(衝突する)ことによるエラーではなく、ambiguous(曖昧な)エラーとなります。

import java.sql.*;
import java.util.*;

・・・

Date date = new Date(); // × The type Date is ambiguous

また、ambiguous(曖昧な)エラーは両方が * によるインポートである場合に発生し、一方を明示的にインポートしている場合は、仮にシグネチャが同じメソッドが存在していてもエラーにはなりません。ただしその分、逆に * によるインポートに含まれるクラスは完全修飾名でなければ参照できなくなります。

import static org.mockito.Mockito.*;
import static sample.wildcard_sample.com.example.util.CalendarUtils.after;

import java.sql.*;
import java.util.Date;

・・・

// オンデマンドインポートと重複があっても個別のインポート文が別にあればエラーにはならず特定できる
after(0);    // com.example.util.CalendarUtils.after;
new Date(0); // java.util.Date

// 逆にオンデマンドインポートに含まれる方を使いたい場合は完全修飾クラス名の指定が必要になる
org.mockito.Mockito.after(0); // org.mockito.Mockitoafter;
new java.sql.Date(0);         // java.sql.Date

方法としては、①完全修飾クラス名を指定する、②通常の方法でCalendarUtilsをインポートしクラス名を付けてメソッドを呼び出す、③CalendarUtils.afterのみ明示的に静的インポートを行う、のいずれかでこのエラーを回避することができます。

com.example.util.CalendarUtils.after(0); // ① 完全修飾クラス名
CalendarUtils.after(0);                  // ② import com.example.util.CalendarUtils;
after(0);                                // ③ import static com.example.util.CalendarUtils.after;

しかし、そもそも Mockito.after の方は使用しておらず、またその存在を知らなかったり後から追加されたりした場合は原因の究明が困難になってしまいます。そのため、全体の見通しを良くするためにも、初めからMockitoクラスのstaticメソッドも * を使わず全て個別に静的インポートしておけば良いという考え方もできます。

以上のような理由から、一般に * を使用した全体のインポートは避けられる傾向にあります。例えば JUnit 5 のユーザーガイドに掲載されているサンプルコードは、* によるインポート文を使用せず、全てのクラスを個別にインポートしています。

以下の例では、import org.junit.jupiter.api.*; とすれば一行で済むところ、複数行に渡って必要なクラスのみを個別に記述しています。

import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class StandardTests {

・・・略・・・

}

JUnit 5 User Guide > 2. Writing Tests > 2.3. Test Classes and Methods

また次の例では、import org.junit.jupiter.api.Assertions; と単にインポートした上で、クラス名を付加して各メソッドを呼び出すか、静的インポートにする場合は import static org.junit.jupiter.api.Assertions.*; と記述できるところ、こちらも全ての静的インポート文を個別に記述しています。

import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.CountDownLatch;

import example.domain.Person;
import example.util.Calculator;

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

class AssertionsDemo {

・・・略・・・

}

JUnit 5 User Guide > 2. Writing Tests > 2.5. Assertions

パッケージ全体と通常のインポートの違い

注意点ですが、* を使わない個別のインポートが好まれるのは、あくまで可読性の問題で、性能面に差は生じません。パッケージ全体のインポートと聞くと、一見、不要なクラスも全てロードして性能面に悪影響が及ぶように見えてしまいます。

しかし実際には、imported as needed(必要に応じてインポート)される機能のため、インポート文に * を記述したからと言ってパッケージに含まれる全てのクラスがロードされる訳ではなく、実際にそのクラス内で使用されている必要なクラスのみがロードされます。

従って、個別にクラスを指定するインポートと、パッケージ全体のインポートとの違いは可読性のみで、性能に差はありません。

これはパッケージ全体のインポートが、「Type-Import-on-Demand Declarations:オンデマンド(要求に応じた)型インポート宣言」という名称であることからも分かります。

type-import-on-demand declaration allows all accessible classes and interfaces of a named package, class, or interface to be imported as needed.
オンデマンドの型インポート宣言は、名前付きパッケージ、クラス、またはインターフェースの、全てのアクセス可能なクラスとインターフェースを、必要に応じてインポートできるようにします。

Chapter 7. Packages and Modules > 7.5.2. Type-Import-on-Demand Declarations