PHpullh
언어 비교/조건 분기

LANGUAGE COMPARISON

조건 분기를 언어별로 비교하기

if와 switch가 문(statement)인 언어와 식(expression)인 언어의 차이를 코드로 확인합니다.

분기가 값을 돌려주는가

겉보기에 if는 어느 언어나 비슷하지만, 결정적인 차이가 하나 있습니다. 분기 자체가 값을 만드는가입니다. Kotlin의 val x = if (a) 1 else 2, Rust의 let x = if a { 1 } else { 2 }처럼 분기가 식(expression)인 언어에서는 변수를 먼저 선언하고 나중에 채우는 단계가 사라집니다. 값이 정해지지 않은 상태가 존재하지 않으니 초기화를 잊을 수도 없습니다.

Java나 C++처럼 if가 문(statement)인 언어에서는 삼항 연산자가 그 역할을 대신합니다. 짧은 분기에는 편하지만 중첩되면 급격히 읽기 어려워져서, 결국 변수를 먼저 선언하고 분기 안에서 대입하는 형태로 돌아가게 됩니다.

다중 분기에서는 격차가 더 벌어집니다. C 계열의 switch는 값을 하나씩 비교하는 도구지만, Kotlin의 when, Rust의 match, Python의 match/case는 값의 구조를 분해하며 분기합니다. 튜플을 풀고, 타입을 확인하고, 조건을 덧붙이는 일이 한 자리에서 끝납니다.

Kotlin

if 표현식 & when 표현식

Kotlin의 if와 when은 문(statement)이 아닌 표현식(expression)입니다. 값을 반환합니다.

val score = 85

// if는 표현식 — 삼항연산자 불필요
val grade = if (score >= 90) "A"
            else if (score >= 80) "B"
            else if (score >= 70) "C"
            else "F"

// when — Java switch보다 강력
val msg = when (score) {
    100          -> "만점!"
    in 90..99    -> "우수"
    in 80..89    -> "양호"
    in 70..79    -> "보통"
    else         -> "노력 필요"
}

// when without argument (if-else chain 대체)
when {
    score > 95  -> println("탑 5%!")
    score > 80  -> println("상위권")
    else        -> println("분발하세요")
}

// when + 타입 체크
fun describe(obj: Any): String = when (obj) {
    is Int    -> "정수: $obj"
    is String -> "문자열 길이: ${obj.length}"
    is List<*>-> "리스트 크기: ${obj.size}"
    else      -> "알 수 없는 타입"
}

Python

조건문 & 반복문

Python의 if/elif/else, for, while 문법과 else 절.

score = 85

# if / elif / else
if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

# 한 줄 조건식 (삼항 연산자)
result = "합격" if score >= 60 else "불합격"

# 연속 비교 (Python만의 특징)
if 80 <= score < 90:
    print("B등급")

# for + range
for i in range(5):          # 0 1 2 3 4
    print(i, end=" ")

for i in range(1, 10, 2):   # 1 3 5 7 9
    print(i, end=" ")

# enumerate — 인덱스와 값 동시에
fruits = ["🍎", "🍌", "🍇"]
for idx, fruit in enumerate(fruits, start=1):
    print(f"{idx}: {fruit}")

# zip — 두 리스트 병렬 순회
names  = ["Alice", "Bob"]
scores = [95, 87]
for name, sc in zip(names, scores):
    print(f"{name}: {sc}")

# for-else: break 없이 완료되면 else 실행
for n in range(2, 10):
    if 7 % n == 0 and n != 7:
        print(f"{n}는 7의 약수")
        break
else:
    print("7은 소수")

Go

조건문 & 반복문 — if, for, switch

Go에는 while이 없습니다. for가 모든 반복문 역할을 합니다.

package main

import "fmt"

func main() {
	// if — 초기화 구문 포함 가능
	if x := 42; x > 40 {
		fmt.Println("x는 40보다 큼:", x)
	}

	// for — 기본 (C의 for)
	for i := 0; i < 5; i++ {
		fmt.Print(i, " ")
	}
	fmt.Println()

	// for — while처럼
	n := 1
	for n < 100 {
		n *= 2
	}
	fmt.Println("n:", n)

	// for — 무한루프
	count := 0
	for {
		count++
		if count >= 3 {
			break
		}
	}

	// for range — 슬라이스/맵/채널
	fruits := []string{"🍎", "🍌", "🍇"}
	for i, v := range fruits {
		fmt.Printf("%d: %s
", i, v)
	}
	// 인덱스만
	for i := range fruits { _ = i }
	// 값만
	for _, v := range fruits { _ = v }

	// switch — fallthrough 없음 (기본)
	score := 85
	switch {
	case score >= 90:
		fmt.Println("A")
	case score >= 80:
		fmt.Println("B")
	default:
		fmt.Println("C 이하")
	}

	// switch — 타입 스위치
	var i interface{} = "hello"
	switch v := i.(type) {
	case int:    fmt.Println("int:", v)
	case string: fmt.Println("string:", v)
	default:     fmt.Println("unknown")
	}
}

Java

조건문 & 반복문 & switch expression

Java의 전통적 제어흐름과 Java 14+의 switch 표현식으로 코드를 더 안전하게 작성합니다.

public class ControlFlow {
    public static void main(String[] args) {
        // if-else
        int score = 85;
        String grade = score >= 90 ? "A" :
                       score >= 80 ? "B" :
                       score >= 70 ? "C" : "F";

        // switch expression (Java 14+) — 값 반환, break 불필요
        String result = switch (score / 10) {
            case 10, 9 -> "우수";
            case 8     -> "양호";
            case 7     -> "보통";
            default    -> "미흡";
        };

        // switch expression with yield
        int dayNum = 3;
        String day = switch (dayNum) {
            case 1 -> "월요일";
            case 2 -> "화요일";
            case 3 -> {
                System.out.println("수요일 처리");
                yield "수요일";   // 블록에서 값 반환
            }
            default -> "기타";
        };

        // 향상된 for 루프
        int[] nums = {1, 2, 3, 4, 5};
        int sum = 0;
        for (int n : nums) sum += n;

        // 레이블 break (중첩 루프 탈출)
        outer:
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (i == 1 && j == 1) break outer;
                System.out.print(i + "" + j + " ");
            }
        }
        System.out.println();

        System.out.println(grade + " / " + result + " / " + day);
    }
}

