개발

Dart 접근제어자와 키워드 완벽 가이드

static, final, const, private까지 한번에 정리하기

2025년 9월 18일
20분 읽기
Dart 접근제어자와 키워드 완벽 가이드

왜 이런 키워드들이 필요할까?

프로그래밍에서 "누가 무엇에 접근할 수 있는가?"와 "언제 값이 바뀔 수 있는가?"는 매우 중요한 문제입니다.

잘못된 접근이나 수정을 막아야 하는 이유:
• 버그 방지: 예상치 못한 곳에서 값이 바뀌면 추적이 어려움
• 성능 최적화: 불변값은 컴파일러가 최적화 가능
• 팀 협업: 다른 개발자가 건드리면 안 되는 코드 보호
• 메모리 관리: 불필요한 인스턴스 생성 방지

이런 문제들을 해결하기 위해 static, final, const, private 등의 키워드가 존재합니다.

static - "클래스의 것" vs "인스턴스의 것"

static은 "이것은 클래스에 속한다"는 의미입니다. 개별 인스턴스가 아닌 클래스 자체에 속합니다.

1. static 필드 - 클래스 공유 데이터

Dart
class Counter {
  // ❌ 인스턴스 필드 - 각 객체마다 별도 값
  int instanceCount = 0;
  
  // ✅ static 필드 - 모든 객체가 공유하는 값  
  static int totalCount = 0;
  
  Counter() {
    instanceCount++;     // 이 객체의 카운트만 증가
    totalCount++;        // 전체 카운트 증가 (모든 객체 공유)
  }
  
  void showCounts() {
    print('이 객체: $instanceCount, 전체: $totalCount');
  }
}

void main() {
  final counter1 = Counter();  // totalCount = 1
  final counter2 = Counter();  // totalCount = 2
  final counter3 = Counter();  // totalCount = 3
  
  counter1.showCounts();  // 이 객체: 1, 전체: 3
  counter2.showCounts();  // 이 객체: 1, 전체: 3
  counter3.showCounts();  // 이 객체: 1, 전체: 3
  
  // static 필드는 클래스명으로 직접 접근
  print('총 생성된 객체 수: ${Counter.totalCount}');  // 3
}

2. static 메소드 - 유틸리티 함수

Dart
class MathUtils {
  // static 메소드는 인스턴스 없이 호출 가능
  static double calculateCircleArea(double radius) {
    return 3.14159 * radius * radius;
  }
  
  static String formatCurrency(double amount) {
    return '₩${amount.toStringAsFixed(0).replaceAllMapped(
      RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
      (Match m) => '${m[1]},',
    )}';
  }
  
  static bool isEven(int number) {
    return number % 2 == 0;
  }
}

void main() {
  // 인스턴스를 만들 필요 없이 바로 사용
  print(MathUtils.calculateCircleArea(5.0));    // 78.53975
  print(MathUtils.formatCurrency(1234567));     // ₩1,234,567
  print(MathUtils.isEven(42));                  // true
  
  // 이렇게 할 필요 없음:
  // final utils = MathUtils();  // 불필요!
  // utils.calculateCircleArea(5.0);
}

3. static 변수의 실무 활용

Dart
class ApiConfig {
  // 앱 전체에서 공유하는 설정값들
  static String baseUrl = 'https://api.example.com';
  static Duration timeout = Duration(seconds: 30);
  static Map<String, String> defaultHeaders = {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  };
  
  // 디버그 모드 확인
  static bool isDebugMode = false;
  
  // API 키 관리
  static String? _apiKey;
  
  static void setApiKey(String key) {
    _apiKey = key;
  }
  
  static String get apiKey {
    if (_apiKey == null) {
      throw StateError('API 키가 설정되지 않았습니다');
    }
    return _apiKey!;
  }
}

class Logger {
  static void log(String message) {
    if (ApiConfig.isDebugMode) {
      print('[${DateTime.now()}] $message');
    }
  }
}

