게시판 프로그램을 자바로 구현하면서 필수적으로 이해하고 암기해야 하는 핵심 코드와 로직들을 정리했다.
게시판 처음에 적어야 하는 기초 변수 및 자료구조 세팅이다.
int lastArticleId = 0; // 작성되는 게시글들을 1번부터 발급하기 위한 변수
int lastMemberId = 0; // 작성되는 회원번호를 1번부터 발급하기 위한 변수
List<Article> articles = new ArrayList<>(); // 게시글을 담을 상자
프로그램은 사용자가 종료 명령을 내리기 전까지 계속 실행되어야 하므로 무한 반복문으로 시작한다.
while (true) {
// cmd 입력 시 공백 제거를 위해 .trim() 잊지 않기
if (cmd.length() == 0) {
// 아무것도 입력하지 않았을 때를 나타내는 코드
}
if (cmd.equals("exit")) {
// 문자열끼리 비교할 때는 == 을 사용하지 않고 .equals()를 사용한다.
}
}
else if (cmd.equals("member join")) {
System.out.println("== 회원 가입 ==");
String loginId;
String loginPw;
String name;
// ...
회원가입 시 아이디를 입력할 때, 우선 입력되는 아이디를 '참'으로 가정하고 하위 조건들을 확인한 후에 참/거짓을 구별한다.
boolean isJoinableId = true;
for (Member member : members) {
if (member.getLoginId().equals(loginId)) {
isJoinableId = false;
}
}
for (데이터타입 변수명 : 배열/컬렉션명) { ... } 형태로, 회원의 명부를 처음부터 끝까지 다 뒤질 때 사용한다.members: 뒤져볼 전체 회원 명부 리스트Member member: 명부에서 꺼낸 회원 한 명을 잠깐 쥐고 있을 변수member.getLoginId()는 기존 회원의 아이디를, loginId는 새로 가입하려는 아이디를 뜻한다.isJoinableId를 false로 바꾼다. 즉, 위에 선언한 true 상태를 끄는 것이다.if (isJoinableId == false) {
System.out.printf("%s(은)는 이미 사용중인 아이디입니다.\n", loginId);
continue;
}
break로 빠져나오면 바로 다음 비밀번호 입력 단계로 내려가게 된다. continue를 사용하는 것이 훨씬 효율적이다.마지막 번호에 1을 더하는 방식으로 처리해야 번호가 연달아 매겨진다.
int id = lastMemberId + 1;
[필수 암기] 날짜와 시간 도구 클래스 (Util)
import java.text.SimpleDateFormat;
import java.util.Date;
public class Util {
// 현재 날짜와 시간을 문자열로 반환하는 메서드
public static String getNowStr() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new Date());
}
}
String regDate = Util.getNowStr();
String regDate: 가입 날짜를 적어둘 빈칸을 준비하라는 뜻이다.Util: 다른 곳에 따로 만들어 둔 도우미 클래스이다..getNowStr(): Util 도구 상자 안에 있는 기능으로, "지금 당장(Now)의 시간을 글자(String)로 가져와(get)!"라는 뜻이다.Member member = new Member(id, regDate, regDate, loginId, loginPw, name);
new Member(...): new 키워드는 메모리의 빈 공간을 확보하라는 직접적인 명령이다. 확보한 공간에 Member 클래스에 적혀있는 모양대로 틀을 잡고, 괄호 안에 있는 6개의 변수 값들을 그 틀 안의 빈칸에 각각 대입한다 (이때 클래스 내부의 this.id = id; 코드가 실행된다).regDate)를 두 번째와 세 번째 자리에 연속으로 넘겨주어 문법적 오류를 막고 데이터를 채운 것이다.Member member = 부분은 방금 메모리에 만들어진 데이터 덩어리(객체)를 코드에서 조종할 때 필요하다. member라는 이름의 참조 변수를 만들어서 방금 만들어진 메모리 공간의 주소(위치)를 저장해 두는 것이다. 이제 member라는 이름만 부르면 그 데이터 덩어리에 접근할 수 있다.members.add(member);
member)를, 전체 회원을 관리하는 'members 리스트(배열)'의 맨 마지막 칸에 저장하는 명령어이다.게시글 작성 시 고유 번호를 부여하고, 생성된 데이터를 리스트에 저장하는 과정이다.
else if (cmd.equals("article write")) {
System.out.println("== 게시글 작성 ==");
int id = lastArticleId + 1; // 게시물의 주소 위치 설정은 이렇게 한다.
// 맨 밑에 만든 Util 클래스(도구상자)를 이용해 현재 시간을 쉽게 가져온다.
String regDate = Util.getNowStr();
String updateDate = Util.getNowStr(); // 처음엔 작성일과 수정일이 같다.
Article article = new Article(id, regDate, updateDate, title, body);
articles.add(article);
}
new Article(...)은 Article이라는 설계도를 보고, 방금 위에서 입력받은 id, title, content 재료를 다 집어넣어서 새로운 진짜 게시글 1개를 만들어내라는 뜻이다. 즉, 설계도를 보고 진짜 실체를 만들어내는 과정이라고 해석한다.Article article = 부분은 그렇게 만들어진 진짜 게시글(우변)에 article이라는 이름표를 붙여서 내가 컨트롤하겠다는 뜻이다. 앞의 대문자 Article은 타입(설계도 종류)이고, 소문자 article은 임의로 지은 이름표이다.articles는 맨 위에서 만든 게시글을 담아주는 리스트(상자)이다. .add()는 리스트가 제공하는 기능으로, 상자 안에 무언가를 집어넣으라는 뜻이다. 방금 이름표를 붙인 진짜 게시글(article)을 상자에 집어넣는다.명령어의 종류를 파악하고, 검색어 유무에 따라 데이터를 걸러내어 최신순으로 출력하는 기능이다.
else if (cmd.startsWith("article list")) {
String searchKeyword = cmd.substring("article list".length()).trim();
startsWith vs equals):equals()처럼 값이 고정되어 완전히 동일한 것보다 명령어 뒤에 글 번호나 검색어 등이 붙을 것을 판단해야 한다.equals: "이거랑 토씨 하나 안 틀리고 똑같나?" (고정된 명령어)startsWith: "일단 시작은 이걸로 하나? 뒤에 추가적인 게 있어도 괜찮다." (데이터가 포함된 명령어)cmd에 들어있는 전체 문장에서 'article list'의 글자 수만큼 앞부분을 잘라내고, 남은 글자의 양끝 공백을 없앤 최종 결과물을 searchKeyword라는 문자열 변수에 저장하라는 뜻이다.substring(...)은 문자열 절단 함수로 괄호 안에 들어간 숫자(인덱스) 위치부터 맨 끝까지를 잘라서 가져오는 기능이다. .(점)은 접근 연산자로 "앞에 있는 데이터가 가지고 있는 내장 기능(메서드)을 꺼내 쓰겠다"는 연결 고리이다.List<Article> forPrintArticles = articles;
if (searchKeyword.length() > 0) {
forPrintArticles = new ArrayList<>();
for (Article article : articles) {
if (article.getTitle().contains(searchKeyword)) {
forPrintArticles.add(article);
}
}
}
< >) 선언: 자바에서는 새로운 상자(변수)를 만들 때 "이 상자에 앞으로 뭘 담을 건지" 컴퓨터에게 무조건 미리 신고해야 한다. List는 이 상자에 데이터 하나가 아니라 여러 개를 줄줄이 엮어서 담을 거라는 뜻이고, <Article>은 그 내용물로 '게시글'만 허용한다는 뜻이다.forPrintArticles는 처음엔 큰 통을 같이 쓰다가, 플라스틱만 골라내라는 명령(if)이 떨어지면 새로운 빈 봉투(new ArrayList)를 꺼내는 원리다. 그다음 큰 통에서 조건에 맞는 것을 발견할 때마다 그 새 봉투에만 담는다..contains()는 괄호 안에 넣은 글자가 앞에 있는 문장에 들어있는지 없는지만 확인하여 참(true) 아니면 거짓(false)으로 결과를 반환한다.// .isEmpty() : 리스트 안에 내용 유무를 체크해서 '참/거짓'으로 답해주는 자바 전용 탐지기이다.
for (int i = forPrintArticles.size() - 1; i >= 0; i--) {
Article article = forPrintArticles.get(i);
String today = Util.getNowStr().split(" ")[0];
int i = articles.size() - 1은 총 개수에서 제일 끝자리부터 시작한다는 뜻이다. (저장공간이 3개이면 0부터 2까지 마련되므로 최신 것을 부르려면 -1을 해야 한다). 이를 1씩 줄여가며 리스트에 있는 게시글을 뒤에서부터 꺼낸다.get(i)는 상자 리스트에서 i번째 칸에 있는 게시물 하나를 손으로 끄집어내는 것이다. 꺼낸 게시물에 article이라는 임시 이름표를 붙여주는 과정이 출력문 바로 위에서 필요하다. 이렇게 해야 바로 밑줄에서 article.getTitle()처럼 그 게시물에게 말을 걸 수 있다.split) 비유:split(" ")으로 띄어쓰기가 있는 곳을 가위로 싹둑 자른다. 이제 종이가 2장이 되었다. (앞장: "2026-04-12", 뒷장: "23:20:14")[0]은 자른 종이 중에서 0번째(앞장) 종이만 나한테 넘기라는 뜻이다.String today = 부분은 서랍에 보관하라는 뜻으로, 넘겨받은 0번째 종이(날짜)를 책상에 있는 today라는 이름표가 붙은 서랍에 집어넣으라는 뜻이다.today.equals(regDay))하여 출력 형식을 바꾼다. 어떤 article인지 컴퓨터가 모르기 때문에 객체의 값을 꺼내와 명확히 코드를 입력해 줘야 한다.특정 게시글을 조작하기 위해 명령어에서 번호를 추출하고, 리스트를 뒤져 해당 게시물을 찾는 공통 과정이다.
// 1. "article detail 1" 이라는 문장을 띄어쓰기 기준으로 쪼개서, 3번째 조각("1")을 진짜 숫자로 바꾼다.
String[] cmdBits = cmd.split(" ");
int id = Integer.parseInt(cmdBits[2]);
split(" ")을 통해 잘린 조각들이 cmdBits라는 칸막이 상자(배열)에 순서대로 담긴다.[0]번 칸: "article"[1]번 칸: "delete" (또는 detail 등)[2]번 칸: "5" (우리가 필요한 글 번호)Integer.parseInt()를 사용해 [2]번 칸의 글 번호를 진짜 숫자로 변환한다.Article foundArticle = null; // 찾은 게시글을 담을 '임시 빈 상자'
// 2. 리스트 상자를 처음부터 끝까지 샅샅이 뒤진다.
for (Article article : articles) {
if (article.getId() == id) { // "너 번호가 내가 찾는 번호랑 똑같아?"
foundArticle = article; // 찾았다면 임시 상자에 담고
break; // 그만 찾고 나온다.
}
}
null: 지금은 탐색을 시작하기 전이니까, 아무것도 없는 '빈손(null)' 상태이다. 숫자에서는 "없다"를 표현할 때 0이나 -1을 썼지만, 게시물(객체)처럼 덩치가 큰 데이터에서는 비었다는 것을 표현할 때 null이라는 단어를 쓴다.articles.remove(foundArticle); // 제거하는 함수는 remove()이다.
System.out.println("기존 title : " + foundArticle.getTitle());
.)의 의미: 자바에서 점(., Dot)은 한국어로 "~의 안에 있는" 또는 "~가 가지고 있는"이라는 뜻이다.foundArticle은 조금 전 for문을 돌려서 찾아낸 다음 쥐고 있는 게시글 전체(바구니)이다. 따라서 foundArticle.getTitle()은 "내 손에 쥐고 있는 찾은 게시글(foundArticle)의 제목(title)"이라는 뜻이 된다.프로그램 내에서 반복적으로 사용되는 로직을 별도의 함수(Method)로 분리하여 코드의 가독성과 재사용성을 높인다.
회원가입 시 입력한 아이디가 이미 존재하는지 확인하는 반복문이 여러 번 등장하므로, 이를 isJoinableLoginId 함수 안에 넣어 관리한다.
private static boolean isJoinableLoginId(String loginId) {
for (Member member : members) {
if (member.getLoginId().equals(loginId)) {
return false; // 겹치는 아이디 발견 시 가입 불가(false) 반환
}
}
return true; // 겹치는 아이디가 없으면 가입 가능(true) 반환
}
게시글 번호를 통해 특정 게시글을 찾아오는 로직을 함수화한다.
private static Article getArticleById(int id) {
for (Article article : articles) {
if (article.getId() == id) {
return article; // 일치하는 번호의 게시글 반환
}
}
return null; // 찾지 못했을 경우 null 반환
}
매번 수동으로 데이터를 입력하는 번거로움을 줄이기 위해 프로그램 시작 시 테스트 데이터를 생성하는 함수를 실행한다. 이때 다음 번호 발급을 위해 lastId 값을 데이터 개수에 맞춰 초기화한다.
// 게시글 테스트 데이터 생성
private static void articleMakeTestData() {
System.out.println("== 게시글 테스트 데이터 생성 ==");
articles.add(new Article(1, "2026-04-08 12:22:02", "2026-04-09 12:22:02", "제목1", "내용1"));
articles.add(new Article(2, Util.getNowStr(), Util.getNowStr(), "제목2", "내용2"));
articles.add(new Article(3, Util.getNowStr(), Util.getNowStr(), "제목3", "내용3"));
lastArticleId = 3;
}
// 회원 테스트 데이터 생성
private static void memberMakeTestData() {
System.out.println("== 회원 테스트 데이터 생성 ==");
members.add(new Member(1, Util.getNowStr(), Util.getNowStr(), "test1", "test1", "회원1"));
members.add(new Member(2, Util.getNowStr(), Util.getNowStr(), "test2", "test2", "회원2"));
members.add(new Member(3, Util.getNowStr(), Util.getNowStr(), "test3", "test3", "회원3"));
lastMemberId = 3;
}
데이터의 보안과 무분별한 접근을 막기 위해 변수를 private으로 설정하고, 정해진 통로를 통해 데이터를 주고받는다.
외부에서 member.getId()와 같이 정중하게 요청하면, Member 클래스 내부에 private int id로 정의된 값을 복사하여 전달한다. 이를 위해 public int getId() { return id; }와 같은 형식을 취한다.
외부에서 넘겨준 새로운 값을 내 금고(객체 내부 변수) 안에 있는 this.id에 덮어쓴다. 주로 회원 정보 수정 등 데이터 변경이 필요할 때 public void setId(int id) { this.id = id; } 형식을 사용한다.
객체가 생성될 때 호출되는 생성자 블록에서는 넘겨받은 데이터를 객체의 변수에 할당하는 작업이 필요하다.
public Member(int id, String regDate, String updateDate, String loginId, String loginPw, String name) {
this.id = id;
this.regDate = regDate;
// ... 생략 없이 모든 변수 정의
}
생성자를 만들 때는 반드시 this.변수 = 변수 형식을 사용하여, 외부에서 들어온 매개변수와 내부의 필드 변수를 명확히 구분하여 정의해야 한다.
중복되는 코드를 줄이고 효율적으로 데이터를 관리하기 위해 상속 개념을 도입한다.
DTO는 '데이터 전송용 상자'를 의미한다. 게시글(Article)이나 회원(Member)과 같이 로직보다는 데이터를 담아 나르는 것이 목적인 클래스들을 뜻한다.
Article과 Member 클래스에는 공통적으로 id, regDate, updateDate가 존재한다. 자바에서는 중복을 피하기 위해 이러한 공통 요소를 모아 '조상님 클래스'인 Dto를 만든다.
그동안 사용한 private은 자식 클래스에게도 공유되지 않는 철벽 보안이다. 반면 protected는 외부인은 막되, 나를 상속받은 자식들에게는 금고를 열어주는 '가족 전용 열쇠' 역할을 한다. 자식들이 부모의 데이터를 자유롭게 쓰게 하기 위해 사용한다.
Dto 클래스를 상속받으면 자식 클래스에서는 공통 변수를 다시 적을 필요가 없어 코드가 매우 간결해진다.
// Article이 Dto를 상속받음
public class Article extends Dto {
// id, regDate, updateDate는 부모로부터 물려받아 생략 가능
private String title;
private String body;
// 자기만의 데이터인 제목과 내용에만 집중한다.
}
부모 클래스인 Dto 파일 하나만 수정하면 이를 상속받는 모든 자식 클래스(Article, Member 등)에 변경 사항이 한꺼번에 반영된다. 이는 유지보수 측면에서 매우 강력한 이점을 제공한다.
한 줄 요약:
DTO 클래스는 모든 데이터 상자들이 가질 공통 뼈대이며,
protected는 자식들이 이 뼈대를 자유롭게 쓰도록 허용하는 통로이다. 이를 통해 중복을 제거하고 효율적인 설계가 가능하다.