Java 7 이전 핵심 개념 — 객체지향과 컬렉션

Java의 객체지향과 핵심 자료형, 컬렉션 및 예외 처리 개념을 코드 예제로 정리하고 Spring DI 및 백엔드 설계와 연결한다.

목차


1. 객체지향 프로그래밍(OOP)

1.1 개념

객체지향 프로그래밍(Object-Oriented Programming)은 상태(필드)와 행동(메서드)을 가진 객체들이 협력하도록 프로그램을 구성하는 방식이다.

public class Account {
    private long balance;
 
    public Account(long balance) {
        if (balance < 0) throw new IllegalArgumentException("초기 잔액 오류");
        this.balance = balance;
    }
 
    public void deposit(long amount) {
        if (amount <= 0) throw new IllegalArgumentException("입금액 오류");
        balance += amount;
    }
 
    public long getBalance() {
        return balance;
    }
}
  • 클래스: 객체의 구조와 행동을 정의하는 설계도.
  • 객체/인스턴스: 클래스를 기반으로 생성되어 독립적인 상태를 가진 대상.
  • 캡슐화: 데이터와 행동을 묶어 객체가 자신의 상태를 관리하게 한다.
  • 정보 은닉: private 등의 접근 제어자로 내부 구현에 대한 직접 접근을 제한한다. 캡슐화와 정보 은닉은 연관되지만 같은 뜻은 아니다.

1.2 객체지향의 네 가지 특징

특징의미예시
캡슐화(Encapsulation)상태와 행동을 한 단위로 묶어 관리Account.deposit()
상속(Inheritance)기존 클래스의 특성을 확장Dog extends Animal
다형성(Polymorphism)같은 상위 타입으로 다양한 구현을 사용PaymentProcessor
추상화(Abstraction)핵심 계약을 노출하고 구현 세부사항을 감춤interface

1.3 접근 제어자

제어자동일 클래스동일 패키지타 패키지 하위 클래스외부 일반 클래스
privateOXXX
기본(package-private)OOXX
protectedOOO*X
publicOOOO

* protected의 타 패키지 접근은 상속 관계의 접근 조건을 따른다.

1.4 SOLID 원칙

약어정식 명칭핵심 의미
SRP단일 책임 원칙(Single Responsibility Principle)클래스/모듈은 하나의 변경 이유에 집중한다.
OCP개방-폐쇄 원칙(Open-Closed Principle)확장에는 열려 있고 기존 코드 수정에는 닫혀 있도록 설계한다.
LSP리스코프 치환 원칙(Liskov Substitution Principle)하위 타입으로 대체해도 상위 타입의 기대 동작이 유지되어야 한다.
ISP인터페이스 분리 원칙(Interface Segregation Principle)사용하지 않는 메서드에 의존하도록 강요하지 않는다.
DIP의존성 역전 원칙(Dependency Inversion Principle)상위 정책과 하위 구현 모두 구체 클래스보다 추상화에 의존한다.

실무 연결: Spring에서 인터페이스에 의존하고 구현체를 주입받는 구조는 DIP를 실천하는 대표적인 방법이다. DI를 사용했다고 자동으로 SOLID가 지켜지는 것은 아니다.

1.5 static 키워드

static 멤버는 객체마다 따로 존재하는 것이 아니라 클래스 단위로 사용된다. 인스턴스 필드와 달리 클래스의 여러 인스턴스가 같은 static 필드를 공유한다.

class Counter {
    static int totalCount = 0; // 모든 인스턴스가 공유
    int instanceCount = 0;     // 인스턴스마다 별도 보유
 
    Counter() {
        totalCount++;
        instanceCount++;
    }
 
    static int getTotalCount() {
        return totalCount;
    }
}
 
