01a 어노테이션·클래스·접근제어자 📚 전체 맵 01c Lambda·Stream
Series 01 · Java 기초 · 2/3

LOMBOK
OPTIONAL
GENERIC

반복 코드 자동화 · null 안전 처리 · 범용 타입 설계
프로젝트 코드 전체에서 매일 보이는 3가지

01a 어노테이션·클래스·접근제어자 01b Lombok·Optional·Generic 01c Lambda·Stream
01 Java 기초 02 Spring 기초 03 웹/통신 04 프로젝트 코드 05 심화 06 Security 07 JVM 08 DI
이 파일의 학습 목적
01b — Lombok · Optional · Generic을 왜 배우나?

프로젝트 코드를 열면 @Getter @Setter @RequiredArgsConstructor가 모든 클래스 위에 붙어 있다. .orElseThrow()는 Repository 호출마다 등장하고, Page<Board> 같은 꺾쇠 표현도 어디서나 나온다. 01b는 이 3가지를 한 번에 정리한다.

이 파일에서 배울 3가지
  • Lombok — @Getter·@Setter·@Builder·@RequiredArgsConstructor
  • Optional — null 안전 처리·orElseThrow()·ifPresent()
  • Generic <T> — 꺾쇠 의미·Page<Board>·List<BoardDto>
프로젝트 코드에서 등장 위치
  • · @RequiredArgsConstructor — 모든 Service·Controller
  • · .orElseThrow() — Repository 조회 전체
  • · Page<Board>, List<BoardDto> — BoardService
시리즈 순서: 01a 어노테이션·클래스 → 01b ← 지금 여기 → 01c Lambda·Stream → 02 Spring 기초
01 — Lombok
반복적으로 써야 하는 코드(getter·setter·생성자)를 어노테이션으로 자동 생성
⚙️ Lombok이란? Library
❌ Lombok 없이 (직접 다 작성)
public class User {
private Long id;
private String username;

public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String u) {
this.username = u;
}
// 필드 늘수록 코드도 계속 늘어남...
}
✅ Lombok 사용 (어노테이션 하나면 끝)
@Getter
@Setter
public class User {
private Long id;
private String username;
// getter/setter 자동 생성!
// 컴파일 시 Lombok이 자동으로
// getId(), setId() 등을 만들어줌
}
어노테이션자동 생성 내용
@Getter모든 필드의 getXxx() 메서드
@Setter모든 필드의 setXxx() 메서드
@NoArgsConstructor파라미터 없는 기본 생성자 User()
@AllArgsConstructor모든 필드를 받는 생성자 User(id, username, ...)
@RequiredArgsConstructorfinal 필드만 받는 생성자 (DI에 사용)
@ToStringtoString() 메서드 (출력용)
@Builder빌더 패턴 — User.builder().id(1).build()
@Data@Getter+@Setter+@ToString+@EqualsAndHashCode 한번에
Java — @RequiredArgsConstructor — DI의 핵심 ⭐
@RestController
@RequiredArgsConstructor // ← 이 하나가 핵심!
public class UserController {

private final UserService userService; // final 필드

// Lombok이 자동으로 아래 생성자를 만들어줌:
// public UserController(UserService userService) {
// this.userService = userService;
// }

// Spring이 이 생성자를 발견하고 UserService 빈을 찾아서 자동 주입
// → 이것이 DI (의존성 주입)
}
💡 @RequiredArgsConstructor + final = DI의 황금 조합
private final UserService userService 한 줄이면
Lombok이 생성자 만들고 → Spring이 UserService 빈 찾아서 주입.
1차~3차 프로젝트 모든 Controller·Service에서 이 패턴이 반복됨.
02 — Optional
값이 있을 수도 없을 수도 있는 상황을 안전하게 처리 — NullPointerException 방지
🎁 Optional이란? Null Safety
🎁 비유 — 선물 상자
Optional = 선물 상자
→ 상자 안에 선물(값)이 있을 수도, 없을 수도 있음
→ 열어보기 전까진 모름

Optional 없이 → 값이 없는데 바로 쓰면 NullPointerException 발생 → 프로그램 터짐
Optional 쓰면 → 값이 없을 때 대응 방법을 강제로 작성하게 됨 → 안전한 코드
❌ Optional 없이 (위험)
User user = userRepository
.findByUsername("hong");
// null일 수 있음!

user.getPassword();
// user가 null이면
// NullPointerException 터짐!
✅ Optional 사용 (안전)
Optional<User> optional =
userRepository.findByUsername("hong");

