Flutter講義:その1(Dart言語について①)

Flutter/Dart講義の1回目。かなり基本的な言語構造から説明をしていきますよ。

とか言いつつ、半分は「メモ」チック。なぜならもともと自分の学習用メモを加筆修正しながら書いているから*1

キーワード/予約語

詳しくはDartの公式ページに記載があるのでそちらを参照のこと*2

dart.dev

簡単に言えば、これらのキーワードは変数名やクラス名、メソッド名など、プログラマが定義できるモノの名前として使ってはいけない単語である。

細かく整理すると、キーワードの中にも何種類かあって、

  • コンテキストキーワード(限定された場所で使用すると意味を持つキーワード)
  • ビルトイン識別子(特にクラス名・型名として使えないキーワード)
  • 非同期処理専用キーワード(非同期処理実行時に意味を持つキーワード)
  • その他の「予約語」(プログラマが再定義することができないキーワード)

があるが、コードのわかりやすさ(≒可読性)を考えるとキーワードを無暗に変数名などにしてはいけない。

データ型(Data types)

api.dart.dev

Dartは基本的には静的型付け言語*3。データを(変数に)格納するためにはあらかじめ変数の型を指定する必要がある(例外あり:後述)。

特筆すべき点として、Dart言語(>=2.10)*4では「Null型」が存在する。また、この影響でNull型以外の型は基本的にnull値を持つことを許されない。

この点については別項(講義③予定)にて説明する。

また、「静的型付け言語」と言いつつも、動的な型が存在する(var、dynamic)。そして、型指定をしなくても変数(等)の定義は可能(次項にて説明)。

String型に関して追記*5

String型の文字列リテラルはシングルクォーテーションまたはダブルクオーテーションで囲む。

なお、シングルクォーテーションで囲むことが推奨されているが、ダブルクォーテーションでもエラーにはならない。また、囲みを最初シングル、後をダブル、またはその逆はエラー。囲みの最初をシングルにした場合は後もシングルにしないといけない(カッコの囲い方と同じ考え方)。

String firstName = 'Tom';
String lastName = "Araya";
// どちらの変数もString型のリテラル値が格納される。特にエラーになるわけではないが、推奨されるのはシングルクォーテーション。
// 全く余談だが、Tom Araya氏はメタルバンドSlayerのベーシスト・ボーカリスト。どうでもいい...。

変数と定数

「値を格納するための箱」という説明でだいたい理解できるはず*6

前項(データ型)で述べているように、静的型付け言語のため、変数も定数も(基本的には)型の指定が必要になる。

なお、変数・定数などの名前の付け方、いわゆるコーディング規約については別途項目を設けるが、しばらくは「なんとなくこんな感じ」でサンプルコードを書いていくことにする。

変数(Variables)

一時的に値を格納するための「箱」。値を格納することを「代入」と言い、変数は値を「再代入」することが可能。

型名を指定し(省略不可)、そのあとに変数名を書く(変数の宣言)。また、変数宣言と同時に値の代入をすることも可能(変数の定義)。

例:

String yourName;  // 変数の宣言
int yourAge = 18;  // 変数の宣言と代入を同時に行う(定義)

動的な型指定について(var、dynamicの違い)

前述のように、変数宣言時に動的な型指定を行うことが可能となる。ただし、型指定を動的に行うキーワードについては明確な違いがあるので注意が必要。

「var」で型指定を行う場合は、

  • 変数定義のみを最初に行う場合(つまり、最初に変数定義のみ行い、あとで再代入をする場合)は動的に型が変わる
  • 変数宣言をした場合は、宣言時に代入された値の型で固定される*7

例:

var name = "Tommy";
name = 16;
// 2行目でnameに再代入している値が整数型(int)であり、変数宣言時に代入した文字列型(String)と異なるためエラーになる

また、「dynamic」で型指定を行う場合には、変数定義のみの場合でも変数宣言をした場合でも再代入時には動的に型が変わる。

実装時の区別について(私見)

正直「var」は使わないかな、というのが今の私のスタイル。ただ、「とりあえず変数を作っておいてあとで何を入れるか考える」という感じでvarを使うことはある(あとで入ってくる値を見て型を書き直す)。

「dynamic」はと言うと、自分の書いているプログラムの外側から入ってくるものに関して使うことが多い。DBだったりAPIだったり。DBはある程度型がわかっている(DBのカラムも型があるので)からある特定のカラムの情報を代入するなら事前に型指定をするけれど。

定数(Constants)

プログラムの実行時に、一度代入した内容を変更できない「箱」。

キーワード「const」または「final」を使って定義する。下例のように、const/finalキーワードのあとに型名を指定し(省略可能)、そのあとに定数名を書く。

余談だが、行区切りはセミコロンである。

例:

// constを使うケース
// const [DataType] constName;
const int userAge = 18;

// finalを使うケース
// final [DataType] finalName;
final String userName = "Nikki Bass";

constとfinalの違いについて

厳密に(プログラム的に)言うと、constキーワードを使うと、プログラムのコンパイル時に値が代入され、finalキーワードの場合はプログラム実行時に代入される、という違いがある。

が、どちらも「定数」であり、いつどのタイミングで代入されようが「定数(不変の値)」なので、普段はそれほど意識しなくてもよさそうではある。

このあたり(constとfinalの違い)はQiitaのこの記事の説明がわかりやすい。

qiita.com

