1. 소개
Java 8 이전까지 인터페이스는 오로지 “계약”이었습니다. 즉, 추상 메서드만 있고 구현이나 로직은 전혀 없었죠. 하지만 Java 8부터 인터페이스는 조금 더 “자족적”이 되었습니다. 이제 default 메서드뿐 아니라 static 메서드도 포함할 수 있습니다.
인터페이스의 static 메서드는 구현 클래스가 아닌 인터페이스 자체에 속하는 메서드입니다. 객체를 생성할 필요가 없으며 인터페이스 이름으로 직접 호출합니다.
비유:
인터페이스의 static 메서드는 사무실 벽에 붙어 있는 안내서와 같습니다. 모든 직원(클래스)이 사용할 수 있지만, 안내서는 어느 특정 직원에게 속한 것이 아닙니다.
인터페이스의 static 메서드를 사용하면 해당 인터페이스와 연관된 보조 기능을 한데 묶으면서, 구현 클래스들의 네임스페이스를 어지럽히지 않을 수 있습니다.
인터페이스에서 static 메서드의 문법
static 메서드는 인터페이스 내부에서 static 키워드로 선언합니다. 구현(중괄호 안의 코드)을 가질 수 있으며, 인터페이스 이름으로만 호출됩니다.
예시:
public interface MathUtils {
static int sum(int a, int b) {
return a + b;
}
static double average(int a, int b) {
return (a + b) / 2.0;
}
}
인터페이스 static 메서드 호출:
int result = MathUtils.sum(5, 7); // 12
double avg = MathUtils.average(10, 20); // 15.0
주의:
인터페이스의 static 메서드는 구현 클래스나 그 객체를 통해 호출할 수 없습니다! 인터페이스 이름으로만 호출해야 합니다.
2. 인터페이스의 static 메서드와 default 메서드는 어떻게 다른가?
static 메서드:
- 인터페이스 자체에 속한다.
- 구현 클래스에 상속되지 않는다.
- 객체를 통해 호출할 수 없다.
- 구현 클래스에서 재정의할 수 없다.
- 인터페이스 이름으로만 호출할 수 있다.
default 메서드:
- 인터페이스를 구현한 클래스의 객체(인스턴스)에 속한다.
- 구현 클래스에서 재정의할 수 있다.
- 구현 클래스의 객체를 통해 호출할 수 있다.
- 구현 클래스에 상속된다.
정리: default 메서드는 객체의 능력을 확장하고, static 메서드는 인터페이스 자체의 능력을 확장합니다.
비교 예시:
interface Printer {
default void print(String text) {
System.out.println("Default: " + text);
}
static void info() {
System.out.println("Printer interface v1.0");
}
}
class ConsolePrinter implements Printer {}
public class Main {
public static void main(String[] args) {
Printer.info(); // 인터페이스를 통해 static 메서드 호출
ConsolePrinter cp = new ConsolePrinter();
cp.print("Hello!"); // 객체를 통해 default 메서드 호출
// cp.info(); // 오류! static 메서드는 객체로 호출할 수 없습니다
}
}
3. 인터페이스에 static 메서드가 필요한 이유
과거에는 인터페이스와 관련된 유틸리티 기능을 추가하려면, Utils 또는 Helper 같은 접미사를 가진 별도의 클래스를 만들어야 했습니다:
public interface Movable {
void move(int x, int y);
}
public class MovableUtils {
public static void resetPosition(Movable m) {
m.move(0, 0);
}
}
이제는 이를 인터페이스 안에서 직접 할 수 있습니다:
public interface Movable {
void move(int x, int y);
static void resetPosition(Movable m) {
m.move(0, 0);
}
}
이렇게 하면 코드가 더 논리적이고 응집력 있게 됩니다. 인터페이스와 관련된 메서드가 이제 그 인터페이스 안에 함께 존재하기 때문입니다.
장점:
- 인터페이스 계약 옆에 유틸리티 기능을 모아 둘 수 있다.
- 구현 클래스의 네임스페이스를 ‘어지럽히지’ 않는다.
- 가독성과 유지보수성이 향상된다.
4. 인터페이스 static 메서드의 제약과 특징
인터페이스의 static 메서드는 구현 클래스에 상속되지 않습니다.
구현 클래스의 객체나 클래스 이름으로는 호출할 수 없습니다. 오직 인터페이스 이름으로만 가능합니다!
인터페이스의 static 메서드는 항상 구현을 포함합니다. abstract나 default가 될 수 없습니다.
항상 구현을 포함합니다.
static 메서드는 인터페이스의 인스턴스 메서드나 변수에 접근할 수 없습니다.
오직 다른 static 멤버(예: static final 상수)만 접근할 수 있습니다.
인터페이스의 static 메서드는 private일 수도 있습니다(Java 9+).
인터페이스 내부에서만 사용할 보조용 private static 메서드를 만들 수 있습니다.
5. 예제: Movable 인터페이스의 static 메서드
Movable 인터페이스에 static 메서드를 어떻게 추가하는지 살펴봅시다. Movable 인터페이스는 여러 클래스(예: 로봇, 동물, 운송수단)가 구현한다고 가정합니다.
1단계. static 메서드를 포함한 인터페이스 선언:
public interface Movable {
void move(int x, int y);
static void resetPosition(Movable obj) {
obj.move(0, 0);
}
static double distance(int x1, int y1, int x2, int y2) {
int dx = x2 - x1;
int dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
}
2단계. 클래스로 인터페이스 구현:
public class Robot implements Movable {
private int x, y;
public Robot(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public void move(int x, int y) {
System.out.println("로봇이 점 (" + x + "," + y + ")으로 이동합니다");
this.x = x;
this.y = y;
}
public void printPosition() {
System.out.println("현재 위치: (" + x + "," + y + ")");
}
}
3단계. 인터페이스의 static 메서드 사용:
public class Main {
public static void main(String[] args) {
Robot robby = new Robot(10, 15);
robby.printPosition();
// 인터페이스 static 메서드로 위치 초기화
Movable.resetPosition(robby);
robby.printPosition();
// 인터페이스 static 메서드로 두 점 사이 거리 계산
double dist = Movable.distance(0, 0, 10, 15);
System.out.println("거리: " + dist);
}
}
결과:
현재 위치: (10,15)
로봇이 점 (0,0)으로 이동합니다
현재 위치: (0,0)
거리: 18.027756377319946
참고:
우리는 Movable.resetPosition(robby)를 호출하며, robby.resetPosition()가 아닙니다. static 메서드는 특정 객체가 아니라 인터페이스에 논리적으로 속하는 연산에 적합합니다.
6. 인터페이스의 private static 메서드
때로는 인터페이스 내부 용도의 보조 메서드가 필요합니다(예: 여러 static 또는 default 메서드에서 중복 코드를 없애기 위해). Java 9부터 인터페이스는 private static 메서드를 지원합니다.
예시:
public interface Logger {
static void logInfo(String message) {
log("INFO", message);
}
static void logError(String message) {
log("ERROR", message);
}
private static void log(String level, String message) {
System.out.println("[" + level + "] " + message);
}
}
이제 log()는 인터페이스 밖에서는 접근할 수 없고, 다른 static 메서드 내부에서만 사용됩니다.
7. 표준 Java 라이브러리에서의 static 메서드
인터페이스의 static 메서드는 표준 Java 라이브러리에서 활발히 사용되며, 특히 컬렉션과 함수형 인터페이스에서 자주 볼 수 있습니다.
예시:
- Comparator.comparing(), Comparator.reverseOrder() — Comparator 인터페이스의 static 메서드.
- Predicate.isEqual() — Predicate 인터페이스의 static 메서드.
- List.of(), Set.of(), Map.of() (Java 9+) — 불변 컬렉션을 만드는 static 메서드.
Comparator 예시:
import java.util.Comparator;
public class Main {
public static void main(String[] args) {
Comparator<String> cmp = Comparator.reverseOrder();
int res = cmp.compare("a", "b"); // 양수: 역순이므로 "a" > "b"
System.out.println(res);
}
}
8. 인터페이스 static 메서드에서 자주 하는 실수
실수 №1: 구현 클래스의 객체로 static 메서드를 호출하려고 함.
이렇게는 동작하지 않습니다! 인터페이스의 static 메서드는 인터페이스 이름으로만 호출합니다. 예를 들어 Movable.resetPosition(obj)처럼 호출해야 하며, obj.resetPosition()가 아닙니다.
실수 №2: 구현 클래스에서 인터페이스의 static 메서드를 재정의하려고 함.
static 메서드는 상속되지도, 재정의되지도 않습니다! 클래스에 같은 이름의 static 메서드를 선언하더라도, 이는 인터페이스와 무관한 전혀 다른 메서드입니다.
실수 №3: static 메서드는 인스턴스 멤버에 접근할 수 없다는 점을 잊음.
인터페이스의 static 메서드는 static 멤버(예: static final 상수)만 사용할 수 있으며, 인스턴스 메서드나 변수에는 접근할 수 없습니다.
실수 №4: default 메서드와의 혼동.
default 메서드는 객체로 호출하고, static은 인터페이스 이름으로만 호출합니다. 혼동하지 마세요!
GO TO FULL VERSION