Counter first = new Counter();
Counter second = new Counter();
System.out.println(Counter.getTotalCount()); // 2
System.out.println(first.instanceCount);     // 1
System.out.println(second.instanceCount);    // 1
  • static 필드: 클래스 단위로 공유한다. 변경 가능한 static 상태는 동시성 문제에 유의해야 한다.
  • static 메서드: 인스턴스 없이 클래스명으로 호출할 수 있으며, this나 인스턴스 필드에 직접 접근할 수 없다.
  • static 초기화 블록: 클래스 초기화 과정에서 실행된다(동일 클래스 로더로 정의한 클래스 기준 일반적으로 한 번).
  • static 메서드 숨김: 하위 클래스에서 같은 시그니처의 static 메서드를 선언해도 오버라이딩과 달리 컴파일 타임 타입을 기준으로 선택한다. 자세한 내용은 2.2 다형성과 동적 디스패치를 참고한다.

static은 단순히 “클래스 로딩 시 Method Area에 저장된다”라고 외우기보다 클래스 초기화 시점과 JVM의 논리적 런타임 데이터 영역을 구분해서 이해하는 편이 정확하다.

1.6 Wrapper Class와 오토박싱

Wrapper Class는 int, long, boolean 같은 기본형(primitive)을 Integer, Long, Boolean 같은 객체 타입으로 다루기 위한 클래스다. 제네릭 타입 인자로 기본형을 쓸 수 없기 때문에 컬렉션에서 자주 사용한다.

기본형Wrapper Class
intInteger
longLong
doubleDouble
booleanBoolean
charCharacter
Integer boxed = 10; // 오토박싱: int → Integer
int value = boxed;  // 언박싱: Integer → int
List<Integer> numbers = new ArrayList<Integer>();
numbers.add(10);     // 오토박싱

주의할 점:

  • 언박싱 시 NPE: Integer count = null; int n = count;는 NullPointerException이 발생한다.
  • == 주의: 두 Wrapper 객체 사이의 ==는 참조 동일성을 비교한다. 숫자 값 비교에는 equals() 또는 언박싱 후 기본형 비교가 적합하다.
  • Integer 캐시: Integer.valueOf()는 최소 -128부터 127까지 캐시를 이용하므로 일부 값에서 참조가 우연히 같아질 수 있다. 캐시 여부를 값 비교의 근거로 사용하면 안 된다.
  • 성능: 반복 연산에서 불필요한 박싱/언박싱은 객체 생성 및 성능 비용을 만들 수 있다.

1.7 String, StringBuilder, StringBuffer

String은 **불변(immutable)**이다. 문자열을 결합하거나 변형한 결과는 기존 문자열을 수정하는 것이 아니라 새 문자열 값으로 표현한다. StringBuilder와 StringBuffer는 내용을 수정할 수 있는 가변 문자열 도구다.

항목StringStringBuilderStringBuffer
가변성불변가변가변
변경 API변경 결과로 새 String을 얻음append(), insert() 등append(), insert() 등
동기화불변이므로 상태 변경 없음동기화 제공하지 않음주요 메서드 동기화
일반적 선택문자열 값·식별자단일 흐름에서 반복 결합공유 가변 버퍼의 동기화가 필요할 때 신중히 선택
String text = "Java";
text.concat(" 7");
System.out.println(text); // Java (원래 문자열 그대로)
text = text.concat(" 7");
System.out.println(text); // Java 7
 
StringBuilder builder = new StringBuilder("Java");
builder.append(" 7");
System.out.println(builder); // Java 7

문자열 연결이 언제나 새 객체를 연속으로 만드는 것은 아니다. 컴파일 시 상수 결합이나 JVM 최적화가 적용될 수 있다. 성능이 중요할 때는 실제 반복 패턴을 기준으로 판단해야 한다. 또한 StringBuffer의 개별 메서드가 동기화되어 있다고 해서 여러 호출을 결합한 전체 작업이 자동으로 원자적이 되는 것은 아니다.

1.8 POJO (Plain Old Java Object)

