Day 30 — 모던 자바의 마지막 조각들: 텍스트 블록·가드 패턴·이름 없는 변수
목차 20
지난 시간, 우리는 record(레코드) 로 값을 담는 객체를 한 줄로 줄였고, sealed(봉인) 로 종류를 닫았어요. 그리고 마지막에 switch 패턴 매칭으로 알림 종류를 갈래별로 처리하는 걸 처음 맛봤죠. 종류에 따라 갈래를 척척 나누는 그 강력함을 봤어요.
오늘은 거기에 살을 더 붙여요. "좋아요인데 그중에서도 인기 글에 달린 좋아요만" 처럼 한 번 더 조건을 거는 가드 패턴(when) 을 배우고, 긴 글을 따옴표 걱정 없이 적는 텍스트 블록("""), 안 쓰는 값을 밑줄 한 글자로 비우는 이름 없는 변수(_), 그리고 순서가 있는 컬렉션을 또렷하게 다루는 새 약속까지 익혀요. 그동안 배운 모던 자바 위에 얹는, 편의를 더해주는 마지막 조각들이에요.
재밌는 사실 하나. Day 1 부터 Day 5 까지 우리가 썼던 그 간단한 void main(), 기억하시죠? 그게 사실 자바의 최신 버전에서 정식 문법으로 채택된 기능이었어요. 오늘 그 비밀도 풀어볼게요. 시작해봐요!
🎯 학습 목표
- 텍스트 블록(
""") 으로 여러 줄 문자열을 보이는 모양 그대로 적을 수 있어요. - 텍스트 블록이 SQL·JSON·HTML 같은 실무 문자열에서 왜 빛나는지, 들여쓰기가 어떻게 자동 정리되는지 알아요.
switch패턴 매칭에 가드(when) 를 붙여 "타입 + 조건" 으로 더 잘게 갈래를 나눌 수 있어요.- 이름 없는 변수(
_) 로 "받지만 안 쓰는 값" 을 분명하게 비울 수 있어요. - 순서가 있는 컬렉션의 공통 약속(
getFirst·getLast·reversed) 으로 첫·끝·뒤집기를 또렷하게 다뤄요. - Day 1~5 의
void main()이 자바 최신 버전의 정식 기능이 됐다는 걸 이해해요. - 오늘 배운 조각들을 한 흐름에 모아 "알림 카드 화면 데이터" 를 만들어봐요.
오늘의 로드맵
- Step 1 — 텍스트 블록 등장: 여러 줄 문자열을 보이는 그대로.
- Step 2 — 텍스트 블록 실전: SQL·JSON·HTML 을 깔끔하게.
- Step 3 —
switch패턴 매칭 심화: 가드 패턴when. - Step 4 — 이름 없는 변수
_: 안 쓰는 값 비우기. - Step 5 — 순서 있는 컬렉션의 공통 약속.
- Step 6 — 간소 소스 파일:
void main()하나로. - Step 7 — 종합: 알림 카드 화면 데이터 만들기.
- Step 8 — 모던 자바 6일의 회고와 다음 여정.
Step 1: 텍스트 블록 등장 — 여러 줄 문자열을 보이는 그대로
여러 줄짜리 문자열을 만들어야 할 때가 많아요. 프로필 소개글, 게시물 캡션, 안내 메시지처럼요. 그런데 지금까지 우리가 알던 방법으로 여러 줄을 적으려면 꽤 번거로웠어요. 줄바꿈은 \n 으로 직접 적고, 줄마다 따옴표로 감싸고, + 로 이어 붙여야 했거든요.
먼저 옛날 방식으로 프로필 소개글을 적어볼게요.
// com/instagram/javabasic/modern/TextBlockBasics.java
public static void main(String[] args) {
// 옛날 방식 — 줄마다 따옴표 + 더하기 + \n 이 섞여서 읽기 어려워요.
String oldBio = "안녕하세요, 재훈입니다.\n" +
"사진 찍는 걸 좋아해요.\n" +
"서울에서 활동 중이에요.";
System.out.println(oldBio);
}
따옴표, \n, + 가 한데 섞여서 정작 중요한 "내용" 이 눈에 잘 안 들어와요. 글이 길어질수록 더 심해지고, \n 하나를 깜빡하면 두 줄이 한 줄로 붙어버리는 실수도 잦아요.
여기서 텍스트 블록(text block) 이 등장해요. 큰따옴표 세 개(""") 로 열고 닫으면, 그 안에 보이는 모양 그대로가 문자열이 돼요. 같은 소개글을 텍스트 블록으로 다시 적어볼게요.
// 텍스트 블록 — """ 안에 보이는 그대로가 문자열이에요. \n 도, + 도 필요 없어요.
String newBio = """
안녕하세요, 재훈입니다.
사진 찍는 걸 좋아해요.
서울에서 활동 중이에요.""";
System.out.println(newBio);
// 두 방식이 만든 문자열이 글자 하나까지 똑같은지 확인해요.
System.out.println("두 문자열이 똑같나요? " + oldBio.equals(newBio));
실행하면 두 문자열이 똑같나요? true 가 나와요. 옛날 방식과 텍스트 블록이 만든 문자열이 글자 하나까지 완전히 같다는 뜻이에요. 결과는 같은데, 적는 사람 입장에서 텍스트 블록 쪽이 훨씬 읽기 편하죠.
옛날 방식 텍스트 블록
────────────────────────── ──────────────────────────
"안녕하세요, 재훈입니다.\n" + """
"사진 찍는 걸 좋아해요.\n" + 안녕하세요, 재훈입니다.
"서울에서 활동 중이에요."; 사진 찍는 걸 좋아해요.
서울에서 활동 중이에요."""
↑ 따옴표·\n·+ 가 내용을 가림 ↑ 보이는 모양 = 문자열
게시물 캡션처럼 줄바꿈이 많은 글도 텍스트 블록으로 담으면 한눈에 들어와요.
String caption = """
오늘의 노을 🌅
#노을 #하늘 #일상
위치: 한강공원""";
🙋 학생 질문 — "튜터님, 텍스트 블록 안에서 따옴표(")를 그냥 써도 되나요?"
네, 좋은 질문이에요. 텍스트 블록 안에서는 큰따옴표 하나(") 를 이스케이프(앞에 \ 붙이기) 없이 그냥 적어도 돼요. 예를 들어 "username": "jaehoon" 같은 글을 그대로 넣을 수 있죠.
옛날 방식이었다면 따옴표마다 \" 로 바꿔야 해서 \"username\": \"jaehoon\" 처럼 지저분해졌을 거예요. 그래서 따옴표가 많이 들어가는 JSON 이나 HTML 을 적을 때 텍스트 블록이 특히 빛나요. 바로 다음 단계에서 그 모습을 직접 볼 거예요.
💡 텍스트 블록의 핵심은 한 문장이에요. "화면에 보이는 모양이 곧 문자열" 이에요.
\n도+도 따옴표 이스케이프도 신경 쓸 필요 없이, 적고 싶은 글을"""사이에 그대로 적으면 돼요.
Step 2: 텍스트 블록 실전 — SQL·JSON·HTML 을 깔끔하게
텍스트 블록이 실무에서 가장 빛나는 곳이 세 군데예요. SQL 쿼리, JSON 데이터, HTML 마크업. 셋 다 "여러 줄에 들여쓰기가 있는 긴 글" 이라는 공통점이 있어요. 이런 글을 옛날 방식으로 적으면 따옴표와 \n 의 바다에 빠지지만, 텍스트 블록으로 적으면 원본 모양 그대로 읽혀요.
먼저 SQL 쿼리예요. SQL 은 데이터베이스에서 데이터를 골라오는 명령어인데, 지금은 깊이 몰라도 괜찮아요. 여기서는 그냥 "여러 줄짜리 문자열" 의 예시로만 봐주세요. 실제 데이터베이스에 연결하는 게 아니라, 글자를 담은 문자열을 만드는 것뿐이에요.
// com/instagram/javabasic/modern/TextBlockPractice.java
public static void main(String[] args) {
// 1) SQL 쿼리 — 코드 안에 들여써도, 가장 왼쪽 글자를 기준으로 공통 들여쓰기는 잘려나가요.
String sql = """
SELECT username, followers
FROM member
WHERE followers >= 1000
ORDER BY followers DESC""";
System.out.println(sql);
}
여기서 신기한 점 하나를 짚어요. 코드에서는 SELECT 앞에 공백을 한참 들여썼는데, 막상 출력해보면 SELECT 가 맨 앞(0칸) 부터 시작해요. 텍스트 블록이 "모든 줄이 공통으로 가진 왼쪽 들여쓰기" 를 알아서 잘라주거든요. 이걸 자동 들여쓰기 정리라고 생각하면 돼요.
코드에 적힌 모습 실제 문자열 (공통 들여쓰기 잘림)
────────────────────────── ──────────────────────────
␣␣␣␣␣␣␣␣␣␣␣␣SELECT username SELECT username
␣␣␣␣␣␣␣␣␣␣␣␣FROM member FROM member
␣␣␣␣␣␣␣␣␣␣␣␣WHERE followers... WHERE followers...
↑ 12칸 공통 들여쓰기 ↑ 공통 12칸이 통째로 잘려나감
정말 잘리는지 코드로 확인해볼 수도 있어요. 첫 줄의 맨 앞 공백 수를 세보면 0칸 이 나와요.
// 들여쓰기 자동 정렬 확인 — 코드에선 들여썼지만, 실제 문자열은 공통 들여쓰기가 잘려요.
String firstLine = sql.lines().findFirst().orElse("");
System.out.println("SQL 첫 줄 맨 앞 공백 수: "
+ (firstLine.length() - firstLine.stripLeading().length()) + "칸");
다음은 JSON 이에요. JSON 은 데이터를 주고받을 때 널리 쓰는 형식인데, 중괄호와 따옴표가 잔뜩 들어가요. 텍스트 블록으로 적으면 따옴표를 이스케이프할 필요 없이 모양 그대로 담겨요.
String json = """
{
"username": "jaehoon",
"followers": 1240,
"posts": 42
}""";
마지막은 HTML 이에요. 화면에 그릴 카드 한 조각도 모양 그대로 담을 수 있어요.
String html = """
<div class="profile">
<h2>@jaehoon</h2>
<p>팔로워 1240명</p>
</div>""";
세 경우 모두, 만약 옛날 방식으로 적었다면 따옴표마다 \" 를 붙이고 줄마다 \n 을 넣느라 원래 모양을 알아보기 힘들었을 거예요. 텍스트 블록 덕분에 SQL 은 SQL 답게, JSON 은 JSON 답게, HTML 은 HTML 답게 읽혀요.
⚠️ 한 가지만 기억해요. 여기서 SQL 문자열을 만들었다고 해서 데이터베이스에 연결되는 건 전혀 아니에요. 텍스트 블록은 그저 "여러 줄 글자를 담는 도구" 일 뿐이에요. 데이터베이스 연결은 한참 뒤 과목에서 배워요.
Step 3: switch 패턴 매칭 심화 — 가드 패턴 when
지난 시간에 switch 패턴 매칭을 배웠어요. 알림이 좋아요인지, 댓글인지, 팔로우인지 그 타입에 따라 갈래를 나눴죠. 그런데 실무에서는 "같은 좋아요라도 경우를 더 나누고 싶을 때" 가 있어요. 예를 들어 인기 글에 달린 좋아요는 특별히 강조하고 싶은 거죠.
먼저 지난 시간처럼, 타입만 보고 갈래를 나누는 버전이에요. 우리가 지난 시간에 만든 sealed 알림 세 종류(LikeNotification·CommentNotification·FollowNotification) 를 그대로 다시 써요.
// com/instagram/javabasic/modern/GuardPatternDemo.java
// 가드 없는 버전 — 타입만 보고 한 가지 문장으로 안내해요.
public static String basic(Notification n) {
return switch (n) {
case LikeNotification l -> l.actor() + "님이 좋아요를 눌렀어요.";
case CommentNotification c -> c.actor() + "님이 댓글을 남겼어요.";
case FollowNotification f -> f.actor() + "님이 팔로우했어요.";
};
}
좋아요는 무조건 "OO님이 좋아요를 눌렀어요" 한 문장으로만 나와요. 인기 글이든 아니든 구분이 없죠. 여기에 조건을 한 겹 더 얹는 게 가드 패턴이에요. case 뒤에 when 을 붙이고 조건을 적으면, "이 타입이면서 + 이 조건도 참일 때만" 그 갈래로 가요.
// 가드 있는 버전 — when 뒤 조건이 참일 때만 그 case 로 가요.
public static String detailed(Notification n) {
return switch (n) {
// 인기 글("노을 사진")에 달린 좋아요는 따로 강조해요.
case LikeNotification l when l.postTitle().equals("노을 사진") ->
"🔥 인기 게시물! " + l.actor() + "님도 '" + l.postTitle() + "' 을 좋아해요.";
case LikeNotification l ->
l.actor() + "님이 '" + l.postTitle() + "' 을 좋아해요.";
// 댓글 내용이 길면(20자 이상) "정성 댓글" 로 따로 안내해요.
case CommentNotification c when c.text().length() >= 20 ->
"💬 정성 댓글! " + c.actor() + "님: " + c.text();
case CommentNotification c ->
c.actor() + "님 댓글: " + c.text();
case FollowNotification f ->
f.actor() + "님이 팔로우했어요.";
};
}
같은 LikeNotification 인데 갈래가 둘로 나뉘었어요. 글 제목이 "노을 사진" 이면 위쪽 갈래(인기 게시물 강조) 로, 아니면 아래쪽 갈래(평범한 안내) 로 가요. 댓글도 마찬가지로 내용이 20자 이상이면 "정성 댓글" 로 따로 안내해요.
여기서 순서가 정말 중요해요. switch 는 위에서 아래로 차례차례 검사하다가, 처음으로 맞는 갈래를 만나면 거기서 멈춰요. 그래서 조건이 까다로운(구체적인) 갈래를 위에, 조건 없는(일반적인) 갈래를 아래에 둬야 해요.
switch 검사 순서 (위 → 아래, 처음 맞는 곳에서 멈춤)
────────────────────────────────────────────────
① case LikeNotification l when 제목 == "노을 사진" → 맞으면 여기서 멈춤 🔥
② case LikeNotification l → ①이 안 맞을 때만 여기로
③ case CommentNotification c when 길이 >= 20 → 정성 댓글 💬
④ case CommentNotification c → 그 외 댓글
⑤ case FollowNotification f → 팔로우
🙋 학생 질문 — "튜터님, 가드가 붙은 case 를 일반 case 보다 아래에 두면 어떻게 되나요?"
좋은 질문이에요. 만약 case LikeNotification l (조건 없는 일반 갈래) 을 위에 두고, case LikeNotification l when ... (가드 붙은 갈래) 을 아래에 둔다면, 좋아요 알림은 무조건 위쪽 일반 갈래에서 먼저 잡혀버려요. 아래의 가드 갈래에는 영영 도달하지 못하죠.
자바도 이걸 알아채고, "아래 갈래는 절대 실행될 수 없다" 며 컴파일 단계에서 오류로 막아줘요. 그래서 규칙은 간단해요. 구체적인(조건이 까다로운) 갈래를 위로, 일반적인 갈래를 아래로. 좁은 그물을 먼저 던지고, 넓은 그물을 나중에 던지는 셈이에요.
💡 가드(
when) 는 한마디로 "타입 검사를 통과한 뒤 한 번 더 거르는 조건문" 이에요.case 타입 when 조건은 "이 타입이면서 + 이 조건도 참일 때" 만 골라줘요. 덕분에 같은 타입 안에서도 경우를 더 잘게 나눌 수 있어요.
Step 4: 이름 없는 변수 _ — 안 쓰는 값 비우기
지난 시간 switch 패턴 매칭에서 record 분해를 배웠죠. case LikeNotification(String actor, String title) 처럼 괄호로 record 안의 값을 한꺼번에 꺼냈어요. 그런데 가끔은 꺼낸 값 중 일부만 쓰고, 나머지는 안 쓸 때가 있어요.
예를 들어 알림에서 "누가 보냈는지(actor)" 만 알면 되고 "어떤 글인지(title)" 는 필요 없다고 해봐요. 안 쓰는 title 에도 굳이 이름을 지어주면, 코드를 읽는 사람이 "이 title 은 어디서 쓰는 거지?" 하고 괜히 찾아 헤매게 돼요.
이럴 때 쓰는 게 이름 없는 변수, 밑줄 한 글자 _ 예요. "이 값은 받긴 받지만 안 쓰고 버린다" 는 뜻을 한눈에 보여줘요.
// com/instagram/javabasic/modern/UnnamedVariableDemo.java
// 좋아요는 누가(actor) 눌렀는지만 쓰고, 어떤 글인지(postTitle)는 _ 로 버려요.
public static String actorOf(Notification n) {
return switch (n) {
case LikeNotification(String actor, _) -> actor;
case CommentNotification(String actor, _, _) -> actor;
case FollowNotification(String actor) -> actor;
};
}
LikeNotification(String actor, _) 를 보면, 첫 번째 값(actor) 만 변수로 받고 두 번째 값은 _ 로 버렸어요. 댓글은 값이 셋인데 그중 actor 만 쓰고 나머지 둘은 _ 로 버렸죠. 코드만 봐도 "아, 여기선 보낸 사람만 쓰는구나" 가 바로 읽혀요.
CommentNotification(String actor, _, _)
│ │ │
│ │ └─ text → 안 씀, 버림
│ └───── title → 안 씀, 버림
└───────────── actor → 이것만 사용
_ 는 switch 분해 말고도 여러 곳에서 같은 뜻으로 쓰여요. 반복문에서 "값은 안 보고 횟수만 셀 때", 그리고 예외를 "잡기만 하고 내용은 안 볼 때" 도 안 쓰는 변수를 _ 로 비울 수 있어요.
// for-each 에서 값은 안 보고 횟수만 셀 때 — 꺼낸 원소를 _ 로 버려요.
int count = 0;
for (String _ : List.of("좋아요", "댓글", "팔로우")) {
count++;
}
// catch 에서 예외 객체를 안 쓸 때 — 잡기만 하고 객체는 _ 로 버려요.
try {
int followers = Integer.parseInt(raw);
System.out.println("팔로워: " + followers);
} catch (NumberFormatException _) {
System.out.println("숫자가 아니라 변환에 실패했어요. 기본값 0 으로 둘게요.");
}
반복문에서는 꺼낸 문자열을 안 쓰고 그저 횟수만 세니까 _ 로 버렸어요. catch 에서는 예외가 났다는 사실만 알면 되고 예외 객체의 자세한 내용은 안 보니까 역시 _ 로 비웠죠. Day 21 에서 예외를 배울 때 catch (NumberFormatException e) 처럼 e 라고 적었던 걸 기억하시죠? 그 e 를 안 쓴다면 _ 로 비우는 게 더 솔직한 표현이에요.
🙋 학생 질문 — "튜터님, 그냥 변수 이름을 unused 같은 걸로 지으면 안 되나요?"
그렇게 해도 동작은 해요. 하지만 두 가지 차이가 있어요.
첫째, _ 는 "안 쓴다" 는 의도를 자바 자신도 알아요. 그래서 한 코드 안에서 _ 를 여러 번 써도(예: (_, _, _)) 이름이 겹친다고 혼나지 않아요. unused 같은 진짜 이름은 같은 자리에서 두 번 쓰면 "이름이 중복됐다" 며 오류가 나요.
둘째, 읽는 사람 입장에서 _ 는 "이건 안 쓰는 값이구나" 가 즉시 보여요. unused 라고 적혀 있으면 "혹시 어딘가 쓰나?" 하고 한 번 더 확인하게 되죠. 의도를 분명하게 드러내는 게 _ 의 진짜 장점이에요.
💡 이름 없는 변수
_는 "받지만 버린다" 를 한 글자로 선언하는 것 이에요. 안 쓰는 값에 가짜 이름을 붙여 읽는 사람을 헷갈리게 하는 대신,_로 "여긴 안 쓴다" 를 분명히 밝혀요.
Step 5: 순서 있는 컬렉션의 공통 약속
Day 19 에서 List·LinkedList 같은 컬렉션을 배웠어요. 그때 목록의 첫 번째 원소는 list.get(0), 마지막 원소는 list.get(list.size() - 1) 로 꺼냈죠. 마지막을 꺼낼 때 size() - 1 을 적는 게 살짝 번거롭고, 깜빡 잘못 적으면 엉뚱한 곳을 짚는 실수도 났어요.
자바 최신 버전에서는 "순서가 있는 컬렉션" 들에게 공통 약속을 하나 정해줬어요. 첫 번째는 getFirst(), 마지막은 getLast(), 앞에 추가는 addFirst(), 뒤에 추가는 addLast(), 뒤집기는 reversed(). 이 약속의 이름이 순서 있는 컬렉션(Sequenced Collection) 이에요.
"최근 본 스토리" 목록으로 살펴볼게요. 먼저 본 사람이 앞에, 방금 본 사람이 뒤에 있다고 해봐요.
// com/instagram/javabasic/modern/SequencedCollectionDemo.java
// 최근 본 스토리 순서 — 먼저 본 사람이 앞, 방금 본 사람이 뒤에 있어요.
List<String> recentStories = new ArrayList<>(List.of("jaehoon", "minji", "dana"));
// 옛날 방식 vs 새 방식 — 결과는 같지만 새 방식이 훨씬 또렷해요.
System.out.println("옛날 첫 번째: " + recentStories.get(0)
+ " / 옛날 마지막: " + recentStories.get(recentStories.size() - 1));
System.out.println("새 첫 번째: " + recentStories.getFirst()
+ " / 새 마지막: " + recentStories.getLast());
두 줄의 결과는 똑같아요. 하지만 getFirst()·getLast() 쪽이 "첫 번째를 달라", "마지막을 달라" 는 뜻을 단어 그대로 보여줘서 훨씬 또렷하죠. size() - 1 같은 계산이 사라졌어요.
앞·뒤에 새 원소를 끼워 넣는 것도, 순서를 뒤집는 것도 한 단어로 끝나요.
// addFirst / addLast — 앞이나 뒤에 바로 끼워 넣어요.
recentStories.addFirst("suho"); // 가장 앞(가장 오래전 본 사람)으로
recentStories.addLast("yuna"); // 가장 뒤(방금 본 사람)로
// reversed() — 순서를 뒤집은 모습을 돌려줘요(원본은 그대로).
System.out.println("뒤집은 순서: " + recentStories.reversed());
System.out.println("원본은 그대로: " + recentStories);
reversed() 는 뒤집은 결과를 새로 돌려줄 뿐, 원본은 그대로 둬요. "최근 본 스토리" 를 최신순으로 보여주고 싶을 때 원본을 망가뜨리지 않고 뒤집을 수 있어서 안전해요.
원본: [suho, jaehoon, minji, dana, yuna]
│ │
getFirst() getLast()
↓ ↓
suho yuna
reversed(): [yuna, dana, minji, jaehoon, suho] ← 뒤집은 새 결과
원본: [suho, jaehoon, minji, dana, yuna] ← 그대로 보존
가장 좋은 점은, 이 약속을 List 뿐 아니라 LinkedList·Deque(양쪽으로 넣고 빼는 큐) 도 똑같이 따른다는 거예요. 타입이 달라도 메서드 이름이 같으니, 한 번 익히면 어디서나 똑같이 쓸 수 있어요.
// 같은 약속을 LinkedList 도 그대로 따라요 — 타입이 달라도 메서드 이름이 같아요.
LinkedList<String> timeline = new LinkedList<>(List.of("아침 게시물", "점심 게시물", "저녁 게시물"));
System.out.println("타임라인 첫 글: " + timeline.getFirst()
+ " / 마지막 글: " + timeline.getLast());
🙋 학생 질문 — "튜터님, 그럼 HashSet 도 getFirst() 가 되나요?"
아니요, 그게 핵심이에요. getFirst()·getLast() 는 "순서가 있는" 컬렉션만의 약속이에요. List·LinkedList·Deque 처럼 원소가 줄을 서 있는 컬렉션이라야 "첫 번째", "마지막" 이라는 말이 의미가 있죠.
반면 Day 19 에서 배운 HashSet 은 원소들이 정해진 순서 없이 흩어져 담겨요. 순서가 없으니 "첫 번째" 가 무엇인지 정할 수 없고, 그래서 getFirst() 같은 약속에 끼지 않아요. "순서가 있느냐" 가 이 약속에 들어올 자격을 가르는 기준이에요.
💡 순서 있는 컬렉션의 약속은 이렇게 기억하면 편해요. "줄을 서 있는 컬렉션이면, 첫·끝·뒤집기를 똑같은 이름으로" 다룰 수 있어요.
getFirst·getLast·reversed가List·LinkedList·Deque어디서나 통해요.
Step 6: 간소 소스 파일 — void main() 하나로
이번 단계에서는 오늘의 작은 비밀을 풀어요. Day 1 부터 Day 5 까지, 우리는 프로그램을 이렇게 시작했어요. 클래스도 안 쓰고, 그냥 void main() 하나만 적었죠. 그러다 Day 6 부터 public static void main(String[] args) 라는 정식 형태로 바꿨고요.
그때 그 간단한 void main() 이 사실은 자바 최신 버전의 정식 기능이었어요. 이름은 간소 소스 파일(Compact Source File). 패키지 선언도, 클래스 선언도 없이 void main() 메서드 하나만 적으면 그대로 실행되는 형태예요.
// day30/CompactSourceDemo.java
void main() {
System.out.println("=== Compact Source File 실행 ===");
String username = "jaehoon";
int followers = 1240;
System.out.println("@" + username + " 님, 환영해요!");
System.out.println("등급: " + grade(followers));
}
처음 자바를 배울 때 public static void main(String[] args) 라는 긴 주문을 통째로 외워야 했던 부담, 기억하시죠? 그게 부담스러웠던 건 사실 자바 설계자들도 알고 있었어요. 그래서 초보자가 첫 줄부터 막히지 않도록, 클래스로 감싸지 않고도 void main() 하나로 시작할 수 있게 정식 문법으로 만든 거예요.
게다가 void main() 옆에 평범한 도우미 메서드도 나란히 둘 수 있어요. 클래스로 감싸지 않아도요.
// void main() 옆에 평범한 메서드를 나란히 둘 수 있어요 — 클래스로 감싸지 않아도 돼요.
String grade(int followers) {
if (followers >= 1000) {
return "인플루언서";
} else if (followers >= 100) {
return "성장 중";
} else {
return "새내기";
}
}
실행하면 환영 인사와 함께 등급: 인플루언서 가 출력돼요. 클래스 없이, 짧은 코드만으로 바로 결과를 보는 거죠.
정식 형태 간소 소스 파일
──────────────────────────── ────────────────────────────
public class Demo { void main() {
public static void main(String[]... ...
... }
} String grade(int f) { ... }
}
↑ 클래스로 감싸는 의식이 필요 ↑ 클래스 없이 바로 시작
한 가지 약속이 있어요. 이 파일은 패키지가 없는 형태라, 패키지로 묶인 modern 폴더 안에는 둘 수 없어요. 그래서 패키지에 묶이지 않는 day30 폴더에 따로 뒀어요. Day 1~5 에서 쓰던 그 day01 폴더와 같은 방식이에요.
🙋 학생 질문 — "튜터님, 그럼 앞으로 큰 프로그램도 다 이렇게 짧게 짜면 되나요?"
간소 소스 파일은 "작은 것" 에 어울려요. 간단한 연습 코드, 아이디어를 빠르게 시험해보는 스크립트, 알고리즘 한 조각을 테스트하는 경우처럼요. 클래스로 감싸는 준비 과정 없이 바로 코드로 들어갈 수 있으니까요.
하지만 여러 사람이 함께 만드는 큰 프로그램은 여전히 클래스와 패키지로 잘 정리하는 게 좋아요. 우리가 Day 8 부터 도메인 객체들을 domain·service·repository 폴더로 나눠 담았던 것처럼요. 규모가 커지면 "어디에 무엇이 있는지" 정리하는 구조가 큰 힘이 되거든요. 간소 소스 파일은 그 구조가 아직 필요 없는 작은 일에 알맞은 도구라고 기억하면 돼요.
💡 오늘의 작은 반전이에요. Day 1~5 에서 쓰던
void main()이 사실 자바 최신 버전의 정식 기능 이었어요. 작은 연습이나 스크립트엔 이 짧은 형태가, 큰 프로그램엔 클래스·패키지 구조가 어울려요.
Step 7: 종합 — 알림 카드 화면 데이터 만들기
이제 오늘 배운 조각들을 한 흐름에 모아볼게요. 만들 건 "알림 카드 화면 데이터" 예요. 알림 목록을 받아서, 중요한 알림만 골라내고, 종류·조건에 따라 다른 메시지로 바꾼 다음, 텍스트 블록으로 예쁜 카드 한 장을 그려내요.
한 알림을 한 줄 메시지로 바꾸는 일부터 봐요. 여기에 오늘 배운 세 가지가 한꺼번에 일해요. switch 패턴 매칭, 가드(when), 그리고 이름 없는 변수(_) 예요.
// com/instagram/javabasic/modern/ModernNotificationCard.java
// 알림 하나를 한 줄 메시지로 — switch + when 가드 + 이름 없는 변수 _ 가 함께 일해요.
public static String toLine(Notification n) {
return switch (n) {
// 인기 글에 달린 좋아요는 강조. 글 제목만 쓰고 actor 는 _ 로 버려요.
case LikeNotification(_, String title) when title.equals("노을 사진") ->
"🔥 인기 글 '" + title + "' 에 좋아요가 쌓이고 있어요";
// 그 외 좋아요는 누가 눌렀는지(actor)만 쓰고 제목은 _ 로 버려요.
case LikeNotification(String actor, _) ->
"❤️ " + actor + "님이 좋아요를 눌렀어요";
case CommentNotification(String actor, _, String text) ->
"💬 " + actor + "님: " + text;
case FollowNotification(String actor) ->
"➕ " + actor + "님이 팔로우했어요";
};
}
한 메서드 안에 오늘의 세 조각이 자연스럽게 어우러졌죠. 인기 글 좋아요는 가드(when) 로 갈래를 따로 빼고, 그 갈래에서는 제목만 쓰니 actor 는 _ 로 버렸어요. 반대로 일반 좋아요는 누가 눌렀는지만 쓰니 제목은 _ 로 버렸고요.
다음은 알림 목록에서 중요한 것만 골라 메시지로 바꾸는 단계예요. Day 26~27 에서 배운 Stream(흐름) 이 여기서 다시 등장해요. 팔로우 알림은 빼고(filter), 남은 알림을 한 줄 메시지로 바꿔(map) 모아요.
// 알림 목록 → 좋아요·댓글만 남긴 뒤(filter) → 한 줄 메시지로 바꿔(map) 모아요.
public static List<String> importantLines(List<Notification> inbox) {
return inbox.stream()
.filter(n -> !(n instanceof FollowNotification))
.map(ModernNotificationCard::toLine)
.toList();
}
마지막은 화면용 카드를 그리는 단계예요. 여기서 텍스트 블록이 또 한 번 빛나요. 카드 테두리(┌─┐) 와 내용을 텍스트 블록으로 모양 그대로 그리고, 빈칸(%s·%d) 에 실제 값을 끼워 넣어요. 카드 머리글에 들어갈 프로필은 지난 시간에 만든 ProfileCard record 를 그대로 써요.
// 프로필 + 알림 줄들을 텍스트 블록 한 장으로 그려요.
public static String render(ProfileCard profile, List<String> lines) {
String body = String.join("\n", lines);
return """
┌─────────────────────────────┐
@%s (%s · 팔로워 %d명)
───────────────────────────────
%s
└─────────────────────────────┘""".formatted(
profile.username(), profile.grade(), profile.followers(), body);
}
이제 알림함을 하나 만들어 흘려보내면, 오늘 배운 조각들이 차례로 일해서 이런 카드 한 장이 완성돼요.
┌─────────────────────────────┐
@jaehoon (인플루언서 · 팔로워 1240명)
───────────────────────────────
🔥 인기 글 '노을 사진' 에 좋아요가 쌓이고 있어요
❤️ suho님이 좋아요를 눌렀어요
💬 minji님: 색감 너무 예뻐요
└─────────────────────────────┘
전체 흐름을 한눈에 보면 이래요. 텍스트 블록·가드·이름 없는 변수·Stream·record 가 각자 제 역할을 맡아서, 따로따로 배운 조각들이 하나의 결과물로 모이는 거예요.
알림 목록
│ filter — 팔로우 빼기 (Stream, Day 26)
↓
좋아요·댓글만 남음
│ map — toLine() 으로 한 줄씩 (switch + when + _)
↓
한 줄 메시지 목록
│ render — 텍스트 블록 카드에 끼워 넣기 (""")
↓
완성된 알림 카드 한 장
💡 오늘 배운 조각들은 따로 노는 문법이 아니에요. 텍스트 블록은 화면을 그리고, 가드(
when) 는 갈래를 잘게 나누고, 이름 없는 변수(_) 는 안 쓰는 값을 비우고,Stream은 목록을 다뤄요. 진짜 코드에서는 이렇게 여러 조각이 한 흐름에서 함께 일해요.
Step 8: 모던 자바 6일의 회고와 다음 여정
오늘로 모던 자바를 다루는 여정(Day 25~30) 이 마무리돼요. 잠깐 멈춰서 지난 6일을 돌아보면, 우리가 꽤 먼 길을 왔다는 게 보일 거예요.
Day 25 람다 — 동작을 값처럼 넘기기 (-> 화살표)
↓
Day 26 Stream (1) — 목록을 흐름으로: filter·map
↓
Day 27 Stream (2) — 흐름을 모으기: collect·reduce
↓
Day 28 Optional — "값이 없을 수도 있음" 을 안전하게
↓
Day 29 record / sealed — 값 객체를 짧게, 종류를 닫기
↓
Day 30 텍스트 블록·가드·_·순서 컬렉션 — 마지막 편의 조각들
처음엔 for 반복문으로 목록을 하나하나 돌며 직접 거르고 모았어요. 지금은 Stream 으로 "걸러서(filter), 바꿔서(map), 모은다(collect)" 를 한 흐름에 적어요.
값이 없을지 모르는 상황은 Optional 로 감싸 안전하게 다루고, 값을 담는 객체는 record 한 줄로 줄였죠. 종류가 정해진 것들은 sealed 로 닫고 switch 패턴 매칭으로 빠짐없이 처리하고요. 오늘은 거기에 텍스트 블록·가드·이름 없는 변수·순서 컬렉션이라는 편의 조각까지 더했어요.
이 도구들의 공통점이 하나 있어요. 모두 "같은 일을 더 짧고, 더 안전하고, 더 읽기 좋게" 만들어준다는 거예요. 기능 자체가 완전히 새로운 게 아니라, 우리가 이미 할 줄 알던 일을 더 좋은 방식으로 표현하게 해줘요. 그래서 모던 자바를 배우면, 같은 결과를 내면서도 코드가 훨씬 깔끔해져요.
자, 그런데 한 가지 의문이 남아요. 지금까지 우리는 코드를 작성하면 IDE 의 "Run" 버튼 하나로 실행했어요. IDE 가 뒤에서 알아서 컴파일하고 실행해줬죠. 그런데 IDE 없이, 여러 사람이 함께 만드는 진짜 프로젝트는 어떻게 빌드하고 실행할까요? 이 질문이 다음 여정의 출발점이에요.
💡 모던 자바 6일을 한 문장으로 정리하면 이래요. "이미 할 줄 알던 일을, 더 짧고 안전하고 읽기 좋게." 람다·Stream·Optional·record·sealed, 그리고 오늘의 조각들까지 — 전부 같은 목표를 향한 도구들이에요.
마무리
오늘은 모던 자바의 마지막 편의 조각들을 한자리에 모았어요.
- 텍스트 블록(
""") — 화면에 보이는 모양이 곧 문자열.\n도+도 따옴표 이스케이프도 필요 없어요. SQL·JSON·HTML 처럼 여러 줄 글에서 특히 빛나고, 공통 들여쓰기는 자동으로 정리돼요. - 가드 패턴(
when) —switch패턴 매칭에 조건을 한 겹 더.case 타입 when 조건으로 같은 타입 안에서도 갈래를 잘게 나눠요. 구체적인 갈래를 위로, 일반적인 갈래를 아래로. - 이름 없는 변수(
_) — "받지만 안 쓰는 값" 을 한 글자로 비워요. switch 분해, 반복문,catch어디서나 "여긴 안 쓴다" 를 분명히 밝혀요. - 순서 있는 컬렉션 —
getFirst·getLast·addFirst·addLast·reversed. 줄을 서 있는 컬렉션이면List·LinkedList·Deque어디서나 같은 이름으로 첫·끝·뒤집기를 다뤄요. - 간소 소스 파일 — Day 1~5 의
void main()이 자바 최신 버전의 정식 기능. 작은 연습·스크립트엔 이 짧은 형태가 어울려요.
지난 시간 배운 record·sealed·switch 패턴 매칭 위에 오늘의 조각들이 자연스럽게 얹혔어요. 마지막 종합 단계에서 그 조각들이 한 흐름에서 함께 일하며 알림 카드 한 장을 그려내는 모습까지 봤죠.
다음 시간엔 — IDE 밖으로 한 걸음
지금까지 우리는 IDE 의 "Run" 버튼에 기대 코드를 실행했어요. 버튼만 누르면 IDE 가 알아서 컴파일하고 실행해줬죠. 다음 시간엔 그 버튼 뒤에서 무슨 일이 일어나는지 들여다봐요. 여러 사람이 함께 만드는 프로젝트를 빌드하고 실행하는 자동화 도구(Gradle) 를 처음 배우고, 모던 자바를 포함해 지금까지 익힌 것들을 전체적으로 복습하면서 다음 과목으로 가는 다리를 놓을 거예요.
오늘 배운 조각들은 앞으로 만들 모든 코드에서 자연스럽게 다시 만나게 돼요. 정말 수고 많으셨어요!
과제
오늘 배운 텍스트 블록·가드 패턴·이름 없는 변수·순서 컬렉션을 직접 써보며 익히는 과제예요. 모두 우리 인스타그램 도메인 위에서 풀어봐요. modern 패키지의 오늘 예제들을 옆에 두고 비교하면서 작성하면 편해요.
과제 1: 게시물 공유 카드 만들기 — 텍스트 블록
상황 배경: 게시물을 친구에게 공유할 때 보여줄 "공유 카드" 문자열을 만들려 해요. 제목·작성자·좋아요 수를 받아서, 여러 줄짜리 카드 모양 글자로 만들어주는 거죠. Step 1~2 에서 본 텍스트 블록과, Step 7 의 render 에서 본 %s·%d 끼워 넣기를 떠올려보세요.
🎯 해결 미션:
shareCard(String title, String author, int likeCount)메서드를 만드세요. 세 값을 받아 아래 모양의 문자열을 텍스트 블록으로 돌려주면 돼요.- 카드 모양은 이렇게요. (제목·작성자·좋아요 수 부분에 받은 값이 들어가요.)
📷 노을 사진
by @jaehoon
❤️ 1240
shareCard("노을 사진", "jaehoon", 1240) 을 부르면 위 세 줄짜리 문자열이 나와야 해요. 텍스트 블록 안에 빈칸(%s·%d) 을 두고 .formatted(...) 로 값을 끼워 넣어보세요.
과제 2: 알림 우선순위 매기기 — 가드 패턴 when + 이름 없는 변수 _
상황 배경: 알림함을 정리하면서, 각 알림에 "얼마나 중요한지" 우선순위 점수를 매기려 해요. 같은 좋아요라도 인기 글에 달린 거면 더 중요하고, 같은 댓글이라도 내용이 길면 더 정성스러운 거죠.
Step 3 의 가드(when) 와 Step 4 의 이름 없는 변수(_) 를 함께 써봐요. 지난 시간 만든 sealed 알림 세 종류(LikeNotification·CommentNotification·FollowNotification) 를 그대로 활용하세요.
🎯 해결 미션: priority(Notification n) 메서드를 만들어, switch 패턴 매칭 + 가드(when) 로 아래 점수를 돌려주세요.
- 좋아요인데 글 제목이
"노을 사진"이면 →3(인기 글 좋아요) - 그 외 좋아요 →
1 - 댓글인데 내용이 20자 이상이면 →
2(정성 댓글) - 그 외 댓글 →
1 - 팔로우 →
2
점수를 매길 때 안 쓰는 값(예: 일반 좋아요에서 글 제목) 은 _ 로 비워보세요. 가드가 붙은 갈래를 위에, 일반 갈래를 아래에 두는 순서도 신경 써보고요.
과제 3: 최근 본 프로필 순서 다루기 — 순서 있는 컬렉션
상황 배경: "최근 본 프로필" 목록을 다뤄요. 먼저 본 사람이 앞, 방금 본 사람이 뒤에 줄을 서 있어요. 여기서 "가장 먼저 본 사람", "방금 본 사람", "최신순으로 뒤집은 목록" 을 꺼내려 해요. Step 5 의 getFirst·getLast·reversed 를 써보세요.
🎯 해결 미션: String[] 이 아니라 순서 있는 컬렉션(List) 위에서 아래 세 가지를 구해보세요. 입력은 ["jaehoon", "minji", "dana"] 처럼 줄 서 있는 이름 목록이에요.
firstViewer(List<String> viewers)— 가장 먼저 본 사람(첫 번째) 을 돌려줘요. (getFirst)latestViewer(List<String> viewers)— 방금 본 사람(마지막) 을 돌려줘요. (getLast)recentOrder(List<String> viewers)— 최신순(방금 본 사람이 맨 앞) 으로 뒤집은 목록을 돌려줘요. 원본은 망가뜨리지 말고요. (reversed)
["jaehoon", "minji", "dana"] 를 넣으면 첫 번째는 jaehoon, 마지막은 dana, 뒤집은 목록은 [dana, minji, jaehoon] 이 나와야 해요.
생각해볼 주제
혼자 고민해도 좋고, 동료와 토론해도 좋아요. 정답을 외우기보다, "나라면 어떻게 설명할까" 를 떠올리며 읽어보세요.
1. 텍스트 블록은 항상 더 좋을까?
오늘 텍스트 블록이 여러 줄 문자열을 얼마나 깔끔하게 만들어주는지 봤어요. 그러면 앞으로 모든 문자열을 텍스트 블록으로 적으면 될까요? 그런데 "안녕하세요" 같은 한 줄짜리 짧은 문자열에 """ 세 개를 열고 닫으면 오히려 더 거추장스러울 수도 있어요.
"여러 줄이고 들여쓰기가 있는 글" 과 "짧은 한 줄 글" 을 떠올리며, 텍스트 블록이 어울리는 경우와 일반 따옴표("...") 가 나은 경우의 경계를 한번 그어보세요.
2. 가드(when) 와 if-else 사슬, 언제 무엇이 더 읽기 좋을까?
Step 3 에서 가드(when) 로 알림을 잘게 갈랐어요. 그런데 사실 같은 분기를 if-else 를 줄줄이 이어서도 만들 수 있어요. if (좋아요이고 인기글) ... else if (좋아요면) ... 처럼요.
둘 다 같은 일을 하는데, switch + when 으로 적을 때와 if-else 사슬로 적을 때 각각 어떤 장단점이 있을지 떠올려보세요. 특히 "종류가 sealed 로 닫혀 있을 때 컴파일러가 빠진 갈래를 잡아준다" 는 점이 어느 쪽에 힘을 실어주는지도 생각해보면 좋아요.
3. 이름 없는 변수 _ 는 코드를 더 친절하게 만들까, 정보를 숨길까?
_ 는 "이 값은 안 쓴다" 를 분명히 보여준다고 배웠어요. 그런데 반대로 생각하면, _ 로 비운 곳에는 "원래 거기 어떤 값이 있었는지" 가 이름으로 남지 않아요. 예를 들어 CommentNotification(String actor, _, _) 만 보면, 버린 두 값이 각각 무엇이었는지(글 제목·내용) 는 record 정의를 따로 봐야 알 수 있죠.
"안 쓰는 걸 분명히 한다" 는 장점과 "버린 값이 무엇인지 이름으로 안 보인다" 는 면을 견주어, _ 를 쓰면 좋은 경우와 차라리 이름을 남기는 게 나은 경우를 한번 정리해보세요.
✅ 예시 답안정답 보기
오늘 과제는 세 가지 새 문법을 하나씩 손에 익히는 흐름이에요. 과제 1 은 텍스트 블록으로 여러 줄 카드를 그리고, 과제 2 는 가드(when) 와 이름 없는 변수(_) 로 알림을 잘게 갈래 나누고, 과제 3 은 순서 있는 컬렉션으로 첫·끝·뒤집기를 다뤄요. modern 패키지의 오늘 예제(TextBlockBasics·GuardPatternDemo·SequencedCollectionDemo)를 옆에 두고 비교하면서 보면 편해요.
과제 예시답안
과제 1 예시답안 — 게시물 공유 카드 만들기
핵심 접근
여러 줄짜리 카드 모양을 만들 때 옛날처럼 "📷 " + title + "\n" + ... 로 잇기 시작하면 금세 지저분해져요. 카드는 줄바꿈이 있는 여러 줄 글이니, 텍스트 블록(""") 이 딱 맞아요.
요령은 두 가지예요. 먼저 텍스트 블록 안에 카드 모양을 그대로 그리고, 값이 들어갈 자리는 빈칸(%s·%d) 으로 비워둬요. 그다음 .formatted(...) 로 그 빈칸에 받은 값을 순서대로 끼워 넣으면 끝이에요. 숫자인 좋아요 수는 %d, 글자인 제목·작성자는 %s 를 써요.
예시 구현
// com/instagram/javabasic/modern/solution/day30/PostShareCard.java
public static String shareCard(String title, String author, int likeCount) {
return """
📷 %s
by @%s
❤️ %d""".formatted(title, author, likeCount);
}
텍스트 블록이 카드 세 줄을 보이는 모양 그대로 담고, .formatted(title, author, likeCount) 가 %s·%s·%d 자리에 값을 차례로 채워요. shareCard("노을 사진", "jaehoon", 1240) 을 부르면 📷 노을 사진 / by @jaehoon / ❤️ 1240 세 줄짜리 문자열이 나와요.
닫는 """ 를 마지막 글자(%d) 바로 뒤에 붙인 점도 눈여겨보세요. 만약 닫는 """ 를 다음 줄에 두면 끝에 빈 줄(\n) 이 하나 더 붙어요. 카드가 딱 세 줄이길 바란다면 마지막 글자 뒤에 바로 닫아요.
채점 포인트
| 확인할 점 | 왜 중요한가 |
|---|---|
텍스트 블록(""") 으로 만들었는가 |
+ 와 \n 으로 이으면 오늘 배운 문법의 의도를 비켜가요. 여러 줄은 텍스트 블록이 깔끔해요 |
빈칸을 %s·%d 로 두고 .formatted(...) 로 채웠는가 |
값을 문자열 중간에 끼워 넣는 표준 방법이에요. 좋아요 수는 숫자라 %d |
닫는 """ 를 마지막 글자 뒤에 붙였는가 |
다음 줄에 두면 끝에 빈 줄이 생겨 카드가 네 줄이 돼요 |
| 결과가 정확히 세 줄인가 | 📷·by @·❤️ 세 줄이 의도대로 나오는지 확인하는 핵심 결과예요 |
흔한 실수
- 텍스트 블록 대신
"📷 " + title + "\n" + "by @" + author + ...로 이어 붙여요. 결과는 비슷해도 따옴표와\n이 섞여 읽기 어렵고, 오늘 배운 텍스트 블록을 안 쓴 셈이에요. - 좋아요 수에
%s를 써요. 동작은 하지만, 숫자 값에는%d를 쓰는 게 "이건 숫자다" 를 분명히 해요. - 닫는
"""를 다음 줄에 둬서 끝에 빈 줄이 붙어요. 카드가 한 줄 더 길어 보이면 이걸 의심해보세요.
과제 2 예시답안 — 알림 우선순위 매기기
핵심 접근
"같은 좋아요라도 인기 글이면 더 중요" 처럼 타입 안에서 경우를 더 나눠야 하니, 가드(when) 가 필요한 대목이에요. 지난 시간 만든 sealed 알림 세 종류를 그대로 받아서, switch 패턴 매칭에 조건을 한 겹 더 얹어요.
두 가지를 신경 써요. 먼저 가드가 붙은(조건이 까다로운) 갈래를 일반 갈래보다 위에 둬요. 그래야 인기 글 좋아요가 일반 좋아요 갈래에 먼저 잡혀버리지 않아요. 그리고 점수를 매길 때 안 쓰는 값은 _ 로 비워요. 좋아요 종류만 알면 되는 갈래에서는 안의 값을 통째로 _ 로, 댓글 내용만 보는 갈래에서는 나머지 두 값을 _ 로 버려요.
예시 구현
// com/instagram/javabasic/modern/solution/day30/NotificationPriority.java
public static int priority(Notification n) {
return switch (n) {
// 인기 글("노을 사진")에 달린 좋아요는 더 중요해요 — 가드 붙은 갈래를 위에 둬요.
case LikeNotification(_, String title) when title.equals("노을 사진") -> 3;
// 그 외 좋아요 — actor·postTitle 둘 다 안 쓰니 통째로 _ 로 비워요.
case LikeNotification _ -> 1;
// 내용이 20자 이상인 정성 댓글 — text 만 보고 actor·postTitle 은 _ 로 비워요.
case CommentNotification(_, _, String text) when text.length() >= 20 -> 2;
// 그 외 댓글
case CommentNotification _ -> 1;
// 팔로우
case FollowNotification _ -> 2;
};
}
첫 번째 갈래는 좋아요를 분해하되 글 제목만 받고(title), 보낸 사람은 _ 로 버린 뒤 when 으로 "노을 사진" 인지 확인해요. 두 번째 갈래 case LikeNotification _ 은 좋아요인데 안의 값을 아무것도 안 쓰니 통째로 _ 로 비웠어요. 댓글도 같은 방식으로, 정성 댓글(20자 이상) 갈래에서는 text 만 받고 나머지는 _ 로 버렸죠.
Notification 이 sealed 라 종류가 셋으로 닫혀 있으니 default 가 필요 없어요. 세 종류를 전부 처리했다는 걸 자바가 확인해주거든요.
채점 포인트
| 확인할 점 | 왜 중요한가 |
|---|---|
가드(when) 로 인기 글·정성 댓글을 갈랐는가 |
타입만으로는 못 나누는 "조건 분기" 가 오늘 과제의 핵심이에요 |
| 가드 붙은 갈래를 일반 갈래보다 위에 뒀는가 | 순서가 바뀌면 인기 글 좋아요가 일반 갈래에 먼저 잡혀버려요 |
안 쓰는 값을 _ 로 비웠는가 |
"이 값은 안 쓴다" 를 분명히 드러내는 게 과제의 의도예요 |
default 없이 통과하는가 |
sealed 라 세 종류가 닫혀 있어 빠짐없음이 보장돼요 |
흔한 실수
- 일반 좋아요 갈래(
case LikeNotification _) 를 가드 붙은 갈래보다 위에 둬요. 그러면 인기 글 좋아요가 위에서 먼저 잡혀 영영 3점이 안 나오고, 자바가 "아래 갈래는 도달 불가" 라며 오류로 막아요. - 안 쓰는 값에
_대신 진짜 이름(actor·postTitle) 을 붙이고 그냥 안 써요. 동작은 하지만, 읽는 사람이 "이 값 어디서 쓰지?" 하고 헷갈려요. 안 쓰면_로 비우는 게 솔직해요. - 안 써도 되는
default:를 굳이 넣어요.sealed라 빠진 종류가 없으니default는 닿지 않는 코드예요.
과제 3 예시답안 — 최근 본 프로필 순서 다루기
핵심 접근
"가장 먼저 본 사람", "방금 본 사람", "최신순 목록" 을 꺼내는 일이에요. 옛날엔 첫 번째를 get(0), 마지막을 get(size() - 1) 로 꺼냈지만, 오늘 배운 순서 있는 컬렉션의 약속을 쓰면 getFirst()·getLast() 한 단어로 또렷해져요.
한 가지 주의할 점은 "원본을 망가뜨리지 않기" 예요. 뒤집을 때 reversed() 는 원본을 그대로 두고 뒤집힌 모습만 돌려줘요. 여기에 List.copyOf(...) 로 한 번 더 떠서 독립된 목록으로 넘기면, 나중에 원본이 바뀌어도 돌려준 결과가 흔들리지 않아 안전해요.
예시 구현
// com/instagram/javabasic/modern/solution/day30/RecentViewers.java
// 가장 먼저 본 사람(첫 번째)을 돌려줘요.
public static String firstViewer(List<String> viewers) {
return viewers.getFirst();
}
// 방금 본 사람(마지막)을 돌려줘요.
public static String latestViewer(List<String> viewers) {
return viewers.getLast();
}
// 최신순(방금 본 사람이 맨 앞)으로 뒤집은 목록을 돌려줘요. 원본은 그대로 둬요.
public static List<String> recentOrder(List<String> viewers) {
return List.copyOf(viewers.reversed());
}
firstViewer 는 getFirst() 로 맨 앞을, latestViewer 는 getLast() 로 맨 뒤를 또렷하게 꺼내요. size() - 1 같은 계산이 사라졌죠. recentOrder 는 reversed() 로 뒤집힌 모습을 받고, List.copyOf(...) 로 독립된 목록을 떠서 돌려줘요.
["jaehoon", "minji", "dana"] 를 넣으면 첫 번째는 jaehoon, 마지막은 dana, 뒤집은 목록은 [dana, minji, jaehoon] 이 나와요. 그리고 이 메서드들을 부른 뒤에도 원본은 [jaehoon, minji, dana] 그대로예요.
채점 포인트
| 확인할 점 | 왜 중요한가 |
|---|---|
getFirst()·getLast() 를 썼는가 |
get(0)·get(size()-1) 대신 오늘 배운 또렷한 약속을 쓰는 게 과제의 의도예요 |
reversed() 로 뒤집었는가 |
직접 반복문으로 뒤집지 않고 한 단어로 끝내는 게 핵심이에요 |
| 원본이 그대로 보존되는가 | reversed() 와 copyOf 는 원본을 안 건드려요. 원본이 바뀌면 감점이에요 |
| 결과 순서가 정확한가 | 첫·끝·뒤집기 세 결과가 의도대로 나오는지 확인하는 핵심이에요 |
흔한 실수
Collections.reverse(viewers)로 뒤집어요. 이건 원본 목록 자체를 뒤집어 망가뜨려요. 원본을 지키려면reversed()처럼 "뒤집힌 모습을 새로 돌려주는" 방법을 써야 해요.- 여전히
get(0)과get(viewers.size() - 1)로 꺼내요. 결과는 맞지만 오늘 배운getFirst()·getLast()의 또렷함을 못 살려요. reversed()결과를 그대로 돌려주고copyOf를 빼요. 대개는 괜찮지만, 돌려준 결과가 원본과 연결돼 있어 원본이 바뀌면 같이 흔들릴 수 있어요.copyOf로 떠두면 마음이 놓여요.
생각해볼 주제 예시답안
생각해볼 주제 1 예시답안 — 텍스트 블록은 항상 더 좋을까?
[문제 상황 요약]
텍스트 블록이 여러 줄 문자열을 깔끔하게 만들어주니 "앞으로 모든 문자열을 """ 로" 쓰고 싶은 마음이 들어요. 그런데 "안녕하세요" 같은 짧은 한 줄에 """ 세 개를 열고 닫으면 오히려 거추장스러워 보여요. 텍스트 블록이 어울리는 경우와 일반 따옴표가 나은 경우를 어떻게 나눌까요?
[튜터의 가이드 및 해설]
기준은 한 문장이에요. "여러 줄이고 들여쓰기가 있는 글인가, 짧은 한 줄인가."
텍스트 블록이 빛나는 곳은 "여러 줄에 모양이 있는 긴 글" 이에요. 오늘 본 SQL·JSON·HTML, 그리고 여러 줄 카드처럼요. 줄바꿈이 많고 따옴표가 들어가는 글일수록, \n 과 \" 를 일일이 적는 대신 보이는 모양 그대로 담을 수 있어 이득이 커요.
반대로 일반 따옴표가 나은 곳은 "짧은 한 줄 글" 이에요. "username", "노을 사진", 메시지 한 토막 같은 건 그냥 "..." 가 더 짧고 분명해요. 한 줄짜리에 텍스트 블록을 쓰면 """ 를 열고 닫는 두 줄이 오히려 군더더기가 되죠.
쉽게 떠올리는 법은 이래요. "이 글에 줄바꿈이 있나?" 를 물어보세요. 줄바꿈이 여럿이고 모양을 지키고 싶으면 텍스트 블록, 한 줄로 끝나는 짧은 글이면 일반 따옴표예요. 도구가 좋다고 모든 곳에 쓰는 게 아니라, 글의 모양에 맞춰 고르는 거예요.
🎯 면접관을 홀리는 핵심 멘트
"저는 텍스트 블록을 '여러 줄에 모양이 있는 글'에 씁니다. SQL·JSON·HTML 처럼 줄바꿈과 따옴표가 많은 글은 텍스트 블록으로 원본 모양 그대로 담아 가독성을 높이고, 한 줄짜리 짧은 문자열은 일반 따옴표가 더 간결하다고 봅니다. 새 문법이라고 무조건 쓰는 게 아니라, 글의 형태에 맞춰 고르는 판단이 중요하다고 생각합니다."
생각해볼 주제 2 예시답안 — 가드(when) 와 if-else 사슬, 언제 무엇이 더 좋을까?
[문제 상황 요약]
Step 3 에서 가드(when) 로 알림을 잘게 갈랐어요. 그런데 같은 분기를 if (좋아요이고 인기 글) ... else if (좋아요면) ... 처럼 if-else 사슬로도 만들 수 있죠. 둘 다 같은 일을 하는데, switch + when 과 if-else 사슬은 각각 어떤 장단점이 있을까요?
[튜터의 가이드 및 해설]
핵심은 "종류가 닫혀 있는가" 와 "안의 값을 바로 꺼내 쓰는가" 두 가지예요.
switch + when 이 유리한 곳은 종류가 sealed 로 닫힌 경우예요. 오늘 Notification 처럼 종류가 셋으로 닫혀 있으면, switch 는 "세 종류를 전부 다뤘는지" 를 자바가 확인해줘요. 나중에 종류가 하나 늘면 컴파일 단계에서 "이거 빠졌어!" 라고 잡아주죠. if-else 사슬은 이런 빠짐없음 확인을 못 해줘서, 새 종류가 늘어도 조용히 지나칠 수 있어요. 게다가 case LikeNotification(_, String title) 처럼 안의 값을 바로 꺼내(분해) 쓸 수 있는 것도 switch 패턴 매칭의 장점이에요.
if-else 가 편한 곳은 조건이 타입과 상관없이 자유로울 때예요. "팔로워가 1000명 넘고 가입한 지 30일 지났고..." 처럼 여러 값을 복잡하게 조합하는 조건은 if 로 풀어 쓰는 게 더 자연스러울 수 있어요. 타입으로 갈래를 나누는 일이 아니라면 굳이 switch 를 고집할 필요는 없죠.
정리하면, "종류가 닫혀 있고 타입별로 안전하게 갈라야 하는" 분기는 switch + when, "타입과 무관하게 값들을 자유롭게 조합하는" 조건은 if-else 예요. 특히 sealed 와 만났을 때 컴파일러가 빠진 갈래를 잡아주는 안전함이 switch 쪽에 큰 힘을 실어줘요.
🎯 면접관을 홀리는 핵심 멘트
"저는 종류가
sealed로 닫혀 있고 타입별로 갈래를 나누는 분기에는switch+when을 씁니다. 컴파일러가 빠진 종류를 잡아주고, 패턴 매칭으로 안의 값을 바로 분해해 쓸 수 있기 때문입니다. 반대로 타입과 무관하게 여러 값을 복잡하게 조합하는 조건은if-else가 더 자연스럽다고 봅니다. 가드의 진짜 가치는sealed와 만났을 때의 빠짐없음 보장이라고 생각합니다."
생각해볼 주제 3 예시답안 — 이름 없는 변수 _ 는 친절할까, 정보를 숨길까?
[문제 상황 요약]
_ 는 "이 값은 안 쓴다" 를 분명히 보여준다고 배웠어요. 그런데 반대로 보면, _ 로 비운 곳에는 "원래 거기 어떤 값이 있었는지" 가 이름으로 남지 않아요. CommentNotification(String actor, _, _) 만 봐서는 버린 두 값이 무엇인지 record 정의를 따로 봐야 알 수 있죠. _ 는 코드를 더 친절하게 만들까요, 정보를 숨길까요?
[튜터의 가이드 및 해설]
이건 _ 의 트레이드오프를 묻는 주제예요. 의도를 분명히 한다는 장점과, 버린 값의 이름이 안 남는다는 면이 동시에 있어요.
_ 가 친절한 경우. "안 쓴다" 가 분명히 보이는 게 더 도움이 될 때예요. 안 쓰는 값에 actor·title 같은 진짜 이름을 붙여두면, 읽는 사람이 "이 값 어디서 쓰지?" 하고 괜히 코드를 훑게 돼요. 특히 record 정의가 가까이 있어 안의 값이 무엇인지 쉽게 알 수 있다면, _ 로 비우는 게 "여긴 신경 안 써도 돼" 라는 신호라 오히려 읽기 편해요.
_ 가 아쉬운 경우. 버린 값이 무엇인지가 그 자리에서 중요할 때예요. record 정의가 멀리 있거나, 값의 순서가 헷갈리기 쉬운 경우라면, (_, _, String text) 만 보고는 앞의 두 값이 무엇이었는지 바로 안 떠올라요. 이럴 땐 차라리 이름을 남겨 "여기엔 actor 와 title 이 있지만 안 쓴다" 를 보여주는 게 나을 수 있어요.
정리하면, "안 쓴다는 사실이 중요하고 값의 정체가 쉽게 파악되는" 경우에는 _ 가, "버린 값이 무엇인지가 그 자리에서 헷갈리기 쉬운" 경우에는 이름이 어울려요. _ 는 코드를 간결하게 해주는 좋은 도구지만, "분명함" 과 "정보" 사이의 균형을 보고 고르는 게 핵심이에요.
🎯 면접관을 홀리는 핵심 멘트
"이름 없는 변수
_는 '안 쓴다'는 의도를 한 글자로 드러내 가독성을 높이지만, 동시에 버린 값의 정체가 이름으로 남지 않습니다. 그래서 저는 record 정의가 가까워 값을 쉽게 파악할 수 있는 곳에는_로 의도를 분명히 하고, 버린 값이 무엇인지가 그 자리에서 헷갈리기 쉬우면 이름을 남깁니다. 간결함과 정보 전달 사이의 균형을 상황에 맞게 고르는 게 중요하다고 생각합니다."
