🔗 자바 상속
1. 인터페이스의 핵심 개념 정리
1) 인터페이스(Interface)란?
- 메서드의 이름과 실행할 때 주어져야하는 매개변수들, 어떤 결과를 받는다는 기능의 규격(Contract)을 정의하고, 이를 외부에 공개하여 자식 클래스쪽에서 해당 규격대로 구현을 위임/강제하는 자바의 핵심 객체 지향 문법 구조.
- 느슨한 결합(Loose Coupling): 요소들이 완전히 독립된 것은 아니지만, 서로에 대한 정보가 최소한으로만 연결되어 있어 변경에 유연한 상태. 인터페이스를 사용하면 사용자(다른 클래스)는 내부 구현이 어떻게 돌아가는지 몰라도 인터페이스 규칙만 보고 코드를 작성할 수 있다. (시스템 유연성이 극대화.)
ℹ️ 클래스가 특정 인터페이스를 구현하면 그 인터페이스가 그 클래스의 상위 타입이라고 함
2) 핵심 문법적 특징
- 추상 메서드의 강제성: 인터페이스 안의 메서드는 몸통({ })이 없는 추상 메서드가 기본값.
- 이를 물려받은 자식 클래스는 반드시 해당 메서드를 오버라이딩(구현)해야 하며, 구현하지 않으면 컴파일 에러가 발생
- 함수 구현 없이 접근 제어자, 리턴 타입, 메서드의 이름, 매개변수만 정의
- 다중 상속(구현) 가능: 자바의 클래스는 단 하나의 클래스만 상속(extends)받을 수 있지만, 인터페이스는 여러 개를 동시에 구현(implements) 가능하다.
- 클래스 다중 구현: 클래스에 인터페이스를 여러 개 가져다 붙이기 가능(상속이 아닌 구현).
- 인터페이스 간 상속: 인터페이스끼리는 여러 개의 인터페이스를 동시에 상속(다중 상속)해서 확장 가능
- 이유: 인터페이스는 기본적으로 실제 실행되는 코드가 없는 메서드 선언부만 가지고 있기 때문에, 여러 인터페이스에서 같은 이름의 메서드를 물려받더라도 어차피 구현하는 클래스에서 딱 한 번만 새로 정의하면 되므로 충돌이 나지 않음 (모호성이 없음)
- 구현체의 제한: 인터페이스의 구현체는 실체(객체)를 만들어낼 수 있는 인스턴스화 가능한 클래스 타입(레코드, 열거형 포함)으로 제한
// 예시 1: 클래스 상속은 단 하나만!
class SmartPhone extends Phone implements Camera, Computer {
// 대신 인터페이스는 여러 개를 구현(implements)할 수 있음
}
// 예시 2: 열거형이 인터페이스를 구현
interface FeeCalculator {
int calculate(int amount);
}
enum PaymentMethod implements FeeCalculator {
CREDIT_CARD {
@Override
public int calculate(int amount) {
return (int) (amount * 0.02); // 2% 수수료
}
},
BANK_TRANSFER {
@Override
public int calculate(int amount) {
return 0; // 수수료 없음
}
}
}
💡 추상 메서드: C++의 순수 가상 함수(함수 뒤에 '=0') 기능과 동일. 클래스나 인터페이스 안에서 구현부 없이 자식 클래스(구현 클래스)에게 똑같은 이름으로 구현을 강제하는 '추상' 성격을 가짐
💡 인터페이스끼리는 기능을 강제하는 구현을 할 수 없음. 다만, 기존 인터페이스를 물려받아 더 큰 설계도를 만드는 상속(extends)은 가능.
더보기
✨ 인터페이스 상속 예시
1) 기존 코드를 망가뜨리지 않고 기능을 확장할 때 (하위 호환성)
- 상황: 새로운 기능이 추가된 B라는 인터페이스가 필요해짐
- 기존 A 인터페이스에 직접 새 메서드를 추가해 버리면, A를 쓰고 있던 기존 클래스들 전체에 컴파일 에러
// 표준 버전 (기존 가전제품 설계도)
interface Electronics {
void turnOn();
void turnOff();
}
// 확장 버전 (기존 설계도를 상속받아 원격 제어 기능을 추가)
interface SmartTV extends Electronics {
void wifiConnect();
}
2. 공통 자격(역할)을 묶어서 더 구체적인 자격을 만들 때
- 자바 표준 라이브러리(JDK)가 이 방식을 가장 적극 활용.
- 만약 아래를 상속 없이 하나의 거대한 인터페이스로 만들었다면, 순서가 필요 없는 구조(Set 등)를 만들 때도 불필요한 메서드들을 억지로 구현해야 함. (역할을 층층이 세분화할 때 필수 사용)
- Iterable: 내부 데이터를 하나씩 꺼내볼 수 있는(반복 가능한) 구조라는 알리는 아주 단순한 자격증
- Collection: Iterable을 상속받아서, 데이터 추가(add)나 삭제(remove)도 가능한 구조라는 조금 더 구체적인 자격증
- List: Collection을 또 상속받아서, 순서(index)까지 보장하는 구조라는 완성형 설계도가 됨.
3) 여러 개의 설계도를 하나로 합칠 때 (다중 상속)
interface Drivable { void drive(); }
interface Flyable { void fly(); }
// 두 설계도를 합쳐서 새로운 종합 설계도를 만듦
interface FuturisticCar extends Drivable, Flyable {
void autonomousDrive();
}
3) 인터페이스의 세부 규칙 및 팁
- 자동 제어자 (생략 가능):
- 메서드: 제어자를 생략하면 자동으로 public abstract(공개 추상 메서드)
- 변수(필드): 인터페이스에 선언되는 모든 변수는 자동으로 public static final(상수)이 됩니다.
- protected 절대 사용 불가: 누구나 구현할 수 있는 '공개된 표준 규격'을 만드는 것이 목적이므로, 특정 자식에게만 접근을 허용하는 protected 개념은 인터페이스의 설계 철학과 맞지 않아 사용 불가
- 자바 8 이후의 예외 (추상 성격이 떼어짐): 기본적으로 추상 성격을 가지지만, 자바 8부터는 default와 static, private(자바 9 이후) 메서드를 통해 예외적으로 일반 메서드(실행 코드가 있는 메서드)도 가질 수 있음
💡 구현 시 필수 Tip! 인터페이스를 클래스에서 구현할 때는 메서드 앞에 반드시 public을 명시해야 함. 자바에서는 오버라이딩할 때 부모보다 접근 범위를 좁힐 수 없다는 규칙이 있는데, 인터페이스의 메서드는 이미 public이기 때문에 구현 클래스에서 생략하면(접근 제어자가 default로 좁아져) 컴파일 에러가 발생
📊 추상 클래스 vs 인터페이스
둘 다 상속받은 자식 클래스가 무언가를 강제로 구현하게 만든다는 점에서 비슷하지만, 목적이 다름
| 추상 클래스 (abstract class) | 인터페이스 (interface) | |
| 핵심 목적 | 상속 관계를 통해 부모의 기능을 확장하고 재사용함. | 클래스의 동일한 동작(기능)을 보장함. |
| 상속/구현 | 단일 상속만 가능 (extends) | 다중 구현 가능 (implements) |
| 논리적 관계 | "~은 ~이다" (IS-A) 예: 고양이는 동물이다 (Cat extends Animal) |
"~을 할 수 있다" (CAN-DO) 예: 비행기는 날 수 있다 (Airplane implements Flyable) |
| 내부 구성 | 미완성 부모 클래스로서 일반 변수, 일반 메서드, 추상 메서드를 자유롭게 섞어 가질 수 있음 |
순수 설계도로서 추상 메서드와 상수가 중심이며, 실제 코드는 예외적으로만 허용됨 |
ℹ️ 추가 설명
추상 클래스(Abstract Class): 추상 메서드를 포함할 수 있고 직접 객체를 만들 수 없는 '미완성 부모 클래스' (규격 + 유산 상속)
구체 클래스(Concrete Class): 추상 클래스와 대비되는 개념으로, 모든 메서드가 구현되어 있어 실제 객체(인스턴스)를 생성할 수 있는 완전한 클래스를 뜻함(레코드와 열거형도 내부적으로는 구체 클래스 범주에 속함).
2. 기본적인 문법과 예시 코드
1) 접근 제어자
- ① 인터페이스 자체의 접근 제어자
- public: 모든 패키지에서 접근 가능
- default (package-private): 접근 제어자를 생략했을 때 적용되는 기본값이며, 같은 패키지 내부에서만 접근 가능 (표준이 아닌 클래스와 마찬가지로 생략시 기본 적용값)
- ② 인터페이스 내부 멤버의 접근 제어자
- public (생략 시 기본값): 인터페이스는 외부에 공개하기 위한 규격(계약서)이기 때문에, 제어자를 생략하면 일반 클래스와 달리 무조건 public이 적용 (규칙이 달라 개발자가 직접 package-private를 지정하는 것은 불가능)
- private (Java +): default나 static 메서드를 구현할 때, 인터페이스 내부에서만 공통으로 사용하는 중복 코드를 분리하기 위한 도우미 메서드 용도(메서드만 가능). 외부에 노출되지 않고 쓰기 위함.
- ③ 내부 멤버별 상세 특징
- 멤버 변수 (필드)
- 무조건 public static final 규칙을 따름
- 제어자를 생략하더라도 컴파일러에 의해 자동으로 상수가 됨
- 일반 메서드 (추상 메서드)
- 무조건 public abstract 규칙을 따름
- 구현부({})가 없으며, 생략해도 자동으로 public이 적용
- 디폴트 메서드 (Default Method)
- 특징: Java 8부터 도입되었으며, 인터페이스 내부에서도 구현부({})를 가질 수 있는 메서드. 이를 통해 제한적인 다중 상속 효과를 낼 수 있다.
- 충돌 해결 법칙: 여러 인터페이스를 한 클래스에서 구현할 때, 디폴트 메서드 간에 시그니처(이름과 매개변수)가 중복되어 충돌이 발생하면 자바 컴파일러가 에러를 발생시킨다. 이때 개발자는 구현 클래스에서 해당 메서드를 직접 오버라이딩(재정의)하여 문제를 해결해야 함.
- 멤버 변수 (필드)
interface Calculator {
// public 추상 메서드
int add(int a, int b);
// public default 메서드
default int addPrint(int a, int b) {
log("더하기 연산 시작"); // 내부 private 메서드 호출
return add(a, b);
}
// 💡 인터페이스 내부에서만 사용하는 private 메서드!
private void log(String msg) {
System.out.println("[LOG] " + msg);
}
}
💡 Tip: 특정 패키지 내부에서만 공유하는 인터페이스 규칙을 만들고 싶다면 내부 멤버가 아니라 인터페이스 자체를 default로 선언해야 함
💡 한 줄 요약: 인터페이스 자체는 public과 default(생략시)를 가질 수 있고, 인터페이스 내부 멤버는 기본적으로 public이지만 내부 로직 캡슐화를 위해 private 메서드도 가질 수 있다. (단, protected는 불가능!)
2) 예시 코드
- 인터페이스를 만들 때는 interface 키워드를 사용하고, 클래스에서 이를 구현할 때는 implements 키워드를 사용
- @Override 생략 가능 (없는 걸 선호하는 경우도 있음)
- 결과: 사용하는 쪽에서는 어떤 기기인지 세세하게 알 필요 없이 Remocon이라는 인터페이스만 보고 조작할 수 있음
// 1. 인터페이스 정의
interface Remocon {
void turnOn(); // public abstract가 자동으로 붙음
void turnOff();
}
// 2. 인터페이스 구현 클래스 (TV)
class TV implements Remocon {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
}
// 3. 인터페이스 구현 클래스 (오디오)
class Audio implements Remocon {
@Override
public void turnOn() {
System.out.println("오디오를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("오디오를 끕니다.");
}
}
// 위를 사용
public class Main {
public static void main(String[] args) {
// 인터페이스를 통한 다형성 활용
Remocon device1 = new TV();
Remocon device2 = new Audio();
device1.turnOn(); // "TV를 켭니다." 출력
device2.turnOn(); // "오디오를 켭니다." 출력
}
}
3. 인터페이스를 왜 사용할까? (장점)
- 다형성(Polymorphism) 구현: 위 코드처럼 하나의 인터페이스 타입으로 여러 가지 구현 객체를 동일한 방식으로 제어할 수 있어 프로그램의 유연성이 극대화
- 개발 시간 단축 (협업 용이): 프로젝트를 할 때 인터페이스(설계도)만 먼저 짜두면, 한 사람은 그 인터페이스를 사용하는 코드를 짜고, 다른 사람은 인터페이스를 구현하는 클래스를 짜는 방식으로 동시 개발 가능
- 결합도 낮추기 (Decoupling): A 클래스가 B 클래스를 직접 호출하던 강한 결합(Tight Coupling) 상태에서, 중간에 인터페이스(Interface)라는 중중재자를 두어 A가 B를 몰라도 되게 만드는 '과정' 그 자체 (클래스 간의 직접적인 의존을 줄이기). 디커플링을 통해 느슨한 결합 상태로 만들면, TV 클래스를 스마트TV 클래스로 바꾸더라도 이를 사용하는 다른 코드들을 수정할 필요가 없어짐. (유지보수에 매우 유리)
4. 자바 핵심 내장 인터페이스
① 마커 인터페이스 : Serializable
추상 메서드가 전혀 선언되어 있지 않고, 단순히 객체의 속성을 표시(Marking)하기만 하는 인터페이스
- 역할: 이 인터페이스를 구현한 객체는 '직렬화(Serialization)'가 가능함을 뜻함
- 활용: 객체를 대용량 스토리지에 저장하거나 네트워크를 통해 다른 곳(Peer)으로 전송할 때 사용. (예: 파이썬 등으로 학습시킨 AI 모델의 수많은 파라미터 객체들을 그대로 파일로 저장할 때 필수적인 개념)
💡 Q. 메서드가 없는 Serializable 변수인데, 왜 메서드에 접근할 수 있을까?
- A. 자바의 다형성 때문. 인터페이스 변수에는 이를 구현한 실제 객체의 참조값이 담김. 자바의 모든 객체는 최상위 클래스인 Object를 자동 상속 (여기선 AbstractCollection를 통해서 암시적으로) 받으므로, 변수를 통해 Object가 가진 기본 메서드(toString(), equals() 등)에 정상적으로 접근할 수 있음.
② 데이터 그룹의 표준 : Iterable과 Collection
여러 개의 데이터를 묶어서 관리하는 자바 자료구조(Collection Framework)의 근간이 되는 인터페이스들
[Iterable] : 순회 가능 양식 제공 (최상위)
▲
[Collection] : 데이터 그룹 관리 표준 (추가/삭제/크기 확인 등)
- Iterable: "반복(순회)할 수 있다"는 의미를 부여. 이 인터페이스 덕분에 자바의 for-each 문을 사용할 수 있게 됨.
- Collection: Iterable을 상속받은 인터페이스로, 데이터를 추가(add), 삭제(remove), 크기 확인(size)하는 '데이터 관리의 표준 메서드'들을 강제하여 전체적인 코드의 일관성을 유지.
'Language & Tool > Java' 카테고리의 다른 글
| ☕Java 제네릭(Generic) 이해하기 (0) | 2026.06.11 |
|---|---|
| ☕Java 제어문 총정리 (+ 레이블) (0) | 2026.06.08 |
| ☕OOP 핵심: 부모 클래스를 재사용하여 새로운 클래스를 작성하는 상속(Inheritance) (0) | 2026.06.05 |
| 🌱변수 생성시 위치별 초기화 규칙 (0) | 2025.09.14 |
| 🌱JAVA 패키지 정리 모음 (0) | 2025.09.08 |