Dart 変数とデータ型 — 5 つの宣言方法

変数はデータの入れ物、型は入れ物の形である — 適切な入れ物を選べば、コードは安全になる。

1. 学べること


2. 開発者のリアルな物語

(1) 課題:場当たり的な変数宣言がランタイムクラッシュを招く

Bob が DataPipeline の初期バージョンを開発していたとき、彼はすべての変数を var で宣言し、途中で型をよく変更した。リファクタリング中に誤って int orderCountString orderCount に変更してしまった。コンパイルはエラーを報告せず(動的型)、実行時に orderCount * price が例外を投げ、50 万件のバッチ処理が中断され、レポートの納品が 4 時間遅れた。

(2) 型安全な解決法

Dart は緩いものから厳しいものまでさまざまな宣言方法を提供する:const(コンパイル時定数)→ final(実行時定数)→ var(推論型)→ late(遅延初期化)→ dynamic(動的型)。これらを適切に使い分ければ、コンパイラがバグの発見を助けてくれる。

DART
// DataPipeline 設定のベストプラクティス
const int maxRecords = 1000000;       // コンパイル時定数
final String pipelineName;             // 実行時定数(1 回だけ設定)
var processedCount = 0;                // int として推論、ミュータブル
late String outputPath;                // 後で初期化
// dynamic は可能な限り避ける
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

(3) 効果


3. 5 つの宣言方法

(1) 宣言方法決定ツリー

100%
graph LR
  subgraph 宣言
    A[var] --> B[型推論]
    C[final] --> D[実行時定数]
    E[const] --> F[コンパイル時定数]
    G[late] --> H[遅延初期化]
    I[dynamic] --> J[動的型]
  end
  subgraph 組み込み型
    K[int] --- L[double]
    M[String] --- N[bool]
  end
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

▶ サンプル:var — 型推論

