공공부하자개발 · 영어 학습 노트
자바
초급자바의 뼈대0/8 완료
  • 00개발 환경과 첫 프로그램
  • 01변수, 데이터 타입, 형변환
  • 02연산자와 제어문
  • 031차원 · 2차원 배열
  • 04클래스, 객체, 생성자, 오버로딩
  • 05접근 제어자와 캡슐화
  • 06메서드 활용 패턴 (초급)
  • 07enum, 패키지와 import, static 과 final
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 초급 › 02 / 8

연산자와 제어문

섹션 7진행 0 / 8
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 산술 연산자

연산자 의미 예 (a=7, b=2) 주의
+ 덧셈 / 문자열 연결 a + b → 9 문자열이 끼면 연결로 바뀜
- 뺄셈 a - b → 5
* 곱셈 a * b → 14 오버플로우 주의
/ 나눗셈 a / b → 3 정수끼리면 몫만. 0으로 나누면 ArithmeticException
% 나머지 a % b → 1 음수의 나머지는 음수: -7 % 2 → -1

+의 이중성은 초보자를 자주 괴롭힙니다.

java
System.out.println(1 + 2 + "개");     // "3개"  — 왼쪽부터 계산, 1+2=3 후 문자열 연결
System.out.println("개수: " + 1 + 2);  // "개수: 12" — 문자열 만나는 순간부터 전부 연결
System.out.println("개수: " + (1 + 2)); // "개수: 3"

%의 실무 활용: 짝홀 판별(n % 2 == 0), 순환 인덱스((i + 1) % size), 자릿수 추출(n % 10은 일의 자리), N개마다 줄바꿈(i % 5 == 0).

0으로 나누기: 정수 / 0은 ArithmeticException으로 프로그램이 죽지만, 실수 / 0.0은 Infinity 또는 NaN(Not a Number)이 됩니다. 예외가 안 나서 오히려 더 위험합니다.

2.2 비교 연산자

결과는 항상 boolean입니다.

연산자 의미 예
== 같다 a == b
!= 다르다 a != b
<, <=, >, >= 크기 비교 a >= 18

원시 타입은 ==로 값을 비교하지만, 참조 타입은 ==가 주소 비교입니다(01 레슨). 문자열과 객체는 equals를 씁니다. 실수의 == 비교도 오차 때문에 위험합니다. Math.abs(a - b) < 1e-9 같은 허용 오차 비교를 씁니다.

2.3 논리 연산자와 단축 평가

연산자 의미 특징
&& AND (둘 다 참) 단축 평가: 왼쪽이 false면 오른쪽 안 봄
|| OR (하나라도 참) 단축 평가: 왼쪽이 true면 오른쪽 안 봄
! NOT
&, | 비단축 AND/OR 항상 양쪽 다 평가
^ XOR (하나만 참)

단축 평가(short-circuit evaluation)는 성능 최적화이면서 동시에 안전 장치입니다.

java
String name = null;
if (name != null && name.length() > 0) { ... }

name != null이 false이므로 &&는 오른쪽 name.length()를 실행하지 않습니다. 만약 &를 썼다면 오른쪽도 평가되어 NPE가 납니다. 이 패턴("null 검사를 왼쪽에 두고 &&로 잇기")은 자바 코드 어디에나 있습니다.

같은 원리로 비용이 큰 검사는 오른쪽에 둡니다.

java
if (cache.contains(key) || database.exists(key))   // 캐시에 있으면 DB 조회 생략

2.4 비트 연산자

정수를 2진수 비트 단위로 다룹니다. 일반 업무 코드에선 드물지만 권한 플래그, 해시 계산, 네트워크 프로토콜, 성능 최적화에서 만납니다.

연산자 의미 예 (a=0b1100=12, b=0b1010=10)
& AND a & b → 0b1000 = 8
| OR a | b → 0b1110 = 14
^ XOR a ^ b → 0b0110 = 6
~ NOT (보수) ~a → -13
<< 왼쪽 시프트 a << 1 → 24 (×2)
>> 오른쪽 시프트 (부호 유지) a >> 1 → 6 (÷2), -8 >> 1 → -4
>>> 오른쪽 시프트 (부호 무시, 0 채움) -8 >>> 28 → 15

