분기 제어 흐름

1. if / if-else 기본 분기

  1. if 소괄호 안에는 반드시 불 표현식을 적어야 하며, 암시적으로 불 타입으로 변환하는 행위는 불가합니다.
  2. else if 구문을 여러 개 이어서 쓸 수 있고, 마지막에 선택적으로 else를 추가할 수 있습니다.
  3. 처리 로직이 한 줄일 경우 중괄호를 생략할 수 있지만, 가독성을 높이기 위해 전체 코드에 통일해서 중괄호를 사용하는 게 권장됩니다.
void main() {
  bool isRaining = false;
  bool isSnowing = true;

  if (isRaining) {
    print("비옷 챙기기");
  } else if (isSnowing) {
    print("두꺼운 외투 입기");
  } else {
    print("오픈카 타고 드라이브하기");
  }
}Code language: Dart (dart)

실행 결과

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
두꺼운 외투 입기Code language: CSS (css)

if만 단독으로 사용하는 경우도 가능합니다

void main() {
  bool isRaining = false;
  bool isSnowing = true;

  if (isRaining) {
    print("비옷 챙기기");
  }
}Code language: Dart (dart)

혹은 if else만 따로 작성할 수도 있습니다

void main() {
  bool isRaining = false;
  bool isSnowing = true;

  if (isRaining) {
    print("비옷 챙기기");
  } else {
    print("오픈카 타고 드라이브하기");
  }
}Code language: Dart (dart)

2. if-case 패턴 매칭 분기 (Dart 3.0 이상 버전 지원)

  1. 패턴이 일치하면 변수를 디스트럭처링해 현재 스코프 내에 바인딩합니다.
  2. 매칭에 실패하면 else 구문으로 넘어가며, when 가드를 붙여 추가적인 불 판단 로직을 작성할 수 있습니다.
  3. 단일 값에 단일 패턴을 비교할 때만 쓰는 구문이므로, 여러 패턴을 한 번에 처리해야 한다면 switch를 사용하세요.
void main() {
  final pair = [100, 220];
  // 길이가 2이고 두 요소 모두 정수 타입인 리스트에 매칭
  if (pair case [int x, int y]) {
    print("좌표:$x,$y");
  } else {
    throw FormatException("유효하지 않은 좌표");
  }
}Code language: Dart (dart)

실행 결과

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
좌표:10,20Code language: CSS (css)

2: when 가드와 결합한 if-case

void main() {
  final data = [8, 9];
  if (data case [int a, int b] when a > 5 && b > 5) {
    print("두 숫자 모두 5보다 큼");
  } else {
    print("숫자가 조건을 충족하지 않음");
  }
}Code language: Dart (dart)

실행 결과

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
두 숫자 모두 5보다 큼Code language: CSS (css)

3. switch 문

전통적인 switch는 문 형식이므로 반환값을 갖지 않습니다.

  1. case에는 문자열, 상수, 논리 OR, 관계 패턴 등 모든 종류의 패턴을 사용할 수 있습니다.
  2. 내용이 존재하는 case는 자동으로 break 처리되므로 직접 break를 적을 필요가 없고, 빈 case는 자동으로 fallthrough 현상이 발생합니다.
  3. 직접 fallthrough를 구현하는 방법은 두 가지입니다:continue 레이블 / 빈 case를 연속해서 작성하기
  4. 어느 case에도 매칭되지 않을 경우 default 구문이 실행되며, when 가드와 함께 사용할 수 있습니다.
1: 기본 문자열 매칭 예시
void main() {
  String command = "OPEN";
  switch (command) {
    case "CLOSED":
      print("닫기 로직 실행");
    case "PENDING":
      print("대기 로직 실행");
    case "OPEN":
      print("열기 로직 실행");
    default:
      print("알 수 없는 명령");
  }
}
Code language: Dart (dart)

실행 결과

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
열기 로직 실행Code language: CSS (css)

2: 빈 case fallthrough + continue 레이블 점프