void main() {
  // 전역 설정
  ApiConfig.isDebugMode = true;
  ApiConfig.setApiKey('secret-key-123');
  
  // 어디서든 같은 설정 사용
  Logger.log('앱 시작');  // [2023-12-01 10:30:45.123] 앱 시작
  
  print('API URL: ${ApiConfig.baseUrl}');
  print('API Key: ${ApiConfig.apiKey}');
}

final - "한 번만 설정 가능"

final은 "값을 한 번만 설정할 수 있다"는 의미입니다. 하지만 런타임에 결정될 수 있습니다.

1. final 필드 - 생성 후 불변

Dart
class User {
  final String id;           // 생성자에서 설정, 이후 변경 불가
  final String name;
  final DateTime createdAt;
  
  String email;              // 변경 가능한 필드
  
  User({
    required this.id,
    required this.name,
    required this.email,
  }) : createdAt = DateTime.now();  // 런타임에 결정되는 final 값
  
  void updateEmail(String newEmail) {
    email = newEmail;        // ✅ 가능
    // name = '새이름';        // ❌ 컴파일 에러! final 필드는 변경 불가
  }
}

void main() {
  final user = User(
    id: 'user123',
    name: '김철수',
    email: 'kim@example.com',
  );
  
  print('ID: ${user.id}');               // user123
  print('생성시간: ${user.createdAt}');     // 2023-12-01 10:30:45.123
  
  user.updateEmail('new@example.com');   // ✅ email은 변경 가능
  // user.id = 'newid';                  // ❌ final 필드는 변경 불가
}

2. final vs var - 언제 무엇을 쓸까?

Dart
void demonstrateFinalVsVar() {
  // ✅ 변경되지 않을 값은 final 사용 (권장)
  final String apiUrl = 'https://api.example.com';
  final DateTime now = DateTime.now();
  final List<String> fruits = ['사과', '바나나', '포도'];
  
  // ✅ 변경될 수 있는 값은 var 사용
  var counter = 0;
  var isLoading = false;
  
  // final 컬렉션의 내용은 변경 가능!
  fruits.add('딸기');              // ✅ 가능 - 리스트 내용 변경
  // fruits = ['새로운', '리스트'];   // ❌ 불가능 - 리스트 자체 교체
  
  // 변경 가능한 변수들
  counter++;                      // ✅ 가능
  isLoading = true;               // ✅ 가능
  
  // final 값들은 변경 불가
  // apiUrl = '새로운 URL';          // ❌ 컴파일 에러
  // now = DateTime.now();         // ❌ 컴파일 에러
  
  print('과일: $fruits');           // [사과, 바나나, 포도, 딸기]
  print('카운터: $counter');        // 1
}

3. late final - 나중에 초기화

Dart
class DatabaseService {
  // late final - 나중에 한 번만 초기화
  late final String connectionString;
  late final DatabaseConnection _connection;
  
  // 초기화 메소드
  Future<void> initialize(String host, int port, String database) async {
    // 한 번만 설정 가능
    connectionString = 'postgresql://$host:$port/$database';
    
    // 복잡한 초기화 과정
    _connection = await DatabaseConnection.connect(connectionString);
    
    print('데이터베이스 연결 완료: $connectionString');
  }
  
  Future<List<Map<String, dynamic>>> query(String sql) async {
    // _connection이 초기화되지 않았으면 에러 발생
    return await _connection.query(sql);
  }
}

void main() async {
  final db = DatabaseService();
  
  // 초기화 전에는 접근 불가
  // print(db.connectionString);  // ❌ 런타임 에러!
  
  await db.initialize('localhost', 5432, 'myapp');
  
  // 이제 접근 가능
  print(db.connectionString);  // postgresql://localhost:5432/myapp
  
  // 재초기화 시도하면 에러
  // db.connectionString = 'new connection';  // ❌ 컴파일 에러!
}

const - "컴파일 타임 상수"

