Dartの基本的な文法についてまとめます。
Dartにおけるコンストラクタの最も原始的な構文は以下のように書けます。コンストラクタの引数としてString型のパラメータname
を受け取り、コンストラクタの本体の中でメンバ変数に代入しています。
class Dog {
String name;
Dog(String name) {
this.name = name;
}
}
以下の構文では、コンストラクタの引数として受け取ったパラメータを直接メンバ変数に代入しています。この構文では、メンバ変数をfinalにすることができます。
class Dog {
final String name;
Dog(this.name);
}
上記の構文を用いた場合にも、コンストラクタ本体を定義することができます。
class Dog {
final String name;
Dog(this.name) {
print(name);
}
}
Dog dog = Dog('pochi'); // => pochi
また、this.name
の部分を{}
で囲うと、コンストラクタ呼び出しを名前付き引数で行うことができます。
class Dog {
final String name;
Dog({this.name});
}
Dog dog = Dog(name: 'pochi');
Dartでは、コンストラクタのオーバーロードができません。その代わりに、Dartでは名前付きコンストラクタを定義することができます。
class Dog {
String name;
Dog(String name) {
this.name = name;
}
Dog.noName() {
name = 'no name';
}
}
Dog noName = Dog.noName();
print(noName.name); // => no name
なお、上記の例において、プライマリコンストラクタではthis
を用いてメンバ変数にアクセスし、名前付きコンストラクタではthis
を用いずにメンバ変数にアクセスしている理由は、前者では名前の隠蔽が発生し、後者では発生しないためです。
Dartにはアクセス修飾子がありません。その代わり、メンバの名前で可視性を与えることができます。メンバの名前が_
から始まるものは、いわゆるprivateな可視性となります。それ以外の名前は、いわゆるpublicな可視性となります。
例えば、以下のクラスのメンバ変数_name
はprivateなメンバ変数となり、同一ライブラリ内からのみアクセス可能です(Dart - Using Access Modifiers (Private & Public) - Woolha)。
// cat.dart
class Cat {
String _name;
Cat(String name) {
_name = name;
}
}
同一ライブラリ内からのみアクセス可能と言うのは、例えば以下のように同一ファイル内にクラスを定義した場合は、他クラスのprivateなメンバにアクセスすることができます。
// cat.dart
class Cat {
String _name;
String _keeper;
Cat(String name) {
_name = name;
Human human = Human();
_keeper = human._name;
}
}
class Human {
String _name = 'human';
}
可視性の範囲外からは、privateなメンバにはアクセスできません。
// main.dart
void main(List<String> arguments) {
Cat cat = Cat('tama');
//cat._name; // 不可
}
privateなメソッドも、メソッド名を_
から始めて定義します。
class Cat {
// 略
void _toilet() {
// do something here
}
}
setterは隠蔽し、getterだけを公開したい時は、以下のように別途getterを定義します。
class Cat {
String _name;
String get name => _name;
// 略
}
拡張メソッド(後述)もprivateにすることが可能です。privateな拡張メソッドは、拡張メソッドを使いたい場所を限定したい時に使います。
extension CatExtension on Cat {
Dog _toDog() {
return Poodle(name: this.name);
}
}
Dartでは、extends
キーワードを用いてクラスの継承を行うことができます。単一継承のみ可能で、多重継承はできません。
以下の例では、抽象クラスDogを継承して、Poodleクラスを実装しています。抽象クラスを定義するためのキーワードはabstract
です。
abstract class Dog {
final String name;
Dog({this.name});
}
class Poodle extends Dog {
Poodle({name}) : super(name: name);
}
Dog poodle = Poodle(name: 'pochi');
抽象クラスでは、抽象メソッドを定義することができます。抽象メソッドは、メソッドのシグネチャのみ定義し、メソッド本体を持たないメソッドです。具象クラスの中では定義できません。また、抽象メソッドは、具象クラスの中で必ずオーバーライドしなければなりません。メソッドをオーバーライドするには、対象となるメソッドに@override
アノテーションを付与します。
abstract class Dog {
// 略
void bark(); // 抽象メソッド
}
class Poodle extends Dog {
// 略
@override
void bark() {
print('bow wow!');
}
}
また抽象クラスにおいては、メソッドの実装を持つことも可能です。この場合、具象クラスにおいてメソッドをオーバーライドする必要は必ずしもありませんが、オーバーライドすることも可能です。この時、必要に応じてsuper.メソッド名()
で継承元のメソッドを呼び出すことが可能です。
abstract class Dog {
// 略
void run() {
print('running...');
}
}
class Poodle extends Dog {
// 略
@override
void run() {
super.run();
print('it is a poodle...');
}
}
メソッドだけでなく、メンバ変数についてもオーバーライドすることが可能です。なお、メンバ変数を抽象的に定義する方法はわかりませんでした。Dart 2.8.2
では実装されていないものと思われます。
abstract class Dog {
// 略
final String size = 'medium';
}
class Poodle extends Dog {
// 略
@override
final String size = 'large';
}
クラスやメソッドのオーバーライドを禁止する機能についても、おそらくDart 2.8.2
では実装されていないです。
Dart 2.7以降、Dartでは型に拡張メソッドを定義して、機能を追加することができます。構文は以下です(Extension methods | Dart)。
extension <extension name> on <type> {
(<member definition>)*
}
以下の例ではint
型に拡張メソッドとしてtoTenTimes()
を定義しています。
extension IntExtension on int {
int toTenTimes() {
return this * 10;
}
}
int
型に対して、あたかもint
型に元からtoTenTimes()
というメソッドが定義されていたかのようにtoTenTimes()
を呼び出すことが可能になります。ただし、インポートは必要です。拡張メソッドの中では、this
で対象のクラスのインスタンスにアクセスすることができます。
int answer = 42.toTenTimes();
print(answer); // => 420
拡張について名前を定義することができます。上の例では、IntExtensionという名前で拡張をしています。拡張に名前を定義している理由は、「APIがコンフリクトした時に備えて拡張は名前を持っている(extensions have names, which can be helpful if an API conflict arises)(Extension methods | Dart)」とのことです。
Dartでは以下の形式でラムダを書くことができます。
(int i) {
return i + 1;
};
変数succに上記のラムダをバインドします。
int Function(int) succ = (int i) {
return i + 1;
};
バインドした変数を経由してラムダを実行します。
print(succ(0)); // => 1
print(succ(100)); // => 101
以下の形式は上記の形式の糖衣構文です。
int Function(int) succ2 = (int i) => i + 1;
print(succ2(0)); // => 1
print(succ2(100)); // => 101
ラムダを渡して実行してみます。
int calculate(int Function(int) func, int input) {
return func.call(input);
}
print(calculate(succ, 42)); // => 43
当然、名前をつけないラムダを渡すこともできます。
print(calculate((int i) => i + 1 , 42)); // => 43
identical
を用いて、参照の等価性を評価することができます。identical
はdart:coreに含まれています。
bool identical(
Object a,
Object b
)
以下の例では、2つの変数の参照が等しいため、identical
はtrueを返します。
Dog dog = Dog(name: 'pochi');
bool result = identical(dog, dog);
print(result); // => true
以下の例では、2つの変数の参照が異なるため、identical
はfalseを返します。
Dog dog = Dog(name: 'pochi');
Cat cat = Cat(name: 'tama');
bool result = identical(dog, cat);
print(result); // => false
変数が異なっても、参照が等価であればidentical
はtrueを返します。
Dog dog1 = Dog(name: 'pochi');
Dog dog2 = dog1;
bool result = identical(dog1, dog2);
print(result); // => true
identical
が返すbool値は、==
の結果とは異なります(以下の例では==
オペレーターをoverrideしてname
が等しければ==
がtrueを返すようにしています)。
Dog dog1 = Dog(name: 'pochi');
Dog dog2 = Dog(name: 'pochi');
bool result = identical(dog1, dog2);
print(result); // => false
print(dog1 == dog2); // => true