お願い
本記事の内容に誤りや改善点がございましたら、コメント等でご指摘いただけますと幸いです。
なお、本記事は個人の学習記録として作成したものです。業務でご利用の際は、公式ドキュメントもあわせてご確認ください。
1. はじめに
会社でFlutter(Dart)を使うことになり、まったく触ったことがなかったため、普段使っているJavaとの比較を交えながら気になったDartの特徴を抜粋して整理してみました。
補足
本記事はサンプルコードを載せていますが、dartpadというサイトを使えばweb上で実行できます!
https://dartpad.dev
2. Dartとは
DartはGoogleが開発した静的型付けのプログラミング言語で、クロスプラットフォーム開発で活用されています。
特徴
- 強い型付けとnull安全機能を搭載
- AOT(Ahead-of-Time)とJIT(Just-In-Time)の両方のコンパイルに対応
- Flutterとの親和性が高く、クロスプラットフォーム開発に対応
- 非同期処理を扱いやすい
async/await構文をサポート
補足
3. プロジェクト作成と実行
Dartは .dart という拡張子のファイルを使用します。
// sample/hello.dart
void main() {
print('Hello');
}
下記のコマンドで実行することができます。
# ファイルのパスを指定して実行
dart run sample/hello.dart
4. ライブラリとpart構文
- Dartでは 1つのソースファイル = 1ライブラリ として扱われます。
(partを使えば複数ファイルをまとめて1つのライブラリとして定義できます。)
5. 型と演算子
サンプルコード
5. 型と演算子
5-1. Java vs Dart 型対応表
| Javaの分類 | Javaの例 | Dartの例 | Dartの挙動 | 備考 |
|---|---|---|---|---|
| 基本型 |
int, double, boolean
|
int, double, bool
|
値コピーのように動く(実際はすべてObject) | Dartはプリミティブもクラス扱い(int などもObject) |
| 文字列 | String |
String |
参照型だがイミュータブル | Javaとほぼ同じ |
| コレクション |
List, Set, Map
|
List, Set, Map
|
参照渡し | Dartはリテラル構文([], {})で生成可能 |
| ラッパー型 |
Integer, Double
|
不要 | DartではプリミティブもObject | オートボクシング不要 |
| レコード/タプル | record |
Record |
値をまとめる軽量構造体(イミュータブル) | Dart独自。Javaのrecordに近い。 |
| null許容 | 基本型は不可 |
int?, String?
|
型ごとに指定 | Dartは基本的にはNull安全 |
5-2. ライブラリの使い方
- 文字列結合
String a = "あいう";
print("結果は $a です"); // 文字列補間(推奨)
print("結果は " + a + " です"); // 文字列連結(冗長。パフォーマンス低下の恐れあり)
- ListとSet
| 特徴 | List | Set |
|---|---|---|
| 順序 | あり(インデックスでアクセス可) | あり(デフォルトがLinkedHashSetの実装になっているため) |
| 重複 | 許可 | 不許可 |
| 主な操作 |
add, removeAt, insert
|
add, remove, contains
|
| 使用例 | 順序付きデータ | 集合演算、重複排除 |
- record(レコード)
Javaのrecordに近い。複数値の返却が便利。
void main() {
// recordの宣言
var user = (id: 1, name: "Taro");
// 名前付きフィールドの分解
var (id: userId, name: userName) = user;
print("id = $userId, name = $userName");
// id = 1, name = Taro
}
void main() {
// 関数の戻り値での分解
var (name, age) = getUser();
print("name = $name, age = $age");
// name = Taro, age = 25
}
(String, int) getUser() {
// record型で返却
return ("Taro", 25);
}
5-3. 比較演算子
-
==,!=,<,>,<=,>=が利用可能 -
==デフォルトでは参照比較
(Stringや数値型など、一部のクラスでは値の比較になるようオーバーライドされている)
6. 変数と定数(スコープ)
サンプルコード
6-1. Java vs Dart 変数のスコープ対応表 ~ 6-3. コンストラクタでの初期化
6-4. null安全関連の演算子
6-5. 引数の指定
6-6. ゲッターとセッター
6-1. Java vs Dart 変数のスコープ対応表
| 種類 | Dartでの定義 | Javaとの違い |
|---|---|---|
| トップレベル変数 |
var / 型指定 / 必要に応じて final / const
|
Javaには存在しない(必ずクラスが必要) |
| インスタンス変数 |
var / 型指定 / 必要に応じて final
|
Dartは late で遅延初期化可 |
| クラス変数 |
static / static final / static const
|
Javaの static と同じ。ただし Dartは static const が使える |
| ローカル変数 | 関数・メソッド内に定義(var / 型指定 / final など) |
Javaと同じ |
補足:
Dartのprivate(ライブラリ内限定)は_プレフィックス で表現。
6-2. final と const の違い
-
final-
特徴:
値はランタイム時に決まる。
利用時までに決まっていればいいので、必ずしもインスタンス生成時に初期化する必要はない。class Person { late final String message; void assignMessage(bool flag) { if (flag) { message = "こんにちは"; } else { message = "Hello"; } } } void main() { var p = Person(); p.assignMessage(true); // 利用前に初期化 print(p.message); // OK(利用時に初期化されている) }再代入は不可だが、参照先オブジェクトは変更できる。
-
使いどころ:
APIレスポンスやユーザー入力など「実行時に決まる値」を固定する。
-
-
const-
特徴:
値はコンパイル時に決まる。
必ず宣言とともに固定値で初期化する。const now = DateTime.now(); // エラー(実行時にしか分からない) const String name = "Alice"; // OK基本的にはクラス変数に使用できない。
(クラスのフィールドは「インスタンスを作って初めて値が入る」ので、コンパイル時には値が決まらないため)
( 静的変数 (static) であれば使用可能)class Person { const String name = "Alice"; // エラー final String name = "Alice"; // OK }インスタンスも「完全に不変(immutable)」になる。
void main() { const p1 = Point(1, 2); // このインスタンスは完全に不変 final p2 = Point(1, 2); // final だけでは中のフィールドは不変でも const ではない }
-
使いどころ:
文字列リテラルやWidgetツリーの中の定数化できる部分。const Text('Hello')のように使うとWidgetの再構築が最適化される。
-
6-3. コンストラクタでの初期化
class Person {
final String name;
final int age;
// パターン1
Person(this.name, this.age);
// パターン2
// Person(String name, int age) : name = name, age = age;
}
6-4. null安全関連の演算子
| 演算子 | 意味 |
|---|---|
? |
null許容型宣言 |
! |
nullではないことを保証 |
?. |
nullならアクセスせずnull返却 |
?? |
nullならデフォルト値を使用 |
??= |
nullなら代入 |
補足
late修飾子を使用するとコンパイラのnullチェックを回避することができる(遅延初期化)。
6-5. 引数の指定
-
省略可能位置引数
-
[]で囲むと省略可能 - デフォルト値を設定できる
void greet(String name, [int age = 0]) { print("Hello $name, age $age"); } greet("Alice"); // age はデフォルト 0 greet("Bob", 25); // age を指定可能 -
-
名前付き引数
-
{}で囲む - 呼び出すときに 引数名を指定する
- デフォルト値を設定できる
- 必須にする場合は
requiredを使う - 引数の順序を変えられる
void greet({required String name, int age = 0}) { print("Hello $name, age $age"); } greet(name: "Alice"); // OK greet(name: "Bob", age: 25); // OK greet(age: 25, name: "Bob"); // OK // greet(age: 30); // エラー(name が必須) -
6-6. ゲッターとセッター
Dartではゲッターとセッターの定義が3パターンに分かれます。
| 変数の種類 | ゲッター | セッター |
|---|---|---|
| public 変数 | 自動であり (クラス外からアクセス可能) |
自動であり (クラス外から設定可能) |
| public 変数 ( final / const ) |
〃 | なし (変更不可) |
| private 変数 | 自動であり (クラス内でのみアクセス可能) |
自動であり (クラス内でのみ設定可能) |
class Person {
// 暗黙のゲッター・セッターがあるパブリック変数
String name = "Alice";
// プライベート変数(カスタムゲッター・セッターでアクセス)
int _age = 20;
// カスタムゲッター
int get age {
print("age を取得しました");
return _age;
}
// カスタムセッター
set age(int value) {
if (value < 0) {
print("年齢は正の値で設定してください");
} else {
print("age を $value に設定しました");
_age = value;
}
}
}
void main() {
var person = Person();
// 暗黙のゲッター・セッター
print("name: ${person.name}"); // Alice
person.name = "Bob"; // セッター呼ばれる(暗黙)
print("name: ${person.name}"); // Bob
// カスタムゲッター・セッター
print("age: ${person.age}"); // age を取得しました → 20
person.age = 25; // age を 25 に設定しました
print("age: ${person.age}"); // age を取得しました → 25
person.age = -5; // 年齢は正の値で設定してください
}
7. 修飾子
7-1. Dart と Java のアクセス修飾子比較表
| Dart | Java | 説明 |
|---|---|---|
public(明示的には書かない) |
public |
Dart ではデフォルトで すべてのクラスや変数がpublic。 |
_ (アンダースコア) |
private |
Dartはライブラリ内であればアクセス可能 |
| なし |
protected / package-private(デフォルト) |
Dart には protected や package-private は存在しない。ライブラリ単位でのアクセス制御が基本。 |
7-2. Dart と Java の継承制御比較表
| Dart | Java | 説明 |
|---|---|---|
extends |
extends |
多重継承できない |
implements |
implements |
多重継承できる |
with |
なし |
with は mixin という機能で使用する。 |
base |
なし | このクラスを implements することを禁止し、extends のみ許可する。プライベートメソッドを含めて、全体の整合性を保つために使用する。 |
interface |
interface |
このクラスを extends することを禁止し、implements のみ許可する(実装を隠蔽し、型定義としてのみ利用させたい場合に使う)。Java の interface とは用途が異なる。 |
7-3. Dart と Java のその他比較表
サンプルコード
sealedクラス
| Dart | Java | 説明 |
|---|---|---|
final |
final |
Dartは基本null安全だが late を使用して遅延初期化できる |
sealed |
sealed |
自身が宣言されたライブラリ以外で、サブクラスの作成を制限できる。 |
abstract |
abstract |
抽象メソッドを含むか、直接インスタンス化させたくない場合に使用。両言語とも同様の意味。 |
mixin / with
|
なし | Dart 独自の仕組み。多重継承をする際に使用する。 |
enum |
enum |
Java とほぼ同じ。 |
external |
なし | Dart 独自の仕組み。Dart 以外(ネイティブ・他ライブラリ)で定義されている実装を使うときに宣言する。 |
static |
static |
Java とほぼ同じ。 |
8. 例外処理
サンプルコード
8. 例外処理
8-1. 例外処理の書き方
try {
int result = 10 ~/ 0; // 整数割り算
} on IntegerDivisionByZeroException catch (e) { // 例外の型を指定
print("0除算: $e");
} catch (e, st) { // 第一引数:例外オブジェクト、第二引数:スタックトレース(省略可)
print("その他: $e");
print(st);
}
補足
dartには割り算が3種類ある
演算子 説明 戻り値 /割り算 double ~/整数割り算 int(小数切り捨て) %剰余(余り) int 今回の場合、
/を使うと double型のInfinityという特別な値を返するだけで例外にならない。
9. その他の記法、実装
サンプルコード
9-3. 名前付きコンストラクタ
9-4. factoryコンストラクタ
9-5. 拡張メソッド
9-1. カスケード記法
オブジェクトに対して連続的に操作できる(Javaにはない記法)。
var buffer = StringBuffer()
..write("Hello")
..write(" World")
..write("!");
print(buffer.toString()); // Hello World!
9-2. パターンマッチング(if-case / switch)
// if-case + when の例
void main() {
(int, int)? point = (10, 20);
if (point case (int x, int y) when x == y) {
print("xとyが同じ: ($x, $y)");
} else if (point case (int x, int y) when x > y) {
print("xの方が大きい: ($x, $y)");
} else if (point case (int x, int y)) {
print("yの方が大きい: ($x, $y)");
}
}
// switch + whenの例
void main() {
int score = 85;
switch (score) {
case var s when s % 2 == 0:
print("偶数のスコア: $score");
break;
case var s when s % 2 != 0:
print("奇数のスコア: $score");
break;
}
}
9-3. 名前付きコンストラクタ
Dartでは、通常のコンストラクタとは別にクラス名.名前でコンストラクタ定義し、使い分けられる。
class Employee {
String name;
int age;
String position;
// 通常のコンストラクタ
Employee(this.name, this.age, this.position);
// 名前付きコンストラクタ(デフォルト値付き)
Employee.guest()
: name = "ゲスト",
age = 0,
position = "未設定";
void introduce() {
print("名前: $name, 年齢: $age, 役職: $position");
}
}
void main() {
var guest = Employee.guest();
guest.introduce(); // 名前: ゲスト, 年齢: 0, 役職: 未設定
}
9-4. factoryコンストラクタ
毎回新しいインスタンスを生成する必要がない場合に使用する。
class Employee {
final String name;
final int age;
final String position;
// プライベートコンストラクタ
Employee._internal(this.name, this.age, this.position);
// キャッシュ用 Map
static final Map<String, Employee> _cache = {};
// factoryコンストラクタ
factory Employee(String name, int age, String position) {
// すでに同じ名前のEmployeeがあれば再利用
if (_cache.containsKey(name)) {
print("既存のインスタンスを返します");
return _cache[name]!;
}
// 新しいインスタンスを生成してキャッシュ
final emp = Employee._internal(name, age, position);
_cache[name] = emp;
print("新しいインスタンスを生成しました");
return emp;
}
void introduce() {
print("名前: $name, 年齢: $age, 役職: $position");
}
}
9-5. 拡張メソッド
既存のクラスに新しいメソッドを追加できる機能
extension StringExtensions on String {
// 文字列を逆順にするメソッド
String reversed() {
return split('').reversed.join();
}
}
void main() {
String text = "Hello";
print(text.reversed()); // olleH
}
10. まとめ
とりあえずDartを触ってみて、個人的に気になった部分をまとめてみました。
(まだ学習途中なので抜けや誤りがあるかもしれません…)
特に印象的だったポイント:
- null安全がデフォルトで組み込まれている(
?,!,??など) -
constとfinalの使い分けが混乱するけど、慣れると便利そう - 名前付き引数や拡張メソッドなどの機能が便利そう
Javaと比較するためにJavaについても改めて調べてみたのですが、そちらもまだまだたくさん勉強するところがあると感じました。
これからFlutterで実際にアプリを作りながら、もっと理解を深めていきたいと思います。