const는 "컴파일 시점에 값이 결정되는 상수"입니다. final보다 더 엄격하며, 성능상 이점이 있습니다.

1. const vs final 차이점

Dart
class ConstantExamples {
  // ✅ const - 컴파일 타임에 값이 결정됨
  static const String appName = 'My Flutter App';
  static const int maxRetries = 3;
  static const Duration shortDelay = Duration(milliseconds: 500);
  
  // ✅ final - 런타임에 값이 결정됨  
  static final String sessionId = DateTime.now().millisecondsSinceEpoch.toString();
  static final DateTime appStartTime = DateTime.now();
  
  // ❌ const는 런타임 값 불가
  // static const DateTime now = DateTime.now();  // 컴파일 에러!
}

void main() {
  // const 값들은 컴파일 시점에 이미 결정됨
  print('앱 이름: ${ConstantExamples.appName}');        // My Flutter App
  print('최대 재시도: ${ConstantExamples.maxRetries}');   // 3
  
  // final 값들은 런타임에 결정됨
  print('세션 ID: ${ConstantExamples.sessionId}');      // 1701234567890
  print('앱 시작: ${ConstantExamples.appStartTime}');    // 2023-12-01 10:30:45.123
}

2. const 생성자 - 메모리 최적화

Dart
class Color {
  final int red;
  final int green;
  final int blue;
  
  // const 생성자 - 컴파일 타임에 인스턴스 생성
  const Color(this.red, this.green, this.blue);
  
  // 미리 정의된 색상들
  static const Color black = Color(0, 0, 0);
  static const Color white = Color(255, 255, 255);
  static const Color red = Color(255, 0, 0);
  static const Color green = Color(0, 255, 0);
  static const Color blue = Color(0, 0, 255);
  
  @override
  String toString() => 'Color($red, $green, $blue)';
}

void main() {
  // const 인스턴스들은 메모리에서 재사용됨
  const color1 = Color(255, 0, 0);
  const color2 = Color(255, 0, 0);
  const color3 = Color.red;
  
  // 모두 같은 메모리 주소를 가짐 (메모리 효율적!)
  print(identical(color1, color2));  // true
  print(identical(color1, color3));  // true
  print(identical(color2, color3));  // true
  
  // 런타임 생성은 매번 새 인스턴스
  final color4 = Color(255, 0, 0);
  final color5 = Color(255, 0, 0);
  print(identical(color4, color5));  // false (다른 메모리 주소)
}

3. const 컬렉션

Dart
class AppConstants {
  // ✅ const 리스트 - 불변 컬렉션
  static const List<String> supportedLanguages = [
    'ko',
    'en',
    'ja',
    'zh',
  ];
  
  // ✅ const 맵 - 불변 맵
  static const Map<String, String> apiEndpoints = {
    'users': '/api/v1/users',
    'orders': '/api/v1/orders',
    'products': '/api/v1/products',
  };
  
  // ✅ const Set - 불변 집합
  static const Set<String> adminRoles = {
    'super_admin',
    'admin',
    'moderator',
  };
}

void main() {
  // const 컬렉션은 완전히 불변
  print(AppConstants.supportedLanguages);  // [ko, en, ja, zh]
  
  // 내용 변경 시도하면 런타임 에러
  try {
    AppConstants.supportedLanguages.add('fr');  // ❌ 런타임 에러!
  } catch (e) {
    print('에러: $e');  // Unsupported operation: Cannot add to unmodifiable list
  }
  
  // 읽기 전용 접근은 가능
  final hasKorean = AppConstants.supportedLanguages.contains('ko');
  print('한국어 지원: $hasKorean');  // true
}

private (_) - "외부 접근 금지"

Dart에서는 _(언더스코어)로 시작하는 이름을 private으로 처리합니다. 같은 파일(라이브러리) 내에서만 접근 가능합니다.

1. private 필드와 메소드

Dart
// user_service.dart 파일
class UserService {
  // ✅ public 필드 - 외부에서 접근 가능
  String version = '1.0.0';
  
