인터페이스와 다형성
추상 클래스 |
abstract 포함, 객체 생성 불가, 상속 필수 |
인터페이스 |
100% 추상, implements 필요, 다중 상속 가능 |
다형성 |
부모 타입 = 자식 객체 참조 가능 |
열거형 (Enum) |
미리 정의된 상수값 집합 |
익명 클래스 |
1회성 클래스 |
중첩 클래스 |
내부 클래스, static / non-static |
1. 추상 메소드
- abstract 키워드를 사용
- 몸체 없이 형식만 존재 (반환형, 이름, 매개변수 목록만 선언)
- 자식 클래스가 반드시 구현
- final과 함께 사용할 수 없음
- 추상 클래스 또는 인터페이스에서 선언
예제
abstract class Animal {
abstract void sound(); // 추상 메서드
}
class Dog extends Animal {
void sound() { System.out.println("멍멍"); } // 구현 필수
}
2. 추상 클래스
- abstract 키워드 사용
- 추상 메서드를 포함할 수 있음
- 객체 생성 불가 (불완전한 클래스)
- 일반 메서드와 필드 포함 가능
추상클래스의 사용
- 유사한 자식 클래스를 묶고자 할 때
- 불완전한 클래스로서 일부 기능만 제공
- 자식이 반드시 구현해야 함
- 자식이 구현하지 않으면 자식도 추상 클래스가 됨
- 일반 클래스 + 인터페이스의 중간적 성격
예제
abstract class Vehicle {
abstract void move(); // 추상 메서드
void stop() { System.out.println("차가 멈췄다."); } // 일반 메서드
}
class Car extends Vehicle {
void move() { System.out.println("자동차가 달린다!"); } // 구현 필수
}
3. 인터페이스
- 100% 추상 클래스
- 모든 메서드는 public abstract (생략 가능)
- 필드는 public static final만 가능 (상수)
- 직접 객체 생성 불가
- 관행적으로 인터페이스명은 형용사 (Runnable, Serializable)
인터페이스 정의
- interface 키워드 사용
- 추상 메서드만 선언 (몸체 X)
- default 또는 static 메서드만 몸체 구현 가능
예제
interface Animal {
void sound(); // public abstract 자동 적용
}
class Dog implements Animal {
public void sound() { System.out.println("멍멍"); }
}
4. 인터페이스의 사용
- 추상 클래스처럼 자식이 구현
- 기능적으로 유사한 클래스 묶을 때 사용
- 인터페이스를 상속받아 새로운 인터페이스 정의 가능
- 다중 상속 가능 (클래스는 불가능)
인터페이스 상속
- 자식 인터페이스가 부모 인터페이스를 상속 (extends 사용)
- 여러 부모 인터페이스를 다중 상속 가능
예제
interface Animal {
void sound();
}
interface Pet extends Animal {
void play();
}
class Dog implements Pet {
public void sound() { System.out.println("멍멍"); }
public void play() { System.out.println("강아지가 놀고 있음"); }
}
5. 인터페이스의 구현
- implements 사용
- 자식 클래스는 인터페이스의 모든 메서드 구현 필수
- 다중 인터페이스 구현 가능
예제
interface Flyable { void fly(); }
interface Swimable { void swim(); }
class Duck implements Flyable, Swimable {
public void fly() { System.out.println("오리가 난다!"); }
public void swim() { System.out.println("오리가 수영한다!"); }
}
6. 디폴트 메소드 (Default Method)
- 인터페이스에서 몸체를 가진 메소드 선언 가능
- default 키워드 사용
- 자식 클래스에서 오버라이딩 가능
- 기존 인터페이스에 새 메서드 추가 시 기존 코드 수정을 피하기 위해 도입
예제
interface Animal {
default void sleep() { System.out.println("동물이 잠을 잔다."); }
}
class Dog implements Animal {} // sleep() 오버라이딩 안 해도 사용 가능
7. 추상클래스, 인터페이스, 클래스의 형변환
- extends와 implements에 따라 상위-하위 자료형 관계 설정
- 상위 타입 변수 = 하위 객체 참조 가능
- 동적 바인딩: 실행 시 실제 객체의 메서드 실행
예제
Animal a = new Dog(); // 업캐스팅 (자동)
a.sound(); // "멍멍"
8. 다형성
- 유사하지만 다양한 기능을 수행
- 자식 → 부모 형변환 가능
- 자식의 구현 방식에 따라 동적 바인딩
다형성의 활용 효과
- 코드 유연성, 재사용성 증가
- 실제 객체 유형을 명시적으로 다룰 필요 없음
오버라이딩 예제
class Parent {
void show() { System.out.println("부모 클래스"); }
}
class Child extends Parent {
void show() { System.out.println("자식 클래스"); }
}
Parent p = new Child();
p.show(); // "자식 클래스" (동적 바인딩)
9. 열거형 (Enum)
- enum 키워드 사용
- 미리 정의된 값만 가질 수 있음
- 필드, 생성자, 메소드 포함 가능
- values() 메서드 제공
예제
enum Color {
RED, GREEN, BLUE;
}
public class Main {
public static void main(String[] args) {
Color c = Color.RED;
System.out.println(c); // RED
}
}
10. 익명 클래스 (Anonymous Class)
- 1회성으로 객체 생성
- 인터페이스 or 추상 클래스 상속받아 사용
- 중괄호 {} 내부가 클래스 몸체
예제
abstract class Animal {
abstract void sound();
}
public class Main {
public static void main(String[] args) {
Animal dog = new Animal() { // 익명 클래스
void sound() { System.out.println("멍멍"); }
};
dog.sound(); // "멍멍"
}
}
11. 중첩 클래스 (Nested Class)
- 클래스 내부에 또 다른 클래스 정의
- private, protected 가능
- 외부 클래스의 멤버와 관련된 작업 처리
1) Non-Static 중첩 클래스 (Inner Class)
- 외부 클래스 객체가 있어야 사용 가능
- 외부 클래스의 모든 멤버 접근 가능
예제
class Outer {
class Inner {
void show() { System.out.println("내부 클래스!"); }
}
}
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 외부 클래스 객체 필요
inner.show(); // "내부 클래스!"
2) Static 중첩 클래스
- 외부 클래스 객체 없이 사용 가능
- 정적 멤버만 접근 가능
예제
class Outer {
static class StaticInner {
void show() { System.out.println("정적 내부 클래스!"); }
}
}
Outer.StaticInner inner = new Outer.StaticInner(); // 바로 생성 가능
inner.show(); // "정적 내부 클래스!"