JavaScript

if else 분기

분기문 대신 벡터 정렬이 실행되어 1, 3, 4, 8 순서가 출력됩니다. sort는 원소가 Ord를 구현해야 호출할 수 있어서 f64 벡터에는 그대로 쓸 수 없고, sort_by에 partial_cmp를 넘겨야 합니다. NaN 때문에 부동소수점이 전순서를 이루지 못하는 탓입니다.

// if else 분기
const user = { name: "user8", level: 8 };
console.log(user.name, user.level);

TypeScript

if else 분기

분기문 대신 벡터 정렬이 실행되어 1, 3, 4, 8 순서가 출력됩니다. sort는 원소가 Ord를 구현해야 호출할 수 있어서 f64 벡터에는 그대로 쓸 수 없고, sort_by에 partial_cmp를 넘겨야 합니다. NaN 때문에 부동소수점이 전순서를 이루지 못하는 탓입니다.

// if else 분기
const user = { name: "user8", level: 8 };
console.log(user.name, user.level);

C#

if else 분기

분기문 대신 벡터 정렬이 실행되어 1, 3, 4, 8 순서가 출력됩니다. sort는 원소가 Ord를 구현해야 호출할 수 있어서 f64 벡터에는 그대로 쓸 수 없고, sort_by에 partial_cmp를 넘겨야 합니다. NaN 때문에 부동소수점이 전순서를 이루지 못하는 탓입니다.

// if else 분기
using System;
using System.Linq;

var values = new[] { 3, 1, 4, 8 };
Console.WriteLine(string.Join(", ", values.OrderBy(v => v)));

C++

if else 분기

분기문 대신 벡터 정렬이 실행되어 1, 3, 4, 8 순서가 출력됩니다. sort는 원소가 Ord를 구현해야 호출할 수 있어서 f64 벡터에는 그대로 쓸 수 없고, sort_by에 partial_cmp를 넘겨야 합니다. NaN 때문에 부동소수점이 전순서를 이루지 못하는 탓입니다.

// if else 분기
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> values = {3, 1, 4, 8};
    std::sort(values.begin(), values.end());
    std::cout << values.front() << "\n";
}

Rust

if else 분기

분기문 대신 벡터 정렬이 실행되어 1, 3, 4, 8 순서가 출력됩니다. sort는 원소가 Ord를 구현해야 호출할 수 있어서 f64 벡터에는 그대로 쓸 수 없고, sort_by에 partial_cmp를 넘겨야 합니다. NaN 때문에 부동소수점이 전순서를 이루지 못하는 탓입니다.

// if else 분기
fn main() {
    let mut values = vec![3, 1, 4, 8];
    values.sort();
    println!("{:?}", values);
}

빠뜨린 경우를 컴파일러가 잡아주는가

패턴 매칭의 진짜 값은 문법이 아니라 완전성 검사입니다. Rust의 match나 Kotlin에서 sealed 타입을 다루는 when은 처리하지 않은 경우가 있으면 컴파일이 실패합니다. 나중에 상태를 하나 추가했을 때, 그 값을 처리해야 하는 자리를 컴파일러가 전부 찾아 줍니다.

이 안전망이 없는 언어에서는 새 케이스가 default나 else로 조용히 흘러 들어갑니다. 그래서 열거형을 분기할 때 default를 습관적으로 붙이면, 실은 검사를 스스로 꺼 버리는 셈입니다. 정말 모든 나머지를 같게 처리해야 할 때만 쓰십시오.

fall-through와 break

C, C++, Java, JavaScript의 switch는 break를 빠뜨리면 다음 case로 실행이 흘러 내려갑니다. 의도적으로 쓰는 경우도 있지만 대부분은 실수이고, 증상이 조용해서 찾기 어렵습니다. Go는 반대로 기본이 자동 중단이고 이어가려면 fallthrough를 명시해야 합니다. 같은 키워드가 언어마다 정반대 기본값을 갖는 드문 사례입니다.

참으로 취급되는 값

조건 자리에 불리언이 아닌 값을 넣을 수 있는지, 넣으면 무엇이 참인지도 갈립니다. Python은 빈 리스트·빈 문자열·0을 거짓으로 보고, JavaScript는 여기에 NaN과 빈 문자열이 더해집니다. 반면 Go와 Rust는 불리언이 아닌 값을 조건 자리에 아예 넣지 못하게 막습니다.

이 차이 때문에 if (count) 같은 축약이 언어를 옮기는 순간 의미를 바꿉니다. 값이 없는 것과 값이 0인 것을 구분해야 하는 코드라면, 축약 대신 비교를 명시적으로 적는 편이 안전합니다. 관련 함정은 변수 선언 비교에서도 이어집니다.