  // ❌ private 필드 - 외부에서 접근 불가  
  String _apiKey = 'secret-key-123';
  int _retryCount = 0;
  
  // ✅ public 메소드
  Future<User> getUser(String id) async {
    _logRequest('Fetching user: $id');  // private 메소드 호출
    
    try {
      return await _makeApiCall('/users/$id');
    } catch (e) {
      return await _handleError(e);
    }
  }
  
  // ❌ private 메소드들 - 내부 구현 세부사항
  void _logRequest(String message) {
    print('[${DateTime.now()}] $message');
  }
  
  Future<User> _makeApiCall(String endpoint) async {
    _retryCount++;
    
    // API 호출 로직 (복잡한 내부 구현)
    await Future.delayed(Duration(milliseconds: 500));
    
    return User(id: '123', name: '김철수');
  }
  
  Future<User> _handleError(dynamic error) async {
    _logRequest('Error occurred: $error');
    
    // 에러 처리 로직
    if (_retryCount < 3) {
      return await _makeApiCall('/users/default');
    }
    
    throw Exception('사용자 조회 실패');
  }
  
  // ✅ public getter로 private 값에 안전하게 접근
  int get currentRetryCount => _retryCount;
  
  // ✅ private 값 변경을 위한 public 메소드
  void resetRetryCount() {
    _retryCount = 0;
  }
}

class User {
  final String id;
  final String name;
  User({required this.id, required this.name});
}

2. private 사용 예제

Dart
// main.dart 파일
import 'user_service.dart';

void main() {
  final userService = UserService();
  
  // ✅ public 멤버 접근 가능
  print('버전: ${userService.version}');
  print('재시도 횟수: ${userService.currentRetryCount}');
  
  // ❌ private 멤버 접근 불가 (컴파일 에러)
  // print(userService._apiKey);        // 에러!
  // userService._logRequest('test');  // 에러!
  // userService._retryCount = 5;       // 에러!
  
  // ✅ public 메소드 호출 가능
  userService.getUser('123');
  userService.resetRetryCount();
}

3. private 생성자 - 싱글톤 패턴

Dart
class AppLogger {
  static AppLogger? _instance;
  
  // ❌ private 생성자 - 외부에서 직접 생성 불가
  AppLogger._internal();
  
  // ✅ public factory 생성자 - 싱글톤 보장
  factory AppLogger() {
    _instance ??= AppLogger._internal();
    return _instance!;
  }
  
  void log(String message) {
    print('[LOG] $message');
  }
}

class DatabaseConnection {
  String _connectionString;
  bool _isConnected = false;
  
  // ❌ private 생성자
  DatabaseConnection._(this._connectionString);
  
  // ✅ public factory 생성자 - 검증 로직 포함
  factory DatabaseConnection.create(String host, int port, String database) {
    if (host.isEmpty) {
      throw ArgumentError('호스트는 비어있을 수 없습니다');
    }
    if (port <= 0 || port > 65535) {
      throw ArgumentError('포트는 1-65535 범위여야 합니다');
    }
    
    final connectionString = 'postgresql://$host:$port/$database';
    return DatabaseConnection._(connectionString);
  }
  
  Future<void> connect() async {
    if (_isConnected) return;
    
    // 연결 로직
    await Future.delayed(Duration(seconds: 1));
    _isConnected = true;
    print('데이터베이스 연결됨: $_connectionString');
  }
  
  bool get isConnected => _isConnected;
}

void main() {
  // ✅ 올바른 사용법
  final logger1 = AppLogger();
  final logger2 = AppLogger();
  print(identical(logger1, logger2));  // true - 같은 인스턴스
  
  final db = DatabaseConnection.create('localhost', 5432, 'myapp');
  db.connect();
  
  // ❌ private 생성자 직접 호출 불가
  // final logger3 = AppLogger._internal();  // 컴파일 에러!
  // final db2 = DatabaseConnection._('test');  // 컴파일 에러!
}

