フラツターメモsyogaku

return () { ... };
無名関数(anonymous function)
関数を返している」
つまり「関数を作って、それ自体を返す」
Function createRecipe() {
return () {
print('今日はカレーを作るよ');
};
}
void main() {
var recipe = createRecipe();//関数を受け取っている
recipe();//受け取った関数を実行

高階関数シンプル 「レシピを受け取って料理を作る料理人」
-型指定しないVar
void cook(Function recipe) {
print('材料を準備して...');
recipe();
print('盛り付けて完成!');
}
void pastaRecipe() {
print('パスタを茹でてソースを絡める');
}
void main() {
cook(pastaRecipe);
}
型を指定しなければ抽象度が高くても許容される自由さがある
型指定するvar
void cook(void Function() recipe) {
print('材料準備!');
recipe();
print('完成!');
}
void pastaRecipe() {
print('パスタを作っているよ');
}
void main() {
cook(pastaRecipe);
}
必ず引数も戻り値もない関数を渡すルールがある

高階関数シンプル 続・「レシピを受け取って料理を作る料理人」
戻り値がある関数の型指定var
void cook(int Function() recipe) {
print('材料準備!');
int result = recipe();
print('出来上がった料理の美味しさレベルは $result 点!');
}
int pastaRecipe() {
print('パスタを作ってるよ!');
return 95;
}
void main() {
cook(pastaRecipe);
}
int Function() recipe→intを返す関数をrecipeという引数として受け取る関数
出力の順番
材料準備! cook() 関数内
パスタを作ってるよ pastaRecipe() 関数内
味のスコアは 95 点! cook() 関数内

無名関数(匿名関数)
名前を持たない関数
var func = (int x){
return x * 2;
};
無名関数 = 名前なしで{}ブロックで定義

ラムダ式(矢印関数)
行で処理&返却を書く関数の省略記法。
var func (int x)=> x * 2;

map のイメージ
final numbers =[1,2,3,4];
final doubled = numbers.map((n) => n * 2).toList();
print(doubled);
mapはそれぞれの要素(n)をn*2にマッピングしている
マップ=変換する

Flutterにおけるオブジェクト指向とは
↓
flutterのベースであるDart言語が「オブジェクト指向プログラミング(OOP)」の概念に基づいている
↓
OOP:Object-Oriented Programming
↓
プログラムの中で、データとそのデータを扱う処理を1つのまとまり(=オブジェクト)として管理し、
この「オブジェクト」を部品のように組み合わせて、大きなシステムを作る考え方
↓
主な概念として
・クラス-オブジェクトの設計図。どんなデータを持ち、どんな動きをするかを定義するもの。
・オブジェクト-クラスから作った実体(実物)。実際に使われるモノ。
・継承-あるクラスの機能を引き継いで、新しいクラスを作ること。
・カプセル化-データをオブジェクト内に隠し、外部から勝手に変更できないようにすること。
・ポリモーフィズム(多態性)-同じ名前のメソッドでも、オブジェクトごとに異なる動作をさせられる性質。
↓
例
・クラス:車クラス(Car)を作成
→速度や色などの情報(プロパティ)、走る・止まるといった動作(メソッド)
・オブジェクト:
→そのクラスから「赤いスポーツカー」や「青いトラック」といった実物を作成。

オブジェクト指向のインスタンス化 → 利用
クラスを作成(設計図を作成)
class Ingredient {
final String name;
final int spiciness; // 辛さレベル(1〜10)
//コンストラクタ(プロパティの初期化)
Ingredient(this.name, this.spiciness);
}
インスタンスを作成(実態の作成)
final chili = Ingredient('唐辛子',9);
final pepper = Ingredient('黒コショウ',3);
final curryPowder = Ingredient('カリーパウダー',5);
出力
void main() {
print('${chili.name}の辛さは${chili.spiciness}');
print('${pepper.name}の辛さは${pepper.spiciness}');
print('${curryPowder.name}の辛さは${curryPowder.spiciness}');
}

高階関数
class Ingredient {
final String name;
final int spiciness; //からさレベル(1~10);
Ingredient(this.name, this.spiciness);
}
List<String> filterMapAndSortBySpiciness(
List<Ingredient> ingredients,
bool Function(Ingredient) filter,
String Function(Ingredient) mapper,
) {
// フィルター&マッピング
List<Ingredient> filtered = ingredients.where(filter).toList();
// 辛さレベルでソート
filtered.sort((a, b) => b.spiciness.compareTo(a.spiciness));
// マッピング
return filtered.map(mapper).toList();
}
// フィルターとマッパー
bool isSpicy(Ingredient ingredient) => ingredient.spiciness >= 5;
String decorateForCurry(Ingredient ingredient) =>
'${ingredient.name} (辛さレベル: ${ingredient.spiciness})';
void main() {
List<Ingredient> ingredients = [
Ingredient('じゃがいも', 1),
Ingredient('スパイスミックス', 8),
Ingredient('鶏肉', 2),
Ingredient('唐辛子', 10),
Ingredient('ブラックペッパー', 6),
Ingredient('玉ねぎ', 1),
];
List<String> spicyIngredients = filterMapAndSortBySpiciness(
ingredients,
isSpicy,
decorateForCurry,
);
print(spicyIngredients);
}

プロパティの概念
プロパティ とは、クラスやウィジェットが持っている「設定項目」や「状態」を指します。
言い換えると、そのウィジェットが「どう表示されるか」「どんな挙動をするか」を決めるもの
例
//すべて「AppBar のプロパティ
AppBar(
title: Text('タイトル'),
backgroundColor: Colors.blue,
centerTitle: true,
elevation: 4,
)
プロパティ 型 説明
-title Widget? AppBar に表示するタイトル (通常 Text ウィジェットを入れる)
-backgroundColor Color? 背景色
-centerTitle bool? タイトルを中央に寄せるかどうか
-elevation double 影の高さ(立体感)
classで定義した変数も「プロパティ」になる
//final String title; も MyHomePage クラスの「プロパティ」です。
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({super.key, required this.title});
State<MyHomePage> createState() => _MyHomePageState();
}
【なぜプロパティを定義するのか?】
-そのウィジェットの「設定値」や「状態」を保持しておくため
Flutter ではウィジェットは再ビルド(再描画)されても、渡された値を元に表示を再構築します。
class MyHomePage extends StatefulWidget {
final String title; // ← このページがどんなタイトルで表示されるかを「保存」しておくため
const MyHomePage({super.key, required this.title});
【再利用性のため】
プロパティを定義しておけば、同じ MyHomePage クラスを色々なタイトルで再利用できます。
MyHomePage(title: 'ホーム')
MyHomePage(title: '設定画面')
「中身のロジックは共通だけど、表示は変えたい」
➡ だからプロパティを持たせる
【コンストラクタを通じて受け渡した値を、そのウィジェットの中で使うため】
Flutter の StatelessWidget や StatefulWidget は final なプロパティを持っており、その値を build メソッド内で参照します。
これにより、ウィジェットは「引数として渡された情報を元に、自分自身をレンダリングする」ことができます。
--イメージ--
「ウィジェットは設計図」
- 設計図には「部品表(プロパティ)」が必要
- それをもとに実物(画面)が組み上がる
【逆にプロパティがない場合】
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
このウィジェットは「外部からの値を一切受け取れない」ことになり、固定値しか表示できない
【まとめ】
-プロパティは「値を保存するため」であり
-「そのウィジェットが必要とする情報」を保持するため
-「柔軟に再利用」するため
-「引数を受け取って表示内容を変える」ため

ステート
【StatelessWidget の役割】
-一度決められたデータ(プロパティ)を元に、UIを一回描画するだけのウィジェット。
-内部で状態を持たず、変化しない表示に使う。
-外部から受け取った値を final で保持し、それを build() 内で使ってUIを作る。
例:
class MyText extends StatelessWidget {
final String text;
const MyText({super.key, required this.text});
Widget build(BuildContext context) {
return Text(text); // text は変わらない
}
}
【StatefulWidget の役割】
-外部から渡された「固定の情報(プロパティ)」を受け取り、状態を持つことができるウィジェットを作るためのクラス。
-実際に「状態」を持つのは State クラスで、この StatefulWidget はあくまで「状態を持つウィジェットを作るための土台」。
-自分では状態を持たず、createState() で状態を管理するクラス(State)を作り出すだけ。
例:
class CounterPage extends StatefulWidget {
final String title;
const CounterPage({super.key, required this.title});
State<CounterPage> createState() => _CounterPageState();
}
-CounterPage はタイトルだけを持っていて、カウントの状態は State 側が持つ。
【State クラスの役割】
-StatefulWidget の「中の状態(変化する値)」を持ち管理する場所。
-setState() を呼ぶことで、状態を更新してUIを再描画させる。
-build() メソッド内では、親である StatefulWidget から渡されたプロパティも参照可能(widget.〇〇 として)。
class _CounterPageState extends State<CounterPage> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
Widget build(BuildContext context) {
return Column(
children: [
Text(widget.title), // ← StatefulWidget のプロパティを参照
Text('$_count'), // ← 状態を持って変化する部分
ElevatedButton(
onPressed: _increment,
child: const Text('増やす'),
),
],
);
}
}
項目 | StatelessWidget | StatefulWidget | State クラス |
---|---|---|---|
主な役割 | 一度決まったUIを描画 | 状態を持つUIの土台 | 実際の状態を持ち UI を更新する |
状態を持てるか | 持てない | 自身は持たない(State クラスが持つ) | 状態を持つ場所 |
値の変更 | 再生成で変更対応 | setState() を通じて変更に対応 | 状態変更と UI 再描画を管理 |