권한 플래그 패턴:

java
final int READ = 1, WRITE = 2, DELETE = 4;   // 각각 비트 하나
int perm = READ | WRITE;                     // 0b011 = 3
boolean canWrite = (perm & WRITE) != 0;      // true
perm |= DELETE;                              // 권한 추가 → 0b111
perm &= ~WRITE;                              // 권한 제거 → 0b101

2.5 대입, 복합 대입, 증감 연산자

연산자 의미 동등 표현
= 대입
+=, -=, *=, /=, %= 연산 후 대입 a += 3 ≡ a = a + 3
++, -- 1 증가/감소

복합 대입은 자동 캐스팅을 포함합니다. byte b = 10; b += 5;는 되지만 b = b + 5;는 오류입니다. +=는 내부적으로 b = (byte)(b + 5)입니다.

전위/후위 증감은 "언제 증가하느냐"가 아니라 "표현식의 값이 무엇이냐"의 차이입니다.

java
int i = 5;
int a = ++i;   // i를 먼저 6으로 만들고, 그 값 6을 a에. a=6, i=6
int j = 5;
int b = j++;   // j의 현재 값 5를 b에 주고, 그 뒤 j를 6으로. b=5, j=6

혼자 쓰이면(i++; 한 줄) 둘은 완전히 같습니다. 다른 표현식 안에 섞을 때만 다릅니다. 가독성을 위해 섞지 마세요. arr[i++] = x; 같은 코드는 C 시대의 유산이고 코드 리뷰에서 지적됩니다.

2.6 삼항 연산자

java
조건 ? 참일_때_값 : 거짓일_때_값

if-else를 값을 만드는 표현식으로 압축한 것입니다.

java
String status = score >= 60 ? "합격" : "불합격";
int fee = age < 19 ? 0 : (age >= 65 ? 5000 : 10000);   // 중첩 가능하지만 한 번까지만

한 줄로 값 하나를 고를 때만 씁니다. 두 번 이상 중첩되거나 각 분기에서 여러 일을 해야 하면 if-else나 switch 표현식이 낫습니다.

2.7 연산자 우선순위

전부 외울 필요는 없고 아래 순서와 "헷갈리면 괄호"만 기억하면 됩니다.

순위 연산자 결합 방향
1 (), [], . , 후위 ++ -- →
2 전위 ++ --, !, ~, 단항 -, 캐스팅 (type) ←
3 * / % →
4 + - →
5 << >> >>> →
6 < <= > >=, instanceof →
7 == != →
8 & →
9 ^ →
10 | →
11 && →
12 || →
13 ? : ←
14 = += -= ... ←

자주 걸리는 함정: &&가 ||보다 높습니다. a || b && c는 a || (b && c)입니다. 비트 연산 &, |는 비교 연산보다 낮습니다. (perm & WRITE) != 0에서 괄호를 빼면 perm & (WRITE != 0)이 되어 컴파일 오류가 납니다.

2.8 if-else

java
if (조건) {
    // 조건이 true
} else if (다른조건) {
    // 앞 조건이 false이고 이 조건이 true
} else {
    // 모두 false
}

조건은 반드시 boolean이어야 합니다. C처럼 if (count)는 안 됩니다(if (count != 0)). else if 체인은 위에서부터 순서대로 검사하고 처음 맞는 하나만 실행합니다. 그래서 조건의 순서가 중요합니다.

java
// 잘못된 순서: 95점도 첫 조건에 걸려 "C"가 됨
if (score >= 70) grade = "C";
else if (score >= 80) grade = "B";      // 절대 도달 못 함
else if (score >= 90) grade = "A";

// 올바른 순서: 좁은 조건부터
if (score >= 90) grade = "A";
else if (score >= 80) grade = "B";
else if (score >= 70) grade = "C";

중괄호는 항상 씁니다. 한 줄이라도 {} 없이 쓰면 나중에 줄을 추가할 때 사고가 납니다(Apple의 유명한 goto fail 버그가 이것입니다).

