Dart チートシート
- 文字列補間
- Null 許容変数
- Null 認識演算子
- 条件付きプロパティアクセス
- コレクションリテラル
- アロー構文
- カスケード
- ゲッターとセッター
- 省略可能な位置パラメーター
- 名前付きパラメーター
- 例外
- コンストラクタでの this の使用
- 初期化子リスト
- 名前付きコンストラクタ
- ファクトリコンストラクタ
- リダイレクトコンストラクタ
- 定数コンストラクタ
- 次にすること
Dart 言語は、他の言語から来たコーダーが簡単に学習できるように設計されていますが、いくつかの独自の機能があります。 このチュートリアルでは、これらの言語機能の中で最も重要なものについて説明します。
このチュートリアルに埋め込まれたエディターには、部分的に完成したコードスニペットがあります。 これらのエディターを使用して、コードを完成させて**実行**ボタンをクリックすることで、知識をテストできます。 エディターには、徹底的なテストコードも含まれています。 **テストコードは編集しないでください**が、テストについて学ぶために自由に学習してください。
ヘルプが必要な場合は、各 DartPad の下にある**...のソリューション**ドロップダウンを展開して、説明と回答を確認してください。
文字列補間
#式の値を文字列内に配置するには、${expression}
を使用します。 式が識別子の場合は、{}
を省略できます。
文字列補間の使用例を次に示します
文字列 | 結果 |
---|---|
'${3 + 2}' | '5' |
'${"word".toUpperCase()}' | 'WORD' |
'$myObject' | myObject.toString() の値 |
コード例
#次の関数は、2 つの整数をパラメーターとして受け取ります。 スペースで区切られた両方の整数が含まれる文字列を返すようにしてください。 例えば、stringify(2, 3)
は '2 3'
を返す必要があります。
String stringify(int x, int y) {
TODO('Return a formatted string here');
}
// Tests your solution (Don't edit!):
void main() {
assert(stringify(2, 3) == '2 3',
"Your stringify method returned '${stringify(2, 3)}' instead of '2 3'");
print('Success!');
}
文字列補間例のソリューション
x
と y
はどちらも単純な値であり、Dart の文字列補間はそれらを文字列表現に変換します。 シングルクォート内で $
演算子を使用してそれらを参照し、間にスペースを入れるだけです
String stringify(int x, int y) {
return '$x $y';
}
Null 許容変数
#Dart は、健全な Null 安全を強制します。 つまり、値は、指定しない限り Null にできません。 言い換えれば、型はデフォルトで Null 非許容です。
例えば、次のコードを考えてみましょう。 Null 安全では、このコードはエラーを返します。 int
型の変数は、値 null
を持つことはできません
int a = null; // INVALID.
変数を作成するときは、型に ?
を追加して、変数が null
になる可能性があることを示します
int? a = null; // Valid.
Dart のすべてのバージョンで、null
は初期化されていない変数のデフォルト値であるため、コードを少し簡略化できます
int? a; // The initial value of a is null.
Dart の Null 安全の詳細については、健全な Null 安全ガイドを参照してください。
コード例
#この DartPad で 2 つの変数を宣言します
- 値が
'Jane'
の Null 許容String
という名前のname
。 - 値が
null
の Null 許容String
という名前のaddress
。
DartPad のすべての初期エラーを無視します。
// TODO: Declare the two variables here
// Tests your solution (Don't edit!):
void main() {
try {
if (name == 'Jane' && address == null) {
// verify that "name" is nullable
name = null;
print('Success!');
} else {
print('Not quite right, try again!');
}
} catch (e) {
print('Exception: ${e.runtimeType}');
}
}
Null 許容変数例のソリューション
2 つの変数を String
の後に ?
を付けて宣言します。 次に、name
に 'Jane'
を割り当て、address
は初期化されていないままにします
String? name = 'Jane';
String? address;
Null 認識演算子
#Dart は、Null になる可能性のある値を処理するための便利な演算子をいくつか提供しています。 1 つは ??=
代入演算子で、変数が現在 Null の場合にのみ変数に値を代入します
int? a; // = null
a ??= 3;
print(a); // <-- Prints 3.
a ??= 5;
print(a); // <-- Still prints 3.
もう 1 つの Null 認識演算子は ??
で、左側の式の値が Null でない限り左側の式を返し、Null の場合は右側の式を評価して返します
print(1 ?? 3); // <-- Prints 1.
print(null ?? 12); // <-- Prints 12.
コード例
#??=
と ??
演算子を代入して、次のスニペットで説明されている動作を実装してみてください。
DartPad のすべての初期エラーを無視します。
String? foo = 'a string';
String? bar; // = null
// Substitute an operator that makes 'a string' be assigned to baz.
String? baz = foo /* TODO */ bar;
void updateSomeVars() {
// Substitute an operator that makes 'a string' be assigned to bar.
bar /* TODO */ 'a string';
}
// Tests your solution (Don't edit!):
void main() {
try {
updateSomeVars();
if (foo != 'a string') {
print('Looks like foo somehow ended up with the wrong value.');
} else if (bar != 'a string') {
print('Looks like bar ended up with the wrong value.');
} else if (baz != 'a string') {
print('Looks like baz ended up with the wrong value.');
} else {
print('Success!');
}
} catch (e) {
print('Exception: ${e.runtimeType}.');
}
}
Null 認識演算子例のソリューション
この演習では、TODO
コメントを ??
または ??=
に置き換えるだけです。 上記のテキストを読んで両方を理解してから、試してみてください
// Substitute an operator that makes 'a string' be assigned to baz.
String? baz = foo ?? bar;
void updateSomeVars() {
// Substitute an operator that makes 'a string' be assigned to bar.
bar ??= 'a string';
}
条件付きプロパティアクセス
#Null になる可能性のあるオブジェクトのプロパティまたはメソッドへのアクセスを保護するには、ドット (.
) の前に疑問符 (?
) を付けます
myObject?.someProperty
上記のコードは、次のコードと同等です
(myObject != null) ? myObject.someProperty : null
1 つの式で ?.
の複数の使用を連結できます
myObject?.someProperty?.someMethod()
上記のコードは、myObject
または myObject.someProperty
が Null の場合に Null を返し (someMethod()
を呼び出しません)。
コード例
#次の関数は、Null 許容文字列をパラメーターとして受け取ります。 条件付きプロパティアクセスを使用して、str
の大文字バージョンを返すか、str
が null
の場合は null
を返すようにしてください。
String? upperCaseIt(String? str) {
// TODO: Try conditionally accessing the `toUpperCase` method here.
}
// Tests your solution (Don't edit!):
void main() {
try {
String? one = upperCaseIt(null);
if (one != null) {
print('Looks like you\'re not returning null for null inputs.');
} else {
print('Success when str is null!');
}
} catch (e) {
print('Tried calling upperCaseIt(null) and got an exception: \n ${e.runtimeType}.');
}
try {
String? two = upperCaseIt('a string');
if (two == null) {
print('Looks like you\'re returning null even when str has a value.');
} else if (two != 'A STRING') {
print('Tried upperCaseIt(\'a string\'), but didn\'t get \'A STRING\' in response.');
} else {
print('Success when str is not null!');
}
} catch (e) {
print('Tried calling upperCaseIt(\'a string\') and got an exception: \n ${e.runtimeType}.');
}
}
条件付きプロパティアクセス例のソリューション
この演習で文字列を条件付きで小文字にする必要がある場合は、次のようにします。str?.toLowerCase()
。 文字列を大文字にする同等のメソッドを使用してください!
String? upperCaseIt(String? str) {
return str?.toUpperCase();
}
コレクションリテラル
#Dart は、リスト、マップ、セットを組み込みでサポートしています。 リテラルを使用して作成できます
final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings = {'one', 'two', 'three'};
final aMapOfStringsToInts = {
'one': 1,
'two': 2,
'three': 3,
};
Dart の型推論は、これらの変数に型を自動的に割り当てることができます。 この場合、推論される型は List<String>
、Set<String>
、および Map<String, int>
です。
または、自分で型を指定することもできます
final aListOfInts = <int>[];
final aSetOfInts = <int>{};
final aMapOfIntToDouble = <int, double>{};
型の指定は、リストをサブタイプのコンテンツで初期化する場合に便利ですが、リストを List<BaseType>
にしたい場合に便利です
final aListOfBaseType = <BaseType>[SubType(), SubType()];
コード例
#次の変数を指定された値に設定してみてください。 既存の Null 値を置き換えます。
// Assign this a list containing 'a', 'b', and 'c' in that order:
final aListOfStrings = null;
// Assign this a set containing 3, 4, and 5:
final aSetOfInts = null;
// Assign this a map of String to int so that aMapOfStringsToInts['myKey'] returns 12:
final aMapOfStringsToInts = null;
// Assign this an empty List<double>:
final anEmptyListOfDouble = null;
// Assign this an empty Set<String>:
final anEmptySetOfString = null;
// Assign this an empty Map of double to int:
final anEmptyMapOfDoublesToInts = null;
// Tests your solution (Don't edit!):
void main() {
final errs = <String>[];
if (aListOfStrings is! List<String>) {
errs.add('aListOfStrings should have the type List<String>.');
} else if (aListOfStrings.length != 3) {
errs.add('aListOfStrings has ${aListOfStrings.length} items in it, \n rather than the expected 3.');
} else if (aListOfStrings[0] != 'a' || aListOfStrings[1] != 'b' || aListOfStrings[2] != 'c') {
errs.add('aListOfStrings doesn\'t contain the correct values (\'a\', \'b\', \'c\').');
}
if (aSetOfInts is! Set<int>) {
errs.add('aSetOfInts should have the type Set<int>.');
} else if (aSetOfInts.length != 3) {
errs.add('aSetOfInts has ${aSetOfInts.length} items in it, \n rather than the expected 3.');
} else if (!aSetOfInts.contains(3) || !aSetOfInts.contains(4) || !aSetOfInts.contains(5)) {
errs.add('aSetOfInts doesn\'t contain the correct values (3, 4, 5).');
}
if (aMapOfStringsToInts is! Map<String, int>) {
errs.add('aMapOfStringsToInts should have the type Map<String, int>.');
} else if (aMapOfStringsToInts['myKey'] != 12) {
errs.add('aMapOfStringsToInts doesn\'t contain the correct values (\'myKey\': 12).');
}
if (anEmptyListOfDouble is! List<double>) {
errs.add('anEmptyListOfDouble should have the type List<double>.');
} else if (anEmptyListOfDouble.isNotEmpty) {
errs.add('anEmptyListOfDouble should be empty.');
}
if (anEmptySetOfString is! Set<String>) {
errs.add('anEmptySetOfString should have the type Set<String>.');
} else if (anEmptySetOfString.isNotEmpty) {
errs.add('anEmptySetOfString should be empty.');
}
if (anEmptyMapOfDoublesToInts is! Map<double, int>) {
errs.add('anEmptyMapOfDoublesToInts should have the type Map<double, int>.');
} else if (anEmptyMapOfDoublesToInts.isNotEmpty) {
errs.add('anEmptyMapOfDoublesToInts should be empty.');
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
// ignore_for_file: unnecessary_type_check
}
コレクションリテラル例のソリューション
各等号の後にリスト、セット、またはマップのリテラルを追加します。 空の宣言の型は推論できないため、指定することを忘れないでください。
// Assign this a list containing 'a', 'b', and 'c' in that order:
final aListOfStrings = ['a', 'b', 'c'];
// Assign this a set containing 3, 4, and 5:
final aSetOfInts = {3, 4, 5};
// Assign this a map of String to int so that aMapOfStringsToInts['myKey'] returns 12:
final aMapOfStringsToInts = {'myKey': 12};
// Assign this an empty List<double>:
final anEmptyListOfDouble = <double>[];
// Assign this an empty Set<String>:
final anEmptySetOfString = <String>{};
// Assign this an empty Map of double to int:
final anEmptyMapOfDoublesToInts = <double, int>{};
アロー構文
#Dart コードで =>
記号を見たことがあるかもしれません。 このアロー構文は、右側の式を実行してその値を返す関数を定義する方法です。
例えば、List
クラスの any()
メソッドへのこの呼び出しを考えてみましょう
bool hasEmpty = aListOfStrings.any((s) {
return s.isEmpty;
});
このコードを記述する簡単な方法は次のとおりです
bool hasEmpty = aListOfStrings.any((s) => s.isEmpty);
コード例
#アロー構文を使用する次のステートメントを完成させてみてください。
class MyClass {
int value1 = 2;
int value2 = 3;
int value3 = 5;
// Returns the product of the above values:
int get product => TODO();
// Adds 1 to value1:
void incrementValue1() => TODO();
// Returns a string containing each item in the
// list, separated by commas (e.g. 'a,b,c'):
String joinWithCommas(List<String> strings) => TODO();
}
// Tests your solution (Don't edit!):
void main() {
final obj = MyClass();
final errs = <String>[];
try {
final product = obj.product;
if (product != 30) {
errs.add('The product property returned $product \n instead of the expected value (30).');
}
} catch (e) {
print('Tried to use MyClass.product, but encountered an exception: \n ${e.runtimeType}.');
return;
}
try {
obj.incrementValue1();
if (obj.value1 != 3) {
errs.add('After calling incrementValue, value1 was ${obj.value1} \n instead of the expected value (3).');
}
} catch (e) {
print('Tried to use MyClass.incrementValue1, but encountered an exception: \n ${e.runtimeType}.');
return;
}
try {
final joined = obj.joinWithCommas(['one', 'two', 'three']);
if (joined != 'one,two,three') {
errs.add('Tried calling joinWithCommas([\'one\', \'two\', \'three\']) \n and received $joined instead of the expected value (\'one,two,three\').');
}
} catch (e) {
print('Tried to use MyClass.joinWithCommas, but encountered an exception: \n ${e.runtimeType}.');
return;
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
アロー構文例のソリューション
積については、*
を使用して 3 つの値を掛け合わせることができます。 incrementValue1
については、インクリメント演算子 (++
) を使用できます。 joinWithCommas
については、List
クラスにある join
メソッドを使用します。
class MyClass {
int value1 = 2;
int value2 = 3;
int value3 = 5;
// Returns the product of the above values:
int get product => value1 * value2 * value3;
// Adds 1 to value1:
void incrementValue1() => value1++;
// Returns a string containing each item in the
// list, separated by commas (e.g. 'a,b,c'):
String joinWithCommas(List<String> strings) => strings.join(',');
}
カスケード
#同じオブジェクトに対して一連の操作を実行するには、カスケード (..
) を使用します。 私たちは皆、このような表現を見たことがあります
myObject.someMethod()
myObject
で someMethod()
を呼び出し、式の結果は someMethod()
の戻り値です。
カスケードを使用した同じ式を次に示します
myObject..someMethod()
依然として myObject
で someMethod()
を呼び出しますが、式の結果は戻り値**ではなく**、myObject
への参照です!
カスケードを使用すると、そうでなければ別々のステートメントが必要になる操作を連結できます。 例えば、条件付きメンバーアクセス演算子 (?.
) を使用して、button
が null
でない場合に button
のプロパティを読み取る次のコードを考えてみましょう
var button = querySelector('#confirm');
button?.text = 'Confirm';
button?.classes.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));
button?.scrollIntoView();
代わりにカスケードを使用するには、*Null 短絡*カスケード (?..
) から始めることができます。これにより、カスケード操作が null
オブジェクトで試行されないことが保証されます。 カスケードを使用すると、コードが短くなり、button
変数が不要になります
querySelector('#confirm')
?..text = 'Confirm'
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'))
..scrollIntoView();
コード例
#カスケードを使用して、BigObject
の anInt
、aString
、および aList
プロパティを 1
、'String!'
、および [3.0]
(それぞれ) に設定し、次に allDone()
を呼び出す単一のステートメントを作成します。
class BigObject {
int anInt = 0;
String aString = '';
List<double> aList = [];
bool _done = false;
void allDone() {
_done = true;
}
}
BigObject fillBigObject(BigObject obj) {
// Create a single statement that will update and return obj:
return TODO('obj..');
}
// Tests your solution (Don't edit!):
void main() {
BigObject obj;
try {
obj = fillBigObject(BigObject());
} catch (e) {
print('Caught an exception of type ${e.runtimeType} \n while running fillBigObject');
return;
}
final errs = <String>[];
if (obj.anInt != 1) {
errs.add(
'The value of anInt was ${obj.anInt} \n rather than the expected (1).');
}
if (obj.aString != 'String!') {
errs.add(
'The value of aString was \'${obj.aString}\' \n rather than the expected (\'String!\').');
}
if (obj.aList.length != 1) {
errs.add(
'The length of aList was ${obj.aList.length} \n rather than the expected value (1).');
} else {
if (obj.aList[0] != 3.0) {
errs.add(
'The value found in aList was ${obj.aList[0]} \n rather than the expected (3.0).');
}
}
if (!obj._done) {
errs.add('It looks like allDone() wasn\'t called.');
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
カスケード例のソリューション
この演習の最適な解決策は、obj..
で始まり、4 つの代入演算が連鎖しています。return obj..anInt = 1
から始め、次に別のカスケード (..
) を追加して、次の代入を開始します。
BigObject fillBigObject(BigObject obj) {
return obj
..anInt = 1
..aString = 'String!'
..aList.add(3)
..allDone();
}
ゲッターとセッター
#単純なフィールドよりもプロパティをより細かく制御する必要がある場合は、いつでもゲッターとセッターを定義できます。
たとえば、プロパティの値が有効であることを確認できます
class MyClass {
int _aProperty = 0;
int get aProperty => _aProperty;
set aProperty(int value) {
if (value >= 0) {
_aProperty = value;
}
}
}
ゲッターを使用して、計算されたプロパティを定義することもできます
class MyClass {
final List<int> _values = [];
void addValue(int value) {
_values.add(value);
}
// A computed property.
int get count {
return _values.length;
}
}
コード例
#価格の private な List<double>
を保持するショッピングカートクラスがあるとします。以下を追加します
- 価格の合計を返す
total
というゲッター - 新しいリストに負の価格が含まれていない限り (その場合はセッターが
InvalidPriceException
をスローする必要があります)、リストを新しいリストに置き換えるセッター。
DartPad のすべての初期エラーを無視します。
class InvalidPriceException {}
class ShoppingCart {
List<double> _prices = [];
// TODO: Add a "total" getter here:
// TODO: Add a "prices" setter here:
}
// Tests your solution (Don't edit!):
void main() {
var foundException = false;
try {
final cart = ShoppingCart();
cart.prices = [12.0, 12.0, -23.0];
} on InvalidPriceException {
foundException = true;
} catch (e) {
print('Tried setting a negative price and received a ${e.runtimeType} \n instead of an InvalidPriceException.');
return;
}
if (!foundException) {
print('Tried setting a negative price \n and didn\'t get an InvalidPriceException.');
return;
}
final secondCart = ShoppingCart();
try {
secondCart.prices = [1.0, 2.0, 3.0];
} catch(e) {
print('Tried setting prices with a valid list, \n but received an exception: ${e.runtimeType}.');
return;
}
if (secondCart._prices.length != 3) {
print('Tried setting prices with a list of three values, \n but _prices ended up having length ${secondCart._prices.length}.');
return;
}
if (secondCart._prices[0] != 1.0 || secondCart._prices[1] != 2.0 || secondCart._prices[2] != 3.0) {
final vals = secondCart._prices.map((p) => p.toString()).join(', ');
print('Tried setting prices with a list of three values (1, 2, 3), \n but incorrect ones ended up in the price list ($vals) .');
return;
}
var sum = 0.0;
try {
sum = secondCart.total;
} catch (e) {
print('Tried to get total, but received an exception: ${e.runtimeType}.');
return;
}
if (sum != 6.0) {
print('After setting prices to (1, 2, 3), total returned $sum instead of 6.');
return;
}
print('Success!');
}
ゲッターとセッターの例の解答
この演習には、2 つの関数が便利です。1 つは、リストを単一の値に縮小できる fold
です (合計の計算に使用します)。もう 1 つは any
で、指定した関数を使用してリスト内の各項目をチェックできます (価格セッターに負の価格があるかどうかをチェックするために使用します)。
// Add a "total" getter here:
double get total => _prices.fold(0, (e, t) => e + t);
// Add a "prices" setter here:
set prices(List<double> value) {
if (value.any((p) => p < 0)) {
throw InvalidPriceException();
}
_prices = value;
}
省略可能な位置パラメーター
#Dart には、位置パラメータと名前付きパラメータの 2 種類の関数パラメータがあります。位置パラメータは、おそらく使い慣れている種類です
int sumUp(int a, int b, int c) {
return a + b + c;
}
// ···
int total = sumUp(1, 2, 3);
Dart では、これらの位置パラメータを角括弧で囲むことでオプションにすることができます
int sumUpToFive(int a, [int? b, int? c, int? d, int? e]) {
int sum = a;
if (b != null) sum += b;
if (c != null) sum += c;
if (d != null) sum += d;
if (e != null) sum += e;
return sum;
}
// ···
int total = sumUpToFive(1, 2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);
オプションの位置パラメータは、常に関数の引数リストの最後になります。別のデフォルト値を指定しない限り、デフォルト値は null です
int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
// ···
}
void main() {
int newTotal = sumUpToFive(1);
print(newTotal); // <-- prints 15
}
コード例
#1 から 5 つの整数を引数に取り、コンマで区切ったそれらの数値の文字列を返す joinWithCommas()
という関数を定義します。関数呼び出しと戻り値の例を次に示します
関数呼び出し | 戻り値 |
---|---|
joinWithCommas(1) | '1' |
joinWithCommas(1, 2, 3) | '1,2,3' |
joinWithCommas(1, 1, 1, 1, 1) | '1,1,1,1,1' |
String joinWithCommas(int a, [int? b, int? c, int? d, int? e]) {
return TODO();
}
// Tests your solution (Don't edit!):
void main() {
final errs = <String>[];
try {
final value = joinWithCommas(1);
if (value != '1') {
errs.add('Tried calling joinWithCommas(1) \n and got $value instead of the expected (\'1\').');
}
} on UnimplementedError {
print('Tried to call joinWithCommas but failed. \n Did you implement the method?');
return;
} catch (e) {
print('Tried calling joinWithCommas(1), \n but encountered an exception: ${e.runtimeType}.');
return;
}
try {
final value = joinWithCommas(1, 2, 3);
if (value != '1,2,3') {
errs.add('Tried calling joinWithCommas(1, 2, 3) \n and got $value instead of the expected (\'1,2,3\').');
}
} on UnimplementedError {
print('Tried to call joinWithCommas but failed. \n Did you implement the method?');
return;
} catch (e) {
print('Tried calling joinWithCommas(1, 2 ,3), \n but encountered an exception: ${e.runtimeType}.');
return;
}
try {
final value = joinWithCommas(1, 2, 3, 4, 5);
if (value != '1,2,3,4,5') {
errs.add('Tried calling joinWithCommas(1, 2, 3, 4, 5) \n and got $value instead of the expected (\'1,2,3,4,5\').');
}
} on UnimplementedError {
print('Tried to call joinWithCommas but failed. \n Did you implement the method?');
return;
} catch (e) {
print('Tried calling stringify(1, 2, 3, 4 ,5), \n but encountered an exception: ${e.runtimeType}.');
return;
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
位置パラメータの例の解答
b
、c
、d
、および e
パラメータは、呼び出し元によって指定されていない場合は null になります。したがって、重要なことは、これらの引数を最終的な文字列に追加する前に、それらが null
かどうかを確認することです。
String joinWithCommas(int a, [int? b, int? c, int? d, int? e]) {
var total = '$a';
if (b != null) total = '$total,$b';
if (c != null) total = '$total,$c';
if (d != null) total = '$total,$d';
if (e != null) total = '$total,$e';
return total;
}
名前付きパラメーター
#パラメータリストの最後に中括弧構文を使用すると、名前を持つパラメータを定義できます。
名前付きパラメータは、明示的に required
としてマークされていない限り、オプションです。
void printName(String firstName, String lastName, {String? middleName}) {
print('$firstName ${middleName ?? ''} $lastName');
}
void main() {
printName('Dash', 'Dartisan');
printName('John', 'Smith', middleName: 'Who');
// Named arguments can be placed anywhere in the argument list
printName('John', middleName: 'Who', 'Smith');
}
予想どおり、null 許容の名前付きパラメータのデフォルト値は null
ですが、カスタムのデフォルト値を指定できます。
パラメータの型が null 非許容の場合、デフォルト値を指定するか (以下のコードに示すように)、パラメータを required
としてマークする必要があります (「コンストラクタのセクション」に示すように)。
void printName(String firstName, String lastName, {String middleName = ''}) {
print('$firstName $middleName $lastName');
}
関数は、オプションの位置パラメータと名前付きパラメータの両方を持つことはできません。
コード例
#MyDataObject
クラスに copyWith()
インスタンスメソッドを追加します。3 つの名前付きの null 許容パラメータを取る必要があります
int? newInt
String? newString
double? newDouble
copyWith()
メソッドは、現在のインスタンスに基づいて新しい MyDataObject
を返し、上記の パラメータ (存在する場合) のデータがオブジェクトのプロパティにコピーされるようにする必要があります。たとえば、newInt
が null 以外の場合、その値を anInt
にコピーします。
DartPad のすべての初期エラーを無視します。
class MyDataObject {
final int anInt;
final String aString;
final double aDouble;
MyDataObject({
this.anInt = 1,
this.aString = 'Old!',
this.aDouble = 2.0,
});
// TODO: Add your copyWith method here:
}
// Tests your solution (Don't edit!):
void main() {
final source = MyDataObject();
final errs = <String>[];
try {
final copy = source.copyWith(newInt: 12, newString: 'New!', newDouble: 3.0);
if (copy.anInt != 12) {
errs.add('Called copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0), \n and the new object\'s anInt was ${copy.anInt} rather than the expected value (12).');
}
if (copy.aString != 'New!') {
errs.add('Called copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0), \n and the new object\'s aString was ${copy.aString} rather than the expected value (\'New!\').');
}
if (copy.aDouble != 3) {
errs.add('Called copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0), \n and the new object\'s aDouble was ${copy.aDouble} rather than the expected value (3).');
}
} catch (e) {
print('Called copyWith(newInt: 12, newString: \'New!\', newDouble: 3.0) \n and got an exception: ${e.runtimeType}');
}
try {
final copy = source.copyWith();
if (copy.anInt != 1) {
errs.add('Called copyWith(), and the new object\'s anInt was ${copy.anInt} \n rather than the expected value (1).');
}
if (copy.aString != 'Old!') {
errs.add('Called copyWith(), and the new object\'s aString was ${copy.aString} \n rather than the expected value (\'Old!\').');
}
if (copy.aDouble != 2) {
errs.add('Called copyWith(), and the new object\'s aDouble was ${copy.aDouble} \n rather than the expected value (2).');
}
} catch (e) {
print('Called copyWith() and got an exception: ${e.runtimeType}');
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
名前付きパラメータの例の解答
copyWith
メソッドは、多くのクラスやライブラリに表示されます。このメソッドでは、オプションの名前付きパラメータを使用し、MyDataObject
の新しいインスタンスを作成し、パラメータのデータを使用してそれを埋め込む (パラメータが null の場合は現在のインスタンスのデータを使用する) 必要があります。これは、??
演算子をさらに練習する機会です。
MyDataObject copyWith({int? newInt, String? newString, double? newDouble}) {
return MyDataObject(
anInt: newInt ?? this.anInt,
aString: newString ?? this.aString,
aDouble: newDouble ?? this.aDouble,
);
}
例外
#Dart コードは、例外をスローしてキャッチできます。Java とは対照的に、Dart の例外はすべてチェックされません。メソッドはスローされる可能性のある例外を宣言せず、例外をキャッチする必要はありません。
Dart は Exception
型と Error
型を提供しますが、null 以外のオブジェクトをスローすることができます
throw Exception('Something bad happened.');
throw 'Waaaaaaah!';
例外を処理する場合は、try
、on
、および catch
キーワードを使用します
try {
breedMoreLlamas();
} on OutOfLlamasException {
// A specific exception
buyMoreLlamas();
} on Exception catch (e) {
// Anything else that is an exception
print('Unknown exception: $e');
} catch (e) {
// No specified type, handles all
print('Something really unknown: $e');
}
try
キーワードは、他のほとんどの言語と同じように機能します。on
キーワードを使用して、型別に特定の例外をフィルタリングし、catch
キーワードを使用して例外オブジェクトへの参照を取得します。
例外を完全に処理できない場合は、rethrow
キーワードを使用して例外を伝播します
try {
breedMoreLlamas();
} catch (e) {
print('I was just trying to breed llamas!');
rethrow;
}
例外がスローされたかどうかに関係なくコードを実行するには、finally
を使用します
try {
breedMoreLlamas();
} catch (e) {
// ... handle exception ...
} finally {
// Always clean up, even if an exception is thrown.
cleanLlamaStalls();
}
コード例
#以下の tryFunction()
を実装します。信頼できないメソッドを実行してから、次のことを行う必要があります
untrustworthy()
がExceptionWithMessage
をスローした場合、例外の型とメッセージを指定してlogger.logException
を呼び出します (on
とcatch
を使用してみてください)。untrustworthy()
がException
をスローした場合、例外の型を指定してlogger.logException
を呼び出します (これにはon
を使用してみてください)。untrustworthy()
が他のオブジェクトをスローした場合、例外をキャッチしないでください。- すべてがキャッチされて処理されたら、
logger.doneLogging
を呼び出します (finally
を使用してみてください)。
typedef VoidFunction = void Function();
class ExceptionWithMessage {
final String message;
const ExceptionWithMessage(this.message);
}
// Call logException to log an exception, and doneLogging when finished.
abstract class Logger {
void logException(Type t, [String? msg]);
void doneLogging();
}
void tryFunction(VoidFunction untrustworthy, Logger logger) {
try {
untrustworthy();
} on ExceptionWithMessage catch (e) {
logger.logException(e.runtimeType, e.message);
} on Exception catch (e) {
logger.logException(e.runtimeType);
} finally {
logger.doneLogging();
}
}
// Tests your solution (Don't edit!):
class MyLogger extends Logger {
Type? lastType;
String lastMessage = '';
bool done = false;
void logException(Type t, [String? message]) {
lastType = t;
lastMessage = message ?? lastMessage;
}
void doneLogging() => done = true;
}
void main() {
final errs = <String>[];
var logger = MyLogger();
try {
tryFunction(() => throw Exception(), logger);
if ('${logger.lastType}' != 'Exception' && '${logger.lastType}' != '_Exception') {
errs.add('Untrustworthy threw an Exception, but a different type was logged: \n ${logger.lastType}.');
}
if (logger.lastMessage != '') {
errs.add('Untrustworthy threw an Exception with no message, but a message \n was logged anyway: \'${logger.lastMessage}\'.');
}
if (!logger.done) {
errs.add('Untrustworthy threw an Exception, \n and doneLogging() wasn\'t called afterward.');
}
} catch (e) {
print('Untrustworthy threw an exception, and an exception of type \n ${e.runtimeType} was unhandled by tryFunction.');
}
logger = MyLogger();
try {
tryFunction(() => throw ExceptionWithMessage('Hey!'), logger);
if (logger.lastType != ExceptionWithMessage) {
errs.add('Untrustworthy threw an ExceptionWithMessage(\'Hey!\'), but a \n different type was logged: ${logger.lastType}.');
}
if (logger.lastMessage != 'Hey!') {
errs.add('Untrustworthy threw an ExceptionWithMessage(\'Hey!\'), but a \n different message was logged: \'${logger.lastMessage}\'.');
}
if (!logger.done) {
errs.add('Untrustworthy threw an ExceptionWithMessage(\'Hey!\'), \n and doneLogging() wasn\'t called afterward.');
}
} catch (e) {
print('Untrustworthy threw an ExceptionWithMessage(\'Hey!\'), \n and an exception of type ${e.runtimeType} was unhandled by tryFunction.');
}
logger = MyLogger();
bool caughtStringException = false;
try {
tryFunction(() => throw 'A String', logger);
} on String {
caughtStringException = true;
}
if (!caughtStringException) {
errs.add('Untrustworthy threw a string, and it was incorrectly handled inside tryFunction().');
}
logger = MyLogger();
try {
tryFunction(() {}, logger);
if (logger.lastType != null) {
errs.add('Untrustworthy didn\'t throw an Exception, \n but one was logged anyway: ${logger.lastType}.');
}
if (logger.lastMessage != '') {
errs.add('Untrustworthy didn\'t throw an Exception with no message, \n but a message was logged anyway: \'${logger.lastMessage}\'.');
}
if (!logger.done) {
errs.add('Untrustworthy didn\'t throw an Exception, \n but doneLogging() wasn\'t called afterward.');
}
} catch (e) {
print('Untrustworthy didn\'t throw an exception, \n but an exception of type ${e.runtimeType} was unhandled by tryFunction anyway.');
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
例外の例の解答
この演習は複雑に見えますが、実際には 1 つの大きな try
ステートメントです。try
内で untrustworthy
を呼び出し、on
、catch
、および finally
を使用して例外をキャッチし、ロガーでメソッドを呼び出します。
void tryFunction(VoidFunction untrustworthy, Logger logger) {
try {
untrustworthy();
} on ExceptionWithMessage catch (e) {
logger.logException(e.runtimeType, e.message);
} on Exception {
logger.logException(Exception);
} finally {
logger.doneLogging();
}
}
コンストラクタで this
を使用する
#Dart は、コンストラクタでプロパティに値を代入するための便利なショートカットを提供します。コンストラクタを宣言するときに this.propertyName
を使用します
class MyColor {
int red;
int green;
int blue;
MyColor(this.red, this.green, this.blue);
}
final color = MyColor(80, 80, 128);
この手法は、名前付きパラメータにも有効です。プロパティ名がパラメータの名前になります
class MyColor {
...
MyColor({required this.red, required this.green, required this.blue});
}
final color = MyColor(red: 80, green: 80, blue: 80);
上記のコードでは、red
、green
、および blue
は、これらの int
値が null にできないため、required
としてマークされています。デフォルト値を追加すると、required
を省略できます
MyColor([this.red = 0, this.green = 0, this.blue = 0]);
// or
MyColor({this.red = 0, this.green = 0, this.blue = 0});
コード例
#MyClass
に、this.
構文を使用してクラスの 3 つのプロパティすべてに値を受け取って代入する 1 行のコンストラクタを追加します。
DartPad のすべての初期エラーを無視します。
class MyClass {
final int anInt;
final String aString;
final double aDouble;
// TODO: Create the constructor here.
}
// Tests your solution (Don't edit!):
void main() {
final errs = <String>[];
try {
final obj = MyClass(1, 'two', 3);
if (obj.anInt != 1) {
errs.add('Called MyClass(1, \'two\', 3) and got an object with anInt of ${obj.anInt} \n instead of the expected value (1).');
}
if (obj.anInt != 1) {
errs.add('Called MyClass(1, \'two\', 3) and got an object with aString of \'${obj.aString}\' \n instead of the expected value (\'two\').');
}
if (obj.anInt != 1) {
errs.add('Called MyClass(1, \'two\', 3) and got an object with aDouble of ${obj.aDouble} \n instead of the expected value (3).');
}
} catch (e) {
print('Called MyClass(1, \'two\', 3) and got an exception \n of type ${e.runtimeType}.');
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
`this` の例の解答
この演習には、1 行の解答があります。this.anInt
、this.aString
、および this.aDouble
をその順序でパラメータとして使用して、コンストラクタを宣言します。
MyClass(this.anInt, this.aString, this.aDouble);
初期化子リスト
#コンストラクタを実装するときに、コンストラクタ本体が実行される前にいくつかのセットアップを行う必要がある場合があります。たとえば、final フィールドは、コンストラクタ本体が実行される前に値を持つ必要があります。この作業は、コンストラクタのシグネチャとその本体の間にある初期化子リストで行います
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
初期化子リストは、開発中にのみ実行されるアサーションを配置するのにも便利な場所です
NonNegativePoint(this.x, this.y)
: assert(x >= 0),
assert(y >= 0) {
print('I just made a NonNegativePoint: ($x, $y)');
}
コード例
#以下の FirstTwoLetters
コンストラクタを完成させます。初期化子リストを使用して、word
の最初の 2 文字を letterOne
プロパティと LetterTwo
プロパティに代入します。追加のクレジットとして、2 文字未満の単語をキャッチする assert
を追加します。
DartPad のすべての初期エラーを無視します。
class FirstTwoLetters {
final String letterOne;
final String letterTwo;
// TODO: Create a constructor with an initializer list here:
FirstTwoLetters(String word)
}
// Tests your solution (Don't edit!):
void main() {
final errs = <String>[];
try {
final result = FirstTwoLetters('My String');
if (result.letterOne != 'M') {
errs.add('Called FirstTwoLetters(\'My String\') and got an object with \n letterOne equal to \'${result.letterOne}\' instead of the expected value (\'M\').');
}
if (result.letterTwo != 'y') {
errs.add('Called FirstTwoLetters(\'My String\') and got an object with \n letterTwo equal to \'${result.letterTwo}\' instead of the expected value (\'y\').');
}
} catch (e) {
errs.add('Called FirstTwoLetters(\'My String\') and got an exception \n of type ${e.runtimeType}.');
}
bool caughtException = false;
try {
FirstTwoLetters('');
} catch (e) {
caughtException = true;
}
if (!caughtException) {
errs.add('Called FirstTwoLetters(\'\') and didn\'t get an exception \n from the failed assertion.');
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
初期化子リストの例の解答
2 つの代入を行う必要があります。letterOne
には word[0]
が代入され、letterTwo
には word[1]
が代入される必要があります。
FirstTwoLetters(String word)
: assert(word.length >= 2),
letterOne = word[0],
letterTwo = word[1];
名前付きコンストラクタ
#クラスに複数のコンストラクタを許可するために、Dart は名前付きコンストラクタをサポートしています
class Point {
double x, y;
Point(this.x, this.y);
Point.origin()
: x = 0,
y = 0;
}
名前付きコンストラクタを使用するには、完全な名前を使用して呼び出します
final myPoint = Point.origin();
コード例
#Color
クラスに、3 つのプロパティすべてをゼロに設定する Color.black
という名前のコンストラクタを指定します。
DartPad のすべての初期エラーを無視します。
class Color {
int red;
int green;
int blue;
Color(this.red, this.green, this.blue);
// TODO: Create a named constructor called "Color.black" here:
}
// Tests your solution (Don't edit!):
void main() {
final errs = <String>[];
try {
final result = Color.black();
if (result.red != 0) {
errs.add('Called Color.black() and got a Color with red equal to \n ${result.red} instead of the expected value (0).');
}
if (result.green != 0) {
errs.add('Called Color.black() and got a Color with green equal to \n ${result.green} instead of the expected value (0).');
}
if (result.blue != 0) {
errs.add('Called Color.black() and got a Color with blue equal to \n ${result.blue} instead of the expected value (0).');
}
} catch (e) {
print('Called Color.black() and got an exception of type \n ${e.runtimeType}.');
return;
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
名前付きコンストラクタの例の解答
コンストラクタの宣言は Color.black():
で始まる必要があります。初期化子リスト (コロンの後) で、red
、green
、および blue
を 0
に設定します。
Color.black()
: red = 0,
green = 0,
blue = 0;
ファクトリコンストラクタ
#Dart は、サブタイプまたは null を返すことができるファクトリコンストラクタをサポートしています。ファクトリコンストラクタを作成するには、factory
キーワードを使用します
class Square extends Shape {}
class Circle extends Shape {}
class Shape {
Shape();
factory Shape.fromTypeName(String typeName) {
if (typeName == 'square') return Square();
if (typeName == 'circle') return Circle();
throw ArgumentError('Unrecognized $typeName');
}
}
コード例
#IntegerHolder.fromList
という名前のファクトリコンストラクタで、TODO();
行を次の内容を返すように置き換えます
- リストに値が **1 つ** ある場合は、その値を使用して
IntegerSingle
インスタンスを作成します。 - リストに値が **2 つ** ある場合は、値を順番に使用して
IntegerDouble
インスタンスを作成します。 - リストに値が **3 つ** ある場合は、値を順番に使用して
IntegerTriple
インスタンスを作成します。 - それ以外の場合は、
Error
をスローします。
成功すると、コンソールに Success!
と表示されます。
class IntegerHolder {
IntegerHolder();
// Implement this factory constructor.
factory IntegerHolder.fromList(List<int> list) {
TODO();
}
}
class IntegerSingle extends IntegerHolder {
final int a;
IntegerSingle(this.a);
}
class IntegerDouble extends IntegerHolder {
final int a;
final int b;
IntegerDouble(this.a, this.b);
}
class IntegerTriple extends IntegerHolder {
final int a;
final int b;
final int c;
IntegerTriple(this.a, this.b, this.c);
}
// Tests your solution (Don't edit from this point to end of file):
void main() {
final errs = <String>[];
// Run 5 tests to see which values have valid integer holders
for (var tests = 0; tests < 5; tests++) {
if (!testNumberOfArgs(errs, tests)) return;
}
// The goal is no errors with values 1 to 3,
// but have errors with values 0 and 4.
// The testNumberOfArgs method adds to the errs array if
// the values 1 to 3 have an error and
// the values 0 and 4 don't have an error
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
bool testNumberOfArgs(List<String> errs, int count) {
bool _threw = false;
final ex = List.generate(count, (index) => index + 1);
final callTxt = "IntegerHolder.fromList(${ex})";
try {
final obj = IntegerHolder.fromList(ex);
final String vals = count == 1 ? "value" : "values";
// Uncomment the next line if you want to see the results realtime
// print("Testing with ${count} ${vals} using ${obj.runtimeType}.");
testValues(errs, ex, obj, callTxt);
} on Error {
_threw = true;
} catch (e) {
switch (count) {
case (< 1 && > 3):
if (!_threw) {
errs.add('Called ${callTxt} and it didn\'t throw an Error.');
}
default:
errs.add('Called $callTxt and received an Error.');
}
}
return true;
}
void testValues(List<String> errs, List<int> expectedValues, IntegerHolder obj,
String callText) {
for (var i = 0; i < expectedValues.length; i++) {
int found;
if (obj is IntegerSingle) {
found = obj.a;
} else if (obj is IntegerDouble) {
found = i == 0 ? obj.a : obj.b;
} else if (obj is IntegerTriple) {
found = i == 0
? obj.a
: i == 1
? obj.b
: obj.c;
} else {
throw ArgumentError(
"This IntegerHolder type (${obj.runtimeType}) is unsupported.");
}
if (found != expectedValues[i]) {
errs.add(
"Called $callText and got a ${obj.runtimeType} " +
"with a property at index $i value of $found " +
"instead of the expected (${expectedValues[i]}).");
}
}
}
ファクトリコンストラクタの例の解答
ファクトリコンストラクタ内で、リストの長さをチェックし、必要に応じて IntegerSingle
、IntegerDouble
、または IntegerTriple
を作成して返します。
TODO();
を次のコードブロックに置き換えます。
switch (list.length) {
case 1:
return IntegerSingle(list[0]);
case 2:
return IntegerDouble(list[0], list[1]);
case 3:
return IntegerTriple(list[0], list[1], list[2]);
default:
throw ArgumentError("List must between 1 and 3 items. This list was ${list.length} items.");
}
リダイレクトコンストラクタ
#コンストラクタの唯一の目的が同じクラスの別のコンストラクタにリダイレクトすることである場合があります。リダイレクトコンストラクタの本体は空で、コンストラクタ呼び出しはコロン (:
) の後に表示されます。
class Automobile {
String make;
String model;
int mpg;
// The main constructor for this class.
Automobile(this.make, this.model, this.mpg);
// Delegates to the main constructor.
Automobile.hybrid(String make, String model) : this(make, model, 60);
// Delegates to a named constructor
Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');
}
コード例
#上記の Color
クラスを覚えていますか? black
という名前付きコンストラクタを作成しますが、プロパティを手動で代入するのではなく、デフォルトのコンストラクタに引数としてゼロを指定してリダイレクトします。
DartPad のすべての初期エラーを無視します。
class Color {
int red;
int green;
int blue;
Color(this.red, this.green, this.blue);
// TODO: Create a named constructor called "black" here
// and redirect it to call the existing constructor
}
// Tests your solution (Don't edit!):
void main() {
final errs = <String>[];
try {
final result = Color.black();
if (result.red != 0) {
errs.add('Called Color.black() and got a Color with red equal to \n ${result.red} instead of the expected value (0).');
}
if (result.green != 0) {
errs.add('Called Color.black() and got a Color with green equal to \n ${result.green} instead of the expected value (0).');
}
if (result.blue != 0) {
errs.add('Called Color.black() and got a Color with blue equal to \n ${result.blue} instead of the expected value (0).');
}
} catch (e) {
print('Called Color.black() and got an exception of type ${e.runtimeType}.');
return;
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
リダイレクトコンストラクタの例の解答
コンストラクタは this(0, 0, 0)
にリダイレクトする必要があります。
Color.black() : this(0, 0, 0);
定数コンストラクタ
#クラスが変更されることのないオブジェクトを生成する場合、これらのオブジェクトをコンパイル時定数にすることができます。これを行うには、const
コンストラクタを定義し、すべてのインスタンス変数が final であることを確認します。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final int x;
final int y;
const ImmutablePoint(this.x, this.y);
}
コード例
#Recipe
クラスを変更して、そのインスタンスが定数になるようにし、次のことを行う定数コンストラクタを作成します
- 3 つのパラメータがあります。
ingredients
、calories
、およびmilligramsOfSodium
(この順序) です。 this.
構文を使用して、パラメータ値を同じ名前のオブジェクトプロパティに自動的に代入します。- コンストラクタ宣言の
Recipe
の直前にconst
キーワードを付けて、定数にします。
DartPad のすべての初期エラーを無視します。
class Recipe {
List<String> ingredients;
int calories;
double milligramsOfSodium;
// TODO: Create a const constructor here"
}
// Tests your solution (Don't edit!):
void main() {
final errs = <String>[];
try {
const obj = Recipe(['1 egg', 'Pat of butter', 'Pinch salt'], 120, 200);
if (obj.ingredients.length != 3) {
errs.add('Called Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) \n and got an object with ingredient list of length ${obj.ingredients.length} rather than the expected length (3).');
}
if (obj.calories != 120) {
errs.add('Called Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) \n and got an object with a calorie value of ${obj.calories} rather than the expected value (120).');
}
if (obj.milligramsOfSodium != 200) {
errs.add('Called Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) \n and got an object with a milligramsOfSodium value of ${obj.milligramsOfSodium} rather than the expected value (200).');
}
} catch (e) {
print('Tried calling Recipe([\'1 egg\', \'Pat of butter\', \'Pinch salt\'], 120, 200) \n and received a null.');
}
if (errs.isEmpty) {
print('Success!');
} else {
errs.forEach(print);
}
}
const コンストラクタの例の解答
コンストラクタを const にするには、すべてのプロパティを final にする必要があります。
class Recipe {
final List<String> ingredients;
final int calories;
final double milligramsOfSodium;
const Recipe(this.ingredients, this.calories, this.milligramsOfSodium);
}
次にすること
#このチュートリアルを使用して、Dart 言語の最も興味深い機能のいくつかを学習またはテストしていただければ幸いです。
次に試せることには、次のものがあります
- 他の Dart チュートリアルを試す。
- Dart 言語ツアーを読む。
- DartPadで遊ぶ。
- Dart SDK を入手する.
特に明記されていない限り、このサイトのドキュメントは Dart 3.5.3 を反映しています。ページの最終更新日: 2024-08-04。 ソースを表示 または 問題を報告する。