例
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedContainer Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'AnimatedContainer Demo'),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, required this.title});
final String title;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: const Center(child: AnimatedContainerApp()),
);
}
}
const MyHomePage({super.key, required this.title});
required this.titleとあるので,MyHomePageはインスタンス化される時に必ずtitleを受け取る
つまり
MyApp → MyHomePage に title を渡していて←Widget ツリー構造の原理から渡される
MyHomePage クラスはそれを final String title; で受け取り
AppBar 内で Text(title) として使用しています。
MyApp → MyHomePage に title を渡す構造は、
「上位のWidgetから下位のWidgetにデータを渡してUIを構築する」という Flutter の基本的な設計パターンに基づいている
「コードのスコープを超えているように見える理由」
MyApp クラスと MyHomePage クラスは別々に定義されてるが
MyApp の build メソッド内で MyHomePage をインスタンス化している
home: const MyHomePage(title: 'AnimatedContainer Demo'),
この時点で MyApp の「中」で MyHomePage を「生成」しているので、MyApp → MyHomePage の関係は完全にスコープ内になる
MyApp は「親Widget」
MyHomePage は「子Widget」
親Widgetは、子Widgetをインスタンス化するときに title などの引数を渡せる
という仕組み
宣言的UI

【Flutter の主要な概念まとめ】
宣言的UI
-状態に基づいてUIを「宣言」するだけ。
-状態が変われば UI は自動で再構築。
-build() メソッドで「この状態ならこう表示」と書く
Widget ツリー
-Flutter のUIはすべて「Widget」で構成。
-小さなWidgetを組み合わせてUIを作っていく。
-親Widget → 子Widget とデータを渡してツリー状に組み立てる。
-ツリーは build() 毎に再構築されるけど、軽量なので問題ない。
State(状態)管理
-StatelessWidget:状態を持たない。渡されたものだけでUIを描画。
-StatefulWidget:状態を State クラス内に持ち、setState() で更新。
-大きくなると Riverpod、Bloc、Provider など外部パッケージで状態管理することも。
ホットリロード・ホットリスタート
-Flutter の爆速開発を支える概念。
-変更を保存すると一瞬でUIに反映(ホットリロード)。
-状態を初期化してリスタートする場合はホットリスタート。
ビルドと再ビルド
-状態が変わるたびに build() が呼ばれてUIが再描画される。
-ただし「Widgetツリーの差分」をFlutterエンジンが最適化してくれるので負荷は低い。
Composition(コンポジション)
-大きなWidgetを作るより、小さなWidgetを組み合わせる思想。
-再利用性・可読性が高まる。
イミュータブル
-すべての Widget は基本的に「変更不可」(immutable)。
-状態が変わったときはWidgetを「作り直す」。
-でも内部的に差分だけを描画してるのでパフォーマンスは良い。
BuildContext
-「今、自分がWidgetツリーのどこにいるか」という情報を持ったオブジェクト。
-親Widgetから値を取得したり、ナビゲーションしたりするときに必須。
-context が分かるとツリー間のやりとりが理解しやすくなる。
Material Design / Cupertino
-Flutterは Material Design (Android風UI) と Cupertino (iOS風UI) 両方に対応している。
-1つのコードでクロスプラットフォームにできる。
Navigator(画面遷移)
-ページ遷移は Navigator.push() / Navigator.pop() を使う。
-ページは Route として管理される。