POJO는 특정 프레임워크의 기반 클래스를 상속하거나 특정 프레임워크 API에 강하게 결합되지 않은 일반적인 Java 객체를 가리키는 용어다.

public class Order {
    private final long id;
 
    public Order(long id) { this.id = id; }
    public long getId() { return id; }
}
  • POJO ≠ JavaBean: JavaBean은 관례적 프로퍼티 접근 메서드와 기본 생성자 등 별도의 관례를 가진다. 모든 POJO가 JavaBean인 것은 아니다.
  • POJO ≠ Spring Bean: Spring Bean은 Spring 컨테이너가 관리하는 객체를 뜻한다. 일반 POJO를 Spring Bean으로 등록할 수 있다.
  • 실무 의미: 순수 Java 객체에 비즈니스 규칙을 담으면 프레임워크 의존을 줄이고 독립적으로 테스트하기 쉬워진다.

2. 상속과 다형성

2.1 상속과 오버라이딩

class Animal {
    String name = "Animal";
 
    void sound() {
        System.out.println("동물 소리");
    }
 
    static void info() {
        System.out.println("Animal 정보");
    }
}
 
class Dog extends Animal {
    String name = "Dog";
 
    @Override
    void sound() {
        System.out.println("멍멍");
    }
 
    static void info() {
        System.out.println("Dog 정보");
    }
}

상속(extends)은 부모의 기능을 하위 클래스에서 확장하는 방식이다. Java 클래스는 직접적인 부모 클래스를 하나만 가질 수 있다.

2.2 다형성과 동적 디스패치

Animal animal = new Dog();
 
animal.sound();                  // 멍멍
System.out.println(animal.name); // Animal
animal.info();                   // Animal 정보
Dog.info();                      // Dog 정보
  • 참조 변수의 선언 타입은 Animal, 실제 생성한 객체 타입은 Dog이다.
  • 인스턴스 메서드 sound(): 실제 객체가 Dog이므로 Dog.sound()를 호출한다. 이렇게 실행할 오버라이딩 메서드가 런타임의 실제 객체 타입에 따라 정해지는 것이 **동적 디스패치(Dynamic Dispatch)**다.
  • 필드 name: 오버라이딩되지 않는다. animal.name은 참조 변수에 선언된 타입 Animal 기준으로 선택된다. Animal.name과 Dog.name은 서로 다른 필드다.
  • static 메서드 info(): 객체가 아닌 클래스에 속한다. 재선언하면 오버라이딩이 아니라 **메서드 숨김(Method Hiding)**이다. 호출 대상이 컴파일 타임 타입에 따라 선택되므로 혼란을 막으려면 Animal.info()처럼 클래스명으로 호출한다.
대상선택 기준위 예시 결과
오버라이딩 가능한 인스턴스 메서드런타임 실제 객체 타입멍멍
인스턴스 필드참조 변수의 선언 타입Animal
static 메서드컴파일 타임 타입 또는 명시된 클래스Animal 정보

암기: 오버라이딩된 인스턴스 메서드는 실제 객체를 따라간다. 필드와 static 메서드는 선언된 타입을 기준으로 선택된다.

2.3 오버로딩 vs 오버라이딩

구분오버로딩(Overloading)오버라이딩(Overriding)
조건같은 이름, 다른 매개변수 목록상속된 인스턴스 메서드를 재정의
상속 필요XO
메서드 선택컴파일 시점런타임 실제 객체 타입
반환 타입반환 타입만 달리해 구별할 수 없음같거나 공변 반환 타입 허용

2.4 상속보다 조합(Composition)을 고려하는 이유

상속은 부모가 가진 구현에 자식이 결합되기 쉽다. 부모의 결제 한도 검증 규칙을 고쳤는데, 이를 상속한 카드/계좌이체 서비스가 모두 영향을 받는 상황을 생각해 볼 수 있다.

