Closed7

Dart入門

masarufuruyamasarufuruya

Dartの言語コンセプト

https://dart.dev/language#important-concepts

  • 変数に配置できるものはすべてオブジェクトであり、すべてのオブジェクトはクラスのインスタンスです。数値、関数、もオブジェクトです。 (健全な null 安全性を有効にしている場合)nullを除いて、すべてのオブジェクトはクラスから継承します。nullObject
    • null 安全性とは
      • Flutter2(Dart2.12以降)からはNon-Nullable By Default(NNBD)の考え方が導入されています
      • NNBDとは、デフォルトの変数宣言ではその変数にnullが入ることが認められないという考え方です
      • Null Safetyのメリット
        • 1つ目は開発者がNullを考慮しなくて良いため、生産性が向上します
        • 2つ目はNullがないことを前提に最適化が行えるため、性能や保守性が向上します
      • int?のようにつけなければ、nullは入れられない
  • Dart は強く型付けされていますが、型を推論できるため、型注釈はオプションです。 ではvar number = 101、numberは型 であると推論されます
  • null 安全性を有効にすると、変数には、許可しない限り、null を含めることはできません。変数を null 可能にするには、型の末尾にnull疑問符 ( ) を付けます。たとえば、型の変数は整数の場合もあれば、 の場合もあります。式が に評価されることは決してないがDart がそれに同意しない場合は、 を追加して、それが null ではないことをアサートできます(null の場合は例外をスローします)。例:?int?nullnull!int x = nullableButNotNullInt!
  • 任意の型が許可されることを明示的に指定する場合は、 型Object?(null 安全性を有効にしている場合)、 を使用するか、型チェックを実行時まで延期する必要がある場合は特殊な型 をObject使用します。dynamic
  • List<int>Dart は、 (整数のリスト) やList<Object>(任意の型のオブジェクトのリスト)などの汎用型をサポートしています
  • Dart は、トップレベルの関数 (などmain()) と、クラスまたはオブジェクトに関連付けられた関数 (それぞれ静的メソッドとインスタンスメソッド) をサポートしています。また、関数内に関数 (ネストされた関数またはローカル関数)を作成することもできます
  • 同様に、Dart はトップレベルの変数だけでなく、クラスまたはオブジェクトに関連付けられた変数 (静的変数とインスタンス変数) もサポートしています。インスタンス変数は、フィールドまたはプロパティと呼ばれることもあります
  • publicJava とは異なり、Dart には、、protectedというキーワードはありませんprivate。識別子がアンダースコア ( _) で始まる場合、その識別子はそのライブラリにプライベートです。詳細については、「ライブラリとインポート」を参照してください
    • Dart言語において、Javaのようなpublicやprivateなどでメンバのアクセスを制御する文法はない
    • Dart言語では、変数名にアンダースコア(_)をつけるかどうかでアクセスを制御する
  • Dart には、式(実行時の値を持つ) と文(実行時の値を持たない) の両方があります。たとえば、条件式 にはorという値があります。これを、値を持たないif-else 文と比較してください。文には 1 つ以上の式が含まれることがよくありますが、式に文を直接含めることはできません。condition ? expr1 : expr2expr1expr2
  • Dart ツールは、警告とエラーの2 種類の問題を報告できます。警告は、コードが動作しない可能性があることを示すもので、プログラムの実行を妨げるものではありません。エラーは、コンパイル時または実行時に発生します。コンパイル時エラーが発生すると、コードはまったく実行されなくなります。実行時エラーが発生すると、コードの実行中に例外が発生します。
   // public変数
   String a = "";
   // private変数
   String _a = "";

Dartの関数

返り値がない場合、返り値の型はvoid型となり、ブロック内では返り値を書かずreturn;で処理を終了します。

// ex. int型の引数を受け取り、String型の値を返す関数
String myFunction(int number){
    return number.toString();
}

void sayHello(){
    print('Hello World!!');
    return;
}
void myGlobalFunction(){
    print('This is global');
}

void _myPrivateFunction(){
    print('This is private');
}

void myFunction(){
        myGlobalFunction(); // OK
        _myPrivateFunction(); // OK:同一ファイル内の為アクセス可能
}
masarufuruyamasarufuruya