DART
void main() {
  var name = 'DataPipeline';   // String として推論
  var count = 1000;            // int として推論
  var rate = 0.08;             // double として推論

  // 同じ型で再代入可能
  name = 'Analytics';          // OK - String のまま
  count = 2000;                // OK - int のまま

  // 型は変更できない
  // count = 'two thousand';   // コンパイルエラー!
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

▶ サンプル:final — 実行時定数

DART
void main() {
  // 実行時に 1 回だけ設定
  final DateTime startTime = DateTime.now();
  final String configPath = '/etc/datapipeline/config.yaml';

  // 再代入不可
  // configPath = '/other/path';  // コンパイルエラー!

  // コレクションの final - 中身は変更可能
  final List<String> sources = ['orders.csv', 'products.csv'];
  sources.add('customers.csv');   // OK - 中身の変更
  // sources = ['new_list'];       // エラー - 参照の再代入不可
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

▶ サンプル:const — コンパイル時定数

DART
void main() {
  // コンパイル時に判明している必要がある
  const int maxRecords = 1000000;
  const double taxRate = 0.08;
  const String version = '1.0.0';

  // const コレクションは深くイミュータブル
  const List<String> formats = ['json', 'csv', 'html'];
  // formats.add('xml');  // エラー!const リストは変更不可

  // const コンストラクタ
  const config = PipelineConfig(batchSize: 10000);
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

▶ サンプル:late — 遅延初期化

DART
class DataPipeline {
  late String outputPath;  // 後で設定

  void configure(String path) {
    outputPath = path;
  }

  late final int maxRecords = _loadMaxRecords();  // 初回アクセス時に計算

  int _loadMaxRecords() {
    print('Loading max records config...');
    return 1000000;
  }
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

▶ サンプル:dynamic — 動的型

DART
void main() {
  dynamic value = 'hello';   // String
  value = 42;                // OK - int になる
  value = [1, 2, 3];        // OK - List になる

  // コンパイル時の型チェックなし - 危険!
  // value.nonExistentMethod();  // コンパイルエラーなし、ランタイムクラッシュ

  // 柔軟性が必要なときは dynamic より Object? を推奨
  Object? safeValue = 'test';
  // safeValue.nonExistentMethod();  // コンパイルエラー - より安全
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

(2) 5 つの宣言方法の比較

宣言方法 ミュータブル 初期化タイミング 安全性 用途
const コンパイル時決定 イミュータブル 宣言時 最高 設定定数、列挙値
final 実行時決定 イミュータブル 宣言時またはコンストラクタ内 ランタイム設定、注入値
var 推論 ミュータブル 宣言時 ローカル変数、カウンター
late 明示的アノテーション ミュータブル 遅延 遅延初期化フィールド
dynamic 実行時 ミュータブル いつでも JSON パース、相互運用

4. 組み込み型の詳細

(1) 数値型

▶ サンプル:int と double

DART
void main() {
  int orderCount = 1500;
  double totalAmount = 52500.75;
  num genericNum = 42;      // num は int と double のスーパークラス
  genericNum = 3.14;        // OK - num は両方を受け入れる

  // 算術演算
  double avgOrderValue = totalAmount / orderCount;
  int roundedDown = totalAmount.toInt();

  // 便利なメソッド
  print(totalAmount.toStringAsFixed(2));  // 52500.75
  print(orderCount.isEven);              // false (1500 は偶数、true)
  print(orderCount.clamp(0, 1000));      // 1000 (最大値にクランプ)
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
範囲 用途
int 64 ビット(JS では異なる) カウント、インデックス
double 64 ビット IEEE 754 金額、比率
num int + double のスーパークラス 一般的な数値

(2) String 型

▶ サンプル:文字列補間と操作

DART
void main() {
  // 文字列補間
  String product = 'DataPipeline';
  int users = 5000;
  print('$product has $users users');               // 単純な補間
  print('Revenue: ${users * 99} USD');              // 式の補間
  print('Average: ${(125000 / users).toStringAsFixed(2)} USD');

  // 複数行文字列
  String description = '''
  DataPipeline - E-Commerce Analytics Tool
  Processes up to 1,000,000 orders per batch
  Supports JSON, CSV, and HTML output formats
  ''';

  // ロー文字列(エスケープ処理なし)
  String path = r'C:\Users\Bob\data\orders.csv';
  String regex = r'\d{4}-\d{2}-\d{2}';  // 日付パターン

  // 便利なメソッド
  String raw = '  Hello, World!  ';
  print(raw.trim());                  // 'Hello, World!'
  print(raw.toUpperCase());           // '  HELLO, WORLD!  '
  print('USD 1,500.00'.replaceAll(',', ''));  // 'USD 1500.00'
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
機能 構文
単純な補間 $variable '$name'
式の補間 ${expr} '${a + b}'
複数行文字列 '''...''' トリプルクォート
ロー文字列 r'...' r'\n' はそのまま

(3) bool 型

▶ サンプル:ブール値と条件

DART
void main() {
  bool isProduction = true;
  bool isDebug = false;

  // Dart は条件に明示的な bool を要求する
  int count = 0;
  // if (count) {}  // エラー!bool 必須
  if (count > 0) {  // OK - 明示的な比較
    print('Has records');
  }

  // 論理演算子
  bool shouldProcess = isProduction && !isDebug;
  bool hasData = count > 0 || isProduction;
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

5. 型推論と明示的アノテーション

(1) ベストプラクティス

DART
// 良い - ローカル変数はコンパイラに推論させる
var name = 'DataPipeline';     // String と推論
var count = 100;               // int と推論
var items = <String>[];        // 明示的なジェネリック、List と推論

// 良い - 公開 API は明示的に型付け
String get displayName => name;
int get maxCapacity => 1000000;

// 良い - 変更されない値には final を使う
final startTime = DateTime.now();

// 避ける - ローカル変数に不要な明示型
// String name = 'DataPipeline';  // 冗長
// int count = 100;               // 冗長
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
シナリオ 推奨 理由
ローカル変数 var / final 冗長性を削減、推論で十分
公開 API 明示的アノテーション 明確なドキュメント、安定したインターフェース
コレクションジェネリック 明示的ジェネリック List<dynamic> として推論されるのを避ける
コンストラクタパラメータ 明示的アノテーション 明確な契約

6. Bob のシナリオ:DataPipeline の設定変数

▶ サンプル:設定変数の設計

DART
// DataPipeline 設定定数
const String appName = 'DataPipeline';
const int defaultBatchSize = 10000;
const double defaultTaxRate = 0.08;

// ランタイム設定
class PipelineConfig {
  final String version;
  final int batchSize;
  final String outputFormat;

  PipelineConfig({
    this.version = '1.0.0',
    this.batchSize = defaultBatchSize,
    this.outputFormat = 'json',
  });

  @override
  String toString() =>
      '$appName v$version | Batch: $batchSize | Format: $outputFormat';
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

7. 完全なサンプル:DataPipeline 変数宣言の実践

DART
// ============================================
// DataPipeline 変数宣言デモ
// すべての宣言型の正しい使い方を示す
// ============================================

// コンパイル時定数 - 実行間で決して変わらない
const String appName = 'DataPipeline';
const int maxRecords = 1000000;
const double taxRate = 0.08;
const List<String> supportedFormats = ['json', 'csv', 'html'];

class Order {
  final String id;          // 構築後にランタイム定数
  final double amount;      // 構築後にランタイム定数
  late String status;       // 処理ロジックで設定

  Order({required this.id, required this.amount});

  double get tax => amount * taxRate;
  double get total => amount + tax;

  String get formattedAmount =>
      '\$${amount.toStringAsFixed(2)} USD';
}

class DataPipeline {
  final String name;
  var processedCount = 0;              // ミュータブルカウンター
  late final DateTime startTime;       // 初回実行時に設定

  DataPipeline({required this.name});

  void start() {
    startTime = DateTime.now();
    print('$appName started at $startTime');
  }

  void processOrder(Order order) {
    processedCount++;
    order.status = 'processed';
    print('Order ${order.id}: ${order.formattedAmount} '
        '(tax: \$${order.tax.toStringAsFixed(2)} USD)');
  }

  void printSummary() {
    print('\n=== Summary ===');
    print('Pipeline: $name');
    print('Processed: $processedCount orders');
    print('Capacity: $processedCount / $maxRecords');
  }
}

void main() {
  final pipeline = DataPipeline(name: 'E-Commerce Analytics');
  pipeline.start();

  final orders = [
    Order(id: 'ORD-001', amount: 1500.00),
    Order(id: 'ORD-002', amount: 3250.50),
    Order(id: 'ORD-003', amount: 890.25),
  ];

  for (final order in orders) {
    pipeline.processOrder(order);
  }

  pipeline.printSummary();
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

出力:

TEXT
DataPipeline started at 2024-01-15 10:30:00.000
Order ORD-001: $1500.00 USD (tax: $120.00 USD)
Order ORD-002: $3250.50 USD (tax: $260.04 USD)
Order ORD-003: $890.25 USD (tax: $71.22 USD)

=== Summary ===
Pipeline: E-Commerce Analytics
Processed: 3 orders
Capacity: 3 / 1000000

❓ よくある質問

Q: var と final のどちらを使うべきですか? A: 再代入しない変数は final を優先してください。変更が必要な場合は var を使います。公式 Dart lint ルールの prefer_final_localsfinal の優先を推奨しています。

Q: const と final の違いは何ですか? A: const はコンパイル時定数で、値はコンパイル時に決定されている必要があります。final は実行時定数で、1 回だけ代入できますが、値は実行時に計算できます。DateTime.now()const にできません。

Q: どんなときに late を使うべきですか? A: 宣言時点で初期化できないが、使用前に必ず初期化されることが保証できる場合に使ってください。一般的なシナリオは依存性注入や構築後の設定です。誤用は実行時に LateInitializationError を引き起こします。

Q: dynamic と Object? の違いは何ですか? A: dynamic はすべての型チェックを無効にし、コンパイラはメソッド呼び出しを検証しません。Object? は型チェックを保持し、Object のメソッドのみ呼び出せます。柔軟性が必要なときは Object? を推奨します。

Q: Dart の int は Web と VM で同じですか? A: 正確には同じではありません。Dart VM では int は 64 ビット整数です。JS にコンパイルされると、int は JS Number の制限(53 ビット精度)の影響を受けます。大きな数値演算には注意が必要です。

Q: String にシングルクォートとダブルクォートのどちらを使えますか? A: どちらも問題なく、全く同じ効果があります。公式 Dart スタイルガイドではシングルクォートが推奨され、文字列自体にシングルクォートを含む場合のみダブルクォートを使います。

Q: num 型はいつ使うべきですか? A: パラメータや変数が intdouble の両方を受け入れる必要がある場合に使ってください。numintdouble のスーパークラスで、基本的な算術演算をサポートします。


📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):次の変数を宣言してください:const のアプリケーション名、final の現在時刻、var のカウンター。それぞれの値と型を表示しましょう。
  2. 中級(難易度 ⭐⭐)num 型のパラメータを受け取り、その数値を USD 形式の文字列にして返す関数を書いてください(例:formatUSD(1500.5)"$1,500.50 USD" を返す)。intdouble の両方の入力を扱います。
  3. 上級(難易度 ⭐⭐⭐)late を使ってデータベース接続文字列を遅延初期化し、const でデフォルト値を定義し、final で環境変数から読み込んだ設定を保存する Config クラスを設計してください。この 3 つの宣言方法の連携を実演しましょう。

← 前のレッスン | 次のレッスン →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%