BuildContextとは
-自分がWidgetツリーのどこにいるかを表すもの」
-親Widgetやツリー上にある情報にアクセスするための“参照ポイント”
抽象:
「Widgetツリーの森の中で、自分がどの木の枝に立っているかを教えてくれる地図のピン」みたいなもの。
メタ思考のようなもの
例
//親Widgetの情報を取得するとき
Theme.of(context).colorScheme.primary
context を通じて「自分より上にあるTheme」の情報を取得している
//ナビゲーション(画面遷移)するとき
Navigator.of(context).push(...)
//Scaffold を探して取得する
Scaffold.of(context)
子Widgetの中で親の Scaffold を使いたいときにも context が必要
context を使って、「この位置から画面遷移を開始という」という合図をしている。
なぜ「context」が必要か
-FlutterのUIは入れ子構造(ツリー)で、
上にあるWidget(親)が持っている情報を取得したいときに、
「自分がツリーのどこにいるか」を知らないと辿れないから。
つまり:
context を使うと、自分より上にある情報にアクセスできる
contextの「階層」を意識する
context は「そのWidgetの位置」なので、
子Widgetのcontextからは親の情報を取れるけど、逆はできない。
(親のcontextで下の階層を探しても見つからない)
応用
Builder ウィジェットを使って context を切り替える
例えば Scaffold の中で context を使いたいけど、
位置的に Scaffold より上の context しかない場合:
Scaffold(
body: Builder(
builder: (context) {
// ここなら Scaffold.of(context) が使える
return ElevatedButton(
onPressed: () {
final scaffold = Scaffold.of(context);
scaffold.showSnackBar(SnackBar(content: Text('Hello')));
},
child: Text('Show Snackbar'),
);
},
),
);
//Builder を使うことで「ここから新しい context を作り直す」イメージ。
まとめ
-よく使う場所
build(BuildContext context) メソッド、Navigator、Theme取得、Scaffold操作など
-気をつけること
initState では使わない! 親のcontextでは子を辿れない!

