Dart
DartはGoogleが開発したオブジェクト指向プログラミング言語で、Webやモバイルアプリケーションの開発に適しています。Dartの特徴は、柔軟性と高速性に優れている点です。
このチュートリアルでは、Dartの基本的な構文や機能について学びます。初心者から中級者まで、プログラミング経験に関係なく、このチュートリアルを通じてDartを習得することが可能です。
Dart について
[編集]DartはGoogleによって開発されたオブジェクト指向のプログラミング言語です。Webアプリケーションやモバイルアプリケーションの開発に使用されています。
Dartは静的型付けの言語で、ライブラリやフレームワークが豊富に用意されています。FlutterというUIフレームワークもDartで書かれており、Flutterにより、多くの種類のプラットフォーム向けにアプリケーションを開発することができます。
Dartの特徴
[編集]Dartは、Googleが開発したオープンソースのプログラミング言語で、特にFlutterフレームワークとともにモバイルアプリやWebアプリの開発に使われています。Dartの主な特徴は以下の通りです。
- シンプルで明快な構文: Dartは、C系言語(C、Java、JavaScriptなど)のシンタックスに似ているため、これらの言語に慣れた開発者が容易に習得できます。クラスベースのオブジェクト指向言語であり、直感的な構文が特徴です。
- 強力な型システム: Dartは強い型付けと型推論の両方をサポートします。開発者は明示的に型を指定することもできますが、型推論によってコードが簡潔に保たれます。静的型チェックにより、コンパイル時のエラーを事前に発見できるため、バグの発生を減らします。
- ネイティブおよびJavaScriptコンパイル: Dartはネイティブコード(モバイルアプリの場合)とJavaScript(Webアプリの場合)にコンパイルすることができます。これにより、モバイルとWebの両方で同じコードベースを活用できるマルチプラットフォーム開発が可能です。
- 非同期プログラミング: Dartは、FutureやStreamといった非同期処理を簡潔に扱うための機能を備えています。これにより、ネットワーク通信やファイルI/Oなどの時間のかかる処理を効率的に処理できます。また、
async
とawait
のキーワードを使って、非同期コードを同期的なスタイルで書くことができます。 - AOT(Ahead-of-Time)とJIT(Just-in-Time)コンパイル: Dartは、AOTコンパイル(事前コンパイル)とJITコンパイル(実行時コンパイル)の両方をサポートしています。AOTコンパイルにより、アプリの起動が速く、パフォーマンスが向上します。開発時にはJITコンパイルが使用され、ホットリロードなどの高速なフィードバックが得られます。
- ガベージコレクション: Dartはガベージコレクション(自動メモリ管理)を行うため、メモリ管理の負担を開発者から解放します。これにより、安全で効率的なメモリ使用が保証されます。
- Flutterとの統合: Dartは、GoogleのUIツールキットであるFlutterと密接に連携しています。Flutterを使った開発では、単一のコードベースからiOSやAndroidなど複数のプラットフォーム向けにアプリを作成できます。Dartのパフォーマンスやホットリロード機能が、Flutterの高い開発効率とパフォーマンスを支えています。
- ライブラリとパッケージ管理: Dartは、pub.devを通じて豊富なライブラリやパッケージを提供しています。パッケージ管理システムであるpubを利用して、簡単にパッケージをプロジェクトにインストールし、依存関係を管理することができます。
Dartは、特にクロスプラットフォーム開発やパフォーマンスに重視したアプリケーション開発に最適な言語です。
Dartの文法的特徴
[編集]Dartの文法的特徴は、シンプルで分かりやすく、C系言語に似た構造を持つため、JavaやJavaScript、C++などの言語を使っている開発者にとって親しみやすいものです。以下はDartの主な文法的な特徴です。
- オブジェクト指向言語
- Dartはクラスベースのオブジェクト指向言語です。すべての値がオブジェクトであり、プリミティブ型(intやboolなど)も含めて、すべての型がクラスのインスタンスです。
class Person { String name; int age; Person(this.name, this.age); void greet() { print('Hello, my name is $name and I am $age years old.'); } } void main() { Person person = Person('John', 30); person.greet(); // Hello, my name is John and I am 30 years old. }
- 型推論と静的型付け
- Dartは静的型付けですが、型推論もサポートしています。これにより、変数の型を明示的に指定する必要がない場合がありますが、強力な型チェックにより安全性が確保されます。
var name = 'Alice'; // 型推論でString型と推定される int age = 25; // 明示的にint型を指定
- null安全性
- Dartはnull safetyを導入しており、変数に
null
が許可されるかどうかを型レベルで制御できます。?
を使うことで、変数にnull
を許容できるようになります。 String? nullableName; // nullを許容するString型 nullableName = null; // OK nullableName = "Bob"; // OK
- ファーストクラス関数
- Dartでは、関数もオブジェクトであり、変数に代入したり、他の関数に渡すことができます。また、無名関数(匿名関数)やラムダ式もサポートされています。
void main() { var list = [1, 2, 3]; list.forEach((element) { print(element * 2); // 2, 4, 6 }); }
- 非同期処理(async/await)
- Dartは非同期プログラミングのために
Future
やStream
をサポートしており、async
とawait
を使って非同期処理を同期的なコードスタイルで記述できます。 Future<String> fetchData() async { await Future.delayed(Duration(seconds: 2)); return 'Data received'; } void main() async { String data = await fetchData(); print(data); // 2秒後に 'Data received' と表示される }
- コレクション(リスト、マップ、セット)
- Dartには便利なコレクション型があり、リスト(List)、マップ(Map)、セット(Set)などがサポートされています。リストやマップはジェネリクスを使って型を指定することもできます。
void main() { List<int> numbers = [1, 2, 3]; // 型付きリスト Map<String, int> ages = {'Alice': 25, 'Bob': 30}; // 型付きマップ Set<String> names = {'Alice', 'Bob', 'Charlie'}; // 型付きセット }
- カスケード記法
- Dartでは、オブジェクトに対して複数のメソッドを連続して呼び出すことができるカスケード記法を提供しています。
..
を使うことで、オブジェクトに対する一連の操作を簡潔に記述できます。 void main() { var buffer = StringBuffer() ..write('Hello') ..write(' ') ..write('World!'); print(buffer.toString()); // Hello World! }
- コンストラクタと命名コンストラクタ
- Dartでは、通常のコンストラクタに加えて、命名コンストラクタを定義することができます。これにより、クラスのインスタンスを作成する際に異なる初期化方法を提供することができます。
class Point { int x, y; Point(this.x, this.y); // 名前付きコンストラクタ Point.origin() : x = 0, y = 0; } void main() { var p1 = Point(10, 20); var p2 = Point.origin(); print('${p1.x}, ${p1.y}'); // 10, 20 print('${p2.x}, ${p2.y}'); // 0, 0 }
- ライブラリとパッケージのサポート
- Dartは、モジュール化されたコードをサポートしており、
import
を使って他のライブラリを取り込むことができます。show
やhide
キーワードを使って、特定の部分だけをインポートすることも可能です。 import 'dart:math' show pi; // piだけをインポート void main() { print(pi); // 3.141592653589793 }
- ジェネリクス
- Dartはジェネリクスをサポートしており、型安全性を高め、再利用可能なクラスやメソッドを作成できます。
class Box<T> { T value; Box(this.value); } void main() { var intBox = Box<int>(123); var stringBox = Box<String>('Hello'); print(intBox.value); // 123 print(stringBox.value); // Hello }
Dartの文法は、直感的でありつつも、オブジェクト指向や非同期処理、コレクション操作といった高度な機能をサポートしているため、幅広い用途で効率的にコードを書くことができます。
インストールしないで実行する方法
[編集]DartPad というサイトを使うと、自分の環境にインストールしないでもブラウザ上で Dart のプログラミングが行なえます。
インストール方法
[編集]Get the Dart SDK を入手できます。
Windowsの場合は、公式インストール方法ではパッケージマネージャーの Chocolatey を使うのですが日本では馴染みがないようなので、これとは別にコミュニティによる用意されたインストーラを使うと良いでしょう。⇒ https://gekorm.com/dart-windows/
動作確認
[編集]dartのバージョンは、
dart --version
で確認できます。
PS C:\Users\user1> dart --version Dart SDK version: 2.15.0-168.0.dev (dev) (Thu Sep 30 12:23:13 2021 -0700) on "windows_x64"
のようにバージョン番号が出たら、成功です。
Hello World など
[編集]- コード例
main() { print("Hello world"); }
print関数などのよく使われる関数はdart:core
ライブラリにありますが、dart:core
ライブラリはimport不要で使えます。
実行方法は、コマンドプロンプトあるいはWindows Terminalで、
dart ファイル名.dart
です。
Dartに特徴的な機能
[編集]Dartが初めてのプログラミング言語という人は少ないでしょうから、他の言語にはあまりないDart固有の機能をまとめて紹介します。
null安全性
[編集]Dartではnull安全性が組み込まれており、変数がnullを許容するかどうかを明示的に指定します。例えば、int
型の変数にnullを代入するためにはint?
という型を使用します。この機能により、nullに関連するエラーやバグを減らすことができます。
int? nullableInt = null; int nonNullableInt = 42;
以下は、Dart 2.12で追加されたNull安全性の機能を含んだコードの例です。Null安全性はDartの重要な変更点の一つであり、?
や!
などの新しい機能が導入されました。
void main() { String? nullableString = "Hello"; // ?を使ってnullが許容される文字列型の変数を宣言 if (nullableString != null) { print(nullableString.length); // nullチェック後のnullableStringのプロパティに安全にアクセス } else { print("String is null"); } String nonNullableString = nullableString ?? "Fallback"; // nullの場合に代替値を設定する方法 print(nonNullableString); String nonNullableString2 = nullableString!; // nullチェックをバイパスし、非nullを保証する演算子 print(nonNullableString2); }
この例では、String? nullableString
というnullableな文字列型変数を宣言し、if
文でnullチェックして安全にプロパティにアクセスしています。また、??
演算子を使用してnullableな変数がnullの場合の代替値を設定し、!
演算子を使ってnullチェックをバイパスして非nullを保証しています。
拡張メソッド
[編集]Dartには他の言語にはあまり見られない、拡張メソッドという機能があります。これにより、既存のクラスに新しいメソッドを追加することができます。
extension StringExtension on String { int get doubleLength => this * 2; } void main() { String text = "Hello"; print(text.doubleLength); // 出力: 10 }
コレクションの初期化
[編集]Dartではリストやマップ、セットなどのコレクションを簡潔に初期化するための構文があります。
// リストの初期化 List<int> numbers = [1, 2, 3, 4, 5]; // マップの初期化 Map<String, int> ages = { 'John': 30, 'Alice': 25, 'Bob': 35, }; // セットの初期化 Set<String> fruits = {'apple', 'banana', 'orange'};
これにより、コレクションを宣言して初期化する手順を簡素化できます。
コンストラクタの省略
[編集]クラスのコンストラクタがフィールドの初期化しか行わない場合、{this.field}
のような記述でコンストラクタを省略できます。
class Person { final String name; final int age; Person(this.name, this.age); // コンストラクタの省略 }
これにより、フィールドの初期化を簡潔に表現できます。
変数
[編集]Dartにおける変数は、さまざまな型やスコープに応じて使用でき、強い型付けと型推論がサポートされています。ここでは、Dartにおける変数の宣言とその特徴について説明します。
変数の宣言方法
[編集]Dartでは、変数をvar
、final
、const
、および特定の型を使って宣言することができます。
var
キーワード
[編集]var
は、変数の型を自動的に推論するためのキーワードです。変数に初期値を設定すると、Dartはその値に基づいて変数の型を決定します。
void main() { var name = 'Alice'; // String型に推論される var age = 25; // int型に推論される print('$name is $age years old.'); }
明示的な型指定
[編集]変数の型を明示的に指定することもできます。これにより、コードの可読性を高めたり、型を強制することが可能です。
void main() { String name = 'Alice'; // 明示的にString型を指定 int age = 25; // 明示的にint型を指定 }
final
キーワード
[編集]final
で宣言された変数は、一度だけ代入が可能で、その後変更することができません。値は遅延的に初期化されるため、コンストラクタや計算結果を使用して初期化することができます。
void main() { final date = DateTime.now(); // 実行時に確定する値で初期化出来る // date = DateTime.now(); // エラー: final変数を再度代入できない }
const
キーワード
[編集]const
はコンパイル時に定数値として決定される変数を宣言します。const
で宣言された変数は、実行時に変更することができません。final
と異なり、const
はコンパイル時に完全に決定される必要があります。
void main() { const pi = 3.14159; // コンパイル時定数 // pi = 3.14; // エラー: const変数を再度代入できない }
型推論
[編集]var
を使用すると、Dartは変数に代入された値から自動的にその型を推論します。これにより、型を明示的に書く手間が省け、コードが簡潔になります。
void main() { var number = 42; var name = 'Alice'; print("number.runtimeType = ${number.runtimeType}"); // int print("name.runtimeType = ${name.runtimeType}"); // String }
遅延初期化
[編集]late
キーワードを使うことで、変数を遅延的に初期化することができます。これにより、変数を後で設定することが可能になり、必要なときにだけ初期化されます。
void main() { late String description; // 変数を遅延的に初期化 description = 'This is a delayed initialization.'; print(description); }
スコープ
[編集]変数のスコープは、その宣言位置によって決まります。Dartでは、変数はローカル変数とグローバル変数に分かれます。
ローカル変数
[編集]関数内やブロック内で宣言された変数は、そのスコープ内でのみ有効です。
void main() { if (true) { var message = 'Hello'; // このブロック内でのみ有効 print(message); } // print(message); // エラー: スコープ外でアクセスできない }
グローバル変数
[編集]関数やクラスの外で宣言された変数は、プログラム全体でアクセス可能なグローバル変数です。
String globalVar = 'I am global'; void main() { print(globalVar); // グローバル変数にアクセス }
再代入可能な変数
[編集]Dartでは、var
や型を使って宣言した変数は再代入が可能です。たとえば、変数に別の値を再度代入することができます。
void main() { var name = 'Alice'; name = 'Bob'; // 再代入可能 print(name); // Bobと表示される }
非null型とnull許容型
[編集]Dartにはnull安全性が導入されており、変数がnull
を持つかどうかを型システムで制御します。通常、変数にはnull
を許容しませんが、?
をつけることでnull
を許容する型を定義できます。
void main() { String? nullableString; // nullを許容する型 nullableString = null; // OK nullableString = 'Hello'; // OK }
型キャスト
[編集]Dartでは、変数の型を安全にキャストするために、as
キーワードを使用することができます。また、is
演算子で型チェックを行うことができます。
void main() { var value = 'Hello'; if (value is String) { print(value.length); // is演算子で型チェック } dynamic unknown = 'Dart'; var stringValue = unknown as String; // 型キャスト print(stringValue); // Dart }
まとめ
[編集]Dartの変数は、柔軟で型推論やnull安全性を活用しながら効率的に管理できます。var
やfinal
、const
などのキーワードを使い分けることで、目的に応じた変数宣言が可能で、再代入やスコープもシンプルに管理できます。
文字列への式の埋込み
[編集]文字列の中に、式を埋め込むことができます。
- コード例
main() { const num = 4; print("num + 3 is ${num + 3}\n"); }
- 実行結果
num + 3 is 7
文字列の中に ${ 式 }
の形式で埋込むと、実行時に展開されます。
文字列への変数の埋込み
[編集]文字列の中に埋込む式が単一の変数ならばより簡略化した形式で表現できます。
- コード例
main() { const num = 4; print("num is $num\n"); }
- 実行結果
num is 4
文字列の中に $変数名
の形式で埋込むと、実行時に展開されます。
バックスラッシュ・エスケープ・シーケンス
[編集]改行などに対するバックスラッシュ・エスケープ・シーケンスは他のプログラミング言語と同様に使えます。
「\」そのものを表示したい場合は、先頭にもうひとつ「\」をつけて「\\」のようにするだけです。
- コード例
main() { print("\\n"); }
- 実行結果
\n
型
[編集]以下は、Dartにおける一般的な型とそれぞれのリテラル例を示した一覧表です。
Dartにおける一般的な型とそれぞれのリテラル例 型名 意味 リテラル例 int 整数型 42, -10, 0 double 浮動小数点数型 3.14, -0.5, 2.0 num 数値型(intまたはdouble) 42, 3.14 bool 真理値型 true, false String 文字列型 'Hello', "Dart", '''Multi-line String''' List リスト(配列)型 [1, 2, 3], ['apple', 'banana', 'orange'] Map マップ(連想配列)型 {'name': 'Alice', 'age': 30} Set セット(集合)型 {1, 2, 3}, {'apple', 'banana', 'orange'} Runes Unicode文字の集合 Runes('\u{1F60A}') Symbol シンボル型 #symbolName dynamic 動的型(任意の型を持つことができる) dynamic x = 10; x = 'Now I am a string'; // どんな型でも代入可能 void 戻り値がないことを示す型 void myFunction() { /* 何も返さない */ } Future 非同期操作の結果を表す型 Future<int> fetchData() async { return 42; } Stream データのストリームを表す型 Stream<int> numberStream = Stream.periodic(Duration(seconds: 1), (x) => x); // 1秒ごとに整数を発行
これらはDartでよく使われる基本的な型の一覧です。それぞれの型は異なる種類のデータを表し、リテラル例はその型の値を表す例示です。
void main() { num a = 10; int b = 5; double c = 3.14; String name = "John Doe"; bool isTrue = true; dynamic variable = "I can change type!"; List<int> numbers = [1, 2, 3, 4, 5]; Map<String, int> ages = { "John": 36, "Jane": 25, "Bob": 42, }; // 変数の値を表示 print('Value of a: $a'); print('Value of b: $b'); print('Value of c: $c'); print('Value of name: $name'); print('Value of isTrue: $isTrue'); print('Value of variable: $variable'); // リストの要素を表示 print('Elements in numbers list:'); for (final number in numbers) { print(number); } // マップの要素を表示 print('Entries in ages map:'); ages.forEach((final key, final value) { print('$key is $value years old'); }); // 関数の実行結果を表示 final result = multiply(b, 6); print('Result of multiplication: $result'); } // 関数の定義 int multiply(int x, int y) { return x * y; }
- 実行結果
Value of a: 10 Value of b: 5 Value of c: 3.14 Value of name: John Doe Value of isTrue: true Value of variable: I can change type! Elements in numbers list: 1 2 3 4 5 Entries in ages map: John is 36 years old Jane is 25 years old Bob is 42 years old Result of multiplication: 30
generate() メソッド
[編集]Dartには、コレクションの要素を特定の条件に基づいて動的に生成するためのgenerate
メソッドがあります。これはリストやマップを生成する際に便利な方法です。
List<int> myList = List.generate(5, (index) => index * 2); print(myList); // 出力: [0, 2, 4, 6, 8]
この例では、List.generate
メソッドを使用して、要素数が5個で、各要素がインデックスに2を掛けた値で初期化されたリストを生成しています。
generate
メソッドの構文は次のようになります。
List<E> List.generate<E>(int count, E Function(int index) generator, {bool growable: true})
count
: 生成する要素の数を指定します。generator
: 要素を生成するための関数です。この関数は引数として現在のインデックスを受け取り、その位置の要素を返します。growable
: リストが変更可能かどうかを指定します。デフォルトではtrue
です。
同様に、generate
メソッドはマップを生成するためにも使用できます。
Map<int, String> myMap = Map.fromEntries( List.generate(5, (index) => MapEntry(index, 'Value $index')), ); print(myMap); // 出力: {0: Value 0, 1: Value 1, 2: Value 2, 3: Value 3, 4: Value 4}
この例では、0から4までのキーを持ち、それぞれの値が'Value 0'
から'Value 4'
まで連番になるマップを生成しています。
イテレーションメソッド
[編集]Dartのコレクション(リスト、マップ、セットなど)を操作するために便利なイテレーションメソッドがいくつか用意されています。以下はその一部です。
リストやセットのためのメソッド
[編集]forEach: 各要素に対して特定の処理を行います。
List<int> numbers = [1, 2, 3, 4, 5]; numbers.forEach((number) => print(number)); // 各要素を出力
map: 各要素を変換して新しいリストを作成します。
List<int> numbers = [1, 2, 3, 4, 5]; List<int> doubled = numbers.map((number) => number * 2).toList(); // 各要素を2倍にして新しいリストを作成
where: 条件に一致する要素のみをフィルタリングします。
List<int> numbers = [1, 2, 3, 4, 5]; List<int> evenNumbers = numbers.where((number) => number % 2 == 0).toList(); // 偶数のみを抽出
reduce: 要素を結合して1つの値に縮約します。
List<int> numbers = [1, 2, 3, 4, 5]; int sum = numbers.reduce((value, element) => value + element); // 要素の合計を計算
マップのためのメソッド
[編集]forEach: 各エントリー(キーと値のペア)に対して特定の処理を行います。
Map<String, int> ages = {'John': 30, 'Alice': 25, 'Bob': 35}; ages.forEach((key, value) => print('$key is $value years old')); // 各エントリーを出力
map: キーと値を変換して新しいマップを作成します。
Map<String, int> ages = {'John': 30, 'Alice': 25, 'Bob': 35}; Map<String, String> greetings = ages.map((key, value) => MapEntry(key, 'Hello, $key!')); // 新しいマップを作成
このように、Dartの組み込み型やコレクションのイテレーションメソッドを活用することで、データを効率的に管理・操作できます。
式と演算子
[編集]void main() { // 算術演算子 int num1 = 10; int num2 = 5; print('num1 + num2 = ${num1 + num2}'); // 和 print('num1 - num2 = ${num1 - num2}'); // 差 print('num1 * num2 = ${num1 * num2}'); // 積 print('num1 / num2 = ${num1 / num2}'); // 商 print('num1 % num2 = ${num1 % num2}'); // 余り // 比較演算子 int a = 10; int b = 20; print('a == b : ${a == b}'); // aとbが等しいか? print('a != b : ${a != b}'); // aとbが等しくないか? print('a > b : ${a > b}'); // aがbより大きいか? print('a < b : ${a < b}'); // aがbより小さいか? print('b >= a : ${b >= a}'); // bがa以上か? print('b <= a : ${b <= a}'); // bがa以下か? // 論理演算子 bool val1 = true; bool val2 = false; print('val1 && val2 : ${val1 && val2}'); // val1とval2が両方ともtrueか? print('val1 || val2 : ${val1 || val2}'); // val1とval2のどちらかがtrueか? print('!val1 : ${!val1}'); // val1の否定 // 代入演算子 int x = 10; print('x = $x'); // xの初期値を表示 x += 5; // xに5を加算 print('x += 5 : $x'); // 結果を表示 x -= 3; // xに3を減算 print('x -= 3 : $x'); // 結果を表示 x *= 4; // xに4を乗算 print('x *= 4 : $x'); // 結果を表示 // 条件付き演算子 int age = 20; String message = (age < 18) ? '未成年です' : '成人です'; print('message : $message'); // 結果を表示 }
Dartの演算子の優先順位 優先順位 種類 演算子 15 単項 - ! ~ ++ -- 14 乗算 * / % ~/ 13 加算 + - 12 シフト << >> 11 関係 < <= > >= 10 等価 == != 9 ビット積 & 8 ビット和 ^ 7 ビット和(OR) 6 条件(AND) && 5 条件(OR) || 4 条件式 ? ... : 3 代入 = *= /= ~/= %= += -= <<= >>= &= ^= |= 2 条件 is as 1 条件 if null
コメント
[編集]コメントをソースコードに書くときは、//
や、/*
と*/
を使用します。
1行で終わるコメント
[編集]下記コードのように//
を使うと行末までがコメントになります。
- コード例
void main() { final cat = "Miku"; // 猫の名前 print("The name of the cat is ${cat} \n"); }
- 実行結果
The name of the cat is Miku
複数行に渡るコメント
[編集]下記コードのように/*
と*/
で文を囲むと2行以上をコメントにできます。
- コード例
void main() { /* 動物の種類 猫 犬 兎 */ const animals = ["cat", "dog", "rabbit"]; print(animals); }
- 実行結果
[cat, dog, rabbit]
関数
[編集]Dartでは関数はファーストクラスオブジェクトとして扱われ、変数に代入したり、引数として他の関数に渡したり、戻り値として返したりすることができます。関数には名前付き関数や無名関数、引数付き関数、デフォルト引数などさまざまな特徴があります。
基本的な関数の定義
[編集]Dartで関数を定義するには、`return_type function_name(parameters)`の形式を使います。戻り値の型は省略することもできます。
- 例
int add(int a, int b) { return a + b; } void main() { print(add(2, 3)); // 5 }
アロー関数 (Fat Arrow)
[編集]関数が1つの式だけを返す場合、アロー構文を使って簡潔に書くことができます。
- 例
int add(int a, int b) => a + b; void main() { print(add(2, 3)); // 5 }
無名関数 (Anonymous Function)
[編集]無名関数は、関数に名前を付けずに直接定義して使うことができます。特に高階関数に引数として関数を渡すときなどに便利です。
- 例
void main() { var list = [1, 2, 3]; list.forEach((item) { print(item); // 1, 2, 3 }); }
引数の型とデフォルト値
[編集]Dartでは、引数にデフォルト値を設定することができます。デフォルト値は名前付き引数(`{}`で囲まれる)やオプションの位置引数(`[]`で囲まれる)で使います。
- 例 名前付き引数
void greet({String name = 'Guest'}) { print('Hello, $name!'); } void main() { greet(); // Hello, Guest! greet(name: 'John'); // Hello, John! }
- 例 オプションの位置引数
void greet([String name = 'Guest']) { print('Hello, $name!'); } void main() { greet(); // Hello, Guest! greet('John'); // Hello, John! }
高階関数
[編集]Dartでは、関数を引数として渡したり、関数を戻り値として返すことができます。このような関数は高階関数と呼ばれます。
- 例
void main() { Function applyMultiplier(int multiplier) { return (int value) => value * multiplier; } var doubler = applyMultiplier(2); print(doubler(5)); // 10 }
匿名関数とコールバック
[編集]無名関数はコールバックとしてもよく使われます。例えば、リストの各要素に対して操作を行う場合など。
- 例
void main() { var numbers = [1, 2, 3, 4, 5]; var doubledNumbers = numbers.map((number) => number * 2); print(doubledNumbers); // (2, 4, 6, 8, 10) }
まとめ
[編集]- Dartの関数はファーストクラスオブジェクトで、他の変数と同様に操作できます。
- アロー構文を使うことで簡潔に書ける。
- 無名関数や高階関数、デフォルト引数を使うことで柔軟な関数の定義と利用が可能。
- Dartの関数は静的型付けの柔軟さと動的な使い方を併せ持っています。
ライブラリー
[編集]Dartには、様々な用途に特化した多くのライブラリーが存在します。以下は、Dartでよく使用されるライブラリーの一部です。
dart:async
- Dartの非同期処理を扱うためのライブラリーdart:math
- 数学関数を提供するライブラリーdart:io
- ファイル入出力やネットワーク通信を扱うためのライブラリーdart:convert
- JSONやUTF-8などのエンコーディングやデコーディングを扱うためのライブラリーdart:html
- Webアプリケーションを開発するためのHTML要素を提供するライブラリーhttp
- HTTP通信を扱うためのライブラリーflutter
- モバイルアプリケーションを開発するためのフレームワークであり、UIやネットワーク通信などの機能を提供するライブラリー
これらのライブラリー以外にも、様々な目的に特化したライブラリーが存在しています。Dartの公式ドキュメントや、Dart Pubと呼ばれるパッケージマネージャーで検索することで、必要なライブラリーを見つけることができます。
print関数などのよく使われる関数はdart:core
ライブラリーにあり、dart:core
ライブラリーはimportが不要ですが、それ以外のライブラリーはimportが必要です。
math ライブラリー
[編集]- コード例
import 'dart:math'; num numval = pi; void main() { print(sin(numval / 4)); }
- 実行結果
0.7071067811865475
- ライブラリーのimport
import 'dart:math';
- num型
num numval = pi;
- num型は、数値ならば整数型でも浮動小数点数型でも保持できる変数を宣言します。
- ライブラリー関数の使用
print(sin(pi / 4));
Dartの型システムの説明の前なので、簡単な例の説明に留めました。
制御構造
[編集]Dartには、if-else
文、for
文、while
文、do-while
文、switch
文、forEach
メソッドなど、さまざまな制御構造があります。Dart 3では、いくつかの新しい機能や改善が導入されました。
- Dartプログラミング言語の主要な制御構造のチートシート
void main() { // if文 var x = 10; if (x > 5) { print("x is greater than 5"); } else if (x == 5) { print("x is equal to 5"); } else { print("x is less than 5"); } // forループ for (var i = 0; i < 5; i++) { print("i is $i"); } // for-inループ List<int> numbers = [1, 2, 3, 4, 5]; for (final number in numbers) { print("number is $number"); } // whileループ var count = 0; while (count < 5) { print("count is $count"); count++; } // do-whileループ var i = 0; do { print("i is $i"); i++; } while (i < 5); // switch文 (Dart 3: Enhanced pattern matching) const dynamic color = "red"; switch (color) { case "red": print("color is red"); break; case "green": print("color is green"); break; case "blue": print("color is blue"); break; default: print("color is not recognized"); break; } // break文 for (var i = 0; i < 5; i++) { if (i == 3) { break; } print("i is $i"); } // continue文 for (var i = 0; i < 5; i++) { if (i == 3) { continue; } print("i is $i"); } // Dart 3: "for" with destructuring var points = [ {'x': 1, 'y': 2}, {'x': 3, 'y': 4}, ]; for (var {'x': x, 'y': y} in points) { print("Point: ($x, $y)"); } }
- 実行結果
x is greater than 5 i is 0 i is 1 i is 2 i is 3 i is 4 number is 1 number is 2 number is 3 number is 4 number is 5 count is 0 count is 1 count is 2 count is 3 count is 4 i is 0 i is 1 i is 2 i is 3 i is 4 color is red i is 0 i is 1 i is 2 i is 4 i is 5 Point: (1, 2) Point: (3, 4)
Dart 3の新機能
[編集]- Dart 3以降、
switch
文では「パターンマッチング」がサポートされ、より柔軟な条件分岐が可能になりました。また、for
ループでも「デストラクチャリング」がサポートされ、コレクション内のオブジェクトのフィールドを直接取得することができます。
for文とif文の組合わせ
[編集]- for文とif文の組合わせ
void main() { for (var i = 0; i < 10; i++) { if (i % 2 == 0) { print("$i は2の倍数です。"); } else if (i % 3 == 0) { print("$i は3の倍数です。"); } else { print("$i は2の倍数でも3の倍数でもありません。"); } } }
forEachメソッド
[編集]forEach
メソッドは、リストやマップなどのコレクションの各要素に対して指定した処理を実行するためのメソッドです。
- forEachメソッド
void main() { List<String> fruits = ["apple", "banana", "orange"]; // リストの各要素に対して、処理を実行する fruits.forEach((final fruit) { print(fruit); }); }
また、forEach
メソッドは、ラムダ式を使用して簡略化することもできます。
- forEachメソッドとラムダ式の組み合わせ
void main() { List<String> fruits = ["apple", "banana", "orange"]; // ラムダ式を使った簡略化 fruits.forEach((fruit) => print(fruit)); }
オブジェクト指向プログラミング
[編集]Dartはオブジェクト指向言語であり、クラスを使ってオブジェクトを定義できます。クラスはフィールドとメソッドを持ち、フィールドはオブジェクトの状態を表し、メソッドはオブジェクトの振る舞いを定義します。
以下は、Dartでクラスを定義し、オブジェクトを作成する例です。例として、Personクラスを定義し、名前と年齢をフィールドに持ち、年齢を1歳増やすメソッドを定義します。また、main関数で2つのPersonオブジェクトを作成し、1つのオブジェクトの年齢を増やして表示します。
- クラスの例
// Personクラスの定義 class Person { String name; // 名前 int age; // 年齢 // コンストラクタ Person(this.name, this.age); // 年齢を1歳増やすメソッド void incrementAge() { age++; } } void main() { // Personオブジェクトの作成 var person1 = Person('Alice', 20); var person2 = Person('Bob', 25); // person1の年齢を1歳増やす person1.incrementAge(); // person1とperson2の情報を表示する print('Person 1: ${person1.name}(${person1.age})'); print('Person 2: ${person2.name}(${person2.age})'); }
- 実行結果
Person 1: Alice(21) Person 2: Bob(25)
- まず、Personクラスが定義されています。このクラスには、名前と年齢を表すString型の
name
フィールドとint型のage
フィールドが含まれています。また、このクラスには、名前と年齢を引数として受け取るコンストラクタと、年齢を1歳増やすメソッドincrementAge
が含まれています。 - 次に、
main
関数では、2つのPersonオブジェクトを作成し、1つのオブジェクトの年齢を増やしています。var
キーワードを使って、Personオブジェクトを作成しています。Person('Alice', 20)
は、Person
クラスのコンストラクタを呼び出し、name
を'Alice'、age
を20で初期化しています。 - その後、
person1
オブジェクトのincrementAge()
メソッドが呼び出され、その結果、person1
オブジェクトのage
フィールドが1増えます。 - 最後に、
person1
とperson2
の情報を表示するために、print()
関数が使用されます。それぞれのオブジェクトの名前と年齢が表示されます。
継承
[編集]継承は、既存のクラスを基にして新しいクラスを定義することができます。
- 継承の例
// Personクラスの定義 class Person { String name; // 名前 int age; // 年齢 // コンストラクタ Person(this.name, this.age); // 年齢を1歳増やすメソッド void incrementAge() { age++; } } void main() { // Personオブジェクトの作成 var person1 = Person('Alice', 20); var person2 = Person('Bob', 25); // person1の年齢を1歳増やす person1.incrementAge(); // person1とperson2の情報を表示する print('Person 1: ${person1.name}(${person1.age})'); print('Person 2: ${person2.name}(${person2.age})'); }
- 実行結果
Tama is eating. Meow!
- この例では、
Animal
という親クラスが定義され、name
というインスタンス変数とeat()
というメソッドが定義されています。そして、Cat
という子クラスがAnimal
を継承しています。Cat
はname
を引数に取るコンストラクタとmeow()
というメソッドを持っています。 main
関数では、Cat
のインスタンスであるcat
を作成し、eat()
とmeow()
メソッドを呼び出しています。eat()
メソッドはAnimal
クラスから継承されたもので、meow()
メソッドはCat
クラスで定義されたものです。
コード・ギャラリー
[編集]コードの理解はプログラミング言語の学習において重要です。 以下は、動作する簡潔なコード例をいくつかご紹介します。
エラトステネスの篩
[編集]エラトステネスの篩を、若干 Dart らしく書いてみました。
- エラトステネスの篩
void eratosthenes(int n) { var sieve = List.filled(n + 1, true); for (var i = 2; i * i < sieve.length; i++) { if (sieve[i]) { for (var j = i * i; j < sieve.length; j += i) { sieve[j] = false; } } } for (var i = 2; i < sieve.length; i++) { if (sieve[i]) { print("$i "); } } } void main() { eratosthenes(100); }
eratosthenes
関数は、指定された整数n
までの素数を見つけるために使用されます。この関数内で、sieve
と呼ばれるブール型のリストが作成されます。このリストは、インデックスが数値に対応し、その数が素数であるかどうかを示します。最初はすべての数値が素数であると仮定されます。for
ループは、2からsieve
の長さまでの範囲で回ります。このループは、素数を探すために各数値を確認します。if (!sieve[i]) continue;
は、sieve[i]
がfalse
(素数でないことを示す)場合には処理をスキップし、次の数値を調べるためにループを進めます。素数でない数値の倍数は素数ではないため、これによって非素数を除外します。- 素数である数値
i
の倍数を探すための内側のfor
ループがあります。これは、j
を2 * i
からsieve
の長さまでi
ずつ増やしながらループします。このループでは、sieve[j]
をfalse
に設定することで、j
が素数でないことを示します。 main
関数では、eratosthenes
関数を引数100で呼び出しています。これにより、100以下のすべての素数が見つけられ、それらがコンソールに出力されます。
エラトステネスの篩は、効率的な方法で素数を見つけるアルゴリズムであり、指定された範囲の素数を比較的高速に見つけることができます。
最大公約数と最小公倍数
[編集]最大公約数と最小公倍数を、若干 Dart らしく書いてみました。
- 最大公約数と最小公倍数
int gcd2(int m, int n) => n == 0 ? m : gcd2(n, m % n); int gcd(List<int> ints) { if (ints.isEmpty) { throw ArgumentError("List of integers cannot be empty"); } return ints.reduce(gcd2); } int lcm2(int m, int n) => m * n ~/ gcd2(m, n); int lcm(List<int> ints) { if (ints.isEmpty) { throw ArgumentError("List of integers cannot be empty"); } return ints.reduce(lcm2); } void main() { print("gcd2(30, 45) => ${gcd2(30, 45)}"); print("gcd([30, 72, 12]) => ${gcd([30, 72, 12])}"); print("lcm2(30, 72) => ${lcm2(30, 72)}"); print("lcm([30, 42, 72]) => ${lcm([30, 42, 72])}"); }
- 実行結果
gcd2(30, 45) => 15 gcd([30, 72, 12]) => 6 lcm2(30, 72) => 360 lcm([30, 42, 72]) => 2520
以下は、Dartコードの解説です。
gcd2
関数は、ユークリッドの互除法を使用して整数m
とn
の最大公約数(GCD)を再帰的に計算します。- 三項演算子
n == 0 ? m : gcd2(n, m % n)
を使用して、n
が 0 ならばm
を、そうでなければ再帰的にgcd2
を呼び出します。 gcd
関数は、与えられた整数のリストに対して最大公約数(GCD)を計算します。- リストが空であれば、
ArgumentError
をスローして空のリストは処理できないようにしています。 ints.reduce(gcd2)
を使用して、リスト内の各要素に対してgcd2
を繰り返し適用して最終的な GCD を計算します。lcm2
関数は、整数m
とn
の最小公倍数(LCM)を計算します。m * n ~/ gcd2(m, n)
を使用して、m
とn
の積を最大公約数(gcd2
)で割ってLCMを計算しています。lcm
関数は、与えられた整数のリストに対して最小公倍数(LCM)を計算します。- リストが空であれば、
ArgumentError
をスローして空のリストは処理できないようにしています。 ints.reduce(lcm2)
を使用して、リスト内の各要素に対してlcm2
を繰り返し適用して最終的な LCM を計算します。main
関数は、それぞれの関数を呼び出して結果を出力しています。- 文字列補間 (
${}
) を使用して、関数呼び出しの結果を文字列として表示しています。
二分法
[編集]二分法を、若干 Dart らしく書いてみました。
- 二分法
double bisection(num low_, num high_, double Function(double) f) { double low = low_.toDouble(); double high = high_.toDouble(); double x = (low + high) / 2; double fx = f(x); if ((fx.abs()) < 1.0e-10) { return x; } if (fx < 0.0) { low = x; } else { high = x; } return bisection(low, high, f); } void main() { print('${bisection(0, 3, (double x) => x - 1)}'); print('${bisection(0, 3, (double x) => x * x - 1)}'); }
- 実行結果
0.9999999999417923 1.0000000000291038
- 旧課程(-2012年度)高等学校数学B/数値計算とコンピューター#2分法の例を Dart に移植しました。
このDartコードは、二分法を使用して与えられた関数の根(ゼロとなる点)を見つけます。bisection
関数は、指定された範囲内で与えられた関数f
の根を見つけます。main
関数では、いくつかの関数を用いて根を見つけるためにbisection
関数を呼び出しています。
用語集
[編集]- Dart: Googleによって開発されたオープンソースのプログラミング言語。
- クラス(class): オブジェクト指向プログラミングにおけるデータとそれを操作するメソッドをまとめたもの。
- インスタンス(instance): クラスから生成された実体。
- 継承(inheritance): 親クラスのメソッドや変数を子クラスが引き継いで使用すること。
- インターフェイス(interface): クラスが実装しなければならないメソッドや変数の一覧。
- ライブラリ(library): Dartのコードをパッケージ化して再利用可能にする仕組み。
- 静的型付け(static typing): 変数にどのような型の値しか代入できないかを事前に指定すること。Dartは静的型付けに対応している。
- ミックスイン(mixin): 複数のクラスに同じメソッドや変数を追加することができる仕組み。
- Future: 非同期処理を表すオブジェクト。Dartでは、非同期処理をFutureで表現することが一般的である。
- async/await: 非同期処理をシンプルに扱うための仕組み。Dartでは、async/awaitキーワードを使用することで非同期処理を直感的に実装することができる。
- Flutter: Dartを使用したGoogleによるモバイルアプリケーションフレームワーク。
脚註
[編集]