실무에서의 활용 패턴

1. Repository 패턴에서의 활용

Dart
abstract class UserRepository {
  Future<User> getUser(String id);
  Future<void> saveUser(User user);
}

class ApiUserRepository implements UserRepository {
  // ✅ private - 외부에서 직접 접근하면 안 되는 구현 세부사항
  final String _baseUrl;
  final Map<String, String> _headers;
  static const Duration _defaultTimeout = Duration(seconds: 30);
  
  // ✅ final - 생성 후 변경되면 안 되는 설정
  final Duration timeout;
  
  ApiUserRepository({
    required String baseUrl,
    required Map<String, String> headers,
    this.timeout = _defaultTimeout,
  }) : _baseUrl = baseUrl,
       _headers = Map.unmodifiable(headers);  // 불변 맵 생성
  
  @override
  Future<User> getUser(String id) async {
    final url = _buildUrl('/users/$id');
    return await _makeRequest(url);
  }
  
  @override  
  Future<void> saveUser(User user) async {
    final url = _buildUrl('/users/${user.id}');
    await _makeRequest(url, method: 'PUT', body: user.toJson());
  }
  
  // ❌ private - 내부 구현 로직
  String _buildUrl(String endpoint) {
    return '$_baseUrl$endpoint';
  }
  
  Future<User> _makeRequest(String url, {String method = 'GET', Map<String, dynamic>? body}) async {
    // HTTP 요청 로직
    await Future.delayed(Duration(milliseconds: 500));
    return User(id: '123', name: '김철수');
  }
}

2. State Management에서의 활용

Dart
class CounterState {
  // ✅ private - 외부에서 직접 수정하면 안 되는 상태
  int _count = 0;
  
  // ✅ public getter - 상태 읽기만 허용
  int get count => _count;
  
  // ✅ public 메소드 - 상태 변경을 위한 안전한 인터페이스
  void increment() {
    _count++;
    _notifyListeners();
  }
  
  void decrement() {
    if (_count > 0) {  // 비즈니스 로직으로 보호
      _count--;
      _notifyListeners();
    }
  }
  
  void reset() {
    _count = 0;
    _notifyListeners();
  }
  
  // ❌ private - 내부 구현 세부사항
  final List<VoidCallback> _listeners = [];
  
  void _notifyListeners() {
    for (final listener in _listeners) {
      listener();
    }
  }
  
  void addListener(VoidCallback listener) {
    _listeners.add(listener);
  }
  
  void removeListener(VoidCallback listener) {
    _listeners.remove(listener);
  }
}

typedef VoidCallback = void Function();

void main() {
  final counterState = CounterState();
  
  // ✅ 올바른 사용법
  print('현재 카운트: ${counterState.count}');  // 0
  
  counterState.increment();
  counterState.increment();
  print('카운트: ${counterState.count}');        // 2
  
  counterState.decrement();
  print('카운트: ${counterState.count}');        // 1
  
  // ❌ 잘못된 사용법 (컴파일 에러)
  // counterState._count = 100;           // private 필드 직접 수정 불가
  // counterState._notifyListeners();     // private 메소드 호출 불가
}

3. 설정 클래스 패턴

Dart
class AppConfig {
  // ✅ static const - 앱 전체에서 사용하는 불변 설정
  static const String appName = 'My Flutter App';
  static const String version = '1.0.0';
  static const Duration defaultTimeout = Duration(seconds: 30);
  
  // ✅ static final - 런타임에 결정되는 전역 설정
  static final String buildTime = DateTime.now().toIso8601String();
  
  // ❌ private static - 내부에서만 사용하는 설정
  static const String _encryptionKey = 'super-secret-key';
  static const List<String> _allowedDomains = [
    'api.myapp.com',
    'staging-api.myapp.com',
  ];
  
  // ✅ public 메소드 - 안전한 접근 인터페이스
  static bool isAllowedDomain(String domain) {
    return _allowedDomains.contains(domain);
  }
  
