実践Flutter

Flutterでスマホを中心にアプリ制作していきます。

実践Dart:変数定義・データ型

はじめに

本記事ではDartの基本データ型についてまとめていきます。ほかのプログラミング言語と似ている部分が多いですので、他のプログラミング言語に慣れている方はざっと確認する程度でOKです。

データ型について慣れていない方に説明しておきますと、データ型とはある変数xが整数なのか、浮動小数なのか、あるいは文字列や別の構造体なのかを示すものです。値そのものではなく、そのデータがどういう形をしているかを表しています。

これはDart言語で書かれたコードを実行可能なコードに変換する上でコンパイラが必要とする情報でありますが、逆にコンパイラはこの型をみてある程度プログラムコードが適切に記述されているかを確認することができます。これを利用すると正確性に問題があるコードについては、実行コードに変換する前に、プログラマに警告を出して訂正を促すことができ、テスト実行でデバッグするより遥かに効率よく誤りを修正することができます。

またプログラミングに慣れてくると、型を見ただけでAPIの機能が想像できたり、ソースコードの意味合いを推測することができるようになります。こうなるとソースコードリーディングにおいて緩急をつけて効率よく情報収集ができるようになってきます。

プログラミング言語の中には実行時に型を決定して、プログラミングの時点では型を意識しなくて良いことを売りにするものもありますが、中規模から大規模のソフト開発を効率よく行おうとすると、ある程度型を明示する言語の方が生産性は高まります。





DartPad

Dartの練習をしていくにあたり、ブラウザベースでスマホでも使えるDart実行環境「DartPad」を活用されることをおすすめします。

気になるコードがあったとき、すぐに確認できます。

f:id:linkedsort:20211113110214p:plain


変数定義

変数定義はデータ型と変数名を並べて表記する形で宣言します。JavaやC#など、伝統的な言語と同様のスタイルです。

var myName = "Tom";
var myAge = 29;
var myHeight = 176.5;

データ型の宣言としてvarを使うと、Dartがその型を推論して割り当てます。

上記のコードは下のように明示的に型を指定して宣言しても同じ意味になります。

String myName = "Tom";
int myAge = 29;
double myHeight = 176.5;

コードの可読性を高めるために必要と判断した場合には、型を明示的に表記するとよいです。

Dartは基本的にはより短い表記を推奨していますので、コードを読む人が容易に型を推定できる場合はvar表記が望ましいでしょう。変数が並んでいるときに型が「var」の3文字で揃っていると見た目がきれいという効果もありますね。

dynamic型

varと一見にているものにdynamicがあります。これは「動的に型を決定する」ということを宣言するもので、プログラム実行時に型が決定するもの以外では使わないことが推奨されています。

例えばJSONのデータを読み込んで何らかの型の構造体データを生成するとき、などが動的に型が決定するときになります。何らかの型の値を生成する関数だが、実行時点で色々と変わりうる、というようなケースですね。ですので

dynamic myName = "Tom";

このように表記してもコンパイルはとおりますが、"Tom"はコンパイル時点で型が決定していますので、この使い方は適切ではありません

Dart/Flutterの様々なパッケージのAPIを使っていくと、非常に巧妙にdynamicを使って表記の簡潔性と機能の柔軟性を同時に実現している例に気づいてきたりします。その頃にはdynamicの使い方の雰囲気が分かってくると思いますが、最初は放っておいて大丈夫です。

final修飾子

final修飾子を型の前につけると、その変数は1回のみ値がセットされ、その後は変更されないことを意味します。

コードの設計の意図として、この変数の値は変化するべきではない、再代入されるべきではないというときにfinalを宣言しておきます。

final myAccount = "idName";    
myAccount = "fooName";   //コンパイルが通らない

final宣言した変数に初期化の代入のあとで、代入をするとエラーになります。型チェックされますので、コンパイル時点ではじかれます。

final var myAccount = "idName";  //コンパイルが通らない
final String myAccount = "idName";  //冗長なコード

varはvariableの略、すなわち変化するものという意味ですので、値が変化しないことを意味するfinalと一緒に書くとおかしいですよね。コンパイルできません。

下のString型を明示している方はコンパイルは通りますが、ダブルクォーテーションで囲まれたリテラルから明らかに文字列とわかりますので冗長な表現です。コンパイルは通ります。

const修飾子

const宣言は、コンパイル時に値が決まる定数であることを意味します。これによって実行コードが最適化されるようにコンパイラにヒントを与えます。

値は不変ということになりますので、その意味ではfinalと同様ですが、constの場合は更に強く実行前に値が確定していることを意味します。

const x = 100;
const double d = 1.01;

final宣言した変数が何らかのオブジェクトを指しているとき、そのオブジェクトへの参照の値は変更できませんが、オブジェクトの中のプロパティ変数は変更できます。

一方でconst宣言した変数の場合、オブジェクトへの参照はもちろん、プロパティ変数の値も変更できません。コンパイル時に完全に定数として固定されることがconstの条件です。条件が厳しいですが、その分この不変性を前提とした最適化をコンパイラが実施することができます。

条件に合致する場合はなるべくconst修飾子を付けたほうが実行効率が高まります。多くの場合IDEが「constを付けたほうが良い」「constを付けてはいけない」と波線を付けて警告してくれますのでこれに従っていけばほぼOKです。

データ型