얼리 리턴(early return): 예외 상황을 먼저 걸러내고 빠져나가면 중첩이 줄어듭니다.

java
// 중첩 깊음
if (user != null) {
    if (user.isActive()) {
        if (user.hasPermission()) {
            doWork();
        }
    }
}

// 얼리 리턴
if (user == null) return;
if (!user.isActive()) return;
if (!user.hasPermission()) return;
doWork();

2.9 switch — 구문(옛 방식)

java
switch (dayOfWeek) {
    case 1:
    case 7:
        System.out.println("주말");
        break;
    case 2: case 3: case 4: case 5: case 6:
        System.out.println("평일");
        break;
    default:
        System.out.println("잘못된 값");
}

switch (값)의 값이 case와 일치하는 곳으로 점프한 뒤 아래로 쭉 실행합니다. break를 만나야 멈춥니다. 이것을 fall-through(낙하)라 하며, break를 빼먹으면 다음 case까지 실행되는 유명한 버그의 원인입니다. 위 코드에서 case 1:에 break가 없어서 case 7:로 떨어지는 것은 의도된 fall-through입니다(여러 값 묶기).

switch 가능 타입: byte short char int, String, enum, 그리고 래퍼 클래스. long, double, boolean은 안 됩니다.

2.10 switch — 표현식과 화살표 (JDK 14+, 21 기준 표준)

옛 switch의 문제는 fall-through 버그와 값을 반환하려면 변수를 밖에 두고 각 case에서 대입해야 한다는 번거로움이었습니다. 새 switch는 둘 다 해결합니다.

java
String type = switch (dayOfWeek) {
    case 1, 7 -> "주말";
    case 2, 3, 4, 5, 6 -> "평일";
    default -> throw new IllegalArgumentException("잘못된 값: " + dayOfWeek);
};
특징 옛 switch 구문 새 switch 표현식
구분자 case X: case X ->
fall-through 있음 (break 필요) 없음 (한 case만 실행)
여러 값 case 1: case 7: case 1, 7 ->
값 반환 불가 (변수 대입) 가능 (= switch (...))
모든 경우 처리 선택 표현식으로 쓰면 필수 (default 또는 enum 전체)
여러 줄 블록의 반환 - yield 값;

yield는 화살표 오른쪽이 블록 {}일 때 그 블록의 값을 내보내는 키워드입니다.

java
int fee = switch (memberGrade) {
    case "VIP" -> 0;
    case "GOLD" -> {
        int base = 3000;
        yield base / 2;          // 블록에서는 yield로 값을 내보냄
    }
    default -> 3000;
};

패턴 매칭 switch (JDK 21 정식): 타입에 따라 분기하면서 변수까지 꺼냅니다.

java
Object payload = 42;
String desc = switch (payload) {
    case Integer i when i > 100 -> "큰 정수 " + i;
    case Integer i -> "정수 " + i;
    case String s -> "문자열 " + s;
    case null -> "null";
    default -> "기타";
};

when은 가드 조건(추가 필터)입니다. 중급 레슨의 sealed/record와 함께 쓰면 진가를 발휘합니다.

어느 것을 쓸까? 새 코드는 무조건 화살표(->) 문법입니다. fall-through 버그가 구조적으로 불가능하고, 표현식으로 값을 바로 만들 수 있습니다. 옛 문법은 기존 코드를 읽을 때만 알면 됩니다.

2.11 반복문 — for

java
for (초기화; 조건; 증감) {
    본문
}

실행 순서: 초기화(1회) → 조건 검사 → (true면) 본문 → 증감 → 조건 검사 → ... → (false면) 종료.

java
for (int i = 0; i < 5; i++) {
    System.out.print(i + " ");     // 0 1 2 3 4
}
// i는 여기서 접근 불가 — for 안에서 선언한 변수의 범위는 for 블록까지

i < n을 쓰는 이유: 배열 인덱스가 0부터 n-1이기 때문입니다. i <= n은 마지막에 범위 초과 오류를 냅니다. 자바 개발자는 "0부터 시작, 미만으로 끝" 을 몸에 익힙니다.

