JDK5에서 Static import가 추가되었다.
먼저 static import를 적용하지 않은 일반 코드를 보자.
int i = Math.abs(-20);
double d = Math.acos(Math.PI) * Math.E;
가장 기본적인 용법은 import문 뒤에 static을 붙이고, {패키지.클래스.*} 혹은 {패키지.클래스.멤버} 를 적으면 된다. 위 코드를 static import를 사용하여 작성한 코드는 아래와 같다.
import static java.lang.Math.*;
...
int i = abs(-20);
double d = acos(PI) * E;
하지만 와일드 카드를 사용한 전체 멤버 import는 별로 권장되지 않는다. (공식 가이드)
권장되는 방식은 특정 멤버 필드 혹은 메서드만을 static import 하는 방식으로, 코드 예시는 아래와 같다.
import static java.lang.Math.abs;
import static java.lang.Math.acos;
import static java.lang.Math.PI;
import static java.lang.Math.E;
...
int i = abs(-20);
double d = acos(PI) * E;
Assertions.assertEquals(...);
Assertions.assertEquals(...);
Assertions.assertEquals(...);
Assertions.assertEquals(...);
...
테스트 코드를 작성해본 사람은 알겠지만, 저 assert~ 메서드를 어마무시하게 많이 사용한다. 이런 경우에 static import구문을 사용하면 코드의 가독성을 높일 수 있다.
// JUnit 4.13.2
import static org.junit.jupiter.api.Assertions.assertEquals;
{
assertEquals(...);
assertEquals(...);
assertEquals(...);
assertEquals(...);
...
}
예시에는 Junit 4 버전으로 설명을 했지만, 다른 경우에도 유사하고, 꼭 JUnit이 아니더라도 비슷한 경우가 많다.
이런식으로 코드의 가독성을 높이는데 도움을 주지만, 잘못 사용하는 경우에는 코드의 가독성을 끔찍한 수준으로 떨어트릴수 있다.
import javax.print.DocFlavor;
...
import static java.lang.System.out;
...
DocFlavor flavor = DocFlavor.INPUT_STREAM.POSTSCRIPT;
Doc myDoc = new SimpleDoc(psStream, flavor, null);
PrintRequestAttributeSet attrSet = new HashPrintRequestAttributeSet();
attrSet.add(MediaSizeName.ISO_A4);
PrintService[] services = PrintServiceLookup.lookupPrintServices(flavor, attrSet);
if (services.length > 0) {
DocPrintJob job = services[0].createPrintJob();
try {
out.print(myDoc); //problem is here
job.print(myDoc, attrSet);
} catch (PrintException pe) {}
}
위 코드는 특정 문서를 "프린터로" print 함과 동시에 표준 출력으로 print 하는 코드이다. 같은 이름의 함수가 서로 다른 클래스에서 선언되었음에도 구별하는 것이 쉽지 않다. 만약 IDE와 코드 하이라이팅 기능이 없다고 한다면 정말 끔찍할 것이다.
꼭 가독성의 문제뿐만 아니라 네임스페이스 오염의 문제가 발생할 수도 있다. 아래의 코드 예시를 보자.
import static java.lang.Integer.*;
...
int i = parseInt("");
if(i < MAX_VALUE){
System.out.println("less than MAX_VALUE");
}
import static java.lang.Integer.*;
import static java.lang.Double.*;
...
int i = parseInt("1");
double d = parseDouble("5.0");
if(i < MAX_VALUE){
System.out.println("less than MAX_VALUE");
}
최초에 version 1처럼 작성했을 때는 아무런 문제가 없다. 그러나 version 2로 넘어오면서 Integer와 Double 클래스에 각각 MAX_VALUE라는 static 필드가 있고, MAX_VALUE가 어느 클래스에 속하는 필드를 의미하는 것인지 알 수 없기 때문에 문제가 생기게 된다.
물론 컴파일러가 모호한 문법이라는 오류를 띄워주고 컴파일이 되지는 않는다. 만약 내부 구현에 MAX_VALUE를 사용한 곳이 많다면 수정하는건 꽤나 번거로운 작업이 될것이다.
이것이 와일드 카드를 사용한 static import가 권장되지 않는 이유이다.
그 이유는 이 문서 에서 확인 할 수 있다.
이유는 크게 2가지로, 첫번째는 Mathematical functions이다.
예전 자바 개발자들은 수학 함수들을 사용하는데 Math.abs처럼 함수 앞에 Math가 붙는게 별로 우아하다고 생각하지 않은것 같다.
where "Math" refers to the class whose full name is "java.lang.Math".
This is undesirably verbose for numerical code compared to what one can write in languages such as C, Fortran, and Pascal
C, 포트란, 파스칼은 단순 이름으로 사용하는데 자바는 Math가 붙어 장황하니, Math.abs(x) 대신 abs(x)로 쓰려고 기능을 요청했다는 소리다.
사실 진짜는 두번째 이유인 Named constant이다.
... therefore some programmers have taken to defining such constants in a utility *interface* and then "importing" such constants into a class by the strategem of declaring the class to "implement" the (otherwise vacuous) interface.
몇몇 개발자들이 유틸리티 "인터페이스"에 상수를 만들고, 이를 구현하는 식으로 상수를 사용한다는 소리인데, 이것에 대해 이해하기 위해서는 Constant interface에 대한 이해가 필요하다.
This proposal provides a more straightforward mechanism: define the constants in a utility class, and then import them
상수를 간단하게 사용하려는 목적으로 Constant interface를 사용하는 개발자들을, Constant interface 대신 유틸리티 클래스를 사용하도록 유도하기 위해서 요청된 기능이라고 볼 수 있다.
물론 이 또한, 같은 JDK 버전에서 Enum이 추가된 이후로는 의미가 퇴색된 느낌이 있다.
static import는 정말 자주 사용하는 클래스의 "이름만 보아도 어디에 속하는지 알 수 있는," "정적 멤버" 를 사용하는 데만 쓰도록 하자.