👉 "이 클래스는 이런 기능들을 반드시 제공할 거야!" 라고 약속하는 설계도 같은 것
인터페이스는 abstract, final와 함께 대표적인 규제이다.
인터페이스의 역할은 이렇다. 어떤 클래스가 있고 그 클래스가 특정한 인터페이스를 사용한다면 그 객체는 반드시 인터페이스의 메서드들을 구현해야 한다.
만약 인터페이스에서 강제하고 있는 메소드를 구현하지 않으면 컴파일 조차 되지 않는다.
예시를 보자.
interface I {
public void z(); // 중괄호 없음 비어있는 메서드 클래스에 구현함.
}
class A implements I { // class A 구현
public void z() { // 이 부분이 없으면 Error
}
}
클래스 A는 인터페이스 I를 '구현' 한다.
클래스 A 뒤의 implements I는 이 클래스가 인터페이스 I를 구현하고 있다는 의미다.
그것은 interface I의 맴버인 public void z() 메서드를 클래스 A가 반드시 포함하고 있어야 한다는 뜻이다. 따라서 위의 코드는 문제가 없다.
인터페이스와 상속은 다르다.
✔️ 인터페이스: 하위 클래스에 특정한 메서드가 반드시 존재하도록 강제(implements)
✔️ 상속: 상위 클래스의 기능을 하위 클래스가 물려 받는 것 (extends)
인터페이스는 사용하는 키워드도 다르다.
클래스를 선언 할 때는 class를 사용하지만 인터페이스는 interface를 사용한다.
// 인터페이스 정의
interface Animal {
void sound(); // 메서드 선언 (구현 없음)
void move();
}
// 클래스가 인터페이스 구현
class Dog implements Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
@Override
public void move() {
System.out.println("강아지가 달린다!");
}
}
class Bird implements Animal {
@Override
public void sound() {
System.out.println("짹짹");
}
@Override
public void move() {
System.out.println("새가 난다!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal bird = new Bird();
dog.sound(); // 멍멍
bird.sound(); // 짹짹
}
}
인터페이스를 이용해서 서로가 동일한 메서드를 만들도록 규약을 만들어서 공유한 결과 각자가 상대의 일정이나 구현하는 방식에 덜 영향을 받으면서 애플리케이션을 구축 할 수 있었다.
인터페이스는 어떠한 클래스가 어떠한 메서드를 가지고 있는가, 또한 어떤 멤버변수를 가지고 있느냐에 대한 명세서 같은 느낌이다.
그러면 저 계산기를 구현하고 있는 클래스들은 그 명세서에 적혀있는 대로 명세서에 정의되어 있는 대로 클래스의 멤버를 구체적으로 구현해야지만 컴파일이 되게 되는 것이다.
이렇게 함으로써, 서로간의 커뮤니케이션 미스를 방지할 수 있고 빠른 시간 내에 코드를 완성할 수 있다.
✔️ 하나의 클래스가 여러개의 인터페이스를 구현 할 수 있다.
클래스 A는 메소드 x나 z 중 하나라도 구현하지 않으면 오류가 발생한다.
interface I1 {
public void x();
}
interface I2 {
public void z();
}
class A implements I1, I2 {
public void x() {}
public void z() {}
}
✔️ 인터페이스도 상속이 된다.
interface I3 {
public void x();
}
interface I4 extends I3 {
public void z();
}
class B implements I4 {
public void x() {}
public void z() {}
}
✔️ 인터페이스의 맴버는 반드시 public이다.
interface I5 {
private void x(); // Error
}
위 코드는 오류를 발생한다.
인터페이스는 그 인터페이스를 구현한 클래스를 어떻게 조작할 것인가를 규정한다.
그렇기 때문에 외부에서 제어 할 수 있는 가장 개방적인 접근 제어자인 public만을 허용한다.
public을 생략하면 접근 제어자 default가 되는 것이 아니라 public이 된다. 왜냐하면 인터페이스의 맴버는 무조건 public이기 때문이다.
인터페이스와 추상 클래스는 서로 비슷한 듯 다른 기능이다.
✔️ 인터페이스
: 클래스가 아닌 인터페이스라는 고유한 형태를 지님
: 구체적인 로직이나 상태를 가지고 있을 수 없음
(아예 그냥 추상 메서드로!)
: 멤버 변수를 두지 않고 메서드로만 남기고 싶을 때 많이 사용
: 다중 상속의 효과를 가질 수 있음
✔️ 추상 클래스
: 일반적인 클래스
: 구체적인 로직이나 상태를 가짐
(일부는 구현된 메서드를 가질 수 있고 일부는 추상 메서드를 가질 수 있음.)
interface Myinterface {
void method();
default void newMethod() {}
}
해결책 ➡️ 디폴트 메서드(default method)
👉 즉, 인터페이스 = 규격만 정의 라는 제한을 완화한 것.
✔️ 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
✔️ default 메서드와 조상 클래스의 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고 (우선권), 디폴트 메서드는 무시된다.
interface Animal {
void sound(); // 추상 메서드
// default 메서드
default void breathe() {
System.out.println("숨을 쉰다");
}
}
class Dog implements Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound(); // 멍멍
dog.breathe(); // 숨을 쉰다 (default 메서드 실행)
}
}
👉 즉, 클래스의 static 메서드랑 비슷하지만 인터페이스 소속이라는 차이이다.
interface Animal {
void sound();
// static 메서드
static void info() {
System.out.println("이건 Animal 인터페이스의 static 메서드야!");
}
}
class Dog implements Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound(); // 멍멍
// 인터페이스 static 메서드는 이렇게 호출해야 함
Animal.info(); // 출력: 이건 Animal 인터페이스의 static 메서드야!
}
}
Animal.info();
dog.info()
)로는 부르지 못한다.Dog
는 info()
를 오버라이드할 수 없음.구분 | 선언 방법 | 호출 방법 | 구현 클래스에서 재정의? | 특징 |
---|---|---|---|---|
추상 메서드 (abstract) | void run(); | 객체 참조 통해 호출 | ✅ 반드시 구현해야 함 | 규격(계약)만 정의 |
디폴트 메서드 (default) | default void run() { ... } | 객체 참조 통해 호출 | ✅ 원하면 재정의 가능 | 기본 구현 제공, 필요시 오버라이드 |
정적 메서드 (static) | static void run() { ... } | 인터페이스명.run(); | ❌ 불가 | 공통 유틸리티 제공, 오버라이드 불가 |
interface MyInterface {
// 추상 메서드
void abstractMethod();
// default 메서드
default void defaultMethod() {
System.out.println("default 메서드 실행");
}
// static 메서드
static void staticMethod() {
System.out.println("static 메서드 실행");
}
}
class MyClass implements MyInterface {
@Override
public void abstractMethod() {
System.out.println("추상 메서드 구현");
}
// default 메서드 오버라이드 가능
@Override
public void defaultMethod() {
System.out.println("default 메서드 재정의");
}
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.abstractMethod(); // 추상 메서드 구현
obj.defaultMethod(); // default 메서드 재정의
MyInterface.staticMethod(); // static 메서드 실행
}
}