  static String encryptData(String data) {
    // _encryptionKey를 사용한 암호화 로직
    return 'encrypted_$data';
  }
}

class EnvironmentConfig {
  final String apiBaseUrl;
  final bool isDebugMode;
  final Duration cacheDuration;
  
  // ✅ const 생성자 - 컴파일 타임 상수 환경
  const EnvironmentConfig._({
    required this.apiBaseUrl,
    required this.isDebugMode,
    required this.cacheDuration,
  });
  
  // ✅ 미리 정의된 환경들
  static const EnvironmentConfig development = EnvironmentConfig._(
    apiBaseUrl: 'https://dev-api.myapp.com',
    isDebugMode: true,
    cacheDuration: Duration(minutes: 1),
  );
  
  static const EnvironmentConfig production = EnvironmentConfig._(
    apiBaseUrl: 'https://api.myapp.com',
    isDebugMode: false,
    cacheDuration: Duration(hours: 1),
  );
  
  // ✅ 현재 환경 (런타임에 결정)
  static late final EnvironmentConfig current;
  
  static void initialize(EnvironmentConfig config) {
    current = config;
  }
}

void main() {
  // 환경 초기화
  EnvironmentConfig.initialize(EnvironmentConfig.development);
  
  // 설정 사용
  print('앱 이름: ${AppConfig.appName}');
  print('API URL: ${EnvironmentConfig.current.apiBaseUrl}');
  print('디버그 모드: ${EnvironmentConfig.current.isDebugMode}');
  print('도메인 허용: ${AppConfig.isAllowedDomain('api.myapp.com')}');
  
  // ❌ private 설정에 직접 접근 불가
  // print(AppConfig._encryptionKey);  // 컴파일 에러!
}

헷갈리기 쉬운 개념들 정리

1. static vs instance

Dart
class ComparisonExample {
  // 인스턴스 필드 - 각 객체마다 별도 메모리
  String instanceField = 'instance';
  
  // static 필드 - 모든 객체가 공유하는 하나의 메모리
  static String staticField = 'static';
  
  // 인스턴스 메소드 - 객체를 통해서만 호출 가능
  void instanceMethod() {
    print('Instance method called');
    print('Instance field: $instanceField');
    print('Static field: $staticField');  // static 접근 가능
  }
  
  // static 메소드 - 클래스명으로 직접 호출 가능
  static void staticMethod() {
    print('Static method called');
    print('Static field: $staticField');
    // print('Instance field: $instanceField');  // ❌ 에러! 인스턴스 필드 접근 불가
  }
}

void main() {
  // static 멤버는 인스턴스 없이도 접근 가능
  print(ComparisonExample.staticField);    // static
  ComparisonExample.staticMethod();        // Static method called
  
  // 인스턴스 멤버는 객체를 통해서만 접근
  final obj = ComparisonExample();
  print(obj.instanceField);                // instance
  obj.instanceMethod();                    // Instance method called
}

2. final vs const vs static const

Dart
class ConstantComparison {
  // final - 런타임에 한 번만 설정, 인스턴스마다 다를 수 있음
  final String id = DateTime.now().millisecondsSinceEpoch.toString();
  
  // static final - 런타임에 한 번만 설정, 모든 인스턴스가 공유
  static final String sessionId = DateTime.now().millisecondsSinceEpoch.toString();
  
  // const - 컴파일 타임 상수, 변경 불가
  static const String appName = 'My App';
  
  // static const - 컴파일 타임 상수, 모든 인스턴스가 공유
  static const int maxUsers = 1000;
}

void main() {
  final obj1 = ConstantComparison();
  final obj2 = ConstantComparison();
  
  // final - 인스턴스마다 다른 값
  print('obj1 ID: ${obj1.id}');        // 1701234567890
  print('obj2 ID: ${obj2.id}');        // 1701234567891
  
  // static final - 모든 인스턴스가 같은 값 공유
  print('Session ID: ${ConstantComparison.sessionId}');  // 모두 같은 값
  
  // static const - 컴파일 타임에 결정된 상수
  print('App Name: ${ConstantComparison.appName}');      // My App
  print('Max Users: ${ConstantComparison.maxUsers}');    // 1000
}