参考
【並列処理と並行処理】
並行処理(Concurrency)と並列処理(Parallelism)
並行処理(Concurency)
・CPUが1つでも可能
・A・B・Cという処理を素早く切り替えながら進めて「同時に進んでいるように見せる」
・あくまで「同時進行感」
例:
Aの処理(ちょっと進める)
→Bの処理(ちょっと進める)
→Aに戻って続きを進める
→Cの処理進める
→これを超高速で切り替える
同時にやってるっぽいけどやってない感じ
並列処理(Parallelism)
・CPUコアが複数あるorマルチスレッド対応している場合
・本当に複数の処理が同時に走っている状態
例:
CPUコア1→Aの処理
CPUコア2→Bの処理
CPUコア3→Cの処理
本当に同時進行!
【わかりやすい例え】
・並行処理→1人のインド人が「オニオンを切りつつ、スパイスをいりながら、タンドゥールに手を入れてナンを焼いている」を順番に切り替えてやっている感じ」
・並列処理→3人のインド人がそれぞれカリーセットを出すために同時に調理している感じ
【Dart / Flutter に当てはめると】
・並行処理→async/awaitで非同期処理を切り替えながら進める
・並列処理→Isolate(別プロセス的に動く処理)、もしくはOSレベルのマルチコア処理
Dart の async/await は「並行処理」
Dart の Isolate は「並列処理」
【まとめイメージ図】
並行処理 → 実際は順番にちょっとずつ、でも同時っぽく動く
並列処理 → 本当に複数で同時に動く

