상속(Inheritance)
상속은 여러 클래스에 공통된 속성과 동작이 있을 때, 이를 별도의 클래스(부모 클래스)로 추출하여 자식 클래스가 물려받는 객체지향 프로그래밍의 핵심 개념입니다. 자바에서는 extends 키워드를 사용하여 상속을 구현합니다.
상속의 필요성
- 코드 재사용성 향상: 중복되는 코드를 부모 클래스에 한 번만 작성하면, 여러 자식 클래스에서 이를 재사용할 수 있습니다.
- 클래스 간 관계 설정: 상속을 통해 클래스 간의 계층 구조를 만들고, 'is-a' 관계를 명확히 정의합니다.
상속 구현 기본 문법
// 부모 클래스 (Base class)
public class Parent {
// 공통 멤버 변수 및 메서드
}
// 자식 클래스 (Derived class)
public class Child extends Parent {
// Parent의 public 및 protected 멤버에 직접 접근 가능
}
상속 사용 시 주의사항
- 단일 상속 원칙: 자바는 다중 상속을 지원하지 않지만, 다중 레벨 상속은 허용됩니다.
- 접근 제어: 자식 클래스는 부모 클래스의
private멤버를 직접 상속받을 수 없습니다. - 멤버 접근 우선순위 (가까운 원칙): 자식 클래스에서 변수나 메서드를 찾을 때, 먼저 자기 자신의 멤버를 찾고 없으면 부모 클래스에서 찾습니다. 명시적으로 부모 클래스의 멤버에 접근하려면
super키워드를 사용합니다.super.부모변수super.부모메서드()
- 생성자와 상속: 자식 클래스의 생성자 첫 줄에는 항상
super()가 생략되어 있어, 부모 클래스의 기본 생성자를 먼저 호출한 후 자식 객체를 초기화합니다. 부모 클래스에 기본 생성자가 없으면, 자식 생성자에서 명시적으로super(매개변수)를 호출해야 합니다.
메서드 오버라이딩 (Method Overriding)
부모 클래스로부터 상속받은 메서드의 동작을 자식 클래스에서 재정의하는 것입니다.
오버라이딩의 조건
- 부모 클래스의 메서드 이름, 매개변수 목록, 반환 타입이 완전히 동일해야 합니다.
- 부모 클래스의
private메서드는 오버라이딩할 수 없습니다. - 자식 클래스의 접근 제어자는 부모 클래스의 접근 제어자보다 더 넓은 범위이거나 같아야 합니다.
오버라이딩 예시
public class Parent {
public void display(String message) {
System.out.println("부모 메시지: " + message);
}
}
public class Child extends Parent {
@Override
public void display(String message) {
System.out.println("자식 메시지: " + message);
}
}
추상 클래스 (Abstract Class)와 템플릿 디자인 패턴
추상 클래스
추상 메서드(구현부가 없는 메서드)를 하나 이상 포함할 수 있는 클래스로, 인스턴스화가 불가능합니다.
추상 클래스의 목적
- 인스턴스화 방지: 특정 클래스가 단독으로 객체를 생성하는 것을 막고, 오직 부모 클래스 역할만 하도록 강제합니다.
- 메서드 구현 강제: 자식 클래스가 반드시 특정 메서드를 구현하도록 강제하여, 일관된 인터페이스를 제공합니다.
추상 클래스 사용 예시
public abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
// 추상 메서드
public abstract void makeSound();
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println("멍멍!");
}
}
템플릿 디자인 패턴 (Template Method Pattern)
알고리즘의 골격(템플릿)을 부모 클래스에 정의하고, 세부 구현 단계는 자식 클래스에서 구현하도록 하는 패턴입니다. 전체적인 흐름은 고정하고, 일부 변하는 부분만 자식 클래스에서 오버라이딩합니다.
Static 키워드
static은 멤버 변수나 메서드가 인스턴스가 아닌 클래스 자체에 속하도록 만드는 키워드입니다.
Static의 특징
- 공유 데이터: 모든 인스턴스가 동일한 메모리 공간을 공유합니다.
- 클래스 로딩 시점: 클래스가 JVM에 로딩될 때 생성되며, 객체 생성보다 먼저 존재합니다.
- 접근 방식:
클래스명.멤버형태로 접근하는 것이 일반적입니다. - 제약사항:
- static 메서드 내에서는
this키워드를 사용할 수 없습니다. - static 메서드에서는 오직 static 멤버만 직접 호출할 수 있습니다.
- 인스턴스 메서드에서는 static 멤버에 자유롭게 접근 가능합니다.
- static 메서드 내에서는
Final 키워드
final은 '변경 불가'를 의미하는 키워드입니다.
- final 클래스: 상속을 금지합니다. 예)
String - final 메서드: 오버라이딩을 금지합니다.
- final 변수: 상수(Constant)가 되어 값을 한 번만 할당할 수 있습니다.
final과 abstract는 함께 사용할 수 없습니다.
접근 제어자 (Access Modifiers)
클래스, 메서드, 변수 등의 접근 범위를 제어합니다.
| 접근 제어자 | 범위 |
|---|---|
private |
같은 클래스 내부 |
| (default) | 같은 패키지 내부 |
protected |
같은 패키지 + 다른 패키지의 자식 클래스 |
public |
모든 곳 |
오버라이딩 시 자식 클래스의 접근 제어자는 부모 클래스보다 더 좁은 범위로 설정할 수 없습니다.
인터페이스 (Interface)
인터페이스는 클래스가 따라야 할 '규칙'을 정의하는 청사진입니다.
인터페이스의 특징
- 규칙 정의: 구현해야 할 추상 메서드의 집합을 정의합니다.
- 다중 구현: 클래스는 여러 인터페이스를 동시에 구현할 수 있습니다.
- 인스턴스화 불가: 인터페이스 자체로 객체를 생성할 수 없습니다.
- 생성자 없음: 인터페이스에는 생성자가 없습니다.
인터페이스 구현
public interface Flyable {
void fly(); // public abstract 생략
}
public class Bird implements Flyable {
@Override
public void fly() {
System.out.println("새가 납니다.");
}
}
인터페이스 vs 추상 클래스
| 특징 | 추상 클래스 | 인터페이스 |
|---|---|---|
| 멤버 변수 | 가능 | 상수만 가능 (public static final) |
| 생성자 | 가능 | 불가능 |
| 일반 메서드 | 가능 | Java 8부터 default, static 메서드 가능 |
| 다중 상속/구현 | 단일 상속만 가능 (extends) | 다중 구현 가능 (implements) |
| 목적 | 부분적인 구현 + 공통 기능 상속 | 완전한 추상화 + 규칙 정의 |
다형성 (Polymorphism)
하나의 객체가 여러 가지 타입을 가질 수 있는 성질을 의미합니다.
다형성의 장점
- 코드 재사용성: 부모 타입의 변수로 다양한 자식 객체를 참조하여 코드를 유연하게 작성할 수 있습니다.
- 확장성: 새로운 자식 클래스가 추가되어도 기존 코드를 변경하지 않고 기능을 확장할 수 있습니다.
다형성 구현
public interface Playable {
void play();
void stop();
}
class MP3 implements Playable {
public void play() { System.out.println("MP3 재생"); }
public void stop() { System.out.println("MP3 정지"); }
}
class MP4 implements Playable {
public void play() { System.out.println("MP4 재생"); }
public void stop() { System.out.println("MP4 정지"); }
}
public class Player {
public static void operate(Playable p) { // 다형성 활용
p.play();
p.stop();
}
public static void main(String[] args) {
operate(new MP3()); // Playable p = new MP3()
operate(new MP4()); // Playable p = new MP4()
}
}
다형성과 멤버 접근
- 변수: 컴파일 타임과 런타임 모두 부모 타입의 변수가 참조됩니다.
- 메서드: 컴파일 시 부모 타입의 메서드 존재 여부를 확인하고, 런타임 시에는 실제 객체(자식)의 오버라이딩된 메서드가 호출됩니다.
타입 변환 (다운 캐스팅)과 instanceof
Parent p = new Child(); // 업 캐스팅
// p.childSpecificMethod(); // 오류: 부모 타입은 자식 고유 메서드에 접근 불가
if (p instanceof Child) {
Child c = (Child) p; // 다운 캐스팅
c.childSpecificMethod();
}
내부 클래스 (Inner Class)와 익명 클래스
멤버 내부 클래스
public class Outer {
private int data = 10;
class Inner {
void display() {
System.out.println("외부 클래스 데이터: " + data);
}
}
}
익명 내부 클래스 (Anonymous Inner Class)
클래스 정의와 객체 생성을 동시에 하는 기법으로, 주로 인터페이스나 추상 클래스를 즉석에서 구현할 때 사용됩니다.
interface Greeting {
String sayHello(String name);
}
Greeting g = new Greeting() {
@Override
public String sayHello(String name) {
return "안녕하세요, " + name + "님!";
}
};
Object 클래스
자바의 모든 클래스는 명시적으로 상속하지 않아도 java.lang.Object 클래스를 최상위 부모로 가집니다.
equals(Object obj): 두 객체의 주소값을 비교합니다. String 등의 클래스는 내용 비교를 위해 오버라이딩되어 있습니다.toString(): 객체를 문자열로 표현합니다. 기본적으로클래스명@해시코드를 반환하며, 자주 오버라이딩됩니다.