Java의 변수, 연산자, 조건문, 반복문을 각각 이해했다고 해서 바로 프로그램을 만들 수 있는 것은 아니다. 실제 코드는 여러 문법을 동시에 사용해 하나의 문제를 해결한다.
이 글에서는 기초 문법을 서로 연결하는 연습 방법을 정리한다.
처음부터 코드를 작성하기보다 요구사항을 작은 단계로 나누는 것이 좋다.
예를 들어 간단한 주문 프로그램을 만든다고 해보자.
요구사항:
1. 상품 가격을 입력받는다.
2. 수량을 입력받는다.
3. 총 주문 금액을 계산한다.
4. 50,000원 이상이면 배송비는 무료다.
5. 그렇지 않으면 배송비 3,000원을 추가한다.
6. 최종 결제 금액을 출력한다.
필요한 문법을 연결하면 다음과 같다.
Scanner → 입력
변수 → 값 저장
연산자 → 금액 계산
if → 배송비 결정
출력 → 결과 확인
import java.util.Scanner;
public class OrderApp {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("상품 가격: ");
int price = scanner.nextInt();
System.out.print("수량: ");
int quantity = scanner.nextInt();
int orderAmount = price * quantity;
int deliveryFee;
if (orderAmount >= 50_000) {
deliveryFee = 0;
} else {
deliveryFee = 3_000;
}
int paymentAmount = orderAmount + deliveryFee;
System.out.println("주문 금액: " + orderAmount);
System.out.println("배송비: " + deliveryFee);
System.out.println("최종 결제 금액: " + paymentAmount);
}
}
작은 프로그램이지만 대부분의 기초 문법이 함께 사용된다.
한 번 실행하고 종료하는 프로그램을 메뉴 방식으로 발전시킬 수 있다.
while (true) {
System.out.println("1. 주문 계산");
System.out.println("0. 종료");
int menu = scanner.nextInt();
if (menu == 0) {
break;
}
if (menu == 1) {
// 주문 계산
}
}
여기서 while, if, break, Scanner가 연결된다.
두 숫자를 입력받아 작은 수부터 큰 수까지 출력해보자.
int first = scanner.nextInt();
int second = scanner.nextInt();
int start;
int end;
if (first <= second) {
start = first;
end = second;
} else {
start = second;
end = first;
}
for (int number = start; number <= end; number++) {
System.out.println(number);
}
문제 하나 안에서 조건문과 반복문이 함께 사용된다.
반복문에서 매우 자주 등장하는 구조다.
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println(sum);
핵심은 반복문 밖에 누적 변수를 만들고 반복할 때마다 값을 더하는 것이다.
초기값 → 반복 → 누적 → 결과
이 패턴은 주문 금액 합계, 방문자 수 합계, 점수 합계 등 다양한 곳에서 사용된다.
int sum = 10;
int count = 4;
double average = sum / count;
이 코드의 결과는 2.5가 아니라 2.0이다.
double average = (double) sum / count;
계산 전에 형변환해야 원하는 실수 결과를 얻을 수 있다.
처음에는 main() 안에서 모든 코드를 작성해도 된다.
하지만 프로그램이 길어지면 역할별로 메서드를 분리한다.
static int calculateOrderAmount(int price, int quantity) {
return price * quantity;
}
static int calculateDeliveryFee(int orderAmount) {
if (orderAmount >= 50_000) {
return 0;
}
return 3_000;
}
이제 main()은 전체 흐름을 보여준다.
int orderAmount = calculateOrderAmount(price, quantity);
int deliveryFee = calculateDeliveryFee(orderAmount);
프로그래밍 문제를 오래 고민하는 것만이 항상 좋은 학습은 아니다.
추천하는 순서는 다음과 같다.
특히 마지막 단계가 중요하다. 정답을 읽어서 이해하는 것과 직접 다시 구현할 수 있는 것은 다르다.
초보 단계에서는 중간 값을 출력하는 것만으로도 많은 문제를 찾을 수 있다.
System.out.println("price=" + price);
System.out.println("quantity=" + quantity);
System.out.println("orderAmount=" + orderAmount);
예상 값과 실제 값을 비교하면서 어느 단계에서 잘못되었는지 찾는다.
익숙해지면 IDE의 디버거와 breakpoint를 활용하면 된다.
두 변수의 값을 바꾸는 문제는 단순하지만 대입의 본질을 이해하기 좋다.
잘못된 코드부터 보자.
int first = 10;
int second = 20;
first = second;
second = first;
첫 번째 대입이 끝나는 순간 first의 기존 값 10을 잃어버린다. 그래서 임시 변수가 필요하다.
int temp = first;
first = second;
second = temp;
이 패턴은 "대입하면 오른쪽 값을 읽어 왼쪽을 덮어쓴다"는 규칙을 직접 체감하게 해준다.
두 숫자를 어느 순서로 입력하든 작은 값부터 처리하고 싶다면 먼저 상태를 정규화한다.
int first = scanner.nextInt();
int second = scanner.nextInt();
if (first > second) {
int temp = first;
first = second;
second = temp;
}
for (int value = first; value <= second; value++) {
System.out.print(value);
if (value < second) {
System.out.print(", ");
}
}
핵심은 복잡한 반복문을 만드는 것이 아니라 반복 전에 데이터를 다루기 좋은 상태로 바꾸는 것이다.
int[] responseTimes = {130, 95, 410, 170};
int min = responseTimes[0];
int max = responseTimes[0];
for (int responseTime : responseTimes) {
if (responseTime < min) {
min = responseTime;
}
if (responseTime > max) {
max = responseTime;
}
}
이 코드는 배열 순회 + 조건문 + 상태 갱신을 조합한 대표적인 기초 패턴이다.
서비스별 일간 요청 수가 있다고 가정해보자.
int[][] requests = {
{120, 150, 180},
{90, 110, 130}
};
서비스별 합계를 구하려면 행마다 sum을 새로 만든다.
for (int service = 0; service < requests.length; service++) {
int sum = 0;
for (int day = 0; day < requests[service].length; day++) {
sum += requests[service][day];
}
double average = (double) sum / requests[service].length;
System.out.println("service=" + service + ", avg=" + average);
}
중첩 반복문, 배열 길이, 누적 합, 형변환을 한 번에 연결할 수 있는 좋은 연습이다.
while (true) {
System.out.println("1. 등록");
System.out.println("2. 조회");
System.out.println("3. 종료");
int menu = scanner.nextInt();
switch (menu) {
case 1 -> register();
case 2 -> showList();
case 3 -> {
return;
}
default -> System.out.println("잘못된 메뉴");
}
}
메뉴형 프로그램에는 자연스럽게 다음 개념이 모인다.
반복문 프로그램 계속 실행
Scanner 사용자 선택 입력
조건/switch 기능 분기
메서드 기능별 로직 분리
return 프로그램 흐름 종료
입문 문법을 한 번에 복습하기 좋다.
객체지향을 배우기 전에는 다음처럼 상품 데이터를 따로 관리할 수 있다.
String[] names = new String[5];
int[] prices = new int[5];
int productCount = 0;
상품을 하나 등록할 때 같은 인덱스를 사용한다.
names[productCount] = name;
prices[productCount] = price;
productCount++;
이 방식은 기초 문법을 연습하기에는 좋지만 데이터가 늘어나면 관리가 어려워진다. 예를 들어 재고, 카테고리, 상품 ID가 추가되면 배열이 계속 늘어난다.
이 불편함을 느끼는 것이 다음 단계인 클래스와 객체가 왜 필요한지 이해하는 좋은 출발점이다.
문제가 정답대로 동작했다고 바로 끝내지 말고 두 번째 단계로 코드를 개선한다.
첫 번째 목표:
일단 올바르게 동작하게 만들기
두 번째 목표:
중복 제거
이름 개선
스코프 축소
메서드 추출
매직 넘버 제거
예를 들어 배송비 계산이 여러 곳에 반복된다면 메서드로 추출한다.
static int calculateShippingFee(int amount) {
if (amount >= 50_000) {
return 0;
}
return 3_000;
}
기능을 먼저 완성한 뒤 구조를 개선하는 연습을 반복하면 문법 암기에서 실제 개발 습관으로 넘어갈 수 있다.
코드가 예상대로 동작하지 않으면 무작정 다시 작성하기보다 실행 흐름을 좁힌다.
System.out.println("i=" + i + ", sum=" + sum);
이런 단순 로그만으로도 많은 기초 버그를 찾을 수 있다.
변수, 연산자, 조건문, 반복문, 배열, 메서드가 익숙해졌다면 다음 단계에서는 이 개념들을 더 큰 구조로 확장하면 된다.
클래스와 객체
↓
캡슐화 / 상속 / 다형성
↓
예외 처리
↓
컬렉션(ArrayList, Map 등)
↓
제네릭
↓
파일/네트워크/데이터베이스
↓
웹 백엔드 프레임워크
중요한 것은 한 번 읽고 넘어가는 것이 아니라 작은 프로그램을 직접 만들면서 문법을 계속 다시 사용하는 것이다.