인터페이스(interface)는 서로 다른 시스템이나 객체 간의 소통을 위한 접점을 제공하는 역할을 합니다.
쉽게 말해, 객체 간의 상호작용을 정의하는 규칙으로 사용됩니다.
사전적으로 두 장치를 연결하는 접속기라는 뜻을 가지며, 프로그래밍에서는 클래스가 따라야 할 일종의 "설계도" 역할을 합니다.
자바에서는 클래스를 통한 다중 상속을 지원하지 않지만, 인터페이스를 활용하면 다중 상속의 장점을 구현할 수 있습니다.
인터페이스는 추상 메서드의 구현을 강제하여 코드의 일관성을 유지하도록 돕습니다. 뿐만 아니라, 다양한 프레임워크에서 클래스 간의 통신을 담당하며, 객체 지향 프로그래밍(OOP)에서 결합도(Coupling)을 낮추고 유지보수성을 높이는 디자인 패턴으로도 활용됩니다.
쉽게 말해, 인터페이스는 프로그램의 설계를 돕고, 더 유연한 코드 구조를 만드는 기법입니다.
우리가 반복되는 코드를 줄이기 위해 for문이나 while문을 사용하는 것처럼, 인터페이스는 설계의 이점을 극대화하기 위해 사용하는 개념이라고 볼 수 있습니다.
인터페이스는 클래스가 반드시 구현해야 하는 추상 메서드의 집합이며, 메서드의 구체적인 동작은 이를 구현하는 클래스에서 정의합니다.
즉, 인터페이스는 단순한 “규칙”을 정의하고, 실제 기능은 이를 구현하는 클래스에서 구현하는 방식입니다.
이러한 특징 덕분에 인터페이스는 추상화, 상속과 함께 다형성을 구현하는 객체 지향 프로그래밍(OOP)의 핵심 요소 중 하나로 자리 잡고 있습니다.
결국, 인터페이스를 활용하면 서로 다른 클래스들이 공통된 동작을 할 수 있도록 하면서도, 코드의 유연성과 확장성을 높일 수 있습니다.
abstract 키워드를 붙이지 않아도 자동으로 추상 메서드로 인식됩니다.interface Example {
void abstractMethod(); // 추상 메서드 (public abstract 자동 추가)
}
default 키워드를 사용하여 선언합니다.interface Example {
default void defaultMethod() {
System.out.println("디폴트 메서드 실행");
}
}
static 키워드를 사용하여 인터페이스 내부에서 정의된 정적 메서드입니다.interface Example {
static void staticMethod() {
System.out.println("정적 메서드 실행");
}
}
Example.staticMethod(); // 인터페이스명으로 호출
public static final이 적용됩니다.interface Example {
int VALUE = 100; // public static final 자동 적용
}
System.out.println(Example.VALUE); // 100
인터페이스도 추상 클래스처럼 단독으로 인스턴스를 생성할 수 없습니다.
즉, 인터페이스는 반드시 구현(implements)하는 클래스가 있어야 하며, 해당 클래스에서 모든 추상 메서드를 오버라이딩하여 구현해야 합니다.
[접근제한자] interface 인터페이스명 {
자료형 변수명 = 값; // 상수 (public static final 자동 추가)
반환형 메서드명(매개변수 ...); // 추상 메서드 (public abstract 자동 추가)
default 반환형 메서드명(매개변수 ...) // 디폴트 메서드
static 반환형 메서드명(매개변수 ...) // 정적 메서드
}
public static final이 자동으로 붙는 상수이며, 모든 메서드는 기본적으로 public abstract(추상 메서드)입니다.default 메서드와 static 메서드가 추가되어 인터페이스 내에서 일부 구현을 제공할 수도 있습니다.인터페이스를 구현하려면, 클래스에서 implements 키워드를 사용해야 합니다.
extends(상속)할 수 있지만, 인터페이스는 여러 개를 implements하여 다중 구현이 가능합니다.interface Animal {
void makeSound(); // 추상 메서드
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("멍멍!");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.makeSound(); // 멍멍!
}
}
클래스는 다중 상속이 불가능하지만, 인터페이스는 여러 개를 동시에 구현(다중 구현)할 수 있습니다.
다중 구현을 통해 한 클래스가 여러 역할을 수행할 수 있도록 만들 수 있습니다.
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable { // 인터페이스 다중 구현
@Override
public void fly() {
System.out.println("오리가 난다");
}
@Override
public void swim() {
System.out.println("오리가 헤엄친다");
}
}
public class Main {
public static void main(String[] args) {
Duck duck = new Duck();
duck.fly(); // 오리가 난다
duck.swim(); // 오리가 헤엄친다
}
}
클래스에서 extends 키워드를 사용해 상속을 하는 것처럼, 인터페이스끼리도 extends를 통해 확장할 수 있습니다.
즉, 기존 인터페이스를 확장하여 새로운 기능을 추가하는 방식으로 사용할 수 있습니다.
Object 클래스가 인터페이스의 최고 조상이 아니므로, 클래스를 상속할 수 없습니다.interface A {
void methodA();
}
interface B extends A { // 인터페이스 상속 (확장)
void methodB();
}
class C implements B { // B를 구현하므로, A의 메서드도 구현해야 함
@Override
public void methodA() {
System.out.println("A 메서드 구현");
}
@Override
public void methodB() {
System.out.println("B 메서드 구현");
}
}
public class Main {
public static void main(String[] args) {
C obj = new C();
obj.methodA(); // 출력: A 메서드 구현
obj.methodB(); // 출력: B 메서드 구현
}
}
interface A {
void methodA();
}
interface B extends A {
void methodB();
}
// 인터페이스 C가 A와 B를 다중 상속
interface C extends A, B {
void methodC();
}
// 클래스 D는 C를 구현하므로, A와 B의 메서드도 구현해야 함
class D implements C {
@Override
public void methodA() {
System.out.println("A 메서드 구현");
}
@Override
public void methodB() {
System.out.println("B 메서드 구현");
}
@Override
public void methodC() {
System.out.println("C 메서드 구현");
}
}
public class Main {
public static void main(String[] args) {
D obj = new D();
obj.methodA(); // A 메서드 구현
obj.methodB(); // B 메서드 구현
obj.methodC(); // C 메서드 구현
}
}
자바에서는 클래스의 다중 상속을 금지하지만, 인터페이스끼리의 다중 상속은 허용됩니다.
이는 인터페이스의 구조적 특성과 다중 상속에서 발생할 수 있는 문제를 해결할 수 있는 명확한 규칙이 존재하기 때문입니다.
자바 8 이전에는 인터페이스 내부에 abstract 메서드(추상메서드)만 선언할 수 있었습니다.
즉, 인터페이스끼리 상속해도 메서드의 구현(본문)이 없었기 때문에, 충돌 문제가 발생할 가능성이 없었습니다.
이로 인해 인터페이스 간 다중 상속을 허용해도 문제가 되지 않았습니다.
interface A {
void methodA(); // 추상 메서드
}
interface B {
void methodB(); // 추상 메서드
}
interface C extends A, B {
void methodC();
}
default 메서드 도입으로 인해 충돌 가능성이 생김자바 8부터는 인터페이스에서 default 메서드를 통해 메서드의 기본 구현을 제공할 수 있게 되었습니다.
이는 기존의 인터페이스 개념을 확장하는 유용한 기능이지만, 동시에 다중 상속 시 메서드 충돌이 발생할 가능성을 만들었습니다.
interface A {
default void showMessage() {
System.out.println("A 인터페이스의 메시지");
}
}
interface B {
default void showMessage() {
System.out.println("B 인터페이스의 메시지");
}
}
// 인터페이스 C가 A와 B를 다중 상속
interface C extends A, B {
// 충돌을 해결하기 위해 반드시 오버라이딩 필요
@Override
default void showMessage() {
System.out.println("C 인터페이스에서 충돌 해결");
}
}
인터페이스끼리 default 메서드가 충돌할 경우, 구현 클래스에서 반드시 해결해야 합니다.
이는 다중 상속에서 발생할 수 있는 모호성 문제를 방지하기 위한 설계 방식입니다.
즉, 인터페이스의
default메서드가 충돌하면, 반드시 개발자가 직접 해결해야 하도록 강제되었습니다.
interface A {
default void showMessage() {
System.out.println("A 인터페이스의 메시지");
}
}
interface B {
default void showMessage() {
System.out.println("B 인터페이스의 메시지");
}
}
// 인터페이스 C가 A와 B를 다중 상속 (extends 사용)
interface C extends A, B {
// 충돌을 해결하기 위해 반드시 오버라이딩 필요
@Override
default void showMessage() {
System.out.println("C 인터페이스에서 충돌 해결");
}
}
default 메서드 showMessage()를 가지고 있습니다.extends하면 default 메서드 충돌이 발생합니다.showMessage()를 반드시 오버라이딩해야 합니다.super 키워드를 사용하여 충돌 해결interface A {
default void showMessage() {
System.out.println("A 인터페이스의 메시지");
}
}
interface B {
default void showMessage() {
System.out.println("B 인터페이스의 메시지");
}
}
// A와 B를 동시에 상속하는 인터페이스 C
interface C extends A, B {
@Override
default void showMessage() {
B.super.showMessage(); // B 인터페이스의 showMessage()를 호출하여 충돌 해결
}
}
// C를 구현하는 클래스 D
class D implements C {}
public class Main {
public static void main(String[] args) {
D obj = new D();
obj.showMessage(); // B 인터페이스의 메시지
}
}
super 키워드를 사용하여 특정 부모 인터페이스의 메서드를 호출합니다.자바에서 인터페이스는 다른 클래스가 따라야 할 규칙을 정하는 기본 틀입니다. 즉, 다른 클래스 사이의 중간 매개 역할을 담당합니다.
클래스는 하나의 부모만 상속(다중 상속 금지)할 수 있지만, 여러 개의 인터페이스를 구현(다중 구현)할 수 있습니다.
클래스는 다중 상속이 불가능하지만, 인터페이스끼리는 다중 상속이 가능합니다.
또한, 인터페이스는 클래스와 다르게 객체를 직접 생성할 수 없으며, 반드시 구현 클래스를 통해 객체를 생성해야 합니다.
implements 사용)implements 키워드를 사용하여 구현합니다.class 클래스명 implements 인터페이스명 {}
class 자식클래스명 extends 부모클래스 implements 인터페이스1, 인터페이스2 {}
interface Animal {
void makeSound();
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("멍멍!");
}
}
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Dunk implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("오리가 납니다.");
}
@Override
public void swim() {
System.out.println("오리가 헤엄칩니다.");
}
}
extends 사용)extends를 사용하여 상속할 수 있습니다 (다중 상속 가능).implements 대신 extends를 사용합니다.interface 자식인터페이스명 extends 부모인터페이스 {}
interface A {
void methodA();
}
interface B extends A {
void methodB();
}
class C implements B {
@Override
public void methodA() {
System.out.println("A 메서드 구현");
}
@Override
public void methodB() {
System.out.println("B 메서드 구현");
}
}
static final 상수만 포함 가능인터페이스에서 필드는 반드시 static final 상수로 선언됩니다.
즉, 인터페이스 내에서 일반적인 인스턴스 변수를 선언할 수 없으며, 오직 상수(static final)만 포함할 수 있습니다.
interface Constants {
int MAX_SPEED = 120; // public static final 자동 적용
String ERROR_MESSAGE = "에러 발생";
}
컴파일러가 자동으로 다음과 같이 변환합니다:
interface Constants {
public static final int MAX_SPEED = 120;
public static final String ERROR_MESSAGE = "에러 발생";
}
인터페이스끼리 다중 상속을 할 경우, 상수(static final 필드)는 그대로 상속됩니다.
즉, 하위 인터페이스에서도 부모 인터페이스의 상수를 그대로 사용할 수 있습니다.
interface A {
int VALUE_A = 10; // 상수 (public static final)
}
interface B extends A {
int VALUE_B = 20; // 추가 상수
}
public class Main {
public static void main(String[] args) {
System.out.println(B.VALUE_A); // 10 (A의 상수)
System.out.println(B.VALUE_B); // 20 (B의 상수)
}
}
인터페이스의 필드는 static 이므로, 특정 인터페이스명을 명시하여 접근하면 됩니다.
즉, 다중 상속에서 동일한 이름의 필드가 있더라도 특정 인터페이스를 지정하여 사용하면 문제가 발생하지 않습니다.
interface A {
int VALUE = 10; // public static final 자동 적용
}
interface B {
int VALUE = 20; // public static final 자동 적용
}
// C는 A와 B를 동시에 상속
interface C extends A, B {}
public class Main {
public static void main(String[] args) {
System.out.println(A.VALUE); // 10
System.out.println(B.VALUE); // 20
// System.out.println(C.VALUE); // 컴파일 오류 (모호함)
}
}
A.VALUE 또는 B.VALUE)를 명확히 지정해서 접근해야 합니다.