여러 변수를 다룰 수도 있습니다: for (int i = 0, j = 10; i < j; i++, j--).

2.12 향상된 for (for-each)

java
for (타입 변수 : 배열또는컬렉션) {
    본문
}

인덱스 없이 요소를 하나씩 꺼냅니다. "모든 요소를 처음부터 끝까지 읽기만" 할 때는 이것이 정답입니다. 인덱스 실수가 원천 차단됩니다.

java
int[] prices = {1000, 2500, 3200};
int sum = 0;
for (int price : prices) {
    sum += price;
}

제약: 인덱스를 알 수 없고, 순회 중 배열 요소를 바꿀 수 없습니다(price = 0;은 복사본만 바꿈). 인덱스가 필요하거나 값을 수정해야 하면 일반 for를 씁니다.

2.13 while, do-while

java
while (조건) {       // 조건이 true인 동안 반복. 처음부터 false면 0번 실행
    본문
}

do {                 // 본문을 먼저 1번 실행하고
    본문
} while (조건);      // 조건 검사. 최소 1번은 실행됨

for는 "몇 번 반복할지 알 때", while은 "언제 끝날지 모를 때"(파일 끝까지 읽기, 사용자가 종료할 때까지, 큐가 빌 때까지), do-while은 "메뉴 표시 후 입력 받기"처럼 한 번은 반드시 실행해야 할 때 씁니다.

2.14 break, continue, 라벨

키워드 동작
break 가장 안쪽 반복문(또는 switch)을 즉시 탈출
continue 이번 회차의 나머지를 건너뛰고 다음 회차로
라벨: for (...) + break 라벨; 지정한 바깥 반복문까지 탈출
continue 라벨; 지정한 바깥 반복문의 다음 회차로

중첩 반복문에서 break는 안쪽 하나만 빠져나옵니다. 바깥까지 나가려면 라벨을 씁니다.

java
outer:
for (int i = 0; i < rows; i++) {
    for (int j = 0; j < cols; j++) {
        if (grid[i][j] == target) {
            System.out.println("발견: " + i + "," + j);
            break outer;      // 두 반복문 모두 탈출
        }
    }
}

라벨은 좀처럼 쓸 일이 없어야 합니다. 라벨이 필요할 정도면 안쪽 반복문을 메서드로 빼서 return하는 편이 읽기 좋습니다.

2.15 무한루프

java
while (true) { ... }       // 관례적인 무한루프
for (;;) { ... }           // 같은 뜻

서버의 요청 대기, 게임 루프, 콘솔 메뉴처럼 의도적 무한루프는 반드시 안에 break나 return이 있어야 합니다.

의도치 않은 무한루프의 원인 3가지:

  1. 증감을 빼먹음: while (i < 10) { ... } 에서 i++ 없음
  2. 조건이 절대 false가 안 됨: for (int i = 10; i > 0; i++) (증가하면서 0보다 크길 기다림)
  3. 실수 누적: for (double d = 0; d != 1.0; d += 0.1) — 오차 때문에 정확히 1.0이 안 됨

컴파일러는 while (true) 뒤에 도달 불가능한 코드가 있으면 오류를 냅니다(unreachable statement). 반대로 말하면 while (true)에 break가 없으면 그 뒤 코드는 쓸 수 없습니다.

핵심 원리
  • 2.1 산술 연산자
  • 2.2 비교 연산자
  • 2.3 논리 연산자와 단축 평가
  • 2.4 비트 연산자
  • 2.5 대입, 복합 대입, 증감 연산자
  • 2.6 삼항 연산자
  • 2.7 연산자 우선순위
  • 2.8 if-else
  • 2.9 switch — 구문(옛 방식)
  • 2.10 switch — 표현식과 화살표 (JDK 14+, 21 기준 표준)
  • 2.11 반복문 — for
  • 2.12 향상된 for (for-each)
  • 2.13 while, do-while
  • 2.14 break, continue, 라벨
  • 2.15 무한루프
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제