[TIL 4일 차] 객체지향 프로그래밍의 이해
오늘의 학습
⭐⭐⭐1. 객체지향 프로그래밍의 4가지 핵심 개념
1-01. 상속(Inheritance)
기존 클래스(상위 클래스)의 멤버(필드, 메서드 등)를 새로운 클래스(하위 클래스)가 물려받는 것
- 하위 클래스는 상위 클래스의 기능을 그대로 사용하거나, 필요에 따라 확장하거나 재정의(Override) 가능
- 단일 상속만 허용
class A extends B를 사용하여 상속을 구현
class 상위클래스명 {
// 필드와 메서드
}
class 하위클래스명 extends 상위 클래스명 {
// 상위 클래스의 멤버 사용 가능 + 추가 멤버 정의 가능
}
장점
- 코드 재사용성 증가
- 계층적 구조 설계 (is-a 관계)
- 다형성(Polymorphism) 기반 제공
단점
- 높은 결합도(Coupling)
- 유연성 저하
- 유지보수 어려움
1) 상속 vs 포함
| 구분 | 상속 (Inheritance) | 포함/컴포지션 (Composition) |
|---|---|---|
| ⭐관계 | is-a (a는 b이다) | has-a (a는 b를 가지고 있다) |
| 결합도 | 높음 (강한 결합) | 낮음 (느슨한 결합) |
| 유연성 | 낮음 | 높음 |
| 재사용 범위 | 전체 기능 상속 | 필요한 기능만 선택적으로 사용 |
| 다중 역할 | 불가 | 가능 |
| 변경 대응 | 구조 변경에 취약 | 객체 교체로 유연한 대응 가능 |
객체지향 설계 원칙 중 하나 : ”가능하다면, 상속보다는 컴포지션을 사용하라!”
2) 메서드 오버라이딩
상위 클래스에서 상속받은 메서드를 하위 클래스에서 재정의하여 사용하는 것
- Override : 위에 덮어쓰다
- 즉, 상위 클래스의 메서드를 하위 클래스에서 다시 구현하는 것
(1) 메서드 오버라이딩 성립 조건
| 조건 | 설명 |
|---|---|
| 1 | 메서드 이름, 매개변수, 반환 타입이 부모 메서드와 완전히 같아야 한다 |
| 2 | 접근 제어자는 부모보다 같거나 더 넓은 범위여야 한다 (private → ❌ 오버라이딩 불가) |
| 3 | 예외 선언은 부모보다 같거나 더 구체적인 예외만 가능 |
| 4 | final로 선언된 메서드는 오버라이딩 불가능 |
| 5 | static 메서드는 오버라이딩이 아니라 숨김(hiding) |
(2) @Override 어노테이션
@Override
void run() {
System.out.println("재정의된 메서드");
}
@Override는 오버라이딩이 정확하게 이뤄졌는지 컴파일 타임에 검사- 실수로 이름이나 시그니처가 다르면 오류 발생
- 유지보수 시 개발자들이 빠르게 오버라이딩 메서드를 인식 가능
(3) 오버로딩 vs 오버라이딩
| 항목 | 오버라이딩 (Overriding) | 오버로딩 (Overloading) |
|---|---|---|
| 정의 | 부모의 메서드를 자식이 재정의 | 같은 이름, 다른 매개변수 메서드 여러 개 정의 |
| 시점 | 상속 관계에서 사용됨 | 같은 클래스 내부에서 사용됨 |
| 매개변수 | 동일해야 함 | 달라야 함 |
| 반환 타입 | 동일 (또는 호환 가능) | 무관 |
| 목적 | 부모의 동작을 바꿔 사용 | 다양한 입력을 유연하게 처리 |
3) super과 super() 키워드
| 키워드 | 의미 | 사용 위치 | 호출 대상 |
|---|---|---|---|
this |
현재 객체를 가리키는 참조 변수 | 인스턴스 메서드 내부 | 자신의 인스턴스 멤버 |
this() |
현재 클래스의 다른 생성자 호출 | 생성자 첫 줄 | 자신의 다른 생성자 |
super |
상위(부모) 클래스의 멤버를 참조 | 인스턴스 메서드 내부 | 부모 클래스의 변수/메서드 |
super() |
상위 클래스의 생성자 호출 | 생성자 첫 줄 | 부모 클래스의 생성자 호출 |
4) Object 클래스
Java에서 모든 클래스를 자동으로 상속받는 클래스로, Java 클래스 계층의 최상위 부모 클래스
즉, Java에서 모든 클래스는 Object 클래스로부터 확장(extends)됨
class ParentEX {
// 실제로는 컴파일 시 자동으로 `extends Object`가 붙는다.
}
⬇️
class ParentEX extends Object {
// ...
}
- Java의 모든 객체가 가지고 있는 기본 동작 정의 → 모든 객체를 공통된 방식으로 다룰 수 있음
(1) 대표적인 Object 클래스 메서드
| 메서드명 | 반환 타입 | 설명 |
|---|---|---|
toString() |
String |
객체 정보를 문자열로 반환 |
equals(Object obj) |
boolean |
두 객체가 같은지 비교 (==과 유사하지만 다름) |
hashCode() |
int |
객체의 메모리 기반 해시값 반환 |
wait() |
void |
현재 스레드를 일시정지 (멀티스레드 제어에 사용) |
notify() |
void |
정지된 스레드 하나를 깨움 (멀티스레드 제어에 사용) |
1-02. 캡슐화(Encapsulation)
특정 객체 안에 관련된 속성과 기능을 하나의 단위로 묶고, 외부로부터 데이터를 보호하는 객체지향 프로그래밍(OOP)의 핵심 개념
- 객체가 제공하는 메서드를 통해서만 데이터 접근
- 데이터의 유효성을 검증할 수 있는 메서드를 통해 접근 가능하게 함
- 객체간 결합도를 낮춰 유지보수성을 높임
미사용 시 문제점
- 유효하지 않은 데이터 입력을 막을 수 없음 (
int hp = -100) - 클래스 내부 필드가 변경되면 사용하는 모든 코드에 영향을 미침(필드 변경 시 모든 호출부 수정 필요)
장점
| 장점 | 설명 |
|---|---|
| 🔒 데이터 보호 | 외부에서 필드 직접 접근 제한. setter에서 유효성 검사 가능 |
| 🧱 구조 은닉 | 내부 구조 변경 시 외부 영향 최소화 (API 유지) |
| 🔁 유지보수 용이 | 구조가 바뀌어도 메서드만 유지하면 외부 코드 수정 필요 없음 |
| 🔄 결합도 감소 | 객체 간 관계가 느슨해지고, 테스트 및 재사용성이 향상됨 |
캡슐화는 접근 제어자와 Getter/Setter 메소드로 구현
1) 제어자(Modifier)
클래스, 필드, 메서드, 생성자 등에 부가적인 의미를 부여하는 키워드
- 접근 제어자:
public,protected,(default),private - 기타 제어자:
static,final,abstract,native,synchronized등
접근 제어자는 하나만 적용 가능하지만, 기타 제어자는 복수 적용 가능
2) 접근 제어자(Access Modifier)
클래스 또는 멤버(필드, 메서드)의 접근 범위를 제한하는 키워드
주로 캡슐화의 수단으로 사용되어, 데이터 은닉(Data Hiding)과 보안성 강화를 가능하게 함
| 접근 제어자 | 접근 허용 범위 |
|---|---|
| private | 같은 클래스 내부에서만 접근 가능(외부 접근 완전 차단 → 데이터 보호를 위해 필수적) |
| default | 같은 패키지 내에서 접근 가능 |
| protected | 같은 패키지 + 다른 패키지의 자식 클래스(상속관계)에서 접근 가능 |
| public | 모든 곳에서 접근 가능(공용 API 등에 사용) |
접근 수준 : public > protected > default > private