Dartを試す環境ではDartPadが便利
https://dartpad.dev/

main関数

void main() {
  print("Hello world");
}

四則演算

他の言語と一緒

void main() {
  // 四則演算(+, -, *, /, %)
  print(1 + 2 - 3 * 4 / 5 % 6);
}

変数定義、変数の埋め込み

Dartは強力な型推論機能を持っているため、
型を明示しなくてもvarで変数定義して値を格納することができます。
print関数に$指定で変数の値を埋め込むことができます。

void main() {
  var a = 1;
  print(a);
  a = 2;
  print('\$で変更数の値を埋め込み、$a');
}

基本的なデータ型(built-in)

型注釈はオプションですが、明示的にデータ型を指定することが出来ます。
よく使う型

数値:int, double
文字列:String
真偽:bool
配列:List
セット(ユニークな配列):Set
Key/Valueペア:Map

main() {
  // dartのbuild-in型(明示的な型宣言変数)
  int b = 10;
  double c = 12.3;
  String d = 'abc';
  bool e = true;
  print('$b, $c, $d, $e');
  List f = [1,2,3]; // 配列
  f.add(4);  // 末尾追加
  f.add(4);
  print('$f, ${f.length}, ${f[0]}');
  Set g = {'a', 'b', 'c'}; // 重複を許さない配列
  g.add('d');  // 末尾追加
  g.add('d');  // 重複して入ることはない
  print('$g, ${g.length}, ${g.toList()[1]}'); // 配列に変換するときはtoListを使う
  // Key/Valueペア
  Map h = {
    // Key: Value
    'first': 'one',
    'second': 'two',
    'third': 'three'
  };
  h.addAll({'fourth': 'four'});
  print('$h, ${h.length}, ${h['first']}'); // Keyでアクセス

  // ルーン文字は、文字列のUTF-32コードポイント。\uで文字を指定する
  // 絵文字の表示に使える
  Runes i = new Runes('\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
  print(new String.fromCharCodes(i));

  // Symbolオブジェクトは、Dartプログラムで宣言された演算子または識別子を表します。
  // コンパイル時にAPI名などは変更されてしまうが、Symbolは変更されないため、API参照などの識別に使える
  // ただし、dartライブラリなどの作成者でない限り、ほぼ使う機会はない
  // #で始まる
  #hogehoge;

  // Dartは強く型付けされていますが、Dartは型を推論できるので型注釈はオプションです。
  // 次のコードで、数値はint型であると推論されます。型が想定されていないことを明示的に示したい場合は、特殊な型dynamicを使用します。
  // any型、スクリプト言語の変数と同じような性質を持つ
  dynamic j = 10;
  j = 'a'; // dynamicだと途中で別の型のデータも格納できる
  print(j);

  // varの場合は初期化時の型推論が働くので途中で違う型を入れようとするとエラーになる
  // var j = 0;
  // j = 'a'; // エラー

  // cast、別のデータ型への変換
  print(int.parse('42') == 42); // String => int
  print(double.parse('42.3') == 42.3); // String => double
  print(42.31.toString() == '42.31'); // number => String
}

定数

final、constを使うことで定数を定義できます。
途中で値の変更が許されません。(値が変更されないことが保証される)

main() {
  final k = 1; // finalの変数は初期化時のみ代入可能
  // k = 2; // 再代入しようとするとエラー
  const l = 1; // 定数、再代入はエラーになる(コンパイル時に埋め込まれる)
  // l = 2;
  List m = const [1, 2, 3]; // 定数配列を代入
  // m.add(1); // 定数配列の中身の変更はできない(実行時エラー:Cannot add to an unmodifiable list)
  print('$k, $l, $m');
}
  • finalとconstの違い

    • finalもconstも変数の値を変えたくないときに使います
    final String nickname = 'Bobby';
    final name = 'Bob'; // 型を省略して書くことができます。
    name = 'Alice';  //再代入はできません(静的解析でエラーになります)
    

    Flutter/Dartだと静的解析(文法チェック)で再代入のコードがエラーとなります。

    const String nickname = 'Bobby';
    const name = 'Bob'; // 型を省略して書くことができます。
    
    name = 'Alice';  //再代入はできません(静的解析でエラーになります)
    

まず、大前提として、constはfinalの性質をもっています。
つまり、constの方が、final よりより厳しいキーワードということです。

const を付けた変数は、コンパイル時に定数になります。

  • コンパイルとはDartのような特定のプログラミング言語で書かれたプログラムを、ネイティブ言語やJavaScriptなど、他の言語を用いて書かれた同じ動きをするプログラムに変換することです。
  • コンパイル時に定数になるということは、実行時に定数となるような値は代入できないということです。
  • 例えば、DateTime.now()は実行時に実行時の値の決まる関数となっています。コンパイル時(言語の変換時)には値が決まっていません。
  • そのためこのDateTime.now()は実行時にチェックするfinalの変数には設定できますが、constには設定できません。(以下のDartPadを御覧ください)
  • const に設定できるのは 数や文字列や constのついた変数、 定数の計算結果のような、
    コンパイル時の定数に限られます。
const bar = 1000000; 
const double atm = 1.01325 * bar;

const で設定した値は設定後変更できないということです。
例えば、finalでは以下のようなコードは成り立ちます。

void main() {
  final a = [1, 2, 3];

  a[0] = 4;

  print(a);

}

constで設定したobjectはimmutable(不変)になるということです。
finalよりより厳しい制約で使用するのがconstということですね。
基本はconstを使うのが良さそう。

制御文

  • if, forEach, for, for in, while, do while, switchなどの基本的な制御文が使えます。
  • switch文のみ他の言語と少し違いが有り、連続で実行するためにラベルを明示的に付ける必要があります。
    (ラベルをつけさえすれば、次の処理以外も連続で実行できるため、こっちのほうが優れているかもしれない)
main() {
  // forEach
  var lists = ['l', 'i', 's', 't'];
  lists.forEach((value) { print(value); });
  var sets = {'s', 'e', 't'};
  sets.forEach((value) { print(value); });
  var maps = {'k': 'm', 'e': 'a', 'y': 'p'};
  maps.forEach((key, value) { print('$key $value'); });

  // for
  for (var i = 0; i < 3; i++) {
    print(i);
  }

  // for in
  for (var i in ['f', 'o', 'r', 'i', 'n']) {
    if (i == 'o' || i == 'r') continue;
    print(i);
  }
  for (var i in {'s', 'e', 't'}) {
    print(i);
  }

  // while
  var w = 0;
  while (true) {
    // if文
    if (w == 3) {
      break;
    } else if(w == 1) {
      w++;
      print('wは$w');
    } else {
      w++;
      print('w=$w');
    }
  }

  // do-while
  var dw = 0;
  do {
    dw++;
    print('dw=$dw');
  } while (dw < 3);

  // switch
  var command = 'CLOSED';
  switch (command) {
    case 'CLOSED':
      print('CLOSED');
      continue nowClosed;  // continueの場合、nowClosedラベルを実行する
    nowClosed:
    case 'NOW_CLOSED':
      print('NOW_CLOSED');
      break;
  }
}

関数


// 関数(処理のまとまりを記述する)
// 戻り値の型を指定しない場合はdynamicになる(あまり推奨されていない)
// 戻り値がいらない場合は戻り値にvoidを指定する
int testFunction(){
  const y = 20;
  print('$x, $y'); // 関数の外で定義されているxは参照できる
  return x + y;
}

main() {
  const x = 10;
  // print('$y'); // 関数の内部で定義されているyは参照できない(変数のスコープ)

  // トップレベルでなくても関数内部でも関数定義できる
  // int testFunction(){
  //   const y = 20;
  //   print('$x, $y'); // 関数の外で定義されているxは参照できる
  //   return x + y;
  // }
  var result = testFunction();
  print('$result');

  // 一行の場合、ファットアローで省略記法がかける(JavaScriptのアロー関数と同じ)
  int oneline(a,b) => a + b;
  // この関数と等価
  // int oneline(a,b){ return a + b }
  print(oneline(1,2));

  // {}は名前付き任意引数
  void enableFlags({bool bold, bool hidden}) { print('$bold $hidden'); }
  // 引数ラベルをつけて呼び出す(記述順序は任意)
  // boldにはnull、hiddenにはtrueが渡される
  enableFlags(hidden: true);

  // []は順序付き任意引数、特定の位置以降の引数を省略可能
  // 任意引数にはデフォルト値をもたせることも可能(nullの場合、=の右辺の値が代入される)
  String say(String from, String msg, [String device = 'unknown', String mood]) {
    // ?? 演算子はnullの場合に右側の値が適応される
    return '$from says $msg platform: ${device} mood: ${mood ?? 'unknown'}';
  }
}

例外処理(try-catch)

try-catch文を使うことで例外処理の対処を行うことが出来ます。
try文内部で例外が起きた場合にcatch文に飛びます。(特定の型の例外を捕捉したい場合はon データ型 catchを使います)

main() {
  void errorFunc() {
    try {
      // throw Exceptionで意図的に例外を投げる
      throw Exception('例外です');
    } on Exception catch(e) {
      // 捕まえる型を指定するには on ~~ catch を使う
      // eはException型
      print(e);
      // rethrowでtry-catch-finallyブロックの外に例外を投げ直す事ができる(関数の外などでcatchする必要あり)
      rethrow;
    } finally {
      // finallyブロックは例外の有無にかかわらず実行される、省略可。
      print('finally');
    }
  }

  // 例外処理:try-catch文
  try {
    errorFunc();
  } catch (e, s) {
    // 型を指定しないcatchは、何型かわからない例外全部キャッチする
    // catchに仮引数を2つ指定すると、2つ目はStackTraceオブジェクトが入る
    print(s);
  }
}

クラス

他のオブジェクト指向言語同様、独自のデータ型を定義することが出来ます。
メンバ(変数、関数)を持つことでデータの塊に期待する振る舞いを与えることが出来ます。
Person(this.firstName, this.lastName);のようにすることで「引数→メンバ変数への代入」の省略表記をすることもできます。
また、C++のクラスライクに初期化子で変数を初期化することもできます。(メンバ変数がfinal、constの場合に使う)
名前付きコンストラクタ、factoryコンストラクタはDart独特の機能だと思います。

他の言語同様extendsで継承もできます。
メンバ関数上書きする際(オーバライド)、対象のメンバ関数にメタデータの@overrideをつけます(ただし、任意です)
同名のメンバ関数で引数違いのメンバ関数定義(オーバーロード)は存在しない(エラーになる)ので、やりたい場合は名前付き引数などを使います。

private定義をするには_をつけます。ただし、同ライブラリ内だとアクセスできてしまいます。
静的メンバを定義する(クラス共通のメンバ変数、メンバ関数)にはstaticキーワードをつけます。(他の言語と同じ)
他言語同様、子クラスから親クラスにキャストもできます。(ポリモーフィズム)

main() {
  // インスタンス生成
  var person1 = new Person('Yamada', 'Taro');
  print('${person1.firstName} ${person1.lastName}');
  // メンバ関数呼び出し
  person1.greed();
  // 名前付きコンストラクタ呼び出し
  var person2 = new Person.origin();
  print('${person2.firstName} ${person2.lastName}');

  // 静的メンバはインスタンス化しなくても呼び出し可能(クラス共通の関数、変数)
  print(Person.capacity);
  Person.staticMethod();

  // callメソッドの呼び出し
  print(person1());

  // 実は同ライブラリ内であれば、privateにアクセスできてしまう
  person2._member = 'abc';
  print(person2._member);

  var engineer1 = new Engineer('エン', 'ジニア');
  engineer1.greed();
  // 親クラスにキャストできる
  Person engineer2 = Engineer.instance(true);
  // 呼ばれるのは継承クラスのgreed(ポリモーフィズム)
  engineer2.greed();
  // 明示的に元のクラスにキャストするにはasを使う
  (engineer2 as Engineer).greed();
}

class Person {
  String firstName;
  String lastName;

  // this.フィールド名の引数だけで、フィールドに値を代入できる
  // コンストラクタのブロック内ではすでに代入された状態で使用できる
  // thisを省略すると別の仮引数として扱われてしまう
  Person(this.firstName, this.lastName);

  // privateメンバは便宜上、_から始まる。ただし、同ライブラリ内であればアクセスしようと思えばアクセスできてしまう。
  String _member;

  // 名前付きコンストラクタ
  // 複数のコンストラクタをもたせたいときに使う
  Person.origin() {
    this.firstName = '氏';
    this.lastName = '名';
    // 実はメソッド内はthisを省略してもクラス内のフィールドにアクセスできる
    // firstName = '氏';
    // lastName = '名';
  }

  // メンバ関数(インスタンス化したら呼び出し可能)
  greed() {
    print('Hello ${firstName} ${lastName}');
  }

  // 同名メソッドは作成できない(メソッドのオーバーロード)
  // 引数で処理を分けたい同名メソッドを作りたければ任意引数を使う
  // greed(int a) {}

  // 静的メンバ変数
  static const capacity = 16;

  // 静的メンバ関数
  static void staticMethod() {
    print('Hello');
  }

  // callは特殊なメンバ関数
  // インスタンス名()で呼び出しできる(呼び出し可能なクラス)
  call() => '$firstName $lastName';
}

// extendsでクラスの継承(親クラスのメンバが参照できる)
class Engineer extends Person {
  final String name;

  // 親クラスのコンストラクタを呼ぶには初期化子(:)でsuperを使う
  // メンバ変数の初期化も初期化子内で行える
  Engineer(String firstName, String lastName) : 
    name = '', 
    super(firstName, lastName);

  Engineer.origin():
    name = 'hello',
    super.origin() {
    print('${firstName} ${lastName}');
  }

  // 先頭にfactoryキーワードをつけるとファクトリーコンストラクタとなる
  // 自身のインスタンスを戻り値として返すことを明示できる
  factory Engineer.instance(bool isEngineer) {
    var instance = isEngineer ? new Engineer.origin() : new Person.origin();
    return instance;
  }

  // メソッドの上書き
  // @overrideは任意(けど書いたほうがわかりやすい)
  
  greed() {
    // super.greed(); // メンバ関数内でコンストラクタ以外の親クラスのメンバ関数はsuper.メンバ関数名で呼び出しできる
    print('I am ${firstName}${lastName}');
  }
}

不変コンストラクタはメンバ変数が全てfinalの場合に定義できます。
この場合、インスタンス化された変数はコンパイル定数として扱われます。
一部処理を別コンストラクタに移譲するリダイレクトコンストラクタも作成できます。

main() {
  const p = Point(1, 2);
  print('x=${p.x},y=${p.y}');
  var pp = Point.alongXAxis(3);
  print('x=${pp.x},y=${pp.y}');
}

class Point {
  final int x;
  final int y;

  // 不変コンストラクタ
  // メンバ変数が全部 final 宣言されていると、コンストラクタの頭にconstをつけられる
  // この場合、インスタンス化された変数はコンパイル定数として扱われる
  const Point(this.x, this.y);

  // リダイレクトコンストラクタ
  // 別のコンストラクタに一部処理を移譲する
  const Point.alongXAxis(int x): this(x, 0);
}

ゲッター、セッター

ゲッターはメンバ関数から値を取得する際に実行されるメンバ関数です。
あたかもメンバ変数かのように参照できます。
セッターは、メンバ変数に代入する際に実行されるメンバ関数です。
あたかもメンバ変数かのように代入できます。

main (){
  var rect = Rectangle(3, 4, 20, 15);
  print('right=${rect.right}'); // rightを参照するとゲッターが呼ばれて計算結果を取得。left + width
  rect.right = 12; // rightを変えるとセッターがよばれて
  print('left=${rect.left}'); // leftの結果も変わる
}

class Rectangle {
  int left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // ゲッター:rightのパラメータを参照できる。(実態はleft+widthの計算結果を返す)
  int get right => left + width;
  // セッター:パラメータを代入時にleftを計算する
  set right(num value) => left = value - width;
  int get bottom => top + height;
  set bottom(num value) => top = value - height;
}

抽象クラス

抽象クラスにするにはabstractキーワードをつけます。
抽象クラスはインスタンス化できません。
抽象クラスを継承してメンバ関数を実装します。

main() {
  // abstractクラスはインスタンス化できない
  // var animal = new Animal();
  var cat = new Cat();
  cat.hello();
}

// 抽象クラス
// 処理未定義のメンバ関数を持つクラス、インスタンス化できない
abstract class Animal {
  void hello();
}

// 抽象クラスを継承して未定義メンバ関数を実装する
class Cat extends Animal {
  void hello() {
    print("みゃお");
  }
}

演算子のオーバロード

C++言語のように演算子(operator)をオーバライドすることができます。
クラス同士の演算を定義でき、強力な反面、使い所を間違えると混乱を招くため
数学的な処理などに限定したほうが良いでしょう。

main (){
  var v1 = new Vector(1, 2);
  var v2 = new Vector(2, 3);
  var v3 = v1 + v2;
  print('x=${v3.x},y=${v3.y}');
}

class Vector {
  final int x, y;

  Vector(this.x, this.y);

  // 演算子のオーバーライド(演算子の振る舞いをメソッドの処理に上書きする)
  // C++の演算子のオーバーライドと同じ
  // この例はベクトルの足し算、引き算を定義できる
  // 強力な反面、不用意に使うと混乱を招くのでベクトルや行列計算等の数学的な処理を定義する以外はあまり使わないほうが良い
  Vector operator +(Vector v) => Vector(x + v.x, y + v.y); // Vector + Vector
  Vector operator -(Vector v) => Vector(x - v.x, y - v.y); // Vector - Vector
}

インタフェース

**Dartのクラスはクラスの生成と同時にinterfaceも生成します。(暗黙的なinterface)
**interfaceに存在するのはメンバ関数の定義のみで実装をimprementsで行うことが出来ます。(imprements先クラスで実装を保証させる)

main() {
  // Masterクラスのインスタンスを作成
  var master = new Master('Master');
  print(master.commit('ToDo List'));
  // Masterインタフェースを継承したBranchクラスのインスタンスを作成
  var branch = new Branch();
  print(branch.commit('sort'));

  var director = Director('Tanaka', 'Saburo');
  director.hello();
  director.story();
}

// Dartには interfaceキーワードが存在しませんが、クラスを宣言した時点でそのクラスと同じAPIのinterfaceが勝手に作られます(暗黙的なinterface)
// インターフェイスは実装を持たない
// Masterクラスの宣言であり、commit()メソッドを持ったMasterインターフェイスの宣言でもある
class Master {
  // privateなものはインターフェイスには含まれない
  final _name;

  // コンストラクタもインターフェイスには含まれない
  Master(this._name);

  String commit(String msg) => '${_name} commit ${msg}';
  // このメンバ関数の宣言のみインターフェイスに含まれる(実装は含まれない)
  // String commit(String msg);
}

// implementsでPersonインターフェイスを実装する
class Branch implements Master {
  // privateメンバ変数に関してはゲッターの実装をしないと怒られる
  get _name => '';

  // commitを実装しないと怒られる
  String commit(String msg) => 'Branch commit ${msg}';
}

// extendsの親クラスは1つしか指定できないのに対し、implementsは複数指定できる(Javaと一緒)
// 抽象クラスもimplementsできる
class Director extends Person implements Animal, Point {
  // Personのコンストラクタを継承
  Director(String firstName, String lastName) : super(firstName, lastName);

  // Animalのメンバ関数
  
  void hello() {
    print('I am Director');
  }

  // Pointのゲッター実装
  
  int get x => x;

  // Pointのゲッター実装
  
  int get y => y;

  // Director独自のメンバ関数
  void story() {
    print('Yes we can');
  }
}

ミックスイン

ミックスインを使用するとmixinの実装を引き継ぐことが出来ます。
ただし、with句で引き継ぐmixinにはコンストラクタを指定することは出来ません。

main() {
  // mixin
  var musician = new Musician();
  // Performerクラスの実装を呼び出す
  musician.PerformerMethod();
  // Musicalクラスの実装を呼び出す
  musician.MusicalMethod();
}

class Performer {
  Performer() {
    print('Perfomer');
  }
  void PerformerMethod() {
    print('PerformerMethod');
  }
}
mixin Musical {
  // mixinはコンストラクタは定義できない
  void MusicalMethod() {
    print('MusicalMethod');
  }
}

// ミックスイン、with句でつないだmixinの実装が使える
// 多重継承に似ているが、コンストラクタが定義できるのはextendsしたクラスのみ
class Musician extends Performer with Musical {
  Musician(): super() {
    print('Musician');
  }
}

列挙型

列挙型はデータの識別用に定義しておくと便利です。
indexで0から始まるインデックスにアクセスできます。

main() {
  var c = Color.blue;
  print(Color.green.index == 1);
  // 列挙型はswitchの条件分岐に使える
  switch (c) {
    case Color.green:
      print('green');
      break;
    case Color.blue:
      print('blue');
      break;
    case Color.red:
      print('red');
      break;
    default:
  }
}

// 列挙型
// 列挙子は宣言された順にインデックス(0始まり)が割り振られていて、 index で参照できる。
// enumを継承できなかったり(mixinにも使えない)、enumのインスタンスを自前で生成できない(定数のみしか使えない)
// 実装を持つことが出来ない以外、Javaとほぼ同じ
enum Color { red, green, blue }

ジェネリックス

ジェネリックスはデータの型を利用時に指定することが出来ます。

main() {
  // ジェネリックスのインスタンス化は型を指定する
  var cache = new Cache<String>();
  cache.setByKey('key', 'test');
  print('key=${cache.getByKey('key')}');

  // メソッドのジェネリックス
  // 型の制限をするときは型のextendsを使う
  T sum<T extends num>(List<T> list, T init){
    T sum = init;
    list.forEach((value) { 
      sum += value;
    });
    return sum;
  }

  int r1 = sum<int>([1,2,3], 0);
  print(r1);
  // 型指定しない場合は左辺の型に暗黙的に型推定される
  double r2 = sum([1.1, 2.2, 3.3], 0.0);
  print(r2);
}

// 型だけが違う実装をしたクラスを実装したい場合はジェネリックスを使うと便利
// インスタンス化するときTに型を指定する
class Cache<T> {
  Map<String, T> store = <String, T>{};

  T getByKey(String key) {
    return store[key];
  }

  void setByKey(String key, T value) {
    this.store.addAll(<String, T>{key: value});
  }
}

typedef

関数に別名(型)を定義することが出来ます。
Dartの場合、typedefにジェネリックスを併用することが出来ます。

main() {
  // sort関数をtypedefで定義した型で格納
  Compare<int> sortFunc = sort;
  print('sort: ${sortFunc(1, 2)}');
}

// typedefで関数の別名(型)を定義できる
typedef Compare<T> = T Function(T a, T b);
int sort(int a, int b) => a - b;

文字列操作

よく使う文字列操作

文字列置換
正規表現によるパターン抽出
改行を含んだ文字列リテラル
文字列分解→配列化

main() {
  // 文字列置換
  'Dart Language'.replaceAll('a', '@'); // == 'D@rt L@ngu@ge'

  var address = '東京都港区 1-1-1';
  // 正規表現でマッチするすべてを取得する
  Iterable<Match> matches = new RegExp('.?区').allMatches(address);
  for (Match m in matches) {
    print(m.group(0));
  }

  // 改行を含んだ文字列をリテラルで表現するには'''で囲う
  var multiline = '''
改行
しました''';
  print(multiline);
}

カスケード記法

特定のインスタンスに対して..で続けることでそのインスタンスに対する操作(メンバ関数呼び出し)を続けることが出来ます。

main() {
  // カスケード記法、..で対象のインスタンスに対するメンバ関数呼び出しの操作を続けられる
  var fullString = StringBuffer()
    ..write('Use a StringBuffer for ')
    ..writeAll(['efficient', 'string', 'creation'], ' ')
    ..toString();
  print(fullString);
  // 次と等価
  // var sb = StringBuffer();
  // sb.write('Use a StringBuffer for ');
  // sb.writeAll(['efficient', 'string', 'creation'], ' ');
  // var fullString = sb.toString();
  // print(fullString);
}

非同期関数

非同期関数を作成するにはFuture型を返却します。
Futureに関してはJavaScriptのPromiseと似ています。
async/awaitが使えるため、awaitで同期待ちすることができます。

import 'dart:async'; // 非同期処理

main() async {
  // 非同期関数(async)、Future<データ型>で戻り値を返却する必要がある
  // JavaScriptのPromise関数とほぼ同じ
  Future<String> lookUpVersion() async => '1.0.0';
  var version = await lookUpVersion(); // awaitで同期待ち(async関数内でのみ使える)
  print(version);
}

ジェネレータ

同期のIteratable、非同期のStreamの2種類あります。
JavaScriptのジェネレータに似ています。
呼び出すたびにyieldの値が順次返ります。
Iterableの方はforEachすることですべての値が取得できます。
Streamの方はawait for inで同期待ちしながらすべての値を取得することが出来ます。
listenを使ってコールバック形式で受け取ることもできます。

import 'dart:async'; // 非同期処理

main() async {

  // 同期ジェネレータ、JavaScriptのジェネレータとほぼ同じ、Iterable<データ型>で戻り値を返却する必要がある
  // yieldの値が順次返却される
  Iterable<int> countIterator(int n) sync* {
    int k = 0;
    while (k < n) yield k++;
  }
  var iterator = countIterator(3);
  iterator.forEach((value) { print(value); });

  print('--------');

  // 非同期ジェネレータ、JavaScriptのジェネレータ、Rxのストリームに近い、Stream<データ型>で戻り値を返却する必要がある
  // yieldの値が順次返される
  Stream<int> countStream(int to) async* {
    for (int i = 1; i <= to; i++) {
      yield i;
    }
  }

  var stream = countStream(3);
  // Streamの同期待ちにはawait forを使う。yieldの結果を順次取り出すイメージ
  await for (var value in stream) {
    print(value); 
  }

  print('--------');

  var stream2 = countStream(3);
  // streamの結果を非同期で受け取るにはlistenで結果を待つ
  stream2.listen((value){
    print(value);
  });
}

アイソレータ

高価(高負荷)な処理を別のスレッドに移譲することができます。
正確にはマルチスレッドではなくワーカープロセスなのですが、
JavaScriptのワーカースレッドのような仕組みに処理を移譲することができます。
(ほとんどのコンピュータは、モバイルプラットフォーム上でも、マルチコアCPUを搭載しています。
これらすべてのコアを活用するために、開発者は伝統的に、同時実行されている共有メモリスレッドを使用します。
ただし、共有状態の同時実行はエラーが発生しやすく、コードが複雑になる可能性があります。
Dartはスレッドの代わりに、アイソレータの内部で実行されます。
各アイソレータには独自のメモリヒープがあり、アイソレータの状態には他のアイソレータから直接アクセスできないようになっています。)

ReceivePortクラスで待受ポートを開き、Isolate.spawn関数でアイソレータを実行します。
実行結果はsendで呼び出し元に送信します。

import 'dart:io';
import 'dart:isolate';

main() async {
  final receivePort = ReceivePort();
  final sendPort = receivePort.sendPort;
  // アイソレータ(スレッド処理)を実行
  await Isolate.spawn(isolate, sendPort);
  // アイソレータの結果待ち
  receivePort.listen((msg){
    print("message from another Isolate: $msg");
    exit(0); // main関数を終了する
  });
}

// スレッド処理
void isolate(sendPort) {
  for(int i = 0; i< 10; i ++) {
    print("waiting: $i");
  }
  // 呼び出し元に結果を送信する
  sendPort.send("finished!");
}
masarufuruyamasarufuruya

dartのシングルクォートとダブルクォートはどちらも同じです。シングルクォートで囲まれた文字列にはダブルクォートを含むことができます。逆も可能です。

スプレッド演算子 ”…”とは?
コードリーディング中、以下のようなコードを見たことはありませんか?

final list1 = [1, 2, 3];
final list2 = [4, ...list1, 5];
2行目で使われている”…”をスプレッド演算子と呼びます。

スプレッド演算子は、Listのような複数の値を保持するコレクションに、
複数の値を一気に挿入するのに用います。

このスクラップは2024/10/19にクローズされました