Flutter開発で使うDart言語での関数・クラスの使い方を理解しよう

今回は「Flutter開発で使うDart言語での関数・クラスの使い方を理解しよう」についてご紹介します。
関連ワード (アプリケーション開発、モバイル等) についても詳細と、関連コンテンツとをまとめていますので、参考にしながらぜひ本記事について議論していってくださいね。
本記事は、CodeZine様で掲載されている内容を参考にしておりますので、より詳しく内容を知りたい方は、ページ下の元記事リンクより参照ください。
対象読者
Dart言語について特に知っている必要はありませんが、他の言語の基本を知っている方を対象に説明いたします。特に、JavaScriptやTypeScriptもしくは、Javaなど言語を使ってプログラミングしたことがある方であれば、より理解がしやすくなります。
関数について
Flutterでよく見る関数の使い方で名前付き引数があります。名前付き引数を採用している言語にはC#やSwiftなどがあります。また、JavaScriptやTypeScriptでは、言語レベルでの名前付き引数はサポートしていませんが、同じような効果を得るためにオブジェクトの分割代入の仕組みを使っているコードを見るようになりました。
名前付き引数はコードが見にくくなるといった意見もありますが、使う用途によっては良い面もあり、名前付き引数のメリットを表している一例としてリスト1のコードがあります。
build( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, ), ) );
このコードは計算するための関数ではなく、定義または設定のためのコードです。Dartは主にUIを扱うための言語であるため、UI構造を設定するようなコードを記述する必要があります。そのため少々冗長になってしまいますが、このようなコードのほうが見通しは良くなります。
このように、UIを扱いやすいためにできている言語であることを前提として見ていただくと、Dartの少々わかりにくいと感じる部分でも、実際に使って見ると使いやすいと感じるはずです。
関数定義
続いて、基本的な関数の定義について紹介します。リスト2は関数の定義方法です。
<戻り値の型> <関数名>([<引数の型> <引数値>],[<引数の指定>]){ return <戻り値>; }
この定義に沿って作った簡単な関数がリスト3です。
// (1)定義に沿った関数例 String helloWorld1(String world){ return 'Hello $world'; } // (2)戻り値と引数の型定義を省略 helloWorld2(world){ return 'Hello $world'; } // (3) 1行関数での記述例 String helloWorld4(String world) => 'Hello $world'; // (4)戻り値がいらない場合 void helloWorld3(){ print('Hello'); }
(1)は他の言語とも違わない、標準的な関数の実装例です。そして(2)のように戻り値と引数の型を省略することも可能です。省略した場合の型はdynamicとして扱われます。そのため、厳密には(1)と同じではありません。
(3)は、(1)と同じことを1行関数で書き直したものです。その場合はシグニチャと本体とを「=>」でつなぎます。(4)はreturn文を省略したものです。戻り値がない場合には、戻り値の型にvoidを指定します。戻り値の型を省略した場合には、先ほどと同様にdynamicとして扱われ、その場合にはnullが戻り値となります。
引数の指定方法は、先ほど紹介した名前付き引数以外にもいくつか指定方法があるので、それらの使い方を紹介します。
名前付き引数
Flutterのコードではよく見る引数の指定方法で、名前付き引数では”{}”内に定義する必要があります。リスト4は名前付き引数を使った場合のコード例です。
// (1)引数の定義例 computeSum({ int val1 , int val2}){ return val1 + val2; } void main(){ computeSum( val1 : 3, val2 :5 ); // (2)使い方 computeSum( val1 : 5); // (3)省略が可能 }
(1)の通り引数を定義すると、(2)のようにそれぞれの引数を指定することが可能です。また、指定する引数の順番は関係ありません。また、(3)の通りに引数を省略することも可能です。省略するとnullが設定されてしまうので、実装側で制御しないと正しく動かなくなります。そこで、リスト5のような省略された場合のデフォルト値の設定や、入力を必須にする方法があります。
import 'package:meta/meta.dart'; // (1) 通常形式の引数と名前付き引数の混在 computeSum1(int val1 , { int val2 }){ } // (2) 省略されたときのデフォルト値の指定 computeSum2({ int val1 = 0, int val2 = 0 }){ } // (3) 省略されないようにするためのアノテーション指定 computeSum3({ @required int val1, int val2 = 0 }){ }
(1)は、通常引数と名前付き引数を混合させる場合の例です。必ず指定する必要がある引数は、通常引数として定義し、オプションとなる引数を名前付き引数で指定します。
このように混合させる場合には、通常引数の定義後に、名前付き引数を指定してください。そして、省略されたときのデフォルト値を指定する場合には、(2)のように=(イコール)で値を指定できます。
また、デフォルト値が指定できず、名前付き引数を利用したい場合には、(3)のように@requiredというアノテーションを利用します。ただし、このアノテーションはDartの基本ライブラリだけでは利用できないので、’package:meta/meta.dart’をインポートする必要があります。Flutterプロジェクトであれば、別途、インポートする必要はありません。
オプショナル引数
オプショナル引数は、通常引数の指定方法と同じですが、引数を省略できるようにした指定方法です。オプションとして指定する引数は、[]内に定義します。リスト6は、カンマ区切りの文字列をリスト形式に変換するような関数を想定した場合のコード例です。
// (1)引数の指定 List<String> splitString(String input , [ String split = ',' , int max = 100 ]){ // 省略 } main(){ // (2)実際の利用例 splitString("data1,data2,data3"); splitString("data1-data2-data3","-"); // 区切り文字を"-"に変更 splitString("data1,data2,data3,data4",",",3); // 最大の100から3に変更 }
(1)のように、必須となる引数はオプショナル引数の前に必ず指定します。=(イコール)を使って省略された場合の値を指定します。指定しない場合にはnullが設定されます。そして、実際に利用する場合は(2)の通りに利用します。オプショナルと指定した部分の順番は変更できません。また、途中だけ省略するといったこともできません。
クラスについて
続いてDartでのクラス定義について説明します。
クラスの構造
まず、Dartのクラス構造の基本的な形式を紹介します。DartにはJavaのように、privateやpublicといったアクセス区分を定義するキーワードはありません。そのため、クラスやメソッドは常にアクセス可能です。ただし、フィールドだけはアクセス制限ができます。そして、そのフィールドにアクセスするゲッター・セッターを定義することができます。
リスト7はクラス定義のコード例です。
class UserName { String _name; // (1) privateフィールド // (2)setter set name(String value){ _name = value; } // (3)getter String get name{ // ()は記述しません return _name; } // String get name => _name; // (4) String getDisplayName(){ return _name + "-San"; } }
変数名が(1)のように_(アンダーバー)から始まっている場合は、他のソースコードからアクセスはできないため、制限付きのメンバ変数として利用できます。これは名前を使った暗黙のルールになります。それ以外のメンバ変数ではどこからでもアクセス可能です。
変数に値を設定するセッターは(2)の通りに設定します。また、インスタンス変数にアクセスする場合は、this._nameのようにthisを明示的に指定できます。しかし、Flutterでは一般的にthisは省略されることが多いです。
ゲッターを指定する場合には、(3)のように記述します。ゲッターでの()は記述しません。また、一般的には、(4)のように1行関数形式で記述するケースが多いです。また、メソッドは関数を定義する場合と同様に定義が可能です。
_(アンダーバー)から始まるメンバ変数の制限について
「_」から始まるメンバ変数は、クラス外からアクセスできないプライベート変数のように紹介されますが、厳密には、同じソースコード内であればアクセス可能です。
1つのクラスを1つのファイルで定義している場合は、プライベート変数として扱うことができます。一方で、複数のクラスを1つのソースコード内に定義している場合は、同じソースコードであれば別クラスからであってもインスタンス変数にアクセス可能です。
つまり、privateでもなく、publicでもないメンバ変数が作れます。Java言語で言えば、protectedと似たような役割として扱うことも工夫次第で可能になります。ただし、特別な理由を除いてプライベート変数として扱ったほうが管理もしやすいですし、わかりやすくなります。
コンストラクタ
先ほどのクラスの例では、コンストラクタの記述を省略しましたが、DartでのコンストラクタはJavaやJavaScriptなどに比べても少々複雑になります。ただし、ルールを知れば決して難しいものではありません。
class Hello{ String _greeting; String _lang; Hello(String greeting,String lang){ this._greeting = greeting; this._lang = lang; } // (1) 同じ意味の省略形式 // Hello(this._greeting,this._lang) }
クラス名と同じ名前のメソッドがコンストラクタとして利用されます。また、(1)のように記述することで、ほぼ同じ意味のコンストラクタとして定義可能です。Dartでは、一般的に後者の記述形式が利用されることが多いです。
コンストラクタでの値の初期化
続いて、先ほどの_greetingと_langという変数をfinal指定した場合です。finalとして指定した場合は、コンストラクタで値を初期化する必要があります。リスト9が記述例です。
class Hello{ final String _greeting; // 省略 // Hello(this._greeting,this._lang); // (1)よく使われるコンストラクタの記述方法 Hello() : _greeting = 'Hello' , _lang = 'en'; // (2)初期化リストにて初期値の指定 }
(1)は、先ほどのサンプルの省略形式です。省略形式ではない書き方はfinalの場合は使えません。また、引数で指定しない変数をコンストラクタ時に指定する場合は、(2)のように:(コロン)の後に、カンマ区切りで値を初期化することができます。
名前付きコンストラクタ
Dartでは異なる引数を持つ同名コンストラクタを複数定義できません。そこで、名前付きコンストラクタという機能が存在します。この機能を使うことで、目的別にコンストラクタを複数用意することができます。
class Hello{ // (省略) Hello(this._greeting,this._lang); // (1)異なるデフォルトの値を持つコンストラクタ Hello.empty() : _greeting = '', _lang = ''; Hello.english() : _greeting = 'Hello', _lang = 'en'; Hello.japanese() : _greeting = 'こんにちは' , _lang = 'ja'; // (2)引数を指定した名前付きコンストラクタ Hello.withGreeting(this._greeting) : this._lang = 'en'; // (3)初期値設定での別のコンストラクタを利用する Hello.withLang(String lang) : this('',lang); } main(){ // (4)別名コンストラクタの呼び方 var h1 = Hello.empty(); var h2 = Hello.withGreeting('good morning'); }
名前付きコンストラクタが利用される例として、(1)のように異なるデフォルト値を持つインスタンスを作成する場合があります。(2)のように一部の引数を指定するケースでもよく利用されます。その場合には慣習的にwithというプレフィックスを用いられる場合が多くなっています。
また、(3)のように:(コロン)の後に別のコンストラクタを指定することも可能です。例ではthisのみを使って通常のコンストラクタを呼んでいますが、ここには別名コンストラクタの指定も可能で、リダイレクトコンストラクタという名称で呼ばれています。これらのコンストラクタを使ってインスタンス化する際には、(4)の通りに実行します。
クラスの継承
既存のクラスを継承する場合には、リスト11のようにextendsキーワードを利用してクラスを定義します。
class Hello{ // (省略) } class HelloName extends Hello{ String _name; // (1)コンストラクタ HelloName(this._name,String greeting,String lang) : super(greeting,lang); // (2)メソッドの上書き String sayHello(){ String value = super.sayHello(); return '$value $_name'; } }
継承したクラスでコンストラクタが必要になる場合は、(1)のようにsuperを用いて親クラスのコンストラクタが実行できます。また、(2)のように親クラスのメソッドを呼ぶ場合にも、同様にsuperを使って親クラスのメソッドが利用できます。
注意点として、親クラスのコンストラクタが自動的に継承されることはありません。別名コンストラクタなどの機能もあり、自動的に安全に呼ぶことは難しいですが、このようなところは少々不便を感じる部分でもあります。
抽象クラスとインターフェース
Dartにはインターフェースというものは存在せず、クラスは通常クラスか、抽象クラスのどちらかになります。インターフェース相当のクラスを作りたい場合は、実装がない抽象クラスを作れば、同じ効果を持つクラスが作成できます。
そして、すべてのメソッドを再定義を強制するimplementsキーワードが存在します。これらを組み合わせることで目的が達成できます。少々、わかりにくいのでリスト12のサンプルコードを用いて説明します。
// (1)abstractを用いた抽象クラスの作成 abstract class AbstractHello{ String sayHello(); } // (2)(インターフェースとして扱う)抽象クラスを利用した実装 class Hello implements AbstractHello{ String sayHello() { return 'Hello'; } } // (3)継承(extends)を利用した場合 class HelloDefault extends AbstractHello{ String sayHello() { return 'Hello'; } } // (4)extendsを利用した場合 class HelloEnglish extends Hello {} // (5) implementsを利用した場合 class HelloJapanese implements Hello{ String sayHello() { return 'こんにちは'; } }
抽象クラスを作成する場合には、(1)のようにabstractキーワードをclassの前に付けます。そして、(2)のようにimplementsキーワードを用いた場合には、元となるクラスのメソッドを実装する必要があります。
例えば、(3)のようにextendsを用いてもこの場合にはほぼ同様になりますが、extendsとimplementsが異なるのは、extendsは不足分を実装しなければならないということであり、implementsは元となるメソッドを再定義しなければなりません。
そのため、(4)のように既に単体として完結しているクラスをextendsした場合には、実装を記述する必要はありませんが、(5)のようにimplementsを使用した場合には、再度メソッドを再定義する必要があります。説明だと少々難解に思えますが、組み合わせ方法で同じことができます。
まとめ
今回、関数とクラスの基本について紹介しました。これらは、他の言語と非常に似ていますが、よりコードの記述を少なくするためのちょっとしたアイデアが含まれています。一方で、それらを知らないと疑問に思う部分も出てきてしまいます。Dartにはクラスのコードの再利用を可能にするミックスイン機能やnullセーフなコード記述など、コード記述が少なくなるアイデアが他にも多々あります。
次回は、今回紹介しきれなかったコンストラクタの他の機能に加え、それらの機能を紹介します。
モバイルとは - コトバンク
ASCII.jpデジタル用語辞典 - モバイルの用語解説 - 「自由に動く」や「移動性の」という意味で、携帯可能な小型のコンピューターのことを指す。また、外出先からPHSや携帯電話、公衆電話などを利用してネットワークに接続し、オフィスや自宅のパソコンを操作すること。
モバイルとは何? Weblio辞書
「モバイル」の意味は移動性・携帯性・機動性などがあることを意味する表現のこと。Weblio国語辞典では「モバイル」の意味や使い方、用例、類似表現などを解説しています。
モバイルとは - IT用語辞典 e-Words
モバイル【mobile】とは、携帯電話、携帯できる、可動性の、可搬式の、移動性の、移動型の、動かしやすい、動きやすい、流動的な、機動性の、変わりやすい、などの意味を持つ英単語。ITの分野では、情報機器や通信機器などが持ち運んで別の場所で利用できることや、屋外を移動しながら使用できること、また、通信システムが無線などを用いて移動しながら利用できることを表すことが多い。
mobile とは 意味・読み方・表現 | Weblio英和辞書
1000万語収録!Weblio辞書 - mobile とは【意味】可動性の,車で移動する... 【例文】a mobile clinic... 「mobile」の意味・例文・用例ならWeblio英和・和英辞書
【公式】ワイモバイル(Y!mobile)- 格安SIM・スマホはワイ ...
【公式】ワイモバイル(Y!mobile)、家族で1,480円からご利用可能なiPhone、Android Oneスマートフォン、SIMの料金・サービスをご紹介します。 余ったデータ通信容量を翌月にくりこすことはできますか? [iPhone]他社が販売する iPhone ...
スマートフォン・携帯電話 | ソフトバンク
ソフトバンクのモバイルのページです。モバイル(スマートフォン、携帯電話、タブレットなど)に関する製品情報や料金・割引情報、お得なキャンペーン、対応サービスエリア、サービス・アプリケーション、お客様サポートなどをご紹介します。
モバイル機器をスマートに使いこなす - ITmedia Mobile
携帯電話/スマートフォンにまつわる情報をお届けする”モバイル”の総合情報サイト。新端末のレビューからキャリアの動向まで、モバイルと関わる多様な情報を追いかけます。
楽天モバイル
楽天モバイル公式。4G+5Gが使える料金プラン「Rakuten UN-LIMIT V」が大幅アップグレード!データ1GBまで0円、どれだけ使っても無制限で2,980円の「Rakuten UN-LIMIT VI」、人気のAndroidスマートフォンも続々登場。楽天の携帯キャリアサービスが、これまでの常識をくつがえす!
価格.com - モバイルデータ通信(モバイルWi-Fi) | 製品情報 ...
モバイルデータ通信(モバイルWi-Fi)といえば価格.com! UQ WiMAX、ワイモバイル(Y!mobile)、docomo(ドコモ)、au、SoftBank(ソフトバンク)、SIMフリーをまとめて比較ができる!スペック検索・人気ランキング・モバイルデータ通信料金プラン・クチコミ情報・新製品ニュース等、いろんな便利情報も満載!
LINEモバイル【公式】選ばれる格安スマホ・格安SIM
月額基本利用料3ヶ月500円キャンペーン実施中!LINE使うなら、格安スマホ・格安SIMのLINEモバイル。LINEや主要SNSのデータ通信量がカウントされないデータフリーが人気です。