수업을 들을 때 배웠던 부분인데...
실제로 사용하는 경우가 많지 않다보니 너무 잊어가고 있어 정리하려고 한다!
클래스를 상속받을 때 사용한다.
부모 클래스의 속성을 자식 클래스가 물려받게 되는데... 다중 상속은 불가능하다!
물려받는 과정에서 메서드를 재정의(오버라이딩) 할 수 있다.
// 부모 클래스 (SuperClass)
class Animal {
String name = "동물";
void sound() {
System.out.println("동물 소리");
}
}
// 자식 클래스 (SubClass)
class Dog extends Animal {
void sound() {
System.out.println("멍멍!");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
dog.sound();
}
}
=> 출력 결과는 "동물" / "멍멍" 이다.
보이는 것처럼 부모에서 설정한 sound()라는 메서드를 Dog 클래스에서 재정의 했으며,
상속받았기 때문에 자식 클래스 객체에서 부모 클래스의 변수를 가져다 사용할 수 있다. (dog.name)
인터페이스를 구현할 때 사용한다.
상속과 달리 다중 구현이 가능해진다!
하지만 인터페이스는 추상 메서드만 가지므로, 반드시 메서드 구현이 필요하고!
인터페이스 자체로는 객체 생성이 불가하다.
여기서 반드시 구현이 필요하단 건 사용하기 위해 필요하다는 의미가 아니라,
implements를 하게 되면 해당 interface에 있는 추상 메서드는 모두!! 필수로 구현해야 한다.
// 인터페이스 정의
interface Animal {
void sound();
}
// 인터페이스 구현 (implements)
class Cat implements Animal {
public void sound() {
System.out.println("야옹!");
}
}
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
cat.sound();
}
}
=> 출력 결과는 "야옹" 이다.
인터페이스에 sound()라는 추상 메서드가 정의되어 있고, Cat에서 구현을 진행했다.
🚨 그럼 인터페이스는 변수를 정의할 수 없나?!?
인터페이스는 설계도의 역할이기 때문에 이미 구현된 내용을 가질 수는 없다.
대신 final static을 붙여서 상수는 정의할 수 있다.
interface Animal {
int MAX_AGE = 20;
}
static final int MAX_AGE = 20; 이라고 해도 되고 그냥 타입과 변수명, 값만 써도 static final이 적용된다.
class Dog implements Animal {
void printMaxAge() {
System.out.println("최대 나이: " + MAX_AGE);
}
}
그럼 해당 값을 implements한 클래스에서 바로 사용할 수 있게 된다~!
| 구분 | extends | implements |
|---|---|---|
| 적용 대상 | 클래스(부모-자식 관계) | 인터페이스 |
| 다중 적용 | 불가능 (단일 상속만 가능) | 가능 (여러 개 인터페이스 구현 가능) |
| 메서드 재정의 | 선택적 (오버라이딩 가능) | 필수 (모든 메서드를 구현해야 함) |
| 사용 목적 | 코드 재사용 (속성과 기능 상속) | 강제 규칙 (구현해야 할 기능 지정) |
// 부모 클래스 (SuperClass)
class Animal {
void eat() {
System.out.println("먹는다.");
}
}
// 인터페이스 정의
interface Sound {
void makeSound();
}
// 자식 클래스 (부모 클래스를 상속 + 인터페이스 구현)
class Dog extends Animal implements Sound {
public void makeSound() { // 인터페이스의 메서드 구현
System.out.println("멍멍!");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 상속 메서드
dog.makeSound(); // 인터페이스 구현 메서드 호출
}
}
=> 출력 결과는 "먹는다." / "멍멍!" 이다.
Animal에서 생성한 eat() 메서드는 재정의를 해도 되지만, 하지 않고 그대로 이용!
Sound에서 정의한 makeSound() 메서드는 Dog 자체에서 구현하여 이를 호출했다!
📣 결과적으로, 공통 기능을 각 클래스에서 재정의하고 싶으면 extends!
기능을 강제로 사용하게 하고 싶으면 implements!
extends의 경우 공통 기능을 두되, 필수로 사용해야 하는 강제성은 없다!
부모 클래스를 상속받을 때 일부는 구현된 그대로, 일부는 자식 클래스에서 반드시 구현하도록 설정할 수 있다.
바로 부모 클래스를 추상클래스로 두는 것이다!
// 추상 클래스 선언
abstract class Animal {
// 미리 구현 (공통 사용을 위해)
void eat() {
System.out.println("음식을 먹는다.");
}
// 추상 메서드(반드시 구현해야 함)
abstract void sound();
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍!");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // "음식을 먹는다."
dog.sound(); // "멍멍!"
}
}
이렇게 설정하면, 원래대로 부모의 메서드를 그대로 가져다 쓰기도 하고
자식 클래스에서 필수로 구현해야 할 추상 메서드로 설정할 수 있다!!
물론, 원래대로 eat()도 자식 클래스에서 오버라이드 할 수 있다~
나름대로 extends와 implements를 함께 쓰는 느낌이다.