次にデータの中身の型について見ていきます。

数値型

Dartは二つの基本数値型を持っています。整数型がint、浮動小数点型がdoubleです。双方64ビット表現です。

数値の表記法として、小数点がある数値はdouble、小数点がない数値はintと解釈されます。ただし、型を明示すれば小数点がなくてもdouble型と解釈します。

var x = 10;  // xはint型
var y = 1.0; // yはdouble型
double z = 10; //zはdouble型

またint型やdouble型のデータでは、下記のようなメソッドが使えます:

メソッド 動作
abs() 絶対値を取得
ceil() 小数以下切り上げ
floor() 小数以下切り下げ
round() 小数以下四捨五入

例えば下記のような実行結果になります(コメントにある数値が式の評価結果):

(-5).abs() // 5
3.1.ceil() // 4
3.1.floor() //3
3.4.round() //3
3.5.round() //4

文字列

シングルクォート、あるいはダブルクォートで囲まれた文字が文字列になります。

文字列の途中で「$変数」あるいは「${式}」を入れると変数あるいは式の値が評価され文字列に統合されます。C言語やJavaのString#formatにある文字列中の%dや%sのようなものです。

var x = 10; 
var s = "今年で$x歳になります";
var r = "来年で${x+1}歳になります";
  
print(s);   //今年で10歳になります
print(r);   //来年で11歳になります

シングルクォート、あるいはダブルクォートを3重にすると、複数行の文字列定義ができます。

var t = """
これが
複数文字列
定義です
""";

print(t);   //3行に渡って上記文字列が表示される

文字列の連結は+演算子で「"文字列"+"連結”」のようにして行うことができます。しかしながらこれを繰り返して長い文字列を行っていくと問題が起こる可能性があります。特にループで数百回以上に渡る連結を行う場合は注意が必要です。

これは+演算子で文字列を連結していくと連結するたびに新しい文字列のオブジェクトが生成される仕様のためです。これはJavaやC#と同じ仕様です。

ループ中、一度きりしか登場せずに二度と使わない文字列のために、大量のメモリリソースを消費することになり、最悪の場合アプリがクラッシュする原因になります。

なので文字列連結を行うときはStringBufferを用います:

var buf = StringBuffer();
for(var i=0; i<1000000; i++){
  buf.write("moji$i ");    //文字列連結
}

print(buf);   //moji0 moji1 moji2 ... という表示が続く

ループ中に文字列連結が出現するケースではほとんどStringBufferを使うべきだと覚えておきましょう。

ブール型

論理式の真偽を表すためbool型が用意されています。真を表すリテラルがtrue、偽を表すリテラルがfalseです。

var x = 1;   //xはint型
var b1 = x == 2;  //b1はbool型
var b2 = !b1;  //b2はbool型

print(b1);   //false  (xは2ではないので)
print(b2);   //true  (b1の逆の真偽値)

明示的に型を宣言する場合は「bool」と表記します。

列挙型

列挙型は何らかの名前を列挙した型を定義できる仕組みです。ただの数値ではなく、名前をつけることでコードの可読性を向上させられる場合などに用います。

enum Youbi {Mon, Tue, Wed, Thur, Fri, Sat, Sun}

void main() {
 var day = Youbi.Wed;
 
 print("今日は$day");   //今日はYoubi.Wed
 print("今日は${day.index}");  //今日は2
}

文字列で直接出力するとenumのクラス名も出てしまいます(上記例のYoubi.Wed)。なので文字列ダイレクトで表示するというのはデバッグ用途以外には使えなさそうですね。

enum型にはindexプロパティが付きます。indexには列挙型の先頭から何番目なのかという数値が入ります。0から始まりますので最初から3番目のWedのindexは2になります。

配列

Dartの配列はJavaやC#のように特別なものが用意されているのではなく、実体はListと完全に同じものです。

var list = [1.2, 2.3, -3.4];
print(list[1]);   //2.3

[]演算子でインデックスを指定することで、配列のなかの要素にアクセスできます。0からスタートするのでインデックスが1だと2番目になります。

Listについては別記事の「コレクション」の回で詳細を述べます。ここでは配列とコレクションのListは完全に同じものと覚えておけばOKです。





おわりに

今回はDartの変数定義、データ型についてポイントをまとめました。

説明を読んでいるだけだとvar、final、constあたりが面倒くさそうという感じに見えるかもしれません。

ただほとんどIDEが適切な表記でない場合に波線で指摘してくれて、かつこういう風に直そうか?と提案してくれたりしますので、ポチっとボタンを押すだけで修正してくれたりします。なので実際上ほとんど問題ないです。

そこまでしてくれるならconstやfinalを付けなくても勝手に判断して最適化してくれれば良いのに、と思わなくもないですけれどもね。

コードをあれこれ改変しながら試行錯誤していくと、const修飾子をつけるべき箇所がコロコロ変わったりします。もうそういうときは完全にIDEにおまかせで何も考えずに提案してきた修飾子に従っていけばだいたい問題ないです。このあたりややこしいと思われる方は、気楽に捉えてOKです。

よっぽど初期化が複雑で重たいオブジェクトを扱うときに無駄な初期化を何度もしないように気をつける必要があるくらいで、あとの軽いデータについては今の爆速のスマホやPCにおいてほとんど性能に影響は出てきませんからね。


f:id:linkedsort:20210929231901j:plain