「Dartの非同期イベント処理の仕組み」
参考
Dart(Flutter)の「イベント処理の流れ
Event Queue(イベントキュー)
→「やることリスト」にイベント(タップや再描写)が並んで待っている場所。
例:タップしたり、画面を書き直したりする命令が順番待ちしてる。
・物理→ユーザーがタップした
・システム→アニメーションを再描画したい
・コード→Futureが終わったよ
Event Loop(イベントループ)
→「次、誰の番?」と順番にキューから一個ずつイベントを取り出す仕組み。
言語化→次、どれをやる?」ってイベントキューを覗いて、
1個取り出して Main Isolate に渡します。
ずーっとグルグル回って、次の仕事が来るまで監視してる存在。
Main Isolate(メインアイソレート)
→イベントを受け取って実際に処理(タップの処理や、画面の再描写)を行う場所。
言語化→おっしゃ、きた!」
イベントループから渡されたタスクを実行する人。
UI更新・タップ反応・軽い処理などを担当。
概要
イベントループモデル(Event Loop Model)
もしくは
Dart concurrency model(Dartの並行処理モデル)
抽象的な駆動
Event Queue(やることリスト)
↓
Event Loop(順番係:次やるものを取り出す)
↓
Main Isolate(実際にそのタスクを処理する人)
という順番で作動する
具体的な駆動
【ユーザーがタップ】
↓
[ Event Queue ] に「タップイベント」が追加
↓
[ Event Loop ] 「次はタップイベントね!」と取り出す
↓
[ Main Isolate ] がそのタップ処理を実行する
【補足】
Event Loop は「選別」や「振り分け」はしないです。
→ ただひたすら順番通りに Main Isolate にタスクを渡します。
Main Isolate は「渡されたものだけやる」。自分からキューを探しに行くことはありません。

Isolate(イソレート)
Dart(Flutter)が持つ「並列処理」を実現する仕組みで、完全に独立した処理の単位です。
Dartはシングルスレッドで動きますが、Isolateを使うことで「別スレッドのように同時に処理」できるようになる。
【イメージ】
・Main Isolate = UI担当(メインの人)
・Worker Isolate = 裏方スタッフ(別室で重たい処理をやってくれる人)
【特徴】
・1つのIsolateは、自分専用のイベントループとキューを持っている
・複数のIsolateはメモリを共有しない(完全独立)
・データの受け渡しは「ポート通信」(SendPort/ReceivePort)で行う
【Isolateが必要な理由】
・Flutterのメインスレッド(Main Isolate)はUI描画やタップ処理で忙しい
・重たい処理(画像処理、暗号化、ファイル読み込みなど)をここでやるとUIがカクつく
↓
なので、重い処理は別のIsolateでやって結果だけもらう
Isolateを使う方法(2種類)
・自分でisolateを立ち上げる
Isolate.spawn(myHeavyFunction,myParameter);
//完全に独立した並列実行がスタートする
Flutterで簡単に使える compute()
final result = await compute(myHevyFunction,myParameter);
//内部でIsolateを立ち上げてくれて、終わったら結果を返してくれる便利関数らしい
具体的な使い方と使うべき場面
メインスレッド(UIスレッド)をブロックするような重たい処理を行いたいとき
例えば:
大量データの計算処理
複雑なJSONパース
大きいファイルの圧縮や画像処理
機械学習系の推論や数値計算
長時間かかる暗号化・復号処理
↑これをメインスレッドでやると、UIがフリーズしてしまうのでIsolateで並列処理をさせる
【メイン関数でisolateを起動】
void heavyTask(SendPort sendPort) {
//ここで重たい処理
int result = 0
for (int i = 0; 1 < 11451481088888; i++) {
result +=i;
}
//計算結果を Isolate 呼び出し元に送信
sendPort.send(result);
}
import 'dart:isolate';
void main() asyn {
print('処理開始');
//通信用ポートを作成
final receibePort = Receive();
//Isolate 起動
await Iso;ate.spawn(heavyTask, receivePort.sendPort);
//結果を待つ
receivePort.listen((result) {
print('計算結果: $result');
receivePort.close();
});
print('Isolate に投げたあとでも他の処理は継続できる');
}
メモ
・ReceivePort 作成→メイン側で結果を受け取るためのポートを作成
・Isolate.spawn()→重たい処理を持った関数と sendPort を渡して起動
・receivePort.listen()→ 結果を受け取り次第UI更新やログ出力
・通信はポート経由→値を直接返せないので sendPort.send() でやりとり
【実運用の例】
・UI で「計算中...」スピナーを回しつつ、
・Isolate に投げて
・終わったら結果を受け取って setState() してUI更新
【注意点】
・関数に渡すデータは シリアライズ可能なもの(プリミティブ型 or List / Map) に限られる
・BuildContext などを直接渡すことはできない
・値渡しの世界なので、重たいオブジェクトをそのまま渡すのは非推奨