3. private 범위 이해하기

Dart
// file1.dart
class ClassInFile1 {
  String _privateField = 'private in file1';
  
  void accessOtherPrivate(ClassInFile1 other) {
    // 같은 파일의 같은 클래스라면 private 멤버 접근 가능
    print(other._privateField);  // ✅ 가능
  }
}

class AnotherClassInFile1 {
  void accessPrivate() {
    final obj = ClassInFile1();
    // 같은 파일의 다른 클래스에서도 private 멤버 접근 가능
    print(obj._privateField);    // ✅ 가능
  }
}

// file2.dart (다른 파일)
// import 'file1.dart';
// 
// class ClassInFile2 {
//   void tryAccess() {
//     final obj = ClassInFile1();
//     print(obj._privateField);  // ❌ 에러! 다른 파일에서는 접근 불가
//   }
// }

실무 가이드라인

1. 언제 무엇을 사용할까?

static 사용 시기:
• 유틸리티 함수들 (MathUtils.calculate())
• 전역 설정값들 (AppConfig.apiUrl)
• 싱글톤 인스턴스 관리
• 캐시나 공유 상태

final 사용 시기:
• 생성 후 변경되면 안 되는 필드
• 생성자에서 설정되는 ID, 타임스탬프 등
• 의존성 주입으로 받은 객체들

const 사용 시기:
• 컴파일 타임에 알 수 있는 상수들
• 색상, 크기, 문자열 등 고정값들
• Widget에서 성능 최적화가 필요할 때

private 사용 시기:
• 외부에서 접근하면 안 되는 내부 구현
• 클래스의 내부 상태
• 헬퍼 메소드들

2. 성능 최적화 팁

Dart
class PerformanceOptimized {
  // ✅ static const - 메모리에 한 번만 생성
  static const List<String> categories = ['전자제품', '의류', '도서'];
  
  // ✅ late final - 필요할 때만 계산
  late final String expensiveCalculation = _calculateSomethingExpensive();
  
  // ✅ static final - 앱 전체에서 한 번만 생성
  static final RegExp emailRegex = RegExp(r'^[^@]+@[^@]+\.[^@]+$');
  
  String _calculateSomethingExpensive() {
    // 복잡한 계산...
    return '계산된 값';
  }
  
  // ✅ const 생성자 - Widget에서 성능 향상
  const PerformanceOptimized.constant();
}

// Flutter Widget에서의 활용
class MyWidget extends StatelessWidget {
  const MyWidget({Key? key}) : super(key: key);  // const 생성자
  
  @override
  Widget build(BuildContext context) {
    return const Text('Hello');  // const Widget - 리빌드 시 재생성 안됨
  }
}

정리

Dart의 키워드들을 올바르게 사용하면 더 안전하고 효율적인 코드를 작성할 수 있습니다.

핵심 원칙:

static: 클래스 레벨의 공유 데이터나 유틸리티 함수에 사용
final: 한 번 설정 후 변경되면 안 되는 값에 사용
const: 컴파일 타임 상수로 성능 최적화에 유리
private (_): 외부에서 접근하면 안 되는 내부 구현에 사용

실무에서 기억할 점:

가능한 한 불변성을 선호하세요 (final, const 적극 활용)
외부 인터페이스와 내부 구현을 분리하세요 (private 활용)
성능이 중요한 곳에서는 const를 적극 활용하세요
전역 상태는 static으로 관리하되, 남용하지 마세요

이런 키워드들을 제대로 이해하고 사용하면 버그가 적고, 성능이 좋으며, 유지보수하기 쉬운 코드를 작성할 수 있습니다.
#Dart
#Flutter
#Static
#Final
#Const
#Private