User user = optional
.orElseThrow(() ->
new IllegalArgumentException(
"없는 유저"));
Java — Optional 3가지 처리 방법
Optional<User> optional = userRepository.findByUsername("hong");

// 방법 1: orElseThrow — 없으면 예외 발생 (가장 많이 씀)
User user = optional.orElseThrow(
() -> new IllegalArgumentException("없는 유저입니다.")
);

// 방법 2: ifPresent — 있을 때만 실행
optional.ifPresent(u -> {
request.setAttribute("loginUser", u);
});

// 방법 3: orElse — 없으면 기본값 반환
User user = optional.orElse(null);
⚠️ orElse(null) 주의
orElse(null)은 결국 null을 반환할 수 있어서 Optional을 쓰는 의미가 없어짐.
가능하면 orElseThrow()로 명시적으로 예외를 던지는 게 좋음.
03 — Generic <T>
타입을 나중에 정하는 것 — 어떤 타입이든 담을 수 있는 범용 상자
📦 Generic이란? Type Safety
📦 비유 — 범용 택배 상자
Generic 없이 → 사과 상자, 책 상자, 옷 상자 → 종류마다 별도 상자 필요
Generic 있으면 → 어떤 물건이든 담을 수 있는 범용 상자 하나
→ 상자 열 때 "이건 사과 담는 상자야" 라고 미리 알려주는 것이 <T>
❌ Generic 없이 (타입마다 별도 클래스)
class UserList { User[] items; }
class BoardList { Board[] items; }
class DtoList { BoardDto[] items; }
// 타입 늘수록 클래스도 늘어남...
✅ Generic 사용 (하나로 해결)
class Page<T> { T[] items; }

Page<User> userPage;
Page<Board> boardPage;
Page<BoardDto> dtoPage;
// 하나의 클래스로 모든 타입 처리!
Java — 프로젝트에서 자주 보이는 Generic 표현들
// Optional<T> — User가 있을 수도 없을 수도
Optional<User> optional = userRepository.findByUsername(username);

// Page<T> — Board 목록 + 페이징 정보
Page<Board> boards = boardRepository.findAll(pageable);

// List<T> — BoardDto 목록
List<BoardDto> dtoList = boards.getContent().stream()...

// ResponseEntity<?> — 어떤 타입이든 반환 가능
ResponseEntity<?> response = ResponseEntity.ok(dtoList);

// Map<String, String> — 문자열 키-값 쌍
Map<String, String> result = Map.of("token", token, "role", role);
표현의미프로젝트 사용처
Optional<User>User가 있을 수도 없을 수도userRepository.findByUsername()
Page<Board>Board 목록 + 페이징 정보boardRepository.findAll(pageable)
List<BoardDto>BoardDto 목록stream().map().collect()
ResponseEntity<?>어떤 타입이든 반환Controller 반환 타입
Map<String, String>문자열 키-값 쌍JWT 토큰+role 반환
💡 <?> 와 <T> 차이
<T> → 클래스/메서드 선언할 때 씀. "나중에 타입 정할게"
<?> → 사용할 때 씀. "어떤 타입이든 상관없어" (와일드카드)
프로젝트에서는 주로 <?>가 ResponseEntity에서 보임
정리 — 핵심 연결
Lombok · Optional · Generic — 프로젝트에서 이렇게 함께 쓰임
Java — 실제 프로젝트 코드에서 3가지 모두 등장
@Service
@RequiredArgsConstructor // ← Lombok: 생성자 자동 생성
public class UserService {

private final UserRepository userRepository; // DI

public ResponseEntity<?> login(LoginDto dto) { // ← Generic: 어떤 타입이든

User user = userRepository
.findByUsername(dto.getUsername()) // Optional<User> 반환
.orElseThrow(() -> // ← Optional: null 안전 처리
new IllegalArgumentException("없는 유저"));

// ... 로직 ...

Map<String, String> result = // ← Generic: String-String 맵
Map.of("token", token, "role", role);

return ResponseEntity.ok(result);
}
}
⚙️
Lombok
@RequiredArgsConstructor
→ 생성자 자동생성
→ Spring DI 연결
🎁
Optional
findByUsername() 반환값
→ orElseThrow()로 처리
→ null 안전
📦
Generic
List<BoardDto>
ResponseEntity<?>
Map<String,String>
01a 어노테이션·클래스·접근제어자 📚 전체 맵 01c Lambda·Stream