조합은 필요한 기능을 가진 객체를 **필드로 보유(has-a)**하고 작업을 위임한다. 검증 규칙을 별도 정책 객체로 분리하면 수단별 정책 교체가 쉬워진다.

interface PaymentValidator {
    void validate(long amount);
}
 
class CardValidator implements PaymentValidator {
    @Override
    public void validate(long amount) {
        if (amount <= 0 || amount > 1_000_000) {
            throw new IllegalArgumentException("카드 결제 한도 초과");
        }
    }
}
 
class BankValidator implements PaymentValidator {
    @Override
    public void validate(long amount) {
        if (amount <= 0 || amount > 10_000_000) {
            throw new IllegalArgumentException("계좌이체 한도 초과");
        }
    }
}
 
class PaymentService {
    private final PaymentValidator validator;
 
    PaymentService(PaymentValidator validator) {
        this.validator = validator;
    }
 
    void pay(long amount) {
        validator.validate(amount);
        System.out.println("결제 처리");
    }
}
 
PaymentService card = new PaymentService(new CardValidator());
PaymentService bank = new PaymentService(new BankValidator());
card.pay(500_000);
bank.pay(5_000_000);
비교상속조합
관계is-ahas-a
재사용 방식부모 구현 상속외부 객체에 기능 위임
주요 위험부모 구현 변경이 하위 클래스에 전파구현체를 직접 고정하면 여전히 결합됨
실무 설계안정적인 상위-하위 타입 관계에 적합정책 변경·구현체 교체에 유리

핵심: 조합만으로 자동으로 느슨한 결합이 생기는 것은 아니다. **조합 + 인터페이스 + 의존성 주입(DI)**을 함께 사용하면 구체 구현에 대한 의존을 줄일 수 있다.


3. 추상 클래스와 인터페이스

3.1 추상 클래스

공통 상태와 구현을 제공하고, 일부 동작을 하위 클래스에서 구현하도록 할 때 사용한다.

abstract class Payment {
    protected final long amount;
 
    Payment(long amount) {
        this.amount = amount;
    }
 
    void validate() {
        if (amount <= 0) throw new IllegalArgumentException();
    }
 
    abstract void execute();
}
  • 직접 인스턴스화할 수 없지만 생성자와 인스턴스 필드는 가질 수 있다.
  • 일반 메서드와 추상 메서드를 함께 가질 수 있다.
  • 추상 메서드가 없어도 abstract class로 선언할 수 있다.

3.2 인터페이스

구현체가 따라야 하는 기능 계약을 정의한다.

interface PaymentProcessor {
    void process(long amount);
}
 
class CardProcessor implements PaymentProcessor {
    @Override
    public void process(long amount) {
        System.out.println("카드 결제: " + amount);
    }
}
 
class BankProcessor implements PaymentProcessor {
    @Override
    public void process(long amount) {
        System.out.println("계좌이체: " + amount);
    }
}
항목추상 클래스인터페이스
주목적공통 기반 구현 및 상태 제공기능 계약 제공
생성자 / 인스턴스 상태가능불가능
다중 사용클래스 상속은 하나만여러 인터페이스 구현 가능
Java 7 일반 구현 메서드가능불가능

버전 구분: 인터페이스 default/static 메서드는 Java 8, private 메서드는 Java 9부터 지원한다.

3.3 Spring DI와 연결하기

class PaymentService {
    private final PaymentProcessor processor;
 
    PaymentService(PaymentProcessor processor) {
        this.processor = processor;
    }
 
    void pay(long amount) {
        processor.process(amount);
    }
}
 
PaymentService service = new PaymentService(new CardProcessor());
service.pay(10_000); // 카드 결제

PaymentService는 CardProcessor라는 구체 클래스 대신 PaymentProcessor라는 인터페이스에 의존한다. 이 코드는 일반 Java 생성자 주입이며, Spring 없이도 가능한 DI다.

