TIL - 25.12.24

이준연·2025년 12월 24일

학습 키워드

  • Xmas Bootcamp Village
  • 컬렉션 프레임워크
  • Spring

Xmas Bootcamp Village

크리스마스 이브를 맡아 간단한 Java/CS 관련 문제를 풀어보는 게임을 진행하였습니다. 게임을 진행하면서 몰랐던 내용들과 알았지만 키워드의 뜻을 제대로 인지하지 않아 작성하지 못한 문제들이 있었습니다. 이에 대한 내용을 정리합니다.

super

  • 부모 클래스를 가리키는 키워드입니다.
  • 뜻: 위의, 상위의, 최고의
  • 부모 클래스가 자식 클래스보다 상위에 있다는 의미입니다.
class Parent {
    void display() {
        System.out.println("부모 클래스");
    }
}

class Child extends Parent {
    void display() {
    	// 부모 클래스의 생성자를 호출
        super();
    	
        // 부모 클래스의 display() 호출
        super.display(); 
        
        // 부모 클래스의 필드에 접근
        super.fieldName
        System.out.println("자식 클래스");
    }
}

finally

  • 예외 발생 여부와 관계없이 항상 실행되는 블록 키워드입니다.
  • 주로 파일 닫기, 데이터베이스 연결 해제 등 리소스 정리 작업에 사용됩니다.
try {
    // 예외가 발생할 수 있는 코드
    int result = 10 / 0;
} catch (Exception e) {
    // 예외 처리
    System.out.println("예외 발생!");
} finally {
    // 항상 실행되는 코드
    System.out.println("finally 블록 실행");
}

StringBuilder & StringBuffer

  • 가변 문자열 조작에 쓰이는 클래스입니다.
  • 차이점
    • StringBuilder: 동기화를 지원하지 않아 단일 스레드 환경에서 더 빠릅니다.
    • StringBuffer: 동기화를 지원하여 멀티 스레드 환경에서 안전합니다.
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString(); // "Hello World"

implements

  • 인터페이스를 구현할 때 사용합니다.
  • 뜻: 구현하다, 실행하다, 이행하다
  • 인터페이스로 무엇을 해야하는지 정의하고 클래스로 그것을 구현하기 때문입니다.
interface Animal {
    void sound();
    void move();
}

class Dog implements Animal {
    @Override
    public void sound() {
        System.out.println("멍멍!");
    }
    
    @Override
    public void move() {
        System.out.println("네 발로 걷기");
    }
}

컬렉션 프레임워크

데이터를 저장하고 처리하기 위한 자료구조 클래스들의 집합입니다.

개념

  • Java에서 제공하는 데이터 구조를 표준화한 설계 구조입니다.
  • 다수의 데이터를 효율적이고 체계적으로 관리할 수 있는 방법을 제공합니다.

사용 이유

  • 효율성
    • 자료구조를 사용하여 개발 시간을 단축할 수 있습니다.
    • 최적화된 알고리즘을 통해 성능을 보장받을 수 있습니다.
  • 재사용성
    • 표준화된 방식으로 데이터를 관리할 수 있습니다.
    • 일관된 방식으로 사용할 수 있습니다.

주요 인터페이스 & 클래스

  • List 계열: 데이터를 순차적으로 저장합니다.
    • ArrayList: 배열 기반으로 데이터를 저장하며, 조회속도가 빠릅니다.
    • LinkedList: 노드 기반으로 데이터를 저장하며, 삽입과 삭제가 빠릅니다.
  • Set 계열: 중복을 허용하지 않는 데이터 집합입니다.
    • HashSet: 데이터를 해시 값으로 관리하여 검색 속도가 빠릅니다.
    • TreeSet: 데이터를 정렬된 상태로 유지하며, 이진 검색 트리 기반으로 동작합니다.
  • Map 계열: 키오 값을 쌍으로 저장하며, 키는 중복을 허용하지 않습니다.
    • HashMap: 해시 기반으로 키-값 데이터를 저장하며, 빠른 검색 속도를 제공합니다.
    • TreeMap: 키를 기준으로 정렬된 키-값을 저장합니다.

Map 인터페이스

특정한 키를 통해 값을 빠르게 조회할 수 있는 구조를 제공합니다.

사용 이유

  • 빠른 검색/조회: 키를 통해 O(1) 시간에 데이터 접근이 가능합니다.
  • 연관 데이터 관리: 관련된 데이터를 쌍으로 관리할 수 있습니다.
  • 중복 제거: 키의 중복을 허용하지 않아 유니크한 데이터 관리에 용이합니다.
// 1. 선언
HashMap<String, Integer> map = new HashMap<>();

// 2. 데이터 추가 및 수정
map.put("A", 1);                     // 키가 "A"인 값에 1 저장
map.putIfAbsent("A", 10);            // "A"키가 없을 때만 10 저장
int value = map.getOrDefault("B", 0);// "B"키 조회, 없으면 기본값 0