SendPort/ReceivePort
isolate同士はメモリを共有しないため「ポート」を通じてメッセージを送受信する必要がある
SendPort(送信側subisolate側)
isolate側ではSendPortで受け取る
void myIsolateEntry(SendPort sendPort) {
sendPort.send("処理完了");
}
-SendPort型はdart:isolateライブラリに含まれているクラス。
-Isolate(アイソレート)間でデータを送るときに使う「送信用ポート」を表す。
ReceivePort
mainisolare側ではReceivePortで受け取る
final receivePort = ReceivePort();
Isolate.spawn(myIsolateEntry, receuvePort.sendPort);
receivePort.spawn(myIsolateEntry, receivePort.sendPort);
receivePort.listen((message){
print("受け取ったメッセージ: $message");
});
メモリ共有 | 内容 | 備考 |
---|---|---|
なし | Isolate間ではメモリ共有せず、データはコピー渡し | 他のスレッドと直接やり取りせず安全に処理 |
並列実行 | 可能 | メインアイソレートとは独立して同時並行処理可能 |
イベントループ | 各Isolateが独立して所有 | Main Isolate・Worker Isolate それぞれにある |
データ通信 | SendPort / ReceivePort を使用 | メッセージベースでデータのやり取りを行う |
主な用途 | 画像処理、重い計算、大量データ変換などUI負荷を避けたい処理 | compute() で簡易的に使うことも可能 |

シリアライズ
オブジェクト(インスタンス)をJSONやMap<String,dynamic>のような形式に変換すること
・ネットワーク越しにAPIに送ったり
・データベースに保存したり
・ファイルに保存したり
・SharedPreferencesに保存したり
できるようになる
デシリアライズ
JSONやMap<String,dynamic>からDartのオブジェクトに変換すること
それぞれの具体コード
class User {
final int id;
final String name;
User({required this.id, required this.name});
//JSON → User(デシリアライズ)
factiry User.fromJson(Map<String,dynamic> json) {
return User(
id: json['id'],
name: jdon['name'],
);
)
//User → JSON(シリアライズ)
Map<String, dynamic> toJson() {
return {
'id':id,
'name':name,
};
}
}
自動でできる
json_serializable←パッケージ
//使う時書く

プリミティブ型
・プログラミング言語における基本的なデータ型のこと。
・プリミティブ型とは、「int」や「bool」など、プログラムで基本的な情報を表すための“最小単位のデータ型”のこと。
Dart(Flutter)における主なプリミティブ型
int 42 整数。負の数もOK
double 3.14 小数(浮動小数点数)
num intとdoubleの親クラス
bool true, false 真偽値。条件分岐などに使用
String "こんにちは" 文字列(文字の並び)
Null null 値が存在しないことを示す
Symbol #foo メタプログラミング用途(特殊な場面で使う)
Runes Runes('✨') Unicodeコードポイント(文字列の低レベル操作)

compute() 関数
//あとでやる
import 'package:flutter/foundation.dart';
int heavyComputation(int value) {
// 例: 時間がかかる重い処理
int result = 0;
for (int i = 0; i < value; i++) {
result += i;
}
return result;
}
void main() async {
int result = await compute(heavyComputation, 100000000);
print(result); // 重い計算結果が出る
}

null合体演算子(null-coalescing operator)
? クエスチョン 「nullを許す」型(nullable)にする。例:int?
! バン(bang) 「絶対nullじゃない!」と明示(nullチェック済とみなす)
?? null合体 左がnullなら右を使う。例:x ?? 10
?. セーフアクセス nullならスキップ、nullじゃなければ実行
??= null代入 左がnullのときだけ代入する。例:x ??= 100
//null許容型
int? age; // nullでもOKなint型
age = null; // OK
//nullじゃないと信じて使う(危険なやつ)
int? age = 30;
print(age! + 1); // ageがnullじゃないと信じて +1する
int? age2 = null;
// print(age2! + 1); // ← これはクラッシュする(nullだから!)
//nullのときにデフォルト値を使う
int? age;
int displayAge = age ?? 18; // ageがnullなら18になる
//nullを避けてアクセス(セーフアクセス)
String? name;
print(name?.length); // nameがnullならnull、nullじゃなければlength
// userがnullならこの式もnullで終わる
User? user;
print(user?.name); // userがnullならこの式もnullで終わる
//nullなら代入する
int? age;
age ??= 25; // ageがnullなら25を代入
print(age); // 25
age ??= 30; // もうnullじゃないから変わらない
print(age); // 25のまま
まとめ
・? は「null許容」
・! は「nullじゃないと信じる」
・?? は「nullなら代わりを使う」
・?. は「nullなら何もしない」
・??= は「nullなら代入」