Spring을 사용한다면 구현체를 Bean으로 등록하고 컨테이너가 생성자에 전달한다.

@Component("cardProcessor")
class CardProcessor implements PaymentProcessor {
    @Override
    public void process(long amount) {
        System.out.println("카드 결제: " + amount);
    }
}
 
@Component("bankProcessor")
class BankProcessor implements PaymentProcessor {
    @Override
    public void process(long amount) {
        System.out.println("계좌이체: " + amount);
    }
}
 
@Service
class PaymentService {
    private final PaymentProcessor processor;
 
    PaymentService(@Qualifier("cardProcessor") PaymentProcessor processor) {
        this.processor = processor;
    }
 
    void pay(long amount) {
        processor.process(amount);
    }
}

위 예시는 Spring 환경의 독립적인 코드 조각이다. @Component, @Service, @Qualifier 및 관련 import가 필요하다.

  • 구현 Bean이 둘이라면 @Qualifier 등으로 주입 대상을 선택해야 한다.
  • 위 코드는 항상 카드 처리기를 사용하는 형태다. 요청별로 결제수단을 선택하려면 여러 전략을 주입받아 요청에 따라 선택하는 팩토리/레지스트리 방식 등을 사용해야 한다.
  • 연결: 인터페이스(계약) → 조합(필드 보유) → DI(외부 주입) → 다형성/동적 디스패치(실제 구현 실행).

4. 제네릭(Generics)

4.1 사용 목적

타입을 매개변수로 받아 컴파일 시점에 타입 안전성을 확보하고 형변환을 줄인다.

List<String> names = new ArrayList<String>();
names.add("Java");
// names.add(100); // 컴파일 오류
String first = names.get(0);

Java 7부터 new ArrayList<>()처럼 다이아몬드 연산자로 타입 인자를 생략할 수 있다.

4.2 제네릭 클래스 / 메서드

class Box<T> {
    private T value;
 
    void set(T value) { this.value = value; }
    T get() { return value; }
}
 
static <T> T first(List<T> values) {
    return values.get(0);
}

관례: T(Type), E(Element), K(Key), V(Value).

4.3 와일드카드와 PECS

형태뜻주된 사용
List<?>원소 타입 미상타입 무관 조회
List<? extends Number>Number 또는 하위 타입읽기(Producer)
List<? super Integer>Integer 또는 상위 타입쓰기(Consumer)

PECS: Producer Extends, Consumer Super. extends 컬렉션에는 일반적으로 새 값을 추가할 수 없고(null은 예외), super 컬렉션에서 읽으면 안전하게는 Object로 받는다.

4.4 타입 소거(Type Erasure)

제네릭 타입 인자는 대부분 컴파일 과정에서 소거되어 런타임의 인스턴스 클래스가 같아진다.

List<String> a = new ArrayList<String>();
List<Integer> b = new ArrayList<Integer>();
System.out.println(a.getClass() == b.getClass()); // true

제약 사항: new T(), new T[10], instanceof List<String>은 직접 사용할 수 없다. 기본형은 타입 인자가 될 수 없으므로 List<Integer>를 사용한다. 일부 제네릭 선언 정보는 클래스 메타데이터에 남을 수 있다.


5. 컬렉션 프레임워크

5.1 핵심 구분

인터페이스특징대표 구현체
List순서 있음, 중복 허용ArrayList, LinkedList
Set중복 불허HashSet, TreeSet
Queue대기열/우선순위 처리ArrayDeque, PriorityQueue
MapKey-Value, Key 중복 불허HashMap, TreeMap

List, Set, Queue는 Collection의 하위 인터페이스이며 Map은 별도 인터페이스다.

5.2 ArrayList와 LinkedList

연산ArrayListLinkedList
인덱스 조회O(1)O(n)
끝에 추가평균 O(1)O(1)
중간 삽입이동 때문에 O(n)위치 탐색 O(n), 연결 변경 O(1)
메모리/지역성참조 배열, 접근 지역성 유리노드 객체/참조 오버헤드