実装時の区別について(私見)

プログラムをローレベルで解析し、高速化を考えるなら意識して(メモリの位置が不変であり、高速化が期待できるのでconstを使うとか)考えればいいが、普段のコーディングでそこまで意識する必要はない、というのが私の見解だったりする。

関数(Functions)

PHPでも「ユーザ定義関数」という言葉で表現されている。Javaだと「(インスタンス)メソッド」が近いかな?*8

ざっくり言うと、プログラムのグループ化と言うか、ロジック(プログラム)を格納しておくための枠。

例:

void [functionName] () {} // →戻り値なし

dataType [functionName]() {
  return whatEver;
}  // →戻り値ありの場合

void [functionName] (dataType dt1, dataType dt2) () {
  // some logic…
} // →引数あり(戻り値なし)パターン

この関数は(同じクラスファイルに書かれていれば)別の場所で呼び出すことができる。

ファンクションショートハンド

戻り値がある場合はreturnを書かなくても「=>」で戻り値設定可能である(ただし{}内の処理が1行コードになることが大前提)。

例:

dataType [functionName] () ⇒ whatEver;
// 前項の戻り値ありパターンの関数と同じ挙動になる

特殊なデータ型

何をもって「特殊」か、と言うと、変数の型として、「箱」の形と説明をするには少し苦しいものが存在する。それらについて説明をしていく。

リスト(Lists)

他の言語では「配列(array)」と呼ばれるモノがDartでは「リスト」と呼ばれる。

var name = ['hoge', 'fuga', 'piyo'];

余談だが、リストの中に入れるモノの「型」は必ずしも同じでなくてもよい。その場合、dynamic型のリストを作ることになる。

dynamic型のリストは、データベースからある1行を取得する、と言った時に、複数の型が混在しているようなケースがあり、その場合に使うことができる*9が、Dart言語的には推奨されないらしい。

ちなみに本来の「型」は’List’となり、ジェネリクスでリスト内要素の型指定を行うことになる。上記例を型指定を行う場合はこんな感じ。

List<String> name = ['hoge', 'fuga', 'piyo']
// リストの中にある要素はすべてString型になる

また、他の言語における配列・リストと同様に添え字は0から始まる(上記の例でいえば’hoge’は0番/name[0])。

長さ(length/要素数)もname.lengthみたいな感じで取得可能。いわゆる「リスト(型)」のプロパティメソッドが多数ある。

セット(Sets)

前項「リスト」と同様に「配列」系ではあるが、要素内の重複を許さない(要素内に同じモノ:等価?等値?の重複を許可しない)という違いがある。

リストは大かっこを使うが、セットは中カッコを使って定義する。

var names = {'hoge', 'fuga', 'piyo', 'dare'};
// 正しい宣言
var names_error = {'hoge', 'fuga', 'piyo', 'hoge'};
// →要素4つ目の’hoge’が重複しているので定義不可(エラー)

(リストも同様だが)name.addで要素の追加ができるが、セットに関しては重複している要素を追加しようとしても追加できない。ただし、エラーにはならず重複要素をスキップしてしまうので注意が必要である。

var name={'hoge','fuga','piyo'};
name.add('hoge');
// 重複しているため要素の「追加」ができない(要素数は増えない)が、エラーとはならない

マップ(Maps)

Javaで言うHashMap。key-value形式で格納されるリスト。定義時には中カッコを使う。

なお、keyはユニークである必要あり(valueは異なるキーであれば同じ値が入ってもよい)。

var UNSecretary = {
  'age': 93, 
  'firstName': 'Boutros',
  'middleName': 'Boutros',
  'surName': 'Ghali',
  'occupation': 'UNSG' 
  // 第6代国連事務総長のブトロス・ブトロス=ガリ
}
var DJKhaled = {
  'age': 47,
  'firstName': 'Khaled',
  'middleName': 'Mohammed',
  'surName': 'Khaled',
  'occupation': 'DJ',
  // DJ、プロデューサーのDJキャレド

リスト・セット・マップの使い分け(私見)

「型付け」をきちんと指定したいと考えるのであれば、リストにおいてdynamic型を使うくらいならマップで指定するほうがいいのかな、と思うが、何が入ってくるのかわからないjsonファイルの読み込みみたいなことをする場合にとりあえずdynamicなリストを使ったらいいのかな、とは思う。

セットに関して言えば、「特殊な」リスト、という風に考えればいいだろう。リストの中で重複してはいけないモノがあれば使えばいい、という感じ。ただし、Pythonのタプルのように「変更不可(イミュータブル)」というわけではないので*10、使いどころには悩むところ。

今回の講義はここまで

次回は制御構造の話と演算子リテラルに関する話あたりになる予定。公開は2~3週間後くらいかな。

*1:結局YouTubeへのフリかよ、というご指摘はごもっともw

*2:以降も細かい仕様については公式サイトのリンクを参照する形を取る

*3:CとかJavaとかと同じ

*4:多分

*5:説明する場所がうまく見つからないので突然書いているように見えて大変恐縮です

*6:どのプログラム入門書を見てもその説明をしているし、対面講義においてもその説明をすると100%受講生が理解するので、よほど理解しやすい例なんだな、と思う

*7:型推論」が行われる

*8:Javaインスタンスメソッドとは異なる概念だとは思うけれど

*9:むしろその使い方に限定した方がいい気がする

*10:セットは要素の追加削除が可能