Flutter開発で必要なDart言語の基本を理解しよう

今回は「Flutter開発で必要なDart言語の基本を理解しよう」についてご紹介します。
関連ワード (アプリケーション開発、モバイル等) についても詳細と、関連コンテンツとをまとめていますので、参考にしながらぜひ本記事について議論していってくださいね。
本記事は、CodeZine様で掲載されている内容を参考にしておりますので、より詳しく内容を知りたい方は、ページ下の元記事リンクより参照ください。
対象読者
今回からFlutterの基本について説明します。Dart言語について知っている必要はありませんが、他の言語の基本を知っている方を対象に説明します。特に、JavaScriptやTypeScriptもしくは、Javaなど言語を使ってプログラミングしたことがある方であれば、より理解がしやすくなります。
Dartの基本
前回、Flutterのサンプルプログラムで示しましたが、まずはプログラムスタイルをなんとなく感じてもらえればと思い、記述しました。しかし、実際自分でコードを記述する時には、読んでなんとなくわかるだけでは不十分です。ここでDartの文法について詳細にお伝えすることは難しいですが、Flutterを使う上でも必要なDartの基本や文法について紹介します。
main関数
スクリプト言語に慣れていると、ファイルの上から順に実行される形式が普通といった感覚があります。一方、Dartでは、リスト1のようにmainという関数があり、プログラムが実行する際にはこのmain関数が実行されます。Flutterのサンプルコードにもこのmain関数があります。
main関数からプログラムが始まるのは、C言語やJavaのような言語と同じです。このような慣例を導入したことから、筆者はTypeScriptのような、JavaScriptというスクリプト言語を拡張した流れというよりは、どちらかと言えば、Javaのような言語を最近のJavaScriptの文化に近づけていったように感じます。
void main() { print("hello"); }
プログラムの実行
Dartプログラムの場合、dartコマンドを使うことでプログラムの実行が可能です。dartコマンドはFlutterをインストールしてあれば、既にインストールされているので新たにインストールする必要はありません。
また、dartコマンドはflutterコマンドと同じ場所にあります。PATHを通していない場合には、第2回を参考にしてください。次のリスト2は、先ほどのmainプログラムを実行した結果です。
>dart main.dart hello
また、今回紹介する内容であれば、dartをインストールしていなくても、図1のようにサイト上でも実行して確認できます。