// 3. 존재 여부 확인
if (map.containsKey("A")) { ... }    // "A"키 존재 여부 확인
if (map.containsValue(1)) { ... }    // 값 1 존재 여부 확인

// 4. 삭제
map.remove("A");                     // "A"키 삭제
map.clear();                         // 전체 삭제

// 5. 값 변경하기 (다양한 방법)
HashMap<String, Integer> scores = new HashMap<>();
scores.put("Kim", 80);

// 5-1. 일반적 방식
if (scores.containsKey("Kim")) {
    scores.put("Kim", scores.get("Kim") + 10);
}

// 5-2. compute 활용 (람다 사용)
scores.compute("Kim", (key, oldVal) -> oldVal + 10);

// 5-3. 키가 있을 때만 변경 (computeIfPresent)
scores.computeIfPresent("Kim", (key, oldVal) -> oldVal + 10);

// 6. 크기 확인
int size = map.size();
boolean isEmpty = map.isEmpty();

Set 인터페이스

같은 값이 2번 이상 등장하는 것을 허용하지 않고, 각 요소는 유일한 값을 지닌 자료구조입니다.

사용 이유

  • 중복 제거: 대용량 데이터에서 같은 요서가 반복될 때, 효율적으로 중복을 제거합니다.
  • 유일성 보장: 비즈니스 로직상 고유해야하는 값을 관리하기 위해 사용합니다.
  • 집합 연산 관리: 집합 연산을 쉽게 수행합니다.
  • 메모리 절약: 중복 데이터를 제거하여 메모리 사용량을 감소합니다.
// 1. 선언
HashSet<String> set = new HashSet<>();

// 2. 데이터 추가 및 삭제
set.add("A");        // 요소 "A" 추가 (중복 요소는 추가되지 않음)
set.remove("A");     // "A" 삭제
set.clear();         // 모든 요소 삭제

// 3. 데이터 확인
boolean exists = set.contains("A");  // "A"가 존재하는지 확인
int size = set.size();               // Set의 크기 확인
boolean empty = set.isEmpty();       // 비었는지 확인

// 4. 집합 연산
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
// 합집합: set1에 set2의 요소 모두 추가
set1.addAll(set2);      
// 교집합: set1과 set2에 모두 있는 요소만 남김
set1.retainAll(set2);   
// 차집합: set1에서 set2의 요소 제거 (set1 - set2)
set1.removeAll(set2);

Spring

Annotation & Lombok

어노테이션은 자바 코드에 메타 데이터를 추가하여 코드에 특별한 의미를 부여하거나, 컴파일러와 런타임에 특정 동작을 트리거하기 위해 사용됩니다.

Lombok 라이브러리

  • 보일러 플레이트 코드를 줄여주는 라이브러리로 자동으로 보일러 플레이트 코드를 생성하여 코드의 가독성과 유지보수성을 높여줍니다.
  • 어노테이션 기반으로 동작하며, 주로 컴파일 시점에 소스 코드를 변환하여 필요한 메서드를 자동으로 생성합니다.
  • 보일러 플레이트 코드: 게터/세터 메서드 등과 같이 반복적으로 작성되는 코드를 뜻합니다.

주요 Lombok Annotation

  • @Getter, Setter
    • 클래스의 모든 필드에 대한 게터/세터 메서드를 자동으로 생성합니다.
  • @ToString
    • 객체의 toString() 메서드를 자동으로 생성합니다.
  • @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor
    • 기본 생성자를 생성합니다.
    • 모든 필드를 매개변수로 하는 생성자를 생성합니다.
    • 필수 필드만을 매개변수로 하는 생성자를 자동으로 생성합니다.
  • @Slf4j
    • 클래스에 로그를 남기기 위한 Logger 객체를 자동으로 생성합니다.

Servlet

서버에서 실행되어 클라이언트의 요청을 처리하고, 그 결과를 동적으로 생성하여 응답하는 자바 프로그램입니다.
자바 웹 애플리케이션에서 특정 URL 요청에 대한 비즈니스 로직을 실행하는 핵심입니다.

역할

  • 클라이언트가 보낸 요청을 읽고 해석합니다.
  • 조회, 계산 등 비즈니스 로직을 수행합니다.
  • 결과를 생성하여 클라이언트에게 보냅니다.
  • jakarta.servlet.HttpServlet 클래스를 상속받아 구현합니다.

서블릿 컨테이너

  • 서블릿을 실행하고 관리하며 웹 서버와 통신하게 해주는 전문적인 환경이 필요한데, 이를 서블릿 컨테이너라고 합니다.
  • 서블릿 컨테이너의 역할:
    • 서블릿의 생명주기 관리합니다.
    • HTTP 요청을 받아 서블릿에게 전달합니다.
    • 서블릿의 응답을 받아 클라이언트에게 전송합니다.
    • 동시에 여러 요청이 들어와도 문제없이 처리합니다.
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 요청 정보는 필요없으므로 요청 정보를 꺼내지는 않습니다.
        // 비즈니스 로직 또한 따로 없으므로 작성하지 않습니다.
        resp.setContentType("text/plain; charset=UTF-8"); // 응답 정보 타입 설정
        resp.getWriter().print("hello"); // Response 객체에 응답 정보인 'hello'를 담아줍니다.
    }