String command = "OPEN";
switch (command) {
  case "OPEN":
    print("열기 작업"); // ① OPEN에 매칭되어 출력문 실행
    continue newCase; // ② newCase 레이블로 바로 점프하고 다시 돌아오지 않음

  case "DENIED": 
  case "CLOSED":
    print("닫기 작업");

  newCase: // ③ 점프 대상 레이블
  case "PENDING":
    print("대기 공통 로직"); // ④ command == "PENDING" 검사 없이 곧바로 실행
}Code language: Dart (dart)

실행 결과

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
열기 작업
대기 공통 로직Code language: CSS (css)

이 예시에서 continue newCase;가 newCase로 점프한다 해도 다음 case “PENDING”에는 매칭되지 않을 텐데, 왜 print(“대기 공통 로직”);이 실행되는 걸까요?

많은 개발자들이 궁금해하는 내용입니다.

continue 레이블;는 비교 대상 값을 재검사하지 않고, 레이블이 달린 case 분기 시작점으로 직접 건너뛰어 조건 검사를 생략한 채 분기 내 코드를 바로 실행합니다.

3: switch + when 가드

void main() {
  int score = 88;
  switch (score) {
    case int s when s >= 90:
      print("우수");
    case int s when s >= 60:
      print("합격");
    default:
      print("불합격");
  }
}
Code language: Dart (dart)

출력 결과:합격

먼저 타입이 정수인지 확인한 뒤 변수 s에 값을 할당하고, 이후 when 가드로 2차 검증을 진행합니다. s가 when 뒤 조건을 만족해야 해당 case 내 코드가 실행됩니다. 여기 두 군데에 작성한 int s는 서로 스코프가 분리되어 간섭하지 않습니다.

case int s독립적인 패턴 바인딩으로 변수의 유효 범위가 자기 자신의 case 분기 내에만 한정됩니다.

두 case에 서로 다른 변수명을 사용해도 동작은 완전히 동일합니다.

switch (score) {
  case int a when a >= 90:
    print("우수");
  case int b when b >= 60:
    print("합격");
  default:
    print("불합격");
}Code language: Dart (dart)

4. switch 표현식

Dart 3.0 이상부터 지원하는 문법으로 반환값이 존재하며, 변수에 값을 할당할 때 자주 사용합니다.

switch 문과의 차이점

  1. case 키워드가 없고 => 기호로 패턴과 반환 표현식을 구분합니다.
  2. 모든 분기는 반드시 표현식을 반환해야 하며, 분기 간 콤마로 구분합니다.
  3. 암시적인 fallthrough가 없어 각 분기가 독립적입니다. 매칭되는 패턴이 없을 때는 밑줄 문자 _를 사용하고 default는 쓸 수 없습니다.
  4. 변수 할당, print 호출, 함수 return 등 모든 표현식 자리에서 직접 사용 가능합니다.

1: 기본 문자 연산자 매칭 (논리 OR 패턴)

void main() {
  const slash = 47, star = 42, comma = 44;
  int charCode = 42;

  final token = switch (charCode) {
    slash || star => "연산자",
    comma => "구두점",
    _ => "유효하지 않은 문자"
  };
  print(token);
}
Code language: Dart (dart)

실행 결과

연산자

이 예시는 세 가지 기호의 ASCII 코드를 상수로 정의하고, charCode 변수의 값이 연산자·구두점·기타 무효 기호 중 어느 그룹에 속하는지 구분합니다.

2: switch 표현식 + when 가드

void main() {
  int age = 22;
  String level = switch (age) {
    int a when a < 18 => "미성년자",
    int a when a < 30 => "청년",
    _ => "성인"
  };
  print(level);
}Code language: Dart (dart)

실행 결과

청년

3: 함수 return 내부에 switch 표현식 직접 사용

void main() {
  print(getDesc(3));
}

String getDesc(int num) => switch (num) {
  1 => "일",
  2 => "이",
  _ => "기타 숫자"
};
Code language: Dart (dart)

실행 결과

기타 숫자