3) Getter와 Setter 메서드
객체지향 프로그래밍에서 캡슐화를 구현하는 대표적인 방법 중 하나는 클래스 필드를 private로 선언하고, 외부에서는 public 메서드를 통해 간접적으로 접근하게 만드는 것
이때 사용되는 메서드가 getter 와 setter 메서드
getter메서드 : 필드 값을 읽는 메서드setter메서드 : 필드 값을 설정하는 메서드
(1) 사용하는 이유
- 데이터 보호
- 데이터 유효성 검증
- 내부 구현 변경의 유연성 확보
4) 패키지(Package)
비슷한 목적을 가진 클래스와 인터페이스를 논리적으로 그룹화하는 디렉토리 단위로, 자바 클래스들을 계층적으로 조직화하고 충돌을 방지하는 데 중요한 역할을 한다. 마치 파일 시스템에서 폴더로 파일을 정리하는 것과 같은 개념으로 보면 된다.
- 접근 제어자(
public,protected,default,private)의 적용 범위를 설정하는 기준
(1) 패키지 선언
package practicepack.test;
public class PackageEx {
// ...
}
(2) 대표적인 Java 내장 패키지
java.lang: String, Math, Object 등 기본 클래스 (자동 import 됨)String클래스의 전체 이름 :java.lang.String
java.util: 컬렉션 프레임워크, 날짜 클래스 등java.io: 파일 입출력 관련 클래스java.nio: 효율적인 I/O를 위한 클래스
(3) import 문
패키지에 속한 클래스를 다른 패키지에 사용할 때 사용하는 키워드
import 패키지명.클래스명;
import 패키지명.*; // import 패키지 내 모든 클래스
5) 불변 객체(Immutable Object)
객체가 생성된 이후 그 상태가 절대 변하지 않는 객체로, 내부 필드의 값이 한 번 초기화되면 이후에 변경 불가능
String,Integer,LocalDate
(1) 중요한 이유
- ⭐스레드 안전성(Thread-safe)
- 객체 상태 예측 가능성 증가
- 설계 단순화
- 값 객체에 적합
(2) 설계 원칙
| 번호 | 설계 원칙 | 설명 |
|---|---|---|
| 1 | final 클래스 선언 |
상속을 막아 클래스 구조 고정 |
| 2 | 모든 필드를 private final로 선언 |
외부 접근 및 값 변경을 방지 |
| 3 | 생성자를 통해서만 초기화 | 이후 값 변경 불가 보장 |
| 4 | setter 메서드 제거 | 값 변경 경로를 아예 차단 |
| 5 | 가변 객체 포함 시 복사 사용 | 깊은 복사를 통해 불변성 유지 |
(3) 예시
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public iint getAge() { return age; }
}
6) Java Bean
일정한 규칙을 따르는 Java 클래스로, 주로 데이터를 저장하고 전달하는 용도(ex: DTO, VO)로 사용
(1) 작성 규칙
- 기본 생성자 필수
- 명시적으로 매개변수가 없는 생성자를 제공해야 합니다.
- 모든 필드는
private으로 선언- 외부에서 직접 접근하지 못하도록 캡슐화합니다.
- Getter/Setter 메서드 제공
- 각 필드에 대응하는
getX(),setX()형식의 메서드를public으로 제공합니다.
- 각 필드에 대응하는
- 직렬화 가능 (선택사항)
implements Serializable을 통해 객체 직렬화를 지원할 수 있습니다.
- 패키지에 속해야 함
- Java Bean은 기본 패키지에 둘 수 없으며, 반드시 명시적인 패키지 안에 있어야 합니다.
(2) 특징과 목적
- 표준화된 구조
- 프레임워크(Spring, JSP 등)에서 객체 자동 생성, 데이터 바인딩에 활용
- 높은 호환성
- getter/setter를 통해 다양한 도구 및 라이브러리와 통합 용이
- 유지보수 용이
- 명확한 규칙으로 코드 구조를 빠르게 파악하고 유지보수 쉬움
- 캡슐화 중심
- 내부 상태 보호와 외부 데이터 전달을 분리하여 관리
(3) 불편 객체 vs Java Bean
Java Bean은 DTO/VO 형태로 프레임워크와의 연결, 외부 데이터 입력 등을 쉽게 처리할 수 있게 해주는 표준 구조지만, 상태 변경이 일어나지 않아야 할 경우 Java Bean보다는 불변 객체 설계가 더욱 적합
| 항목 | 불변 객체 | Java Bean |
|---|---|---|
| 필드 변경 가능 | 불가능 (final) |
가능 (setter 제공) |
| 생성자 | 모든 값 초기화 필수 | 기본 생성자 필수 |
| setter 제공 | 제공하지 않음 | 반드시 제공 |
| 용도 | 스레드 안정성, 상태 고정 | 도구와의 연동, 데이터 전달 |
1-03. 다항성(Polymorphism)
영어로 “poly(여러 개의)” + “morphism(형태)”의 합성어로, 하나의 객체가 여러 형태를 가질 수 있는 성질
Java에서는 상위 클래스 타입의 참조 변수로 하위 클래스의 객체를 참조할 수 있는 성질
즉, 하위 타입이 상위 타입으로 형 변환이 가능한 성질
// 부모 타입으로 자식 인스턴스 참조
Friend girlFriend = new GirlFriend();
girlFriend.friendInfo();
// 출력: "나는 당신의 여자친구입니다." (오버라이딩된 메서드 실행)
Friend girlFriend = new GirlFriend();처럼 상위 타입으로 하위 객체를 참조하는 것이 다형성의 핵심- 다형성이 적용되면 참조 변수는 상위 클래스 타입이지만, 실제 호출되는 메서드는 오버라이딩된 하위 클래스의 메서드이다.
| 항목 | 설명 |
|---|---|
| 정의 | 하나의 타입(상위 타입)으로 여러 하위 타입 객체를 다룰 수 있는 특성 |
| 활용 기술 | 메서드 오버라이딩, 오버로딩, 업캐스팅 |
| 장점 | 확장성, 유지보수성, 코드 간결성, 결합도 감소 |
| 전제 조건 | 상속 또는 인터페이스 구현 관계 |
1) 업캐스팅(Up-casting) vs 다운캐스팅(Down-casting)
- 참조 변수의 타입 변환(캐스팅)은 상속 관계를 전제
| 구분 | 업캐스팅 (Up-casting) | 다운캐스팅 (Down-casting) |
|---|---|---|
| 방향 | 하위 클래스 → 상위 클래스 | 상위 클래스 → 하위 클래스 |
| 형 변환 여부 | 묵시적 (형 변환 연산자 생략 가능) | 명시적 (형 변환 연산자 반드시 필요) |
| 사용 목적 | 다형성을 활용해 여러 하위 객체를 동일한 상위 타입으로 처리 | 상위 타입 참조 변수를 통해 하위 타입 고유 기능에 접근 |
| 안전성 | 안전 (컴파일, 런타임 모두 문제 없음) | 위험 (잘못된 캐스팅 시 ClassCastException 발생 가능) |
| 예시 | Vehicle vehicle = new Car(); |
((Car) vehicle).giveRide(); |
2) instanceof 연산자
참조 변수의 타입 변환(캐스팅)이 가능한지 여부를 boolean 값으로 반환하는 연산자로, 참조 변수의 실제 타입 확인 및 안전한 다운 캐스팅에 사용된다.
참조변수 instanceof 클래스명
// System.out.println(cat instanceof Animal); // true
- 참조 변수가 해당 클래스나 그 자식 클래스의 인스턴스라면
true를 반환 - 참조 변수가
null이라면 항성false를 반환
1-04. 추상화(Abstraction)
- 추상의 사전적 정의: 사물이나 개념의 공통적인 본질을 추출하여 단순화하는 것
- Java에서의 추상화는 객체들의 공통적인 속성과 기능을 뽑아 상위 클래스로 정의하는 작업으로, 기존 클래스들의 공통적인 요소들을 뽑아 상위 클래스로 만들어 내는 것이라 할 수 있다.
(1) 추상화 필요성
- 복잡한 시스템을 단순하게 표현
- 공통점을 기준으로 설계하면 코드 재사용성이 높아짐
- 유연한 구조 설계가 가능
- 기능 확장과 교체가 용이(유지보수)
(2) 추상화 구현 방법
- 추상 클래스(
abstract class) : 공통 속성과 일부 구현 메서드를 포함할 수 있음 - ⭐인터페이스(
interface) : 공통 행위만 정의 (JDK 8 이후default메서드 추가 가능)
(3) 추상화 유형
- 행위 중심 추상화 : 객체가 수행해야 할 기능에 초점을 맞춘 추상화(”무엇을 할 수 있나”에 집중)
- 데이터 중심 추상화 : 객체가 가져야 할 데이터에 초점을 맞춘 추상화(”무엇을 가지고 있나”에 집중)
1) abstract 제어자
Java에서 abstract 키워드는 “미완성”을 나타내는 제어자로, 추상 클래스(abstract class)와 추상 메서드(abstract method)를 정의할 때 사용
abstract method(추상 메서드) : 메서드 시그니처만 존재하고, 본문(메서드 바디)이 없는 메서드abstract class(추상 클래스) : 하나 이상의 추상 메서드를 포함하거나, 객체 생성을 제한하기 위해 선언된 클래스
abstract class AbstractExample {
abstract void start(); // 추상 메서드
}
(1) abstract 특징
- 추상 메서드 (
abstract method)- 메서드의 본문이 없는 미완성 메서드입니다.
- 구현이 없기 때문에 하위 클래스에서 반드시 오버라이딩(Override) 해야 합니다.
- 추상 클래스 (
abstract class)- 추상 메서드를 포함하거나, 직접 객체 생성을 막기 위해 사용합니다.
- 불완전한 설계도의 역할을 하며, 이를 상속받는 하위 클래스가 구체적인 구현을 담당합니다.
- 즉, 추상 클래스는 인스턴스화 불가능
2) 추상 클래스
0개 이상의 추상 메서드를 포함하거나 객체 생성을 제한하기 위해 선언된 클래스로, 직접 객체 생성 불가능
(1) 추상 클래스 특징
- 추상 메서드를 0개 이상 포함할 수 있습니다.
- 직접 인스턴스를 생성할 수 없습니다.
- 완성된 메서드(일반 메서드)도 함께 포함할 수 있습니다.
- 상속을 통해 하위 클래스에서 추상 메서드를 오버라이딩하여 완성해야 사용할 수 있습니다.
(2) 추상 클래스 사용 목적
- ⭐상속 구조에서 유연한 설계를 위해 사용
추상 클래스는 상속 구조에서 공통적인 속성과 기능을 미리 정의하고, 각 하위 클래스가 구체적인 구현을 담당하도록 하는 데 유리함
→ 변경에 유연하게 대응할 수 있고, 코드의 중복도 줄일 수 있음
- 공통적인 기능을 강제하기 위한 설계 도구
추상 클래스에 선언된 추상 메서드는 하위 클래스에서 반드시 구현해야 하므로, 공통된 동작을 하위 클래스에 강제할 수 있음
→ 오버라이딩 필수
3) final 키워드
final은 Java에서 “변경 불가” 또는 “확장 불가”의 의미를 가지는 제어자(modifier)로, 클래스, 메서드, 변수에 사용할 수 있으며, 사용하는 위치에 따라 의미가 달라짐
| 위치 | 의미 |
|---|---|
| 클래스 | 변경 또는 확장과 상속이 불가능한 클래스 |
| 메서드 | 오버라이딩이 불가능한 메서드 |
| 변수 | 값 변경이 불가능한 상수 (한 번만 할당 가능) |
4) 인터페이스(interface)
자바에서 추상화를 구현하기 위한 가장 기본적이면서도 강력한 도구로, 객체들이 서로 상호작용할 수 있도록 돕는 약속된 규격을 정의하는 장치
- 클래스가 반드시 구현해야 할 기능의 목록(추상 메서드)만 정의
- 기본적으로 모든 메서드는
public abstract, 모든 필드는public static final로 간주- 즉, 추상 메서드(
public abstract메서드)와 상수(public static final필드)로만 구성
- 즉, 추상 메서드(
- 구현체 클래스는 인터페이스에 정의된 모든 메서드를 반드시 구현해야 합니다.
- 인터페이스는 다중 구현(multiple inheritance)이 가능합니다.
(1) 인터페이스 기본 구조
public interface InterfaceEx {
int ROCK = 1; // public static final 생략 가능
int SCISSORS = 2;
int PAPER = 3;
String getPlayingNum(); // public abstract 생략 가능
void call();
}
- 모든 필드는 자동으로
public static final로 처리되며, 생략 가능 - 모든 메서드는 자동으로
public abstract로 처리되며, 생략 가능 - 메서드 바디는 가질 수 없습니다 (단, Java 8 이후부터는
default,static메서드 허용)
(2) 인터페이스 구현
인터페이스는 인스턴스를 생성할 수 없으며, 반드시 구현체 클래스가 인터페이스를 구현해야 합니다.
class MyClass implements InterfaceEx {
public String getPlayingNum() {
return "1";
}
public void call() {
System.out.println("called!");
}
}
implements키워드를 사용하여 구현- 인터페이스에 정의된 모든 추상 메서드를 반드시 오버라이딩해야 함
(3) 추상 클래스 vs 인터페이스
| 항목 | 추상 클래스 | 인터페이스 |
|---|---|---|
| 인스턴스 생성 | 불가능 | 불가능 |
| 상속/구현 키워드 | extends |
implements |
| 다중 구현 | 불가능 | 가능 |
| 멤버 구성 | 필드, 생성자, 메서드 등 모두 가능 | 추상 메서드, 상수 (Java 8 이후 default/static 메서드 가능) |
| 사용 목적 | 공통 로직 공유 + 강제 구현 | 강제 구현 중심, 역할 명세 |
(4) 인터페이스 장점
- 역할과 구현의 분리로 인한 유연성 → 유지보수성과 재사용성이 높아짐
5) 추상 클래스와 인터페이스
- 추상 클래스 사용 시기
- 공통 속성과 기본 기능이 필요한 경우
- 일관된 기본 동작 제공 + 자식 클래스 확장 허용
- 단일 상속 구조가 적절한 구조
- 인터페이스 사용 시기
- 동작(기능)만 정의하고 구현은 위임하고 싶을 때
- 여러 타입에 동일한 기능을 강제하고 싶을 때
- 다중 구현이 필요한 경우
| 구분 | 추상 클래스 | 인터페이스 |
|---|---|---|
| 사용 키워드 | extends |
implements |
| 상속 수 | 단일 상속만 가능 | 다중 구현 가능 |
| 구성 요소 | 필드, 구현 메서드, 추상 메서드 | 상수, 추상 메서드 (default/static 가능) |
| 용도 | 공통된 상태와 기능 제공 | 동작 규칙 정의 및 구현 강제 |
| 예시 관계 | is-a |
can-do |
⭐⭐⭐2. SOLID 원칙
SOLID는 객체지향 설계에서 유지보수 가능하고, 확장 가능한 코드를 만들기 위한 5가지 설계 원칙의 앞 글자를 모은 약어
⭐2-01. SRP
- SRP(Single Responsibility Principle) : 단일 책임의 원칙
- “하나의 클래스는 하나의 책임만 가져야 한다.”
2-02. OCP
- OCP(Open/Closed Principle) : 개방/폐쇄의 원칙
- “확장에는 열려있고, 수정에는 닫혀있어야 한다.”
2-03. LSP
- LSP(Liskov’s Substitution Principle) : 리스코브 치환의 원칙
- “자식 클래스는 반드시 부모 클래스를 대체할 수 있어야 한다.”
2-04. ISP
- ISP(Interface Segregation Principle) : 인터페이스 분리의 원칙
- “하나의 범용 인터페이스보다 다수의 구체적인 인터페이스가 낫다.”
- “클라이언트는 사용하지 않는 메소드에 의존하면 안 된다.”
⭐2-05. DIP
- DIP(Dependency Inversion Principle) : 의존성 역전의 원칙
- “구체적인 클래스 보다는 인터페이스나 추상 클래스와 관계를 맺어야 한다.”
- “상위 모듈은 하위 모듈에 의존하면 안 된다”
- “추상화에 의존해야지, 구체화에 의존하면 안된다.”
오늘의 개선
오늘 배운 내용 전부가 머리에서 튕겨져 나가는 느낌이 들어서 TIL 작성할 때 하나도 축약을 못한 것 같다. 캡슐화부터 좀 더 꼼꼼히 여러 번 봐야 겠다.
Leave a comment