Java 객체지향 심화 - 상속, 다형성, 추상화
1. 상속 (Inheritance)
상속은 부모 클래스의 필드와 메서드를 자식 클래스가 물려받는 것이다. extends 키워드를 사용한다.
// 부모 클래스 (슈퍼클래스)
class Animal {
String name;
int age;
void eat() {
System.out.println(name + "이(가) 먹이를 먹습니다.");
}
void sleep() {
System.out.println(name + "이(가) 잠을 잡니다.");
}
}
// 자식 클래스 (서브클래스)
class Dog extends Animal {
String breed;
void bark() {
System.out.println(name + "이(가) 멍멍 짖습니다!");
}
}
class Cat extends Animal {
void meow() {
System.out.println(name + "이(가) 야옹~ 합니다.");
}
}
public class InheritanceExample {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "바둑이"; // 부모로부터 상속
dog.age = 3; // 부모로부터 상속
dog.breed = "진돗개"; // 자신의 필드
dog.eat(); // 부모 메서드
dog.sleep(); // 부모 메서드
dog.bark(); // 자신의 메서드
}
}
상속의 특징
- 자식 클래스는 부모 클래스의 메서드를 선언 없이 사용할 수 있다
- 자식 클래스의 메서드는 부모 클래스가 사용할 수 없다
- 자식 클래스끼리는 메서드 공유가 불가능하다
상속의 장점
- 코드 재사용성 향상
- 유지보수 용이
- 계층적 구조 표현
2. 메서드 오버라이딩 (Overriding)
부모 클래스의 메서드를 자식 클래스에서 재정의하는 것이다.
class Animal {
String name;
void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override // 오버라이딩 어노테이션 (선택이지만 권장이라고 함..)
void sound() {
System.out.println(name + ": 멍멍!");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println(name + ": 야옹~");
}
}
public class OverridingExample {
public static void main(String[] args) {
Animal animal = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();
dog.name = "바둑이";
cat.name = "나비";
animal.sound(); // 동물이 소리를 냅니다.
dog.sound(); // 바둑이: 멍멍!
cat.sound(); // 나비: 야옹~
}
}
오버로딩 vs 오버라이딩
| 구분 | 오버로딩 (Overloading) | 오버라이딩 (Overriding) |
|---|---|---|
| 위치 | 같은 클래스 | 상속 관계 |
| 메서드명 | 동일 | 동일 |
| 매개변수 | 다름 | 동일 |
| 반환타입 | 상관없음 | 동일 |
3. super 키워드
super는 자식 클래스에서 부모 클래스를 참조할 때 사용하는 키워드다.
super로 부모 필드/메서드 접근
class Parent {
String name = "부모";
void show() {
System.out.println("Parent의 show()");
}
}
class Child extends Parent {
String name = "자식"; // 부모와 같은 이름의 필드
@Override
void show() {
System.out.println("Child의 show()");
}
void display() {
System.out.println("this.name: " + this.name); // 자식
System.out.println("super.name: " + super.name); // 부모
this.show(); // Child의 show()
super.show(); // Parent의 show()
}
}
super() 생성자
부모 클래스의 생성자를 호출할 때 사용한다. 왜 가능한지는 모르지만 private이 아닌 경우에는 자식클래스에서 부모 클래스의 필드에 대해 조회/변경이 모두 가능하다.
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
String breed;
Dog(String name, String breed) {
super(name); // 부모 생성자 호출
this.breed = breed;
}
}
중요:
super()는 반드시 생성자의 첫 줄에 위치해야 한다!
4. 다형성 (Polymorphism)
부모 타입으로 자식 객체를 참조할 수 있다. 이를 통해 같은 타입으로 다양한 객체를 다룰 수 있다.
public class PolymorphismExample {
public static void main(String[] args) {
// 다형성: 부모 타입으로 자식 객체 참조
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.sound(); // 멍멍! (Dog의 오버라이딩된 메서드)
animal2.sound(); // 야옹~ (Cat의 오버라이딩된 메서드)
// 배열로 여러 타입 관리
Animal[] animals = {new Dog(), new Cat(), new Cow()};
for (Animal a : animals) {
a.sound(); // 각 객체의 오버라이딩된 메서드 호출
}
}
}
5. 추상 클래스와 인터페이스
추상 클래스 (Abstract Class)
미완성 메서드를 포함한 클래스로, 인스턴스를 생성할 수 없다.
abstract class Shape {
String color;
// 추상 메서드 (구현부 없음)
abstract double getArea();
// 일반 메서드 (구현부 있음)
void setColor(String color) {
this.color = color;
}
}
class Circle extends Shape {
double radius;
@Override
double getArea() {
return Math.PI * radius * radius;
}
}
인터페이스 (Interface)
추상 메서드의 집합으로, 클래스가 반드시 구현해야 할 기능을 정의한다.
interface Flyable {
void fly(); // 추상 메서드 (public abstract 생략)
default void land() { // default 메서드
System.out.println("착륙합니다.");
}
static void info() { // static 메서드
System.out.println("Flyable 인터페이스");
}
}
class Bird implements Flyable {
@Override
public void fly() {
System.out.println("새가 날아갑니다.");
}
}
추상 클래스 vs 인터페이스
| 구분 | 추상 클래스 | 인터페이스 |
|---|---|---|
| 키워드 | abstract class |
interface |
| 다중 상속 | 불가능 | 가능 (implements) |
| 변수 | 모든 종류 가능 | public static final만 |
| 메서드 | 추상/일반 모두 가능 | 추상 메서드 (default, static 가능) |
| 목적 | “~이다” (is-a) | “~할 수 있다” (can-do) |
왜 두개를 나눠놨을까? 아직은 알 수 없다. . ..ㅋㅋ
default와 static의 차이
인터페이스에서:
- default: 객체가 있어야 호출 가능, 오버라이딩 가능
- static: 객체 없이 호출 가능, 오버라이딩 불가능
interface MyInterface {
default void defaultMethod() {
System.out.println("default 메서드");
}
static void staticMethod() {
System.out.println("static 메서드");
}
}
class MyClass implements MyInterface {
@Override
public void defaultMethod() { // 오버라이딩 가능
System.out.println("오버라이딩된 default 메서드");
}
// static 메서드는 오버라이딩 불가!
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.defaultMethod(); // 객체 필요
MyInterface.staticMethod(); // 객체 없이 호출
}
}