참고:여기서 switch는 표현식이고 getDesc는 화살표 함수입니다. 화살표 함수는 다른 C 계열 언어에는 없는 Dart 고유 문법입니다.

5. 완전 매칭 검사 (Exhaustiveness Checking)

입력값에 대응하는 매칭 분기가 누락되면 컴파일 오류가 발생하며, 누락된 case를 전부 채우도록 강제합니다. sealed 클래스, enum 열거형은 기본적으로 완전 검사를 지원하므로 _ / default 보충 분기가 필요하지 않습니다.

1: Nullable 불 타입 완전 매칭 누락 오류 (주석을 풀고 실행하면 오류 확인 가능)

void main() {
  bool? nullableBool = null;
  // 아래 코드 주석을 해제하면 컴파일 오류:null 매칭 분기가 없고 _/default 보충도 없음
  /*
  switch (nullableBool) {
    case true:
      print("참");
    case false:
      print("거짓");
  }
  */
  // 수정 방식:null case 추가 혹은 밑줄 _로 보충
  switch (nullableBool) {
    case true:
      print("참");
    case false:
      print("거짓");
    case null:
      print("null 값");
  }
}Code language: PHP (php)

실행 결과

nullCode language: JavaScript (javascript)

2: sealed 봉인 클래스

대수 데이터 타입으로, 모든 케이스를 매칭하도록 강제하는 기능입니다.

import 'dart:math' as math;

// sealed 클래스:하위 클래스는 현재 파일 안에서만 구현 가능
sealed class Shape {}

// 하위 클래스 정사각형:변 길이 필드
class Square implements Shape {
  final double length;
  Square(this.length);
}

//하위 클래스 원:반지름 필드
class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

// 전달받은 하위 클래스 객체를 switch로 매칭해서 넓이를 계산하는 화살표 함수
double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(radius: var r) => math.pi * r * r
  // Shape에 새로운 하위 클래스를 추가하면 해당 case가 없어 컴파일 오류가 발생
};

void main() {
  final Shape circle = Circle(5);
  print(calculateArea(circle));
  final Shape square = Square(4);
  print(calculateArea(square));
}
Code language: Dart (dart)

실행 결과

78.53981633974483
16.0Code language: CSS (css)

3: Enum 열거형 완전 매칭

보충용 밑줄_를 사용할 필요 없음

void main() {
  final Animal animal = Animal.cat;
  String name = switch (animal) {
    Animal.dog => "강아지",
    Animal.cat => "고양이"
  };
  print(name);
}

enum Animal { dog, cat }
Code language: Dart (dart)

출력:고양이

enum 열거형은 값의 범위가 고정되어 예상 밖의 값이 들어올 수 없으므로 별도 보충 분기를 작성할 필요가 없습니다.

6. when 가드

if-case / switch 문 / switch 표현식 전 구문에서 전부 지원합니다.

when의 역할:패턴 매칭에 성공한 뒤 추가적인 불 연산 검사를 진행합니다. 가드의 결과가 false일 경우 switch를 빠져나가지 않고 다음 분기로 넘어갑니다.

1. if-case + when

void main() {
  List<int> arr = [10, 20];
  if (arr case [int x, int y] when x + y > 20) {
    print("두 수의 합이 20보다 큼");
  }
}Code language: Dart (dart)
두 수의 합이 20보다 큼

먼저 두 개의 정수 요소를 가진 리스트인지 매칭한 뒤, when 구문으로 두 수의 합이 20보다 큰지 재확인합니다.

2. switch 문 + when

void main() {
  int num = 15;
  switch(num) {
    case int n when n % 2 == 0:
      print("짝수");
    case int n when n % 2 != 0:
      print("홀수");
  }
}
Code language: PHP (php)

출력:홀수

먼저 정수 타입인지 확인한 뒤 when 내부 조건을 검사합니다.

3. switch 표현식 + when

void main() {
  int num = 15;
  String res = switch(num) {
    int n when n % 2 == 0 => "짝수",
    _ => "홀수"
  };
  print(res);
}
Code language: PHP (php)

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다