1. 상속
자바에서 상속(Inheritance)은 기존 클래스의 속성과 메서드를 새로운 클래스가 물려받아 재사용할 수 있게 해주는 객체지향의 핵심 개념 중 하나입니다. 상속을 사용하면 공통된 코드를 중복 없이 여러 클래스에서 사용할 수 있어 코드의 재사용성과 확장성이 높아집니다. 상속은 extends 키워드를 사용해 구현하며, 기존 클래스는 부모 클래스(슈퍼클래스), 새로 정의한 클래스는 자식 클래스(서브클래스)라고 부릅니다. 자식 클래스는 부모의 기능을 그대로 사용할 수 있을 뿐만 아니라, 필요에 따라 기능을 확장하거나 재정의(오버라이딩)할 수 있습니다.
class 부모클래스 {
// 공통 속성 및 동작 정의
}
class 자식클래스 extends 부모클래스 {
// 부모의 기능을 상속받고 추가적인 기능 정의
}
class Animal {
public void eat() {
System.out.println("먹는다.");
}
}
class Dog extends Animal {
public void bark() {
System.out.println("멍멍 짖는다.");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // 부모의 메서드 사용
dog.bark(); // 자신의 메서드 사용
}
}
1. 메서드 오버라이딩
자식 클래스는 부모 클래스의 메서드를 그대로 사용할 수도 있고, 필요에 따라 재정의(override)하여 자신에게 맞게 바꿀 수도 있습니다.
class Animal {
public void sound() {
System.out.println("동물이 소리를 낸다.");
}
}
class Cat extends Animal {
// 어노테이션 : 자바가 해석이 될 때, 오버라이드된 것을 컨파일러에게 지시를 내릴 때
@Override
public void sound() {
System.out.println("야옹");
}
}
2. 자바에서의 상속 제약 사항
- 자바는 단일 상속만 지원합니다. 즉, 한 클래스는 하나의 부모 클래스만 상속받을 수 있습니다.
class A {}
class B {}
class C extends A, B {} // ❌ 컴파일 에러
- final 클래스는 상속할 수 없습니다.
// final이 붙은 클래스는 상속을 못하게 한다.
final class Car {}
class SportsCar extends Car {} // ❌ 에러 발생
- 생성자는 상속되지 않습니다. 생성자는 자식 클래스가 직접 정의해야 하며, 부모 생성자를 호출하려면 super()를 사용합니다.
3. super 키워드
super는 부모 클래스의 필드나 메서드에 접근할 때 사용합니다. 또한 생성자 안에서 부모 생성자 호출에도 사용됩니다.
class Parent {
public Parent(String msg) {
System.out.println("부모 생성자: " + msg);
}
}
class Child extends Parent {
public Child() {
super("안녕하세요"); // 부모 생성자 호출
System.out.println("자식 생성자");
}
}
class Person {
String name;
public Person(String name) {
this.name = name;
}
public void greet() {
System.out.println("안녕하세요, 저는 " + name + "입니다.");
}
}
class Student extends Person {
String school;
public Student(String name, String school) {
super(name); // 부모 생성자 호출
this.school = school;
}
@Override
public void greet() {
super.greet(); // 부모 메서드 호출
System.out.println("학교는 " + school + "입니다.");
}
}
2. final
final은 자바에서 "변경할 수 없음"을 의미하는 키워드입니다. 변수, 메서드, 클래스에 모두 사용할 수 있으며, 각각의 의미가 조금씩 다릅니다.
1. final 변수: 값 변경 불가 (상수)
final을 변수에 사용하면, 초기화된 이후 값 변경이 불가능한 상수가 됩니다. 선언과 동시에 값을 설정하거나, 생성자에서 한 번만 초기화 가능하며 관례상 대문자로 작성합니다. (MAX_SIZE, PI 등)
final int MAX = 100;
MAX = 200; // ❌ 에러! 값을 바꿀 수 없음
2. final 메서드: 재정의(오버라이딩) 불가
final을 메서드에 사용하면, 자식 클래스에서 그 메서드를 오버라이딩할 수 없습니다. 보안성 유지하며 중요한 로직이 변경되지 않도록 보호합니다.
class Parent {
public final void show() {
System.out.println("부모 클래스 메서드");
}
}
class Child extends Parent {
// public void show() {} // ❌ 오류: final 메서드는 재정의할 수 없음
}
3. final 클래스: 상속 불가
final을 클래스에 사용하면, 그 클래스를 다른 클래스가 상속받을 수 없습니다. 클래스를 변경하거나 확장하지 못하도록 막고 싶을 때 사용합니다. String, Integer 같은 핵심 라이브러리 클래스들이 final로 선언되어 있습니다.
final class Animal {
public void sound() {
System.out.println("동물 소리");
}
}
// class Dog extends Animal {} // ❌ 오류: final 클래스는 상속 불가
종합예제
// 부모 클래스
class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + "가 먹습니다.");
}
public final void sleep() {
System.out.println(name + "는 조용히 잠듭니다. (이건 반드시 이 방식으로!)");
}
public void speak() {
System.out.println(name + "가 소리를 냅니다.");
}
}
// 자식 클래스
class Dog extends Animal {
public Dog(String name) {
super(name);
}
// 오버라이딩: speak() 메서드를 개성 있게 변경
@Override
public void speak() {
System.out.println(name + "가 멍멍 짖습니다!");
}
// 오버로딩: bark() 여러 버전
public void bark() {
System.out.println("멍멍!");
}
public void bark(String target) {
System.out.println(target + "를 보고 멍멍!");
}
public void bark(int times) {
for (int i = 0; i < times; i++) {
System.out.print("멍! ");
}
System.out.println();
}
}
public class Main {
public static void main(String[] args) {
Dog rucy = new Dog("루시");
// 상속받은 메서드
rucy.eat(); // 루시가 먹습니다.
// final 메서드 (오버라이딩 불가)
rucy.sleep(); // 루시는 조용히 잠듭니다.
// 오버라이딩된 메서드
rucy.speak(); // 루시가 멍멍 짖습니다!
// 오버로딩된 메서드들
rucy.bark(); // 멍멍!
rucy.bark("택배 아저씨"); // 택배 아저씨를 보고 멍멍!
rucy.bark(3); // 멍! 멍! 멍!
}
}
3. Object 클래스
자바에서 Object 클래스는 모든 클래스의 최상위 부모 클래스(슈퍼클래스)로, 자바에 존재하는 모든 클래스는 Object를 직접 또는 간접적으로 상속받습니다. 이 클래스는 객체를 비교하거나 문자열로 표현하고, 복제하거나 쓰레기 수집 전에 정리하는 등 객체에 대한 기본적인 동작을 정의한 메서드들을 제공합니다. 대표적인 메서드로는 toString(), equals(), hashCode(), clone(), finalize() 등이 있으며, 개발자는 필요에 따라 이 메서드들을 오버라이딩하여 자신만의 방식으로 객체의 동작을 정의할 수 있습니다. Object는 자바의 클래스 계층 구조에서 기본 뼈대 역할을 하는 핵심 클래스입니다.
class Person {
// 실제로는 class Person extends Object 와 같음
}
Object 클래스가 제공하는 주요 메서드
toString() | 객체를 문자열로 표현 (기본: 클래스이름@해시값) |
equals(Object obj) | 두 객체가 같은지를 비교 (기본: 주소 비교) |
hashCode() | 객체의 해시코드 반환 |
getClass() | 클래스 정보를 담은 Class 객체 반환 |
clone() | 객체 복제 (implements Cloneable 필요) |
finalize() | 가비지 컬렉션 전에 호출 (거의 사용되지 않음) |
@Override
public String toString() {
return "이름: " + name + ", 나이: " + age;
}
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// toString() 오버라이딩
@Override
public String toString() {
return "이름: " + name + ", 나이: " + age;
}
// equals() 오버라이딩
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 같은 객체인지 비교
if (obj == null || getClass() != obj.getClass()) return false; // 클래스 비교
Person other = (Person) obj;
return this.name.equals(other.name) && this.age == other.age;
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("김사과", 20);
Person p2 = new Person("반하나", 25);
Person p3 = new Person("김사과", 20);
System.out.println(p1);
System.out.println(p1.equals(p2));
System.out.println(p1.equals(p3));
}
}
4. 업캐스팅
업캐스팅(Upcasting)은 자식 클래스 객체를 부모 클래스 타입으로 참조하는 것을 말합니다. 즉, 상속 관계에서 자식 클래스의 객체를 부모 클래스의 변수로 담는 것이다.
업캐스팅을 사용하는 이유
- 다형성(Polymorphism)을 구현하기 위해
→ 동일한 부모 타입으로 여러 자식 객체를 통일해서 다룰 수 있음 - 유지보수가 쉬운 코드 구조를 위해
→ 메서드 호출 시 동작은 실제 객체의 메서드(오버라이딩된 것)가 실행됨 - 코드 재사용성과 확장성 향상
class Animal {
public void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("멍멍!");
}
public void wagTail() {
System.out.println("꼬리를 흔듭니다.");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog(); // Dog 객체 생성
Animal animal = dog; // ✅ 업캐스팅 (자동)
animal.sound(); // 멍멍! (오버라이딩된 메서드 호출)
// animal.wagTail(); // ❌ 컴파일 에러! Animal 타입에는 없음
}
}
다운캐스팅과 비교
업캐스팅 | 자식 객체 → 부모 타입으로 참조 (자동) |
다운캐스팅 | 부모 타입 참조 → 다시 자식 타입으로 형변환 (명시적) |
Dog dog = new Dog();
Animal animal = dog; // 업캐스팅 (자동)
Dog dog2 = (Dog) animal; // 다운캐스팅 (명시적)
dog2.wagTail(); // 가능!
5. 이종모음
이종 모음(heterogeneous collection)은 서로 다른 타입의 객체들을 하나의 컬렉션에 담아 처리하는 구조를 의미합니다. 일반적으로 제네릭(Generic)을 사용하지 않거나, 상위 타입(예: Object)으로 업캐스팅하여 다양한 타입의 객체들을 하나의 리스트나 배열 등에 저장할 수 있습니다. 이 방식은 유연성을 제공하지만, 꺼낼 때 다시 원래 타입으로 다운캐스팅해야 하고, 타입 안정성이 떨어지며 런타임 오류의 위험이 있습니다. 이종 모음은 특수한 상황에서 사용되며, 자바에서는 보통 제네릭과 상속을 활용해 동종 모음(homogeneous collection)을 선호합니다.
class Book {
String title;
public Book(String title) {
this.title = title;
}
public void read() {
System.out.println("책 \"" + title + "\"을 읽습니다.");
}
}
public class HeterogeneousArrayExample {
public static void main(String[] args) {
// Object 타입 배열로 이종 모음 구성
Object[] items = new Object[3];
items[0] = "안녕하세요"; // String
items[1] = 100; // Integer (오토박싱)
items[2] = new Book("자바의 정석"); // 사용자 정의 클래스
// 배열 순회하며 타입 확인 및 처리
for (Object obj : items) {
if (obj instanceof String) {
System.out.println("문자열: " + obj);
} else if (obj instanceof Integer) {
System.out.println("정수: " + obj);
} else if (obj instanceof Book) {
Book book = (Book) obj; // 다운캐스팅
book.read();
}
}
}
}
instanceof란?
instanceof는 객체가 특정 클래스의 인스턴스인지 확인하는 연산자입니다. 이 연산자는 객체가 어떤 클래스 또는 그 하위 타입으로부터 생성되었는지를 판별할 때 사용되며, 결과는 true 또는 false를 반환합니다.
객체 instanceof 클래스이름
- 결과는 boolean 타입
- 객체가 해당 클래스 또는 그 자식 클래스의 인스턴스이면 true, 아니면 false
- 주로 다운캐스팅하기 전에 안전하게 타입 확인할 때 사용
- 자바 16부터는 instanceof와 다운캐스팅을 한 줄로 작성할 수 있게 패턴 매칭이 도입
class Book {
String title;
public Book(String title) {
this.title = title;
}
public void read() {
System.out.println("책 \"" + title + "\"을 읽습니다.");
}
}
public class HeterogeneousArrayExample {
public static void main(String[] args) {
// Object 타입 배열로 이종 모음 구성
Object[] items = new Object[3];
items[0] = "안녕하세요"; // String
items[1] = 100; // Integer (오토박싱)
items[2] = new Book("자바의 정석"); // 사용자 정의 클래스
// 배열 순회하며 타입 확인 및 처리
for (Object obj : items) {
if (obj instanceof String str) {
System.out.println("문자열: " + str);
} else if (obj instanceof Integer num) {
System.out.println("정수: " + num);
} else if (obj instanceof Book book) {
book.read(); // 자동으로 Book 타입으로 다운캐스팅됨
}
}
}
}