変数の特徴
一般的な言語ではプリミティブ型と分類される基本的な型が存在します。しかし、Dartでの変数の型はすべてがオブジェクト型の派生タイプであり、基本ライブラリで型が用意されています。
そのため、厳密な基本型という定義があるわけではありませんが、その他の言語で基本的な型に相当するもののみを表1に示しました。Stringについては、他の言語によっては基本型とは区分されていないものもあります。しかし、DartではこのString以外に文字列を扱うための型が無いために、基本型として紹介しています。
型 | 概要 |
---|---|
int | 整数。64bitで管理された整数になります。ただし、Flutter内で使う場合には64bitでの整数ですが、他の実行環境の場合には異なるので注意が必要です。 |
double | 浮動小数点数。 |
num | intとdoubleの親タイプとなる型。 |
bool | 論理値。trueもしくはfalseの値を取ることができます。 |
Null | null値の型。 |
String | 文字列型。Dartでは、Javaなどで存在するcharといった1文字を扱う型はありません。 |
またbyteなどの型も無いので、int型などで代用することになります。
型変換
型変換をする場合には、それぞれのクラスに変換メソッドがあるので、そちらを利用します。例えば、小数点まで管理できるdouble型を、整数型(int)に変換する際には、toIntのようなメソッドを利用します。
double num = 10.5; int intNum = num.toInt(); // 10になる
変数の自動型推論
変数に型制限があると型を毎回指定する面倒がでてしまいます。一方、このような制限があることで、他人が記述したプログラムがわかりやすく、間違いが起きにくくなるというメリットがあります。
そこで最近の言語では、型を決めつつ記述を簡単にするために、型が自動的に決まる場合は省略できるようになっています。この機能を「型推論」と言います。Dartでも同じように型推論があります。リスト4のように型を指定する代わりに、varキーワードを使うことで省略が可能です。
var num1 = 10; // int num1 = 10;と同じ var num2 = 10.0; // double num2 = 10.0;と同じ
型推論を使うとわかりにくくなるケースもあります。そのような例がリスト5です。
var num3 = 12; var num4 = 3; var num5 = num3 / num4; // (1)型推論を使ってdouble型の3.0の値を設定する double num6 = num3 / num4; // (2)型指定を使ってdouble型の3.0の値を設定する
int型の同士の割り算では、結果はdoubleになります。しかし、(1)の書き方では型が少々わかりにくくなります。この場合には、型推論を使わず、(2)のように明示的に型を指定したほうがよいでしょう。なお、3.0ではなく、3という整数型として扱いたいという場合もあります。その場合、double型からint型へ変換が必要です。この変換方法を示したのが、リスト6です。
int num7 = ( num3 / num4 ).truncate().toInt(); // (1)メソッドを使った型変換 int num8 = num3 ~/ num4; // (2)演算子を使って変換
(1)は、小数点以下をtruncateメソッドで削除し、その後toIntにてint型に変換しています。ちなみに、ここでは型変換の例として、(1)のようなコードを書きましたが、除算で整数型の結果を得たいならば、(2)のように「~/」演算子を利用しても構いません。
型を特定しない変数の宣言
型を意識した言語であっても、どうしても型が決められない場合があります。そういった場合に利用できるのが、dynamic型です。例えば、リスト7のように異なる型の値を同じ変数で扱うことができます。
dynamic val; val = "hello"; val = 10;
先ほど紹介したvarを使った型推論を使って、リスト8のように記述できません。これは最初のval変数を指定する際に、”hello”を指定することで文字列型に固定されてしまうためです。
var val = "hello"; val = 10; // エラー
Generic型
型を意識しないような言語において、最もやっかいな問題の1つに、配列などを使って複数の値を格納して管理するケースがあります。例えば、合計値や平均値を計算するための配列であれば、コーディングレベルでは文字列などの追加を禁止したいところです。そのような場合に利用するのが、Generic型です。例えば、リスト9の通りにすることで、リスト配下の要素をint型に限定できます。
List<int> values = []; values.add(10); values.add("ten"); // 文字列の追加はエラー
一方で、型を意識せず値を格納したい場合があります。そういった場合、利用するのが先ほどのdynamicです。また、Generic型の記述は省略することもでできますが、その場合にはdynamicを指定したことと同じ意味になります。
変数宣言キーワード
先ほど変数を利用する場合には、varや型などを指定しましたが、変数にfinalやconstなどのキーワードを指定すると値の変更できなくすることが可能です。これは2つのキーワードは似ていますが、少々意味が異なります。それらの違いの意味を示したのがリスト10です。
import 'dart:math'; int someValue(){ return Random.secure().nextInt(100); // ランダムな値を返す } main(){ // (1)変数の内容を書き換えられないように指定 final int data1 = 100; const int data2 = 100; // (2)どちらも変更できないので、コンパイル時にエラーになる data1 = 120; data2 = 150; // (3)ランダムな値を初期値として指定 final int data3 = someValue(); // (4)以下はエラーになる const int data4 = someValue(); }
リスト(1)のようにfinalやconstを使うと、指定した値(例では100)で固定できます。つまり、定数化が可能です。従って、(2)のようにその後、変数の値を変更しようとすると、コンパイル時にエラーになります。Android StudioなどのIDEを使っていれば、コーディング中にエラーがあることがわかります。この使い方ではどちらを使っても同じ効果になります。
(3)のようにある関数を通じて、ランダムな値を設定するようなケースになると、finalとconstの違いが生じます。(3)のようにfinalを使った場合だとエラーになりませんが、(4)のようにconstを使うことはできません。これは、finalとconstで定数として値を固定するタイミングが異なるからです。finalは実行時に値が固定され、constはコンパイル時に値が固定されます。そのため、(4)ではエラーになります。
JavaScriptのようなスクリプト言語では通常、実行時とコンパイル時を意識することが無いので、この部分も細かい違いですが、わからないと悩んでしまいます。
クラス名の変数名の命名規則
これまでのコードなどを眺めていれば気がつく方もいるかと思いますが、クラス名はUpperCamelCaseというルールで定義されることが望ましいとされています。
これは、大文字で複数の単語をつなげて名称を作るというルールです。ただし、前述した基本的な型で紹介したクラスの中にはこのルールではないものも存在します。また、変数名はlowerCamelCaseというルールで定義されることが望ましいです。最初の単語のみ小文字で初めて、その後は大文字にする形式になります。
また、「_(アンダーバー)」を使うことは推奨されていません。特に、変数名の最初に「_」を使うことは、次回説明しますがクラス内でのプライベート(クラス外からは不可視)変数として特別な意味になるからです。
オブジェクトインスタンスの作成
オブジェクト型のインスタンスを作成する際には、dartは他の言語と同じようにnew演算子を使うことで可能です。ただし、dartの場合、このnewを省略することができます。そのため、リスト11のように記述している内容は同じ意味になります。
var app = new MyApp(); var app = MyApp();
特に、現在ではnewは省略されて記述されるケースが多くなっています。
コレクション
基本的な型の他にプログラマがよく利用するものに、一次元配列(リスト)や連想配列(マップやディクショナリと表現する言語もあります)があります。このような、複数の値を管理するデータセット用のクラスを一般的にコレクションと言います。Dartの基本ライブラリでもこれらの型は提供されているので、それぞれ紹介します。
一次元配列(List)
Dartで配列を管理する場合には、Listが利用できます。Arrayという型は基本ライブラリには無いのでご注意ください。また、リスト12が実際に利用した場合の例です。
main() { // (1)空のリストを作る場合 List<int> list1 = []; // (2)値を設定する場合 List<int> list2 = [2,4,6,8,10]; list2.add(12); // (3) 値を追加する場合 // (3)型が決まらない値を追加する場合 List<dynamic> list3 = ["two","three","four",5,6,7]; // (4)値の取得方法 print(list3.elementAt(0)); // 0番目値の"two"を取得する print(list3[2]); // 2番目値の"four"を取得する // (5)省略した場合には、dynamicと同じ List list4 = [2,4,6,8,10]; list4.add("twelve"); }
空のListを作成する場合、(1)のように[]を使うことで作成できます。また、変数の宣言時に値を設定する場合には、(2)の通りにします。そして、値を追加する場合は、addというメソッドを利用します。また、Generic型で追加できる型を制限していますが、指定したくない場合には(3)のようにdynamic型を指定します。
そして、値を取得するには、(4)のようにelementAtというメソッドを使って取得することもできますが、メソッドを使わず[]を使ってアクセスすることも可能です。(5)のように型を省略できます。その場合には、dynamic型を指定した時と同じになります。そして、値を指定していても、省略した場合にはdynamic型を指定したことと同じです。
ユニークな値の一次元配列(Set)
Setとは、同じ値を含まない一次元配列のようなデータセットを管理するための型です。リスト13が実際に利用した場合の例です。
void main(){ // (1)空のSetを作る場合 Set<int> values = {}; // (2)初期値を指定する場合 Set<int> values2 = {2,3,5}; values2.add(6); // (3)値を追加する場合 values2.add(6); // 同じ値は2つ追加できない // (4)値を取得する方法 int val_2 = values.elementAt(0); // (5) []を使ったアクセスはできません // int val_3 = values[0]; }
空のSetを作成する場合、(1)のように{}を使うことで作成できます。また、変数の宣言時に値を設定する場合には、(2)の通りにします。そして、値を追加する場合は、Listと同じようaddメソッドを使います。しかし、Listと最も異なるのが、同じ値は重複して登録はできません。そして、値の取得は(4)のようにelementAtを使うことができますが、(5)Listと同じように[]を使って値の取得はできません。
連想配列(Map)
Mapとは、キーとそのキーにひも付きいた値を管理するための型です。そのため、辞書型(ディクショナリ)と言われる場合もあります。DartではMapと呼ばれています。リスト14が実際に利用した場合の例です。
void main(){ // (1)空のマップを作成する Map<String,int> values1 = {}; // (2)インスタンス作成時に値を設定する Map<String,int> values2 = {'one' : 1, 'two' : 2}; // (3)値を追加する方法 values1['three'] = 3; // (4)値の取得 print(values2['one']); }
空のMapを作成する場合には、(1)のように{}を使うことで作成ができます。また、変数の宣言時に値を設定する場合には、(2)の通りにします。そして、値を追加する場合は、(3)のように[キー値]を使って追加が可能です。そして、値の取得は[]を使って行えます。
まとめ
今回は、Dart言語の基礎部分について紹介しました。JavaScriptやJavaのような言語と似ていると何度も言及していますが、言語の基本部分でもそれらの特徴を取り入れていると感じられます。
特に、言語仕様部分ではJavaScript部分よりも厳格にし、データなどの扱い方ではJavaのような厳格さを求めていません。これは、Dartと言う言語がUIを扱うための言語であって、必ずしも広い目的で使える汎用言語ではないためです。そのため、目的を満たす制限と使いやすさのための寛容さを、バランスよく反映できたものだと思います。
次回は、関数やクラスについて紹介します。関数やクラスの扱い方になると、Dart言語の特徴がより表れるので、今回紹介した内容を前提にしてもらえればと思います。