일반적인 목록은 ArrayList를 먼저 고려하고, LinkedList가 유리한지는 실제 접근 패턴으로 판단한다.

5.3 HashMap 작동 원리

  1. Key의 hashCode()를 호출한다.
  2. 해시를 가공해 버킷 위치를 계산한다.
  3. 버킷에 저장된 항목과 해시·equals()를 비교한다.
  4. 동일 Key면 Value를 교체하고, 없으면 항목을 추가한다.
  • 해시 충돌: 서로 다른 Key가 동일 버킷을 사용하는 현상.
  • 일반적인 get/put의 기대 시간복잡도는 **O(1)**이며 충돌이나 리사이징으로 비용이 증가할 수 있다.
  • 버전 주의: 충돌 버킷을 조건부 트리화하는 최적화는 Java 8 이후의 HashMap 구현이다. Java 7 시점의 설명과 구분할 것.
  • HashSet도 내부적으로 해시 기반 Map 구조를 사용하므로 동등성 규약이 중요하다.

5.4 HashMap / Hashtable / ConcurrentHashMap

구분HashMapHashtableConcurrentHashMap
스레드 안전성기본적으로 없음메서드 동기화동시성 처리 지원
null Key / Value허용(단일 null Key)불허불허
사용일반적인 Map레거시공유 Map 동시 접근

주의: ConcurrentHashMap이어도 get 후 put 같은 여러 작업이 하나의 원자적 연산으로 바뀌는 것은 아니다. 필요에 따라 putIfAbsent, compute 등을 사용한다. 내부 구현 방식은 Java 버전에 따라 다르다.


6. 예외 처리

6.1 예외 계층

Throwable
├── Error                       (unchecked)
└── Exception
    ├── IOException              (checked)
    ├── SQLException             (checked)
    └── RuntimeException         (unchecked)
        ├── NullPointerException
        └── IllegalArgumentException
구분특징예시
Checked Exception컴파일러가 처리 또는 throws 선언을 요구IOException, SQLException
Unchecked Exception컴파일러가 처리를 강제하지 않음RuntimeException, Error 계열
Error보통 애플리케이션 수준에서 복구하기 어려운 문제OutOfMemoryError

6.2 throw, throws, finally

  • throw: 예외 객체를 실제로 발생시킴.
  • throws: 메서드가 전달할 수 있는 예외를 선언.
  • finally: 일반적인 실행/예외 흐름에서 정리 코드 실행. JVM 강제 종료 등에서는 실행이 보장되지 않음.

6.3 try-with-resources (Java 7)

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    System.out.println(reader.readLine());
} catch (IOException e) {
    e.printStackTrace();
}

AutoCloseable 리소스를 자동으로 닫으며, 처리 중 예외와 종료 중 예외가 같이 나면 후자는 suppressed exception으로 기록될 수 있다.

6.4 커스텀 예외와 Spring 연결

class PaymentException extends RuntimeException {
    PaymentException(String message, Throwable cause) {
        super(message, cause);
    }
}
 
try {
    // 외부 결제 시스템 호출
} catch (IOException e) {
    throw new PaymentException("결제 연동 실패", e);
}

예외를 변환할 때 cause를 보존해야 장애 원인을 추적하기 좋다. 위 예시는 예외 처리 구조를 보여주는 코드 조각이다.

Spring 트랜잭션 기본 규칙: 기본적으로 RuntimeException과 Error는 롤백 대상이고, Checked Exception은 기본 롤백 대상이 아니다. rollbackFor 등으로 규칙을 조정할 수 있다.


7. equals와 hashCode

7.1 ==와 equals()

  • 기본형에 대한 ==: 값 비교.
  • 참조형에 대한 ==: 같은 객체를 참조하는지 비교.
  • equals(): 기본 Object 구현은 참조 동일성 비교지만, 오버라이딩하면 논리적 동등성 비교가 가능.