MVC 패턴

서블릿의 문제점

  • 낮은 응집도: 하나의 서블릿 클래스 내에 HTTP 요청 처리, 비즈니스 로직, 데이터 접근, 응답 생성 로직이 모두 혼재합니다. 이는 단일 클레스가 너무 많은 책임을 갖게 하여 코드의 이해와 테스트를 어렵게 만듭니다.
  • 강한 결합도: 비즈니스 로직이 HttpServletRequest, HttpServletResponse 와 같은 서블릿 API에 직접적으로 의존합니다. 이로 인해 웹 환경 밖에서 단위 테스트를 수행하기가 매우 까다로워 집니다.
  • 반복적인 상용구 코드: 요청 파라미터 파싱, 타입 변환, 응답 컨텐츠 타입 설정 등 모든 요청에 대해 반복적인 코드가 필수적으로 요구됩니다.

MVC

서블릿이 혼자 모든 것을 처리하던 문제를 해결하기 위해 애플리케이션의 코드를 세 가지 역할로 명확하게 나누는 설계 방식입니다.

  • Model: 데이터와 비즈니스 로직을 담당합니다.
  • View: 사용자에게 보여지는 화면을 담당합니다.
  • Controller: 사용자의 요청을 받아 Model과 View를 연결해주는 중간 다리 역할을 합니다.

SSR & CSR

  • SSR: 서버가 웹 페이지에 필요한 모든 데이터를 채워 넣어 완벽하게 조립된 HTML 페이지를 브라우저에게 전달하는 방식입니다.
    • 초기 로딩 속도가 빠르고, 모든 내용이 HTML에 포함되어 있어 검색 엔진 최적화에 유리합니다.
  • CSR: 서버가 거의 텅 빈 HTML 뼈대와 자바스크립트 파일만 보내주는 방식입니다. 클라이언트가 자바스크립트를 실행해 필요한 데이터를 서버에 다시 요청하고, 브라우저가 직접 페이지를 화면에 그립니다.
    • 초기 로딩은 느릴 수 있지만, 이후에는 앱처럼 부드럽고 빠른 화면 전환이 가능합니다.

패턴 & DispatcherServlet

프론트 컨트롤러 패턴

  • 모든 클라이언트 요청을 단일 진입점에서 처리하는 디자인 패턴입니다.
  • 요청에 대한 공통 처리를 중앙에서 효율적으로 관리할 수 있으며, 개별 요청을 처리할 핸들러로 작업을 위임하는 역할을 합니다.
  • 장점:
    • 공통 로직의 중앙화
    • 일관된 처리 흐름
    • 새로운 컨트롤러 추가 시에도 공통 로직 자동 적용
    • 유지보수성 향상

어댑터 패턴

  • 서로 다른 인터페이스를 가진 클래스들을 연결해주는 패턴입니다.
  • 장점:
    • 다양한 컨트롤러 타입을 통일된 방식으로 처리합니다.
    • 새로운 컨트롤러 타입 추가 시 새 어댑터만 만들면 됩니다.
    • 기존 코드 수정 없이 확장이 가능합니다.
// HandlerAdapter 인터페이스 (공통 인터페이스)
public interface HandlerAdapter {
    boolean supports(Object handler); // 처리 가능 여부 판단(boolean)
    
    ModelAndView handle( // 어떻게 처리할 것인지에 대한 로직
      HttpServletRequest request,
      HttpServletResponse response, 
      Object handler) throws Exception;
}

// Controller 구현 1 (HandlerAdapter를 implements)
public class RequestMappingHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return handler instanceof HandlerMethod;  // @RequestMapping 메서드인지 확인
    }
    
    @Override
    public ModelAndView handle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) {
        // @Controller 방식으로 처리
        return invokeHandlerMethod((HandlerMethod) handler, request, response);
    }
}

// Controller 구현 2 (HandlerAdapter를 implements)
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
    @Override
    public boolean supports(Object handler) {
        return handler instanceof Controller;
    }
    
    @Override
    public ModelAndView handle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) {
        // Controller 인터페이스 방식으로 처리
        return ((Controller) handler).handleRequest(request, response);
    }
}

DispatcherServlet

  • 프론트 컨트롤러 패턴을 구현한 스프링 MVC의 핵심적인 프론트 컨트롤러입니다.
@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String hello(Model model) {
        model.addAttribute("message", "Hello, Spring!");
        return "hello";  // view
    }
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello Page</title>
</head>
<body>
    <h1 th:text="${message}">(메시지가 보여지는 곳)</h1>
</body>
</html>
profile
반갑습니다!

0개의 댓글