Dart 演算子 — 算術からカスケード、Null 安全性まで

演算子はコードの動詞である — それを習得すればデータを「動かす」ことができる。

1. 学べること


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

(1) 課題:NullPointerException がバッチタスクをクラッシュさせる

Bob の DataPipeline は、数百万件の注文を処理する際に一部の注文に任意のフィールドが欠けていたため、頻繁にクラッシュした。コードには if (value != null) チェックが散在していたが、1 つが見落とされ、30 万件の処理が停止して完全な再実行が必要になった。

(2) Null 安全性演算子による解決

Dart は 3 つの演算子を提供する — ?.(安全アクセス)、??(null 合体)、??=(null 認識代入)— null 処理を簡潔かつ安全にしてくれる。

DART
// 修正前: 冗長な null チェック
String getCity(Order order) {
  if (order.customer != null) {
    if (order.customer!.address != null) {
      return order.customer!.address!.city;
    }
  }
  return 'Unknown';
}

// 修正後: null 安全演算子
String getCity(Order order) =>
    order.customer?.address?.city ?? 'Unknown';
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

(3) 効果


3. 算術演算子と関係演算子

(1) 算術演算子

▶ サンプル:基本的な算術

DART
void main() {
  int a = 17;
  int b = 5;

  print(a + b);   // 22 - 加算
  print(a - b);   // 12 - 減算
  print(a * b);   // 85 - 乗算
  print(a / b);   // 3.4 - 除算(double を返す)
  print(a ~/ b);  // 3 - 整数除算
  print(a % b);   // 2 - 剰余

  // 前置・後置インクリメント
  int count = 10;
  print(count++); // 10(値を返してからインクリメント)
  print(count);   // 11
  print(++count); // 12(インクリメントしてから値を返す)
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
演算子 意味 結果
+ 加算 17 + 5 22
- 減算 17 - 5 12
* 乗算 17 * 5 85
/ 除算(double を返す) 17 / 5 3.4
~/ 整数除算 17 ~/ 5 3
% 剰余 17 % 5 2

(2) 関係演算子

▶ サンプル:比較演算

DART
void main() {
  int orders = 1500;
  int threshold = 1000;

  print(orders > threshold);     // true
  print(orders < threshold);     // false
  print(orders >= 1500);         // true
  print(orders <= 1500);         // true
  print(orders == 1500);         // true
  print(orders != 1000);         // true

  // 文字列比較は辞書順
  print('apple' < 'banana');     // true
  print('USD' == 'USD');         // true
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

4. 論理演算子とビット単位演算子

(1) 論理演算子

▶ サンプル:論理演算

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

  print(isProduction && !hasErrors);  // true - AND + NOT
  print(isProduction || hasErrors);   // true - OR
  print(!isProduction);               // false - NOT

  // 短絡評価
  String? name = null;
  // name.length > 0 はクラッシュするが、次は安全:
  print(name != null && name.isNotEmpty);  // false (安全)
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
演算子 意味 短絡評価
&& 論理 AND 左が false なら右は評価されない
|| 論理 OR 左が true なら右は評価されない
! 論理 NOT

(2) ビット単位演算子

▶ サンプル:ビット単位演算

DART
void main() {
  int flags = 0b1010;  // 2 進数で 10
  int mask = 0b1100;   // 2 進数で 12

  print(flags & mask);   // 8  (0b1000) - AND
  print(flags | mask);   // 14 (0b1110) - OR
  print(flags ^ mask);   // 6  (0b0110) - XOR
  print(~flags);         // -11 (ビット反転) - NOT
  print(flags << 2);     // 40 (0b101000) - 左シフト
  print(flags >> 1);     // 5  (0b0101) - 右シフト
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

5. 代入演算子と複合代入演算子

▶ サンプル:複合代入

DART
void main() {
  int total = 0;
  double revenue = 0.0;

  total += 100;          // total = total + 100 = 100
  total -= 30;           // total = 70
  total *= 2;            // total = 140
  total ~/= 3;           // total = 46 (整数除算)
  revenue += 52500.75;   // revenue = 52500.75

  // null 認識代入
  String? outputPath;
  outputPath ??= '/tmp/output';  // null の場合のみ代入
  print(outputPath);             // /tmp/output
  outputPath ??= '/other/path';  // 代入されない(既に non-null)
  print(outputPath);             // /tmp/output
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
演算子 等価な表現
+= a = a + b total += 100
-= a = a - b total -= 30
*= a = a * b total *= 2
~/= a = a ~/ b total ~/= 3
??= a = a ?? b path ??= '/default'

6. カスケード演算子 ..?..

(1) カスケード演算子

カスケード演算子 .. を使うと、同じオブジェクトに対して変数名を繰り返さずに複数の操作を実行できる。

100%
graph TD
  A[演算子システム] --> B[算術 + 関係 + 論理]
  A --> C[カスケード ..]
  A --> D[Null 安全性 ?. ?? ??=]
  A --> E[型テスト is/as]
  C --> C1["object..method1()..method2()"]
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

▶ サンプル:カスケード演算子

DART
class DataPipeline {
  String name = '';
  int batchSize = 0;
  bool verbose = false;
  final List<String> sources = [];

  void start() => print('$name started');
}

void main() {
  // カスケードなし - 繰り返し
  final pipeline1 = DataPipeline();
  pipeline1.name = 'Analytics';
  pipeline1.batchSize = 50000;
  pipeline1.verbose = true;
  pipeline1.sources.add('orders.csv');
  pipeline1.start();

  // カスケード使用 - 流暢でクリーン
  final pipeline2 = DataPipeline()
    ..name = 'Analytics'
    ..batchSize = 50000
    ..verbose = true
    ..sources.add('orders.csv')
    ..start();
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

▶ サンプル:null 安全カスケード ?..

DART
class Customer {
  String? name;
  String? email;
}

void main() {
  Customer? customer;

  // null 安全カスケード - オブジェクトが null ならスキップ
  customer?..name = 'Bob'..email = 'bob@example.com';
  print(customer);  // null (カスケードがスキップされた)

  customer = Customer();
  customer?..name = 'Bob'..email = 'bob@example.com';
  print(customer?.name);  // Bob
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
演算子 意味 オブジェクトが null の場合
.. カスケード操作 エラー
?.. null 安全カスケード カスケード全体をスキップ

7. Null 安全性演算子

▶ サンプル:3 つの Null 安全性演算子

DART
class Address {
  final String city;
  Address(this.city);
}

class Customer {
  final String name;
  final Address? address;
  Customer(this.name, {this.address});
}

class Order {
  final String id;
  final Customer? customer;
  Order(this.id, {this.customer});
}

void main() {
  // ?. - null 安全アクセス
  Order order = Order('ORD-001');
  print(order.customer?.name);          // null (安全)
  // print(order.customer.name);        // ランタイムエラー!

  // ?? - null 合体
  String city = order.customer?.address?.city ?? 'Unknown';
  print(city);                          // Unknown

  // ??= - null 認識代入
  String? outputPath;
  outputPath ??= '/tmp/reports';
  print(outputPath);                    // /tmp/reports
  outputPath ??= '/other/path';
  print(outputPath);                    // /tmp/reports (変更なし)
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
演算子 意味 結果
?. 安全アクセス obj?.method() null または呼び出し結果
?? null 合体 value ?? default non-null なら値、null ならデフォルト
??= null 認識代入 var ??= value null の場合のみ代入

8. 型テスト演算子

▶ サンプル:is / is! / as

DART
void main() {
  Object value = '1,500.00 USD';

  // is - 型チェック(bool を返す)
  if (value is String) {
    print('String length: ${value.length}');  // 型昇格!
  }

  // is! - 否定型チェック
  if (value is! int) {
    print('Not an integer');
  }

  // as - 型キャスト(失敗時は例外)
  String text = value as String;       // OK
  // int number = value as int;        // ランタイムエラー!

  // 安全なキャストパターン
  if (value is String) {
    String safe = value;               // キャスト不要(昇格)
    print(safe.toUpperCase());
  }
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。
演算子 意味 失敗時
is 型チェック false を返す
is! 否定型チェック true を返す
as 型キャスト TypeError をスロー

9. 完全なサンプル:演算子を使った DataPipeline 注文処理

DART
// ============================================
// 演算子を使った DataPipeline 注文処理
// すべての演算子カテゴリを示す
// ============================================

class Order {
  final String id;
  final double amount;
  String? discountCode;
  double? discountPercent;

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

  // 割引計算用の null 安全演算子
  double get discountedAmount =>
      amount - (amount * (discountPercent ?? 0));

  double get tax => discountedAmount * 0.08;

  double get total => discountedAmount + tax;

  String formatUSD() => '\$${total.toStringAsFixed(2)} USD';
}

class Report {
  final List<Order> orders = [];
  var totalRevenue = 0.0;
  var orderCount = 0;

  void addOrder(Order order) {
    orders.add(order);
    totalRevenue += order.total;       // 複合代入
    orderCount++;
  }

  double get averageOrderValue =>
      orderCount > 0 ? totalRevenue / orderCount : 0;

  bool get isHighVolume => orderCount >= 1000;

  String get statusLabel => isHighVolume ? 'High Volume' : 'Normal';
}

void main() {
  final report = Report()
    ..addOrder(Order(id: 'ORD-001', amount: 1500.0)
      ..discountPercent = 0.1)
    ..addOrder(Order(id: 'ORD-002', amount: 3250.50)
      ..discountCode = 'SAVE20'
      ..discountPercent = 0.2)
    ..addOrder(Order(id: 'ORD-003', amount: 890.25));

  print('=== DataPipeline Order Report ===');
  for (final order in report.orders) {
    final discount = order.discountPercent != null
        ? '${(order.discountPercent! * 100).toInt()}%'
        : 'None';
    print('  ${order.id}: ${order.formatUSD()} (Discount: $discount)');
  }

  print('\n--- Summary ---');
  print('Orders:     ${report.orderCount}');
  print('Revenue:    \$${report.totalRevenue.toStringAsFixed(2)} USD');
  print('Average:    \$${report.averageOrderValue.toStringAsFixed(2)} USD');
  print('Status:     ${report.statusLabel}');

  // 型テスト
  print('\nType check: report is ${report.runtimeType}');
  print('Is Report: ${report is Report}');
}
TEXT
> 出力: ローカルの DartPad または `dart run` で実行してください。この Dart コースの全例は Dart 3.x / Flutter 3.x ベースです。SDK バージョンにより結果が多少異なる場合があります。

出力:

TEXT
=== DataPipeline Order Report ===
  ORD-001: $1458.00 USD (Discount: 10%)
  ORD-002: $2807.43 USD (Discount: 20%)
  ORD-003: $961.47 USD (Discount: None)

--- Summary ---
Orders:     3
Revenue:    $5226.90 USD
Average:    $1742.30 USD
Status:     Normal

Type check: report is Report
Is Report: true

❓ よくある質問

Q: /~/ の違いは何ですか? A: / は常に double を返します(例:17 / 5 = 3.4)。~/ は整数除算の結果を返します(例:17 ~/ 5 = 3)。整数結果が必要なときは ~/ を使います。

Q: カスケード演算子 .. の戻り値は何ですか? A: .. は最後のメソッドの戻り値ではなく、オブジェクト自身を返し、チェーンの継続を可能にします。これが通常のメソッド呼び出しとの重要な違いです。

Q: ??=?? の違いは何ですか? A: ?? は null 合体演算子で、non-null の値を返します。??= は null 認識代入演算子で、変数が null の場合のみ代入し結果を返します。x ??= yx = x ?? y と同等です。

Q: いつ as の代わりに is を使うべきですか? A: 型チェックには is を推奨します。コンパイラが自動型昇格を行うためです。明示的なキャストが必要な場合や型が確実な場合にのみ as を使いましょう。誤ったキャストは例外をスローします。

Q: ?.... はいつ使い分けますか? A: オブジェクトが null の可能性があるときは ?.. を、確実に non-null なときは .. を使います。?.. はオブジェクトが null の場合、カスケード全体をスキップします。

Q: == は参照比較と値比較のどちらですか? A: Dart の == はデフォルトでは参照比較ですが(Java と同様)、多くの組み込み型(String、int、double)は == をオーバーライドして値を比較します。カスタムクラスは ==hashCode の両方をオーバーライドする必要があります。

Q: ビット単位演算子は実際に頻繁に使われますか? A: 頻繁ではありませんが、権限システム(フラグ)、ネットワークプロトコル解析、暗号化アルゴリズムなどのシナリオでは不可欠です。日々の開発では主に算術演算子と論理演算子が使われます。


📖 まとめ


📝 練習問題

  1. 基礎(難易度 ⭐):算術演算子を使って、合計 75,000,000 USD の 1,500,000 件の注文の平均注文額を計算するコードを書いてください。適切な除算演算子を使いましょう。
  2. 中級(難易度 ⭐⭐):カスケード演算子 .. を使ってオブジェクトを作成・設定し、少なくとも 4 つのプロパティを設定して 2 つのメソッドを呼び出してください。その後、カスケードを使わないバージョンと比較しましょう。
  3. 挑戦(難易度 ⭐⭐⭐)?.????= 演算子を使って SafeValue<T> クラスを実装し、安全な値アクセス、デフォルト値、遅延代入を提供し、すべての操作で NullPointerException が発生しないことを確認してください。

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

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%