String a = new String("Java");
String b = new String("Java");
System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

7.2 두 메서드를 함께 재정의하는 이유

class Member {
    private final long id;
    private final String name;
 
    Member(long id, String name) {
        this.id = id;
        this.name = name;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Member)) return false;
        Member other = (Member) obj;
        return id == other.id;
    }
 
    @Override
    public int hashCode() {
        return Long.hashCode(id);
    }
}

여기서는 ID가 같으면 동일 회원이라는 도메인 규칙을 가정했다. name은 동등성 판단에서 제외한다.

equals() 규약: 반사성, 대칭성, 추이성, 일관성, null 비교 시 false.
hashCode() 규약: equals()가 true면 해시 값이 반드시 같아야 한다. 해시 값이 같다고 equals()가 true일 필요는 없다.

Set<Member> members = new HashSet<Member>();
members.add(new Member(1L, "Kim"));
members.add(new Member(1L, "Lee"));
System.out.println(members.size()); // 1

equals()만 재정의하고 hashCode()를 재정의하지 않으면 해시 기반 컬렉션에서 논리적 중복 판단이 깨질 수 있다.

7.3 가변 Key 문제

HashMap에 넣은 Key의 동등성/해시 계산에 사용되는 필드를 변경하면, 저장할 때의 버킷과 조회할 때의 버킷이 달라져 조회에 실패할 수 있다. 그러므로 Map Key는 가능한 불변 객체를 사용한다.


8. 개념 간 연결

인터페이스로 계약 정의
       ↓
PaymentService가 PaymentProcessor를 필드로 보유 (조합)
       ↓
생성자 또는 Spring 컨테이너가 구현 객체를 전달 (DI)
       ↓
processor.process() 호출
       ↓
실제 주입된 CardProcessor / BankProcessor 메서드 실행 (다형성/동적 디스패치)
기본 개념실무 연결
OOP/캡슐화결제 상태 및 규칙을 객체 안에서 관리
다형성/인터페이스PG·결제수단별 구현을 동일 계약으로 호출
조합/DI서비스 내부에서 구체 구현을 직접 생성하지 않음
제네릭타입 안전한 API 응답·공통 유틸리티
컬렉션거래 목록과 Key 기반 조회 구조
예외장애 원인 보존·업무 예외 분리·트랜잭션 정책
equals/hashCode회원/거래 값 객체의 논리적 동등성 및 해시 자료구조

9. 핵심 점검 질문

  1. 객체지향의 네 가지 특징을 결제 서비스 예제로 설명해 보자.
  2. SOLID의 한글명·영문명과 각 원칙의 핵심 취지는 무엇인가?
  3. Animal a = new Dog()에서 오버라이딩 메서드는 왜 Dog의 메서드가 실행되는가?
  4. 같은 코드에서 필드와 static 메서드는 왜 다른 결과가 나오는가?
  5. 오버로딩과 오버라이딩은 어떻게 다른가?
  6. 상속과 조합의 차이, 조합이 유리한 경우는 무엇인가?
  7. 추상 클래스와 인터페이스는 어떤 기준으로 선택하는가?
  8. 일반 생성자 주입과 Spring DI의 차이는 무엇인가?
  9. 인터페이스 구현체가 두 개일 때 Spring에서 어떻게 선택하는가?
  10. 제네릭의 타입 소거와 PECS는 무엇인가?
  11. ArrayList와 LinkedList의 삽입·조회 비용은 어떻게 다른가?
  12. HashMap의 저장/조회 및 충돌 처리 과정을 설명해 보자.
  13. Checked Exception과 Unchecked Exception의 차이는 무엇인가?
  14. equals()를 재정의할 때 hashCode()도 재정의하는 이유는 무엇인가?
  15. 가변 객체를 HashMap Key로 사용하면 무엇이 위험한가?