01. 상속 (Inheritance)
1. 상속
상속이란 하위 클래스가 상위 클래스의 멤버(필드, 메서드 등)를 물려받아 사용할 수 있게 하는 구조를 의미한다. 즉, 상속이란 하위 클래스는 상위 클래스의 공통된 속성과 기능을 그대로 상속받고, 필요에 따라 자신의 고유한 기능을 추가로 정의하여 확장하거나 상위 메서드를 재정의(Override)할 수 있음을 뜻한다.
이러한 상속 관계는 자바에서 extends라는 키워드를 통해 명시하며, 상속의 구조상 하위 클래스는 상위 클래스의 모든 멤버를 포함하기 때문에, 하위 클래스가 가진 멤버의 개수는 언제나 상위 클래스보다 같거나 더 많다.
// 1. 기본 형태
class 상위클래스명 {
// 필드와 메서드
}
class 하위클래스명 extends 상위클래스명 {
// 상위 클래스의 멤버 사용 가능 + 추가 멤버 정의 가능
}
// 2. 상속 예제
class Person {
String name;
int age;
void learn() { System.out.println("공부를 합니다."); }
void walk() { System.out.println("걷습니다."); }
void eat() { System.out.println("밥을 먹습니다."); }
}
class Programmer extends Person {
String companyName;
void coding() { System.out.println("코딩을 합니다."); }
}
public class HelloJava {
public static void main(String[] args) {
Programmer pg = new Programmer();
pg.name = "박해커";
pg.learn(); // Person의 메서드 사용
pg.coding(); // Programmer 고유 메서드
}
}
2. 상속의 장단점
| 장점 | 단점 |
| 코드 재사용성 증가 >> 공통 코드를 상위 클래스에 작성하고 하위 클래스에서 재사용 |
높은 결합도 (Coupling) >> 하위 클래스가 상위 클래스의 변경에 민감해짐 |
| 계층적 구조 설계 가능 >> 클래스 간 관계를 명확하게 표현 (ISA 관계) |
유연성 저하 >> 자바는 하위 클래스는 하나의 상위 클래스만 가져야 하는 단일 상속 지원 |
| 다형성의 기반 제공 >> 부모 타입으로 다양한 자식 객체 처리 가능 |
상속 계층이 깊어질 경우 유지보수 어려움 >> 중첩 상속이 많아지면 코드 흐름 파악과 오류 추적이 어려워짐 |
| 남용할 경우 유지보수 어려움 >> 상위 클래스가 변경되면 하위 클래스 전체에 영향을 끼침 |
class A {}
class B extends A {}
class C extends B {}
class D extends C {} // D는 A ~ C 까지의 구현에 의존 >> 변경에 매우 취약
3. 다형성과 상속의 관계
상속은 다형성을 가능하게 하는 기초가 된다. 즉, 하위 클래스가 하나의 상위 클래스를 상속함은 다양한 형태의 하위 클래스를 상위 클래스의 타입으로 통합적으로 처리할 수 있는 다형성을 제공한다는 의미와 같다.
// 1. 업캐스팅(Up-Casting): 하위 클래스의 객체가 상위 클래스의 타입으로 변환
Person p1 = new Programmer();
Person p2 = new Dancer();
4. 생성자와 상속 | super vs super()
하위 클래스는 상위 클래스의 모든 멤버를 상속받지만, 생성자는 상속되지 않는다. 따라서 하위 클래스는 생성자를 따로 명시적으로 정의하거나, 상위 클래스의 생성자를 super()를 통해 호출할 수 있다.
여기서 super 키워드란 부모 클래스의 멤버(변수, 메서드)를 참조할 때 사용하는 키워드이며, super() 메서드는 부모 클래스의 생성자를 호출할 때 사용하는 메셔드이다. 특히 super()는 생성자 내부에서만 사용 가능하며, 반드시 생성자의 첫 줄에 기재해야 한다. 이러한 super 및 super()는 상속 관계를 전제로 한다.
// 1. super
public class Example {
public static void main(String[] args) {
SubClass sub = new SubClass();
sub.callNum();
}
}
class SuperClass {
int count = 20; // super.count
}
class SubClass extends SuperClass {
int count = 15; // this.count
void callNum() {
System.out.println("count = " + count); // 15
System.out.println("this.count = " + this.count); // 15
System.out.println("super.count = " + super.count); // 20
}
}
// 2. super() 예시
public class Test {
public static void main(String[] args) {
Student s = new Student();
}
}
class Human {
Human() {
System.out.println("휴먼 클래스 생성자");
}
}
class Student extends Human {
Student() {
super(); // Humun() 우선 수행
System.out.println("학생 클래스 생성자");
}
}
5. super vs super()
| super | super() | |
| 의미 | 상위 클래스의 멤버를 참조 | 상위 클래스의 생성자 호출 |
| 사용 위치 | 인스턴스 메서드 내부 | 생성자 첫 줄 >> 컴파일러가 자동 삽입 |
| 호출 대상 | 부모 클래스의 변수 / 메서드 | 부모 클래스의 생성자 호출 |
6. 포함 관계 (Composition)
포함 관계란 하나의 클래스가 다른 클래스 타입의 객체를 자신의 멤버 변수로 포함하여 사용하는 구조를 의미한다. 즉, A 클래스는 B 클래스를 가진다는 Has-A 관계를 뜻한다. 이러한 포함 관계는 보통 클래스 내부에서 다른 클래스의 기능을 위임받거나 다른 여러 클래스 객체를 조립하여 새로운 객체를 구성하는 용도로 사용한다.
| 상속보다 유용한 경우 | |
| 불필요한 메서드의 상속 방지 | 하위 클래스는 상위 클래스의 전체 메서드 중 필요한 메서드만 골라서 호출 가능 |
| 다중 역할의 수행과 위임 | 여러 개의 객체를 필드로 가져와 각 역할 위임 가능 >> 한 객체가 여러 복합적인 기능을 동시에 수행 가능 |
| 구현의 교체와 변경 가능성 | 프로그램 실행 중에 다른 구현체로 갈아 끼울 수 있는 동적인 유연함 제공 >> 전략 패턴 (Strategy Pattern) |
| 느슨한 결합 | 다른 객체의 내부 구현 변화로부터 영향을 받지 않는 느슨한 결합 >> 낮은 결합도, 높은 독립성 제공 |
class Address {
String city, country;
public Address(String city, String country) {
this.city = city;
this.country = country;
}
public String getCity() {
return this.city;
}
}
public class Employee {
int id;
String name;
Address address; // Address를 포함함
public Employee(int id, String name, Address address) {
this.id = id;
this.name = name;
this.address = address;
}
void showInfo() {
System.out.println(id + " " + name);
System.out.println(address.city + " " + address.country);
}
public static void main(String[] args) {
Address a1 = new Address("서울", "한국");
Employee e1 = new Employee(1, "김코딩", a1);
e1.showInfo();
}
}
7. 상속 vs 컴포지션
| 상속 (Inheritance) |
포함 (Composition) |
|
| 구분 | IS-A 관계 (A는 B이다) | HAS-A 관계 (A는 B를 가지고 있다) |
| 결합도 | 높음 (강한 결합) | 낮음 (느슨한 결합) |
| 유연성 | 낮음 | 높음 |
| 재사용 범위 | 전체 기능 상속 | 핑료한 기능만 선택 |
| 다중 역할 | 불가능 >> Only 단일 상속 |
가능 |
| 변경 대응 | 구조 변경에 취약 | 객체 교체로 유연한 대응 가능 |
8. 메서드 오버라이딩(Method Overriding)
메서드 오버라이딩이란 상위 클래스에서 상속받은 메서드를 하위 클래스에서 재정의하여 사용하는 것을 의미한다. 즉, 메서드 오버라이딩이란 상위 클래스의 메서드를 하위 클래스에서 다시 구현하는 것을 뜻한다. 대개 이렇게 오버라이딩 된 메서드 위에는 @Override 라는 어노테이션을 붙이며, 이는 컴파일러가 해당 메서드가 올바르게 오버라이딩 되었는지 확인하는 수단이 된다. 또한 이러한 메서드 오버라이딩은 다음과 같은 아래의 조건을 모두 충족해야만 수행할 수 있다.
| 조건 | |
| 1 | 메서드 이름, 매개변수, 반환 타입이 부모 메서드와 동일해야 한다. |
| 2 | 접근 제어자는 부모보다 같거나 더 넓은 범위여야 한다. (protected → public) >> 재정의 된 함수의 접근 제어자로 private 불가능 |
| 3 | 예외 선언은 부모보다 같거나 더 좁은 범위의 구체적인 예외만 가능하다. |
| 4 | final로 선언된 메서드는 오버라이딩이 불가능하다. |
| 5 | 정적(static) 메서드는 오버라이딩이 아니라 숨김(hiding)이다. >> 정적 메서드는 객체가 아니라 클래스에 소속된 것으로, 해당 프로그램에서 단 한 개만 존재 |
public class Main {
public static void main(String[] args) {
Bike bike = new Bike();
Car car = new Car();
MotorBike motorBike = new MotorBike();
bike.run();
car.run();
motorBike.run();
}
}
class Vehicle {
void run() {
System.out.println("Vehicle is running");
}
}
class Bike extends Vehicle {
@Override
void run() {
System.out.println("Bike is running");
}
}
class Car extends Vehicle {
@Override
void run() {
System.out.println("Car is running");
}
}
class MotorBike extends Vehicle {
@Override
void run() {
System.out.println("MotorBike is running");
}
}
9. 오버로딩 vs 오버라이딩
| 오버로딩 (Overloading) |
오버라이딩 (Overriding) |
|
| 정의 | 같은 이름을 갖되, 다른 매개변수를 갖는 메서드를 여러 개 정의 | 부모의 메서드를 자식이 재정의 |
| 사용 시점 | 같은 클래스 내부 | 상속 관계 |
| 매개변수 | 반드시 달라야 함 | 반드시 동일해야 함 |
| 반환 타입 | 무관 | 동일 (또는 호환 가능) |
| 사용 목적 | 다양한 입력을 유연하게 처리 | 부모의 동작을 바꿔 사용 |
10. 최상위 클래스, Object 클래스
Object 클래스란 자바에서 모든 클래스가 자동으로 상속받는 클래스이다. 즉, 자바의 모든 클래스는 Object 클래스로부터 확장된 것이다. 이러한 Object 클래스는 자바의 모든 객체가 가지고 있는 기본 동작을 정의하며, 모든 클래스는 범용적으로 사용되는 함수를 호출하는 것이 가능하다.
| Object 주요 메서드 | 반환 타입 | |
| toString() | String | 객체 정보를 문자열로 반환 >> 클래스명@해시코드 |
| equals(Object obj) | boolean | 두 객체가 같은지 비교 >> 두 객체의 hashcode를 비교하는 것으로, 해당 메서드 재정의 시 hashcode도 재정의 필요 |
| hashCode() | int | 객체의 메모리 기반 해시값 반환 >> 객체의 고유한 정수값 반환 |
| wait() | void | 현재 스레드를 일시정지 (멀티스레드 제어에 사용) |
| notify() | void | 정지된 스레드 하나를 깨움 (멀티스레드 제어에 사용) |
// 1. toString() 예제
Object obj = new Object();
System.out.println(obj.toString());
// 2. equals() 예제
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false (주소 비교)
System.out.println(s1.equals(s2)); // true (내용 비교)
02. 캡슐화 (Encapsulation)
1. 캡슐화
캡슐화란 특정 객체 안에 관련된 속성과 기능을 하나의 단위로 묶고, 외부로부터 데이터를 보호하는 객체 지향 프로그래밍의 핵심 개념이다. 객체가 제공하는 메서드를 통해서만 데이터에 접근할 수 있도록 함으로써, 데이터 보호 및 불필요한 외부 노출을 방지한다. 즉, 캡슐화란 은닉과 보호를 통해 외부에서 데이터를 함부로 변경하지 못하는, 항상 예측한 일반적인 결과를 반환하는 불변 객체를 생성하는 것을 뜻한다. 또한 이러한 캡슐화는 접근 제어자와 Getter / Setter 메서드를 통해 구현한다.
| 캡슐화의 장점 | |
| 데이터 보호 | 외부에서 필드 직접 접근 제한 >> setter에서 유효성 검사 가능 |
| 구조 은닉 | 내부 구조 변경 시 외부 영향 최소화 (API 유지) |
| 유지보수 용이 | 구조가 바뀌어도 메서드만 유지하면 외부 코드 수정 불필요 |
| 결합도 감소 | 객체 간 관계가 느슨해지고, 테스트 및 재사용성 향상 |
2. 접근 제어자 (Access Modifier)
| 접근 제어자 | 기타 제어자 | |
| 사용 목적 | 접근 범위를 제한 | 클래스, 필드, 메서드, 생성자 등에 부가적인 의미를 부여 |
| 대표적인 제어자 | public > protected > default > private | static, final, abstract, native, synchronized 등 |
| 사용 가능 개수 | 반드시 한 개 | 복수 조합 가능 |
| 접근 제어자 | 접근 허용 범위 |
| private | 같은 클래스 내부에서의 접근만 허용하며, 외부 접근을 차단 >> 데이터 보호 |
| default | 같은 패키지 내에서만 접근 허용 |
| protected | 같은 패키지 / 다른 패키지의 자식 클래스(싱속)에서의 접근 허용 |
| public | 모든 곳에서 접근 가능 >> 공용 API |
3. Getter와 Setter
Getter / Setter 메서드란 public으로 선언되어, private으로 선언된 클래스의 필드에 간접적으로 접근할 수 있도록 하는 메서드를 의미한다. 즉, 클래스의 필드는 오로지 Getter / Setter 함수를 통해서만 간접적으로 접근할 수 있다.
이처럼 캡슐화 구현의 핵심 수단인 Getter / Setter 메서드는 각각 필드 값을 읽거나 / 설정하는 메서드이다. 필드 접근을 제어함으로써 데이터 보호와 무결성을 높일 수 있으며, 특히 Setter 메서드 내에 검증 로직을 넣어 잘못된 데이터의 입력을 방지할 수 있다. 뿐만 아니라 필요한 필드에만 Getter / Setter를 제공할 수 있으며, 아예 읽기 전용(Getter만 제공) / 쓰기 전용(Setter만 제공) 클래스 설계도 가능하다.
public class Worker {
private String name;
private int age;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 1) return;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
public class GetterSetterTest {
public static void main(String[] args) {
Worker w = new Worker();
w.setName("김코딩");
w.setAge(30);
w.setId(5);
System.out.println("근로자의 이름은 " + w.getName());
System.out.println("근로자의 나이는 " + w.getAge());
System.out.println("근로자의 ID는 " + w.getId());
}
}
4. 패키지 (Paakge)
패키지란 비슷한 목적을 가진 클래스와 인터페이스들을 묶어 관리하는 단위를 의미한다. 즉 패키지란 자바 클래스 및 인터페이스를 논리적으로 그룹화하는 디렉토리 단위를 뜻한다. 파일 시스템에서 폴더로 파일을 정리하는 것과 같은 개념으로, 자바 클래스들을 계층적으로 조직화하고 클래스 이름 충돌을 방지하는데 중요한 역할을 수행한다. 이러한 패키지는 자바 소스 파일의 첫 번째 줄에 pakage 키워드를 통해 선언하며, 패키지를 선언하지 않으면 해당 자바 파일은 이름 없는 기본 패키지에 속하게 된다.
| 대표적인 내장 패키지 | |
| java.lang | String, Math, Object 등 기본 클래스 |
| java.util | 컬렉션 프레임워크, 날짜 클래스 등 |
| java.io | 파일 입출력 관련 클래스 |
| java.nio | 효율적인 I/O를 위한 클래스 |
package practicepack.test; // 실제 디렉터리 경로와 일치해야 함
public class PackageEx {
// 클래스 내용
}
// 특정 패키지에서 다른 패키지 사용
import 패키지명.클래스명;
import 패키지명.*; // 패키지 내 모든 클래스 import
5. 불변 객체 (Immutable Object)
불변 객체란 객체가 생성된 이후 그 상태가 절대 변하지 않는 객체를 의미한다. 즉, 불변 객체 내부에 선언된 필드 값은 한 번 초기화 된 이후에 절대 변경될 수 없다. 이러한 대표적인 불변 객체에는 String, Integer, LocalDate 등이 있다.
| 불변 객체의 장점 | |
| 스레드 안전성 (Thread-Safe) |
여러 스레드에서 동시에 접근해도 상태 변경이 없기 때문에 별도의 동기화 처리 필요 |
| 객체 상태 예측 가능성 증가 | 외부에서 객체 상태를 변경할 수 없으므로, 버그 발생 가능성을 줄이고 안정적인 코드 작성 가능 |
| 설계 단순화 | 상태를 고려한 방어적 코드가 필요 없으므로, 설계와 유지보수가 단순해짐 |
| 값 객체에 적합 | 좌표(Point), 통화(Money), 이름(Name) 등 값으로서 의미를 가지는 객체에 적합 |
| 불변 객체 설계 원칙 | |
| final 클래스 선언 | 상속을 방지하여 클래스 구조를 고정 |
| 모든 필드를 private final로 선언 | 외부 접근 및 값 변경 방지 |
| 생성자를 통해서만 초기화 | 이후 값 변경 불가 보장 |
| setter 메서드 제거 | 값 변경 경로 차단 |
| 갸변 객체 포함 시 복사 사용 | 깊은 복사를 통해 불변성 유지 |
6. 얕은 복사 vs 깊은 복사
| 얕은 복사 (Shallow Copy) | 깊은 복사 (Deep Copy) | |
| 핵심 개념 | 주소값만 복사 (공유) | 실제 값을 새로운 메모리에 복사 (독립) |
| 메모리 구조 | 두 변수가 같은 객체를 가리킴 | 두 변수가 서로 다른 객체를 가리킴 |
| 상호 영향 | 복사본을 수정하면 원본도 변함 | 복사본을 수정해도 원본은 그대로 |
| 비유 | 하나의 집을 가리키는 열쇠를 두 개 만듦 | 똑같은 집을 옆 동네에 하나 더 지음 |
7. 자바 빈 (Java Bean) 규약
자바 빈이란 일정한 규칙을 따르는 자바 클래스로, 객체를 만들 때 지켜야 하는 최소한의 규칙을 의미한다. 이러한 자바 빈은 주로 데이터를 저장하고 전달하는 용도로 사용된다.
| 자바 빈 작성 규칙 | |
| 기본 생성자 필수 | 명시적으로 매개변수가 없는 생성자를 제공해야 한다 |
| 모든 필드는 private으로 선언 | 외부에서 직접 접근하지 못하도록 캡슐화해야 한다 |
| Getter / Setter 메서드 제공 | 각 필드에 대응하는 getX() / setX() 형식의 메서드를 public으로 제공해야 한다. |
| 직렬화 가능 (선택 사항) | implements Serializable을 통해 객체 직렬화를 지원할 수 있어야 한다. |
| 패키지 소속 | 반드시 명시적인 패키지 안에 속해야 한다 |
| 자바 빈의 특징과 목적 | |
| 표준화된 구조 | 프레임워크(Spring, JSP 등)에서 객체 자동 생성, 데이터 바인딩에 활용 |
| 높은 호환성 | Getter / Setter를 통해 다양한 도구 및 라이브러리와 통합 용이 |
| 유지보수 용이 | 명확한 규칙으로 코드 구조를 빠르게 파악하여 유지보수 용이 |
| 캡슐화 중심 | 내부 상태 보호와 외부 데이터 전달을 분리하여 관리 |
public class UserDTO implements Serializable {
// 모든 필드를 private 접근제한으로 설정
private String id;
private String pwd;
private String name;
private java.util.Date enrollDate;
// 기본 생성자 명시적으로 작성
public UserDTO() {}
// 매개변수 있는 생성자는 선택 사항
public UserDTO(String id, String pwd, String name, java.util.Date enrollDate) {
this.id = id;
this.pwd = pwd;
this.name = name;
this.enrollDate = enrollDate;
}
// 설정자(setter)와 접근자(getter)
public void setId(String id) {
this.id = id;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public void setName(String name) {
this.name = name;
}
public void setEnrollDate(java.util.Date enrollDate) {
this.enrollDate = enrollDate;
}
public String getId() {
return id;
}
public String getPwd() {
return pwd;
}
public String getName() {
return name;
}
public java.util.Date getEnrollDate() {
return enrollDate;
}
// 모든 필드의 값을 하나의 문자열로 반환하는 메소드 (필드값 확인용)
public String getInformation() {
return "UserDTO [id=" + this.id +
", pwd=" + this.pwd + ", name=" + this.name +
", enrollDate=" + this.enrollDate + "]";
}
}
8. 불변 객체 vs 자바 빈
| 불변 객체 | 자바 빈 | |
| 필드 변경 가능 | 불가능 (final) | 가능 (setter 제공) |
| 생성자 | 모든 값 초기화 필수 | 기본 생성자 필수 |
| setter 제공 | 제공하지 않음 | 반드시 제공 |
| 용도 | 스레드 안정성, 상태 고정 | 도구와의 연동, 데이터 전달 |
03. 다형성 (Polymorphism)
1. 다형성
다형성이란 하나의 객체가 여러 형태를 가질 수 있는 성질을 의미한다. 즉, 다형성이란 상위 클래스 타입의 참조 변수로 하위 클래스의 객체를 참조할 수 있는 성질을 뜻한다. 기본적으로 상속 관계 또는 인터페이스 구현 관계를 기반으로 하며, 이러한 다형성을 활용한 기술에는 메서드 오버라이딩, 오버로딩, 업캐스팅이 있다. 이때 실제 객체 타입을 기준으로 메서드가 결정되는 것을 동적 바인딩이라고 하며, 이는 다형성의 핵심 개념이다.
| 다형성이 필요한 이유 | |
| 하나의 타입으로 관리 가능 | 하나의 상위 클래스 배열 하나로 다양한 하위 클래스 객체를 통합 관리 가능 |
| 코드 간결화 | 같은 메시지로 다양한 객체를 처리하여 코드 유지보수 용이 |
| 확장성 향상 | 새로운 객체에 기존 메서드 활용 가능 |
| 낮은 결합도 | 다양한 객체를 동일한 메서드로 처리 가능 >> 의존성을 줄여 유연하고 유지보수하기 용이한 설계 |
// 1. 하나의 타입으로 관리
Car[] cars = new Car[5];
cars[0] = new Sonata();
cars[1] = new Morning();
cars[2] = new Avante();
cars[3] = new Grandure();
cars[4] = new Porter();
for (Car car : cars) {
car.move(); // 각자 override된 move() 실행
}
// 2. 코드 간결화
for (Car car : carr) {
car.move();
}
// 3. 확장성 향상
public void move(Car car) {
car.move();
}
move(new Sonata());
move(new Morning());
move(new Santafe()); // 기존 메서드를 그대로 재사용 가능
// 4. 낮은 결합도
public void pay(결제수단) {
결제수단.결제진행();
}
2. 참조 변수의 타입 변환 | Up-Casting vs Down-Casting
타입 변환이란 상속 관계에 있는 클래스 사이에서 특정 멤버(필드, 메서드)의 사용 가능 여부를 제한 또는 확장하는 개념이다.
| 업캐스팅 | 다운캐스팅 | |
| 변환 방향 | 하위 클래스 타입 → 상위 클래스 타입 | 상위 클래스 타입 → 하위 클래스 타입 |
| 형 변환 여부 | 생략 가능 | 반드시 명시 |
| 사용 목적 | 다향성 활용하여 여러 하위 객체를 동일한 상위 타입으로 처리 | 상위 타입 참조 변수를 통해 하위 타입 고유 기능에 접근 |
| 안전성 | 안전 (대부분 자동) | 위험 (ClassCastException 발생 가능) |
| 예시 | Vehicle vehicle = new Car(); | ((Car) vehicle.giveRide(); |
3. instanceof 연산자
instanceof 연산자란 참조 변수의 타입 변환 가능 여부를 boolean 값으로 반환하는 연산자이다. 참조 변수가 해당 클래스나 자식 클래스의 인스턴스인 경우 true를 반환한다. 특히 참조 변수가 null인 경우에는 항상 false를 반환한다.
// 1. 기본 문법
참조변수 instanceof 클래스명
if (참조변수 instanceof 클래스명) {
클래스명 변수명 = (클래스명) 참조변수;
// 안전한 다운캐스팅 후 로직 수행
}
// 2. 예제 코드
public class InstanceOfExample {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal instanceof Object); // true
System.out.println(animal instanceof Animal); // true
System.out.println(animal instanceof Bat); // false
Animal cat = new Cat();
System.out.println(cat instanceof Object); // true
System.out.println(cat instanceof Animal); // true
System.out.println(cat instanceof Cat); // true
System.out.println(cat instanceof Bat); // false
}
}
class Animal {}
class Bat extends Animal {}
class Cat extends Animal {}
4. 다형성 활용 예제
class Coffee {
int price;
public Coffee(int price) {
this.price = price;
}
}
class Americano extends Coffee {
public Americano() {
super(4000);
}
public String toString() {
return "아메리카노";
}
}
class CaffeLatte extends Coffee {
public CaffeLatte() {
super(5000);
}
public String toString() {
return "카페라떼";
}
}
// 커피 종류가 증가해도, Customer 함수 수정 불필요
class Customer {
int money = 50000;
void buyCoffee(Coffee coffee) {
if (money < coffee.price) {
System.out.println("잔액이 부족합니다.");
return;
}
money -= coffee.price;
System.out.println(coffee + "를 구입했습니다.");
}
}
public class AfterPolymorphism {
public static void main(String[] args) {
Customer customer = new Customer();
customer.buyCoffee(new Americano());
customer.buyCoffee(new CaffeLatte());
System.out.println("현재 잔액은 " + customer.money + "원 입니다.");
}
}
04. 추상화 (Abstraction)
1. 추상화
추상화란 사물이나 개념의 공통적인 본질을 추출하여 단순화하는 것을 의미한다. 즉, 객체들의 공통적인 속성과 기능을 추출하여 상위 클래스로 정의하는 것을 뜻한다. 이러한 추상화는 추상 클래스와 인터페이스를 통해 구현하며, 크게 행위 중심 추상화와 데이터 중심 추상화로 나뉜다.
// 1. 행위 중심 추상화: 무엇을 할 수 있나에 집중
interface Runnable {
void run();
}
// 2. 데이터 중심 추상화: 무엇을 가지고 있나에 집중
abstract class Shape {
int x, y;
}
| 추상화의 장점 | |
| 단순한 시스템 표현 | 핵심 요소만 모델링하여 시스템의 이해도를 높임 |
| 코드 재사용성 | 중복 코드 제거 / 유지보수 비용 절감 |
| 유연한 구조 설계 | 구체 구현에 의존하지 않고 인터페이스 기반 설계 가능 |
| 확장성 | 새로운 클래스가 기존 구조를 따르기만 하면 호환 가능 |
2. abstract 키워드
abstract 키워드란 미완성을 나타내는 제어자로, 추상 클래스와 추상 메서드를 정의할 때 사용한다.각 하위 클래스가 구체적인 구현을 강제로 담당하도록 하여 변경에 유연하게 대응할 뿐만 아니라 코드의 중복을 줄이는데 용이하다.
| 추상 클래스 (Abstract Class) |
추상 메서드 (Abstract Method) |
|
| 정의 | 추상 메서드를 0개 이상 포함하는 클래스 >> 불완전한 설계도 |
메서드의 본문이 없는 미완성 메서드 >> 하위 클래스에서 반드시 오버라이딩 |
| 사용 목적 | 직접 객체 인스턴스 생성 방지 | 추상화 구현 |
// 1. 일반적인 추상 클래스
abstract class AbstractExample {
abstract void method(); // 추상 메서드
public void method2() {
System.out.println("완성된 메서드도 정의 가능");
}
}
// 2. 추상 클래스 예제
abstract class Animal {
public String kind;
public abstract void sound();
}
class Dog extends Animal {
public Dog() {
this.kind = "포유류";
}
public void sound() {
System.out.println("멍멍");
}
}
class Cat extends Animal {
public Cat() {
this.kind = "포유류";
}
public void sound() {
System.out.println("야옹");
}
}
class DogExample {
public static void main(String[] args) {
Animal dog = new Dog();
dog.sound();
Cat cat = new Cat();
cat.sound();
}
}
3. 일반 클래스 vs 추상 클래스
| 일반 클래스 | 추상 클래스 | |
| 객체 인스턴스 생성 | 가능 | 불가능 |
| 추상 메서드 포함 여부 | 불가능 | 가능 |
| 상속 여부 | 상속 선택 가능 | 상속 전체 필요 |
| 용도 | 구체 기능 구현 | 공통 구조 설계 |
4. final 키워드
final 키워드란 클래스, 메서드, 변수에게 변경 불가 또는 확장 불가의 의미를 부여하는 제어자이다. 설계의 안정성, 보안성, 유지보수성을 높이기 위해 자주 사용된다.
| final 위치 | |
| 클래스 | 상속이 불가능한 클래스 |
| 메서드 | 오버라이딩이 불가능한 메서드 |
| 변수 | 초기 할당 이후, 값 변경이 불가능한 상수 |
final class FinalClass {
final int x = 100; // 값 변경 불가
final int getValue() { // 오버라이딩 불가
final int localVar = x;
return localVar;
}
}
class SubClass extends FinalClass { // 컴파일 에러: FinalClass는 상속 불가
// public int getValue() { return 200; } // 컴파일 에러: final 메서드는 오버라이딩 불가
}
5. 인터페이스 (Interface)
인터페이스란 객체들이 서로 상호작용할 수 있도록 돕는, 객체의 사용 설명서 또는 규격이다. 즉, 인터페이스란 기능 명세의 집합이다. 이러한 인터페이스 내부에는 객체 클래스가 반드시 구현해야 할 필드(public static final)와 추상 메서드 목록(public abstract)를 정의한다. 뿐만 아니라 인터페이스는 하나의 클래스가 여러 인터페이스를 구현하는 다중 구현(multiple inheritance)이 가능하다.
// 1. 기본 인터페이스 예제
public interface InterfaceEx {
int ROCK = 1; // public static final 생략 가능
int SCISSORS = 2;
int PAPER = 3;
String getPlayingNum(); // public abstract 생략 가능
void call();
}
class MyClass implements InterfaceEx {
public String getPlayingNum() {
return "1";
}
public void call() {
System.out.println("called!");
}
}
// 2. 다중 구현 예제
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
class Duck implements Flyable, Swimmable {
public void fly() {
System.out.println("날아간다!");
}
public void swim() {
System.out.println("수영한다!");
}
}
6. 추상 클래스 vs 인터페이스
| 추상 클래스 | 인터페이스 | |
| 인스턴스 생성 | 불가능 | 불가능 |
| 키워드 | extends | implements |
| 상속 개수 | 단일 상속 | 다중 구현 |
| 멤버 구성 | 필드, 생성자, 메서드 등 모두 가능 | 추상 메서드, 상수 >> Java 8 이후 default / static 메서드 가능 |
| 공통 정의 | 상태 (필드) | 기능 (메서드) |
| 사용 목적 | 공통 상태 / 기능 공유 + 강제 구현 | 강제 구현 중심, 역할 명세, 동작 규칙 정의 |
| 사용 상황 예시 | 상속 계층 설계 (IS-A 관계) | 역할 정의 + 구현 위임 (CAN-DO 관계) |
'[Codeit Review] > 객체 지향 프로그래밍' 카테고리의 다른 글
| [06] UML을 활용한 객체 지향 설계 시각화 (0) | 2026.01.26 |
|---|---|
| [05] 예외 처리 (Exception Handling) (0) | 2026.01.26 |
| [04] 내부 클래스와 익명 클래스, 그리고 열거 타입(ENUM)의 활용 (0) | 2026.01.26 |
| [03] SOLID 원칙 (0) | 2026.01.26 |
| [01] 객체 지향 프로그래밍 (0) | 2026.01.16 |