들어가며
오늘은 자바 기초 내용을 퀴즈로 복습하고,
파일 입출력, 스트림, 멀티스레딩과 동기화 기초를 학습했다.
또한 객체지향 설계를 적용한 Page 실습 프로그램을 구현하면서,
단순 구현을 넘어 클래스 간의 역할과 관계를 고민해볼 수 있었다.
프로세스와 스레드
프로세스 (Process)
실행 중인 프로그램 하나를 의미하며,
각 프로세스는 독립적인 메모리 공간을 가진다.
스레드 (Thread)
프로세스 내부에서 실제 작업을 수행하는 단위이다.
하나의 프로세스는 여러 개의 스레드를 가질 수 있다.
멀티스레딩이 필요한 이유
싱글 스레드는 하나의 작업이 끝날 때까지 다른 작업을 수행할 수 없다.
하지만 멀티스레딩을 사용하면
하나의 작업(예: 다운로드)을 별도 스레드에서 처리하면서
메인 스레드는 동시에 다른 작업을 수행할 수 있다
→ 사용자 경험과 성능이 크게 향상된다.
스레드 생성 방법
Thread 상속
class MyThread extends Thread {
public void run() {
// 작업 내용
}
}
Runnable 구현 (권장)
class MyTask implements Runnable {
public void run() {
// 작업 내용
}
}
Thread는 단일 상속 제한
Runnable은 다른 클래스 상속 가능 + 재사용성 높음
→ 실무에서는 Runnable 방식이 더 많이 사용된다.
start() vs run()
t.run(); // 일반 메서드 호출 (싱글 스레드)
t.start(); // 새로운 스레드 생성
→ 반드시 start()를 사용해야 멀티스레딩이 동작한다.
동기화와 Race Condition
멀티스레딩 환경에서는 여러 스레드가 동시에 같은 데이터에 접근하면서 문제가 발생할 수 있다.
Race Condition
여러 스레드가 동시에 공유 자원 접근
실행 순서에 따라 결과가 달라짐
예: 계좌 잔액이 음수가 되는 문제
synchronized
public synchronized void withdraw() {
// 한 번에 하나의 스레드만 실행
}
특정 영역을 잠궈서 동시에 접근하지 못하도록 함
데이터 안정성을 보장
[실습] Page 프로그램 설계
이번 실습에서는 하나의 프로그램을 여러 클래스로 분리하여 구현했다.
핵심 구조
Page currentPage = new MainPage(sc);
while (currentPage != null) {
currentPage = currentPage.run();
}
설계 포인트
공통 기능 → Page 추상 클래스
각 페이지 → MainPage, HelloPage, InfoPage
run()을 통해 페이지 전환
→ 다형성을 활용하여 어떤 페이지든 동일한 방식으로 실행 가능
느낀 점 (설계 관점)
초반에는 한 클래스에서 절차적으로 만드는 것이 훨씬 쉬웠다.
하지만 객체지향으로 분리하려고 하니:
클래스가 많아지고
관계가 복잡해지고
흐름이 한눈에 안 보이기 시작했다
이 부분에서 난이도가 크게 올라갔다.
→ 객체지향에서는 코드 작성보다 설계가 더 중요하다는 것을 체감했다.
트러블슈팅
할인 금액 계산 오류
// 잘못된 코드
int gradeDiscountAmount = totalPrice - getTotalPrice();
// 수정
int gradeDiscountAmount = totalPrice - getFinalPrice();
→ 변수명과 메서드명을 혼동한 실수
교훈
이름을 명확하게 짓는 것이 중요하다
계산 로직은 한 번 더 검증해야 한다
long 타입 리터럴
long num = 10000000000L;
int 범위를 초과하면 L을 붙여야 함
Setter 사용 신중
Setter는 무분별하게 만들면 위험
필요한 경우에만 제한적으로 사용
정리하며
멀티스레딩은 성능과 사용자 경험을 개선하는 중요한 개념이다
synchronized를 통해 동기화 문제를 해결할 수 있다
객체지향 설계에서는 클래스 간 역할 분리가 핵심이다
작은 실수도 결과에 큰 영향을 주므로 검증이 중요하다
앞으로
객체지향 설계 능력을 키우기 위해
클래스 구조를 먼저 설계하고 구현하는 연습
다양한 예제를 통해 관계 설계 경험 축적
을 계속해 나갈 예정이다.
동훈
•
2026-06-30 09:11
조회수
4
들어가며
오늘은 자바의 객체지향 개념을 확장하여 추상클래스와 인터페이스를 학습하고,
이를 바탕으로 쇼핑몰 주문 시스템을 단계적으로 구현해보았다.
단순한 문법 이해를 넘어서, 실제로 클래스를 설계하고 관계를 구성하는 과정에서
여러 가지를 고민해볼 수 있었다.
추상 클래스 vs 인터페이스
두 개념은 비슷해 보이지만, 목적이 다르다.
구분 추상 클래스 인터페이스
키워드 extends implements
목적 공통 기능 공유 기능(규격) 정의
다중 상속 불가능 가능
필드 일반 변수 가능 상수만 가능
메서드 일반 + 추상 추상, default
언제 사용하는가
추상 클래스
→ "A는 B다" 관계 (is-a)
→ 공통된 속성과 기능을 함께 사용할 때
인터페이스
→ "A는 B를 할 수 있다" 관계 (can-do)
→ 서로 다른 클래스에 동일한 기능을 부여할 때
인터페이스는 구현을 교체하기 쉬워 결합도를 낮출 수 있기 때문에
실무에서 더 자주 사용된다.
생성자와 초기화 (++ 위치)
public BankAccount(String name){
this.name = name;
accountNumber = ++lastAccountNumber;
}
++lastAccountNumber처럼 증가를 먼저 수행해야 올바른 값이 들어간다
lastAccountNumber++를 사용할 경우 기존 값이 먼저 할당된다
생성자와 setter를 함께 사용하는 이유
자동 생성자를 그대로 사용할 경우 검증 로직이 빠질 수 있다.
// 자동 생성자
public Product(String id, String name, int price, int stock) {
this.id = id;
this.name = name;
this.price = price;
this.stock = stock;
}
// 개선된 방식
public Product(String id, String name, int price, int stock) {
this.id = id;
this.name = name;
setPrice(price);
setStock(stock);
}
자동 생성자 기능을 사용하면 검증 로직을 건너뛸 수 있다는 점을 깨달았다.
그래서 setter를 활용한 수동 생성자 구현으로 안정성을 높이는 것이 중요하다고 느꼈다.
캡슐화와 정보 은닉
@Override
public void inform() {
System.out.println("------상품 정보------");
// System.out.println("상품 고유 ID : " + getId()); // 내부용 정보
System.out.println("상품명 : " + getName());
System.out.println("가격 : " + getPrice());
System.out.println("재고 수량 : " + getStock());
}
내부에서만 사용하는 정보는 외부에 노출하지 않는 것이 중요하다
캡슐화를 통해 데이터의 안정성을 유지할 수 있다
Scanner 버퍼 문제
int age = sc.nextInt();
sc.nextLine(); // 버퍼 비우기
String name = sc.nextLine();
nextInt() 이후 nextLine()을 바로 사용하면 빈 문자열이 들어오는 문제가 발생한다
버퍼를 비워주는 처리가 필요하다
구현하면서 느낀 점
쇼핑몰 주문 시스템을 구현하면서
초반에는 구조가 단순해서 쉽게 진행됐지만,
클래스와 메서드가 많아질수록 관계를 관리하는 것이 어려워졌다.
특히 다음 부분에서 부족함을 느꼈다.
클래스 간 책임 분리
데이터 흐름 정리
설계 단계에서 구조를 미리 잡는 것
단순히 기능을 구현하는 것보다,
처음부터 구조를 어떻게 설계하느냐가 더 중요하다는 것을 느꼈다.
정리하며
추상 클래스와 인터페이스는 목적에 따라 선택해야 한다
생성자와 setter를 함께 사용하면 안정적인 초기화가 가능하다
캡슐화를 통해 데이터를 안전하게 보호할 수 있다
작은 구현에서도 설계가 중요하다는 것을 확인했다
앞으로
구현 경험을 더 쌓으면서
단순히 동작하는 코드가 아니라 구조가 명확한 코드를 작성하는 연습이 필요하다.
특히 클래스 간 관계를 설계하는 연습을 반복해볼 예정이다.
동훈
•
2026-06-30 09:11
조회수
3
들어가며
오늘은 어제에 이어 자바 컬렉션과 객체지향 프로그래밍 개념을 복습했다.
특히 메모리 구조와 연결되는 컬렉션 구조와, 객체지향에서 중요한 개념들을 중심으로 정리했다.
ArrayList vs LinkedList
ArrayList와 LinkedList는 모두 List 인터페이스를 구현한 자료구조이지만, 내부 구조와 동작 방식에서 차이가 있다.
ArrayList
ArrayList는 배열 기반 구조로, 데이터가 메모리에 연속적으로 저장된다.
특징
인덱스를 통한 접근이 빠르다
데이터 추가는 끝에 할 때 효율적이다
중간 삽입/삭제 시 데이터 이동이 발생해 느려질 수 있다
LinkedList
LinkedList는 노드(Node) 단위로 데이터를 저장하며, 각 노드가 다음 노드의 주소를 가지고 있는 구조이다.
특징
데이터가 메모리에 연속적으로 저장되지 않는다
중간 삽입/삭제가 빠르다 (주소만 변경)
특정 위치 접근은 느리다 (순차 탐색 필요)
메모리 관점에서 차이
ArrayList → 데이터가 힙에 연속적으로 저장됨
LinkedList → 노드들이 힙에 흩어져 있고, 주소로 연결됨
즉, 구조 차이에 따라 성능 특성이 달라진다.
접근 제어자 (Access Modifier)
자바에서는 변수와 메서드의 접근 범위를 제한하기 위해 접근 제어자를 사용한다.
제어자 접근 범위
private 해당 클래스 내부
default 같은 패키지
protected 같은 패키지 + 자식 클래스
public 전체
왜 필요한가
처음에는 변수를 직접 수정하는 것이 더 편해 보일 수 있다.
하지만 협업 환경에서는 데이터가 무분별하게 변경되면 문제가 발생할 수 있다.
warrior.hp = -500;
이처럼 비정상적인 값이 들어갈 수 있기 때문에
→ 직접 접근을 제한하고, 메서드를 통해 제어하는 것이 중요하다.
오버라이딩 vs 오버로딩
이름이 비슷하지만 개념은 다르다.
구분 오버라이딩 (Overriding) 오버로딩 (Overloading)
의미 기존 메서드 재정의 메서드 추가 정의
관계 상속 관계 같은 클래스
메서드 이름 동일 동일
매개변수 동일 달라야 함
오버라이딩 핵심
부모 클래스의 메서드를 자식 클래스에서 재정의하여, 상황에 맞는 동작을 수행하도록 만드는 것
@Override
public void attack() {
System.out.println("자식 클래스에서 재정의된 공격");
}
업캐스팅된 상태에서도 오버라이딩된 메서드는 실제 객체(자식 클래스)를 기준으로 실행된다.
그래서 부모 타입으로 참조해도, 실행 결과는 자식 클래스 기준으로 동작한다.
업캐스팅 / 다운캐스팅 (다형성)
업캐스팅
자식 객체를 부모 타입으로 다루는 것
GameCharacter character = new Warrior();
자동 형변환
여러 객체를 하나의 타입으로 관리 가능
자식 고유 기능은 사용할 수 없음
다운캐스팅
부모 타입을 다시 자식 타입으로 변환하는 것
Warrior warrior = (Warrior) character;
강제 형변환 필요
자식 고유 기능 사용 가능
instanceof
다운캐스팅 전에 타입을 확인하기 위한 연산자
if (character instanceof Warrior) {
Warrior warrior = (Warrior) character;
}
Pattern Matching
최신 자바에서는 다음과 같이 더 간단하게 작성 가능
if (character instanceof Warrior warrior) {
warrior.smash();
}
정리하며
ArrayList와 LinkedList는 메모리 구조 차이로 인해 성능 특성이 달라진다
접근 제어자는 데이터 보호와 코드 안정성을 위한 중요한 개념이다
오버라이딩은 객체지향에서 동작을 확장하는 핵심 방법이다
업캐스팅과 다운캐스팅을 통해 다양한 객체를 유연하게 다룰 수 있다
느낀 점
업캐스팅을 하면 자식 기능을 못 쓰는 줄만 알았는데, 다운캐스팅을 통해 다시 사용할 수 있다는 점이 인상적이었다.
또한 오버라이딩된 메서드는 실제 객체 기준으로 실행된다는 점도 이해하게 되었다.
컬렉션 부분에서는 단순히 List를 사용하는 것보다, 내부 구조에 따라 성능이 달라진다는 점을 고려해야 한다고 느꼈다.
앞으로
객체지향 개념이 계속 이어지기 때문에 상속과 다형성 개념을 더 명확하게 정리할 필요가 있다.
실제 코드에서 컬렉션과 객체지향 개념을 함께 사용하는 연습을 해볼 예정이다.
동훈
•
2026-06-30 09:11
조회수
3
들어가며
가상융합기술 아카데미 백엔드 첫 수업을 통해 자바 기본 문법을 복습했다.
그중에서도 헷갈리기 쉬웠던 스택/힙 메모리 구조와 가변 매개변수를 중심으로 정리해보았다.
스택(Stack) 메모리 vs 힙(Heap) 메모리
자바에서 변수와 데이터는 모두 메모리에 저장되며, 이 메모리는 크게 스택과 힙으로 나뉜다.
이 구조를 이해하면 참조 타입이 왜 존재하는지도 자연스럽게 이해할 수 있다.
힙(Heap) 메모리
힙 메모리는 크기가 크고, 유동적인 데이터를 저장하는 공간이다.
저장 대상
참조 자료형의 실제 데이터 (String, 배열, 객체 등)
크기가 고정되지 않은 데이터
특징
메모리 크기가 크다
여러 곳에서 공유가 가능하다
주소를 통해 접근하기 때문에 스택보다 속도가 느리다
관리 방식
자바의 가비지 컬렉터(GC)가 자동으로 관리한다
더 이상 사용되지 않는 데이터는 자동으로 제거된다
예시:
String name = "backend";
"backend" → 힙 메모리에 저장
name 변수 → 스택에 생성되고 힙 주소를 저장
스택(Stack) 메모리
스택 메모리는 일시적으로 사용하는 메모리 공간이다.
저장 대상
기본 자료형 값 (int, double 등)
참조 자료형의 주소값
특징
접근 속도가 빠르다
메서드 단위로 생성되고 종료 시 함께 제거된다
사용이 끝나면 즉시 메모리에서 사라진다
핵심 정리
실제 데이터는 힙에 저장된다
스택에는 해당 데이터의 주소가 저장된다 (참조 타입)
정리
구분 스택(Stack) 힙(Heap)
저장 내용 기본값, 주소값 실제 데이터
속도 빠름 느림
크기 작음 큼
수명 메서드 종료 시 제거 GC에 의해 관리
접근 범위 제한적 공유 가능
가변 매개변수
메서드를 정의할 때 매개변수의 개수가 달라질 경우,
기존에는 오버로딩을 여러 개 만들어야 했다.
이 문제를 해결하기 위해 가변 매개변수를 사용할 수 있다.
선언 방법
public static int sum(int... nums) {
int total = 0;
for (int num : nums) {
total += num;
}
return total;
}
int... nums는 내부적으로 배열(int[])로 처리된다.
사용 방법
sum(1, 2, 3);
sum(10, 20, 30, 40);
sum();
콤마(,)로 자유롭게 개수를 전달할 수 있다
배열을 직접 전달하는 것도 가능하다
주의사항
가변 매개변수는 반드시 마지막에 위치해야 한다
public void test(String name, int... nums)
하나의 메서드에는 하나만 사용할 수 있다
오버로딩이 되어 있는 경우, 더 구체적인 메서드가 우선 호출된다
add(int a, int b)
add(int... a)
add(1, 2) → 일반 메서드 호출
느낀 점
처음에는 String 같은 것도 스택에 저장되는 줄 알았는데, 실제 데이터는 힙에 저장되고 변수에는 주소만 저장된다는 점이 살짝 헷갈렸다.
참조 타입이 왜 필요한지 스택과 힙의 구조를 통해 이해할 수 있었고, 단순 문법보다 내부 동작을 같이 이해하는 것이 중요하다고 느꼈다.
앞으로
객체와 배열을 배우면서 힙 메모리 개념을 더 깊게 이해할 필요가 있다.
이후에는 코드 작성 시 메모리 구조를 함께 고려하는 연습을 해볼 예정이다.
동훈
•
2026-06-30 09:10
조회수
4