Notification
이 웹페이지는 https://dart.dev 를 한글로 번역한 사이트 입니다. 번역이 처음이고 Dart를 처음 공부하며 번역을 하는 것이어서 오역이 있을 수 있습니다. 이 페이지 자체가 문제가 된다면 바로 조치하겠습니다.
현재 Language ➡️ Tour 까지 완료한 상태이고, 진행 중에 있습니다.
This web page is a site that translates https://dart.dev into Korean. It is my first time to translate and I am also studying Dart for the first time, so there may be a misunderstanding. If the site itself is a problem, I will take care of it right away.
The translation work has been completed by Language ➡️ Tour and it is still in progress.
Contact: aspalt85@gmail.com
Dart로의 여정
이 페이지에서는 다른 프로그래밍 언어를 사용 할 줄 안다는 가정 하에 변수, 연산자, 클래스 및 라이브러리에 이르는 각 주요 Dart 기능을 사용하는 방법을 보여줍니다. 언어에 대한 간략한 설명을 보고 싶다면 샘플 페이지를 참고하세요!
Dart의 core 라이브러리를 더 배우고 싶다면 library tour를 참고하세요. 언어의 자세한 정보를 얻고 싶다면 Dart language specification을 참고하세요.
Dart 프로그램의 기본
생략
중요한 컨셉
Dart를 학습 할 때 다음을 잘 기억해야합니다.
변수로 할당 할 수 있는 모든 것은 object(객체)이고 모든 object는 class의 instance 입니다. numbers, functions 그리고 null까지 모두 objects 입니다. null을 제외하고(만약 sound null safety가 가능하다면) 모든 objects들은 Object class를 상속받습니다.
Dart는 타입에 엄격하지만(strongly typed), Dart는 타입을 추정할 수 있기 때문에 타입을 명시하는 것(type annotations)은 자율에 맡깁니다.
만약 당신이 null safety가 가능하다면, 당신이 어떤 변수가
null
값을 갖는 것을 허락하지 않았을 때 그 변수는 null 값을 갖을 수 없게 할 수 있습니다. 당신은 ? 표시를 변수의 타입 끝에 붙혀서null
값이 할당 될 수 있게(nullable) 만들 수 있습니다. (String? cat = null
이런 식으로,String cat = null
이건 안 된다는 말이다.) 만약 어떤 변수가null
값을 갖지 못하게 하고 싶다면 ! 를 표시하면 된다. (nullable를 non-nullable로 바꿔준다는 말이다. )만약에 어떤 타입이든 적용이 가능하다고 명시하고 싶다면
Object?
나Object
를 타입으로 정해주면 된다. 만약에 런타임 까지 타입 체킹을 미뤄야 한다면 special type dynamic 을 사용하세요.Dart는 List
<int>
나List<Object>
같은 generic 타입을 지원합니다.Dart는 className=또는 Object에 있는(tied) 함수뿐 아니라,
main()
같은 top-level 함수를 지원합니다. 함수 안에 함수를 선언하는 것도 가능합니다.비슷하게 Dart는 className=또는 Object에 있는(tied) 변수뿐 아니라, top-level 변수를 지원합니다. 인스턴스 변수는 fields나 properties로 알려져 있습니다.
Java와 다르게 Dart는
public,
protected,
private
같은 keywords를 사용하지 않습니다. 만약 식별자(identifier 변수의 이름 정도로 생각)가 underscore(_)로 시작한다면 그것은 해당 library에 귀속(private)되는 것 입니다. 더 자세한 정보는 Libraries and visibility에서 확인하세요.Dart에는 expressions(런타입 values를 갖는 것)와 statements(갖지 않는 것)가 있습니다. expression의 예를 들면,conditional expression
condition ? expr1 : expr2
는expr1, expr2
이라는 value를 갖습니다. 그것을 value를 갖지 않는 if-else statement와 비교해 보세요. statement는 한개 혹은 다수의 expressions을 갖을 수 있지만 expression은 statement를 갖을 수 없습니다.Dart tools는 발생되는 문제를 두가지 방법으로 알려줍니다: warnings 그리고 errors. Warnings는 당신의 코드가 아마도 실행되지 않을 것이라고 알려주지만, 프로그램 자체가 실행되지 못하게 막지 않습니다. Errors는 컴파일 타임, 런타임으로 나뉩니다. 컴파일 타임 에러는 코드가 실행되는 것 자체를 막습니다. 런타임 에러는 코드가 실행되는 동안 exception을 발생시킵니다.
Keywords
다음의 표는 Dart에서 특별히 관리하는 words 입니다.
위의 words들을 식별자로 사용하지마세요. 만약 식별자로 사용하고 싶다면 어깨글자(위 표의 1,2,3)으로 표시된 keywords를 식별자로 사용하는 것이 가능합니다.
1
로 표시된 단어들은contextual keywords
로 특정한 장소에서만 의미를 갖습니다. 어디서든 식별자로 사용이 가능합니다.2
로 표시된 단어들은built-in identifiers
입니다. 이 keywords들은 거의 모든 곳에서 식별자로 사용이 가능하지만, 클래스나 타입의 이름, import prefixes로 사용은 불가능 합니다.3
으로 표시된 단어들은 asynchrony support와 관련된 제한된 단어들 입니다. await이나 yield를async, async*, sync*
로 표시된 함수의 바디에서 식별자로 사용 할 수 없습니다.
표의 나머지 단어들은 모두 reserved words
(예약된 단어 => 예약되었으므로 사용할 수 없다는 말)이므로 식별자로 사용이 불가능 합니다.
변수
다음은 변수를 선언하고 초기화하는 예입니다.
var name = 'Bob';
변수는 참조(references)를 저장합니다. 위의 name
이라는 변수는 "Bob"이라는 값을 갖는 String
object의 참조를 가집니다.
name
의 타입은 String
으로 추정되지만, 타입을 특정하므로써 변환이 가능합니다. object가 single 타입으로 강제되어 있지 않다면 Object
타입으로 명시하세요.(필요하다면 dynamic
을 사용하는 것도 가능합니다.)
Object name = 'Bob';
다른 방법으로는 추정 될 타입으로 선언하는 것 입니다.
String name = 'Bob';
초기 값(Default value)
nullable type을 갖고 초기화 되지 않은 변수는 초기 값으로 null
값을 가집니다. (만약 당신이 null safety 를 사용 할 줄 모른다면, 모든 변수는 nullable type을 가집니다.) 숫자를 포함한 Dart의 모든것은 Objects이기 때문에 숫자 타입(numeric types)도 초기값으로 null 값을 가집니다.
int? lineCount;assert(lineCount == null);
null safety 처리를 할 줄 안다면, non-nullable 변수를 사용하기 전에 반드시 초기화 해야 합니다.
int lineCount = 0;
지역 변수를 반드시 선언된 곳에서 초기화할 필요는 없지만, 사용하기 전에는 해야합니다. 예를 들면, print()
로 lineCount
가 넘겨질 때 까지 Dart에서 lineCount
가 non-null 이라는 것을 알 수 있기 때문에 다음 코드는 유효합니다.
int lineCount;if (weLikeToCount) {lineCount = countLines();} else {lineCount = 0;}print(lineCount);
Top-level, 클래스 변수는 느리게 초기화되며, 초기화 코드는 변수를 처음 사용할 때 실행됩니다.
Late 변수
Dart 2.12 에서 late
수식어가 추가 되었고 2가지 사용 방법이 있습니다.
- 선언 후에 초기화된 non-nullable 변수를 선언하는 것
- 느리게(Lazily) 변수를 초기화하는 것
Dart의 제어 흐름 분석(control flow anlysis)이 언제 non-nullable 변수가 사용되기 전에 non-null 값으로 할당 되는지 감지할 수 있지만, 종종 실패할 때도 있습니다. top-level 변수나 instance 변수는 종종 Dart가 그것들이 언제 할당 되는지 결정하지 못하기 때문에, 결정하는 것을 시도하지 않습니다.
만약 당신이 어떤 변수가 사용되기 전에 할당 되는 것이 확실하다면, 하지만 Dart는 그것을 모를 때, 변수를 late
로 선언하므로서 에러를 방지할 수 있습니다.
late String description;void main() {description = 'Feijoada!';print(description);}
변수를 late
로 선언하면서 동시에 초기화하는 경우, initializer가 변수를 처음 사용할 때 실행됩니다. 이러한 느린 초기화(lazy initialization)은 때때로 유용합니다:
사용되지 않을 변수를 초기화하는 것은 낭비가 됩니다.
instance 변수를 초기화하는 중에, 그 변수의 initializer는
this
에 접근을 해야합니다.
다음의 예에서,late
로 선언한temperature
변수가 사용되지 않는 다면, 리소스가 많이 소모되는 _readThermometer()
함수는 호출 되지 않습니다.
// This is the program's only call to _readThermometer().late String temperature = _readThermometer(); // Lazily initialized.
Final And Const
만약 어떤 변수의 값을 바꿀 생각이 없다면, var
이나 다른 타입으로 변수를 선언하는 대신, final
이나 const
를 사용하세요. final 변수는 오직 한 번만 선언 될 수 있고, const 변수는 컴파일 타임 상수(constant) 입니다.(Const 변수는 암묵적으로 final와 같습니다.)
다음은 final
변수를 선언하고 할당하는 예 입니다.
final name = 'Bob'; // Without a type annotationfinal String nickname = 'Bobby';
final
변수는 값을 수정할 수 없습니다.
name = 'Alice'; // Error: a final variable can only be set once.
컴파일 타임 상수(compile time constant)를 생성하고 싶다면, const
를 사용하세요. 만약 const 변수가 className= level 이라면, static const
로 선언하세요. 변수를 선언 할 때 숫자나 문자 리터럴 같은 컴파일 타임 상수로 선언하거나, 산술 연산의 결과를 숫자 상수로 설정합니다.
const bar = 1000000; // Unit of pressure (dynes/cm2)const double atm = 1.01325 * bar; // Standard atmosphere
const
키워드는 단지 상수 변수를 선언할 때만 쓰이는 것은 아닙니다. 상수 값을 생성할 뿐만 아니라 상수 값을 생성하는 생성자를 선언하는 데도 사용할 수 있습니다.
var foo = const [];final bar = const [];const baz = []; // Equivalent to `const []`
당신은 const
를 선언 할 때, 위의 baz
처럼const
를 생략 할 수 있습니다. 더 자세한 정보를 원한다면, DON’T use const redundantly를 참고하세요.
당신은 final, const가 아닌 변수의 값을 변경 할 수 있습니다. 그 변수가 const
였다고 해도 말이죠.
foo = [1, 2, 3]; // Was const []
하지만 const
변수는 수정 할 수 없습니다.
baz = [42]; // Error: Constant variables can't be assigned a value.
type checks와 casts(is
와as
), collection if,spread operators(...
와 ...?
) 를 사용해서 상수를 정의 할 수도 있습니다.
const Object i = 3;// Where i is a const Object with an int value...const list = [i as int]; // Use a typecast.const map = {if (i is int) i: 'int'}; // Use is and collection if.const set = {if (list is List<int>) ...list}; // ...and a spread.
const
를 사용한 상수 선언에 대해 더 알아보고 싶다면, Lists,Maps,class를 참고하세요.
Built-in types
Dart는 다음의 타입들을 지원합니다.
Numbers(
int
,double
)Strings(
String
)Booleans(
bool
)Lists(
List,
arrays라고도 불립니다.)Sets(
Set
)Maps(
Map
)Runes(
Runes
; 종종characters
API로 대체됩니다.)Symbols(
Symbol
)The value
null
(Null
)
위와 같은 타입들은 literals를 사용해 객채를 생성하는 것이 가능합니다. 예를 들면, 'this is a string'
는 문자 리터럴 이고, true
는 boolean 리터럴 입니다.
Dart의 모든 변수들은 className=의 인스턴스인 객체이므로, 변수를 초기화 할 때, 생성자(constructors)를 사용합니다. 예를 들면, Map()
생성자를 map을 생성하기 위해 사용합니다.
아래와 같은 다른 타입들도 Dart에서 특별한 역할을 수행합니다.
Object
:Null
을 제외한 모든 Dart class의 superclassEnum
: enums의 superclassFuture
과Stream
: asynchrony support 작업을 할 때 사용합니다.Iterable
:for-in loops 와 동기generator functions에 사용됩니다.Never
: expression의 평가(실행 정도로 봐도 됨)를 성공하지 못함을 나타냅니다. 항상 예외(exception)을 발생시키는 함수에 많이 사용됩니다.dynamic
: 정적인 타입 체킹을 불가능하게 합니다. 보통Object
이나Object?
을 사용합니다. (Map
)void
: 어떤 값이 사용되지 않을 것이라는 뜻을 나타냅니다. 보통 반환 값의 타입으로 사용됩니다.
Understanding null safety의 top-and-bottom에 묘사되어 있는 것 처럼, Object
, Object?
, Null
, Never
클래스들은 클래스 계층에서 특별한 역할을 수행합니다.
Numbers
Dart에서는 숫자를 두 가지 방법으로 표현합니다:
- int
사용하는 플랫폼에 따라서 정수 값은 64비트 이하로 표현됩니다. 네이티브 플랫폼에서는 -263 to 263 - 1 까지 표현됩니다. 웹에서는 Javascript numbers (가수부가 없는 64-bits 부동소수점 표현) -253 to 253 - 1 사이의 수로 표현됩니다.
- double
IEEE 754 standard를 따라 64-bit (배정도) 부동 소수점 표현을 사용합니다.
int
와 double
은 모두 num의 subtypes 입니다. num 타입은 +, -, /, * 같은 기본적인 연산자 사용이 가능하고, abs()
, ceil()
, floor()
같은 함수 사용도 가능합니다. ( >> 같은 Bitwise 연산자는 int
클래스에 정의되어 있습니다.) 만약 당신이 찾는 것을 num과 num의 subtypes가 갖고있지 않다면, dart:math 라이브러리를 참고하세요.
Integers는 소수점을 갖지 않는 수를 말합니다. 아래는 리터럴 inteager를 정의하는 예입니다.
var x = 1;var hex = 0xDEADBEEF;var exponent = 8e5;
만약 숫자가 소수점을 가진다면, double이 됩니다. 아래는 리터럴 double을 정의하는 예입니다.
var y = 1.1;var exponents = 1.42e5;
변수를 num 타입으로 선언하는 것 또한 가능합니다. 그렇게 한다면 변수는 integer이면서 double 값이 됩니다.
num x = 1; // x can have both int and double valuesx += 2.5;
Integer 리터럴은 필요할 때 자동으로 double 형으로 바뀝니다.
double z = 1; // Equivalent to double z = 1.0.
다음은 string을 number로 바꾸고 그 반대의 경우도 보여주는 코드 입니다.
// String -> intvar one = int.parse('1');assert(one == 1);// String -> doublevar onePointOne = double.parse('1.1');assert(onePointOne == 1.1);// int -> StringString oneAsString = 1.toString();assert(oneAsString == '1');// double -> StringString piAsString = 3.14159.toStringAsFixed(2);assert(piAsString == '3.14');
int
타입은 전통적인 bitwise shift( >>
,>>>
, <<
, ), complement(~
), AND (&
), OR (|
) and XOR (^
) 연산자 사용이 가능합니다. 그리고 그러한 지원은 bit fields를 마스킹하거나 조작 할 때 매우 편리합니다. 예를 들면
assert((3 << 1) == 6); // 0011 << 1 == 0110assert((3 | 4) == 7); // 0011 | 0100 == 0111assert((3 & 4) == 0); // 0011 & 0100 == 0000
더 많은 예를 보고 싶다면 bitwise and shift operator를 참고하세요.
Strings
Dart의 string(String
객체)는 UTF-16 인코딩 방식으로 표현됩니다. 작은따옴표, 큰따옴표를 사용해서 string을 생성 할 수 있습니다.
var s1 = 'Single quotes work well for string literals.';var s2 = "Double quotes work just as well.";var s3 = 'It\'s easy to escape the string delimiter.';var s4 = "It's even easier to use the other delimiter.";
${expression}
를 사용하여 string 안에 식(expression)을 삽입할 수 있습니다. 만약에 식이 식별자라면, {} 를 생략해도 됩니다. 어떤 object와 동일한 string을 얻기 위해서, Dart는 object의 toString()
함수를 호출합니다.
var s = 'string interpolation';assert('Dart has $s, which is very handy.' == 'Dart has string interpolation, ' 'which is very handy.');assert('That deserves all caps. ' '${s.toUpperCase()} is very handy!' == 'That deserves all caps. ' 'STRING INTERPOLATION is very handy!');
+
연산자를 사용하거나, adjacent string literals 를 사용하여 strings를 합칠 수 있습니다.
var s1 = 'String ' 'concatenation' " works even over line breaks.";assert(s1 == 'String concatenation works even over ' 'line breaks.');var s2 = 'The + operator ' + 'works, as well.';assert(s2 == 'The + operator works, as well.');
multi-line string을 만드는 또 다른 방법은 삼중 따옴표( ''' or """ )를 사용하는 것 입니다.
var s1 = ''' You can create multi-line strings like this one. ''';var s2 = """This is also a multi-line string.""";
r
을 prefixing(접두사)하면 "raw" string을 만들 수 있습니다.
var s = r'In a raw string, not even \n gets special treatment.';
유니코드 문자가 어떻게 stirng으로 표현되는지 자세히 알고 싶다면, Runes and grapheme clusters를 참고하세요.
Interplated 수식이 null, numeric, string, boolean 값을 판단하는 컴파일 상수라면, 리터럴 문자는 컴파일 타임 상수입니다.
// These work in a const string.const aConstNum = 0;const aConstBool = true;const aConstString = "a constant string";// These do NOT work in a const string.var aNum = 0;var aBool = true;var aString = "a string";const aConstList = [1, 2, 3];const validConstString = "$aConstNum $aConstBool $aConstString";// const invalidConstString = "$aNum $aBool $aString $aConstList";
Strings에 대한 더 자세한 정보를 원한다면, Strings and regular expressions를 참고하세요.
Booleans
Boolean 값을 표현하기 위해 Dart는 bool
타입을 가지고 있습니다. Boolean 리터럴인 true
과 false
, 오직 이 두개의 객체만이 bool 타입을 가집니다.
Dart의 type safety는 if (nonbooleanValue)
나 assert (nonbooleanValue)
같은 코드를 사용 할 수 없음을 의미합니다. 대신에, 다음과 같이 명시적으로 값을 확인합니다.
// Check for an empty string.var fullName = '';assert(fullName.isEmpty);// Check for zero.var hitPoints = 0;assert(hitPoints <= 0);// Check for null.var unicorn;assert(unicorn == null);// Check for NaN.var iMeantToDoThis = 0 / 0;assert(iMeantToDoThis.isNaN);
Lists
거의 모든 프로그래밍 언어에서 가장 많이 사용되는 collection은 array이나, 순서가 있는 객체의 그룹일 것입니다. Dart에서 arrays는 List 객체이므로 많은 사람들이 lists라고 부릅니다.
Dart의 list 리터럴은 Javascript의 array 리터럴과 비슷합니다. 다음은 Dart list의 예입니다.
var list = [1, 2, 3];
Dart collection literal의 마지막 아이템에 콤마를 붙일 수 있습니다. 이것은 trailing comma이라 부르고, 별다른 효과는 없지만 복사-붙혀넣기를 할 때 발생할 수 있는 에러를 방지해줍니다.
var list = [ 'Car', 'Boat', 'Plane',];
Lists는 0 이 시작 값의 인덱스이고, list.length - 1
이 마지막 값의 인덱스인 zero-based 인덱싱을 합니다. Javascript에서 하는 것 처럼 list의 길이를 얻거나, list의 값에 접근 할 수 있습니다.
var list = [1, 2, 3];assert(list.length == 3);assert(list[1] == 2);list[1] = 1;assert(list[1] == 1);
컴파일 타임 상수인 list를 선언하고 싶다면 const
를 list 리터럴 앞에 추가하면 됩니다.
var constantList = const [1, 2, 3];// constantList[1] = 1; // This line will cause an error.
Dart 2.3에서 spread 연산자(...
) 와 null-aware spread 연산자(...?
) 가 추가되었습니다. 두 연산자 모두 collection에 다수의 값을 간결하게 삽입할 수 있게 해줍니다.
예를들면 다른 list에 어떤 list의 모든 값을 삽입하기 위해 spread 연산자(...
) 를 사용할 수 있습니다.
var list = [1, 2, 3];var list2 = [0, ...list];assert(list2.length == 4);
만약 spread 연산자 값이 null 일 수도 있다면, null-aware spread 연산자(...?
) 를 사용해서 exceptions을 방지 할 수 있습니다.
spread 연산자에 대해 더 자세이 알고 싶다면, spread operator proposal을 참고하세요.
Dart에서는 조건(if
)과 반복(for
)을 사용해서 collection을 만들 수 있게 collection if 와 collection for을 지원합니다.
다음은 길이가 3 또는 4가 되는 list를 collection if를 사용해 생성하는 예입니다.
var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];
다음은 collection for를 활용하는 예 입니다.
var listOfInts = [1, 2, 3];var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];assert(listOfStrings[1] == '#1');
collection if
와 for
에 대해 더 자세히 알고 싶다면, control flow collections proposal를 참고하세요.
List 타입은 lists를 조작 할 수 있는 간단하고 많은 메소드를 가지고 있습니다. lists에 대해 더 자세히 알고 싶다면, Generics과 Collections를 참고하세요.
Sets
Dart에서의 set은 고유한 항목들의 순서가 없는 집합 입니다. Dart의 Sets 은 set 리터럴 및 Sets type로 지원됩니다.
다음은 set 리터럴을 이용한 간단한 Dart의 set 예제입니다.
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
빈 set을 생성하고 싶다면, {}
나 Set
에 타입을 붙혀서 사용하면 됩니다.
var names = <String>{};// Set<String> names = {}; // This works, too.// var names = {}; // Creates a map, not a set.
add()
나 addAll()
를 사용하여 존재하는 set에 아이템을 추가할 수 있습니다.
var elements = <String>{};elements.add('fluorine');elements.addAll(halogens);
.length
를 사용하여 set의 크기를 알 수 있습니다.
var elements = <String>;elements.add('fluorine');elements.addAll(halogens);assert(elements.length == 5);
set을 컴파일 타임 상수로 선언하고 싶다면, const
를 set 리터럴 앞에 추가하세요.
final constantSet = const { 'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine',};// constantSet.add('helium'); // This line will cause an error.
Set은 list와 마찬가지로 spread 연산자 (...
과 ...?
) collection if, for
을 지원합니다. 더 자세한 정보를 얻고 싶다면, list spread 연산자와 list collection 연산자를 참고하세요.
Sets에 대한 더 자세한 정보를 얻고 싶다면, Generics와 Sets를 참고하세요.
Maps
일반적으로, map은 keys와 values를 사용하는 객체입니다. keys와 values 모두 어떤 타입의 객체든 가능합니다. 모든 key는 고유하지만, value는 중복된 값을 사용하는 것이 가능합니다. Dart는 map 리터럴과 Map 타입으로 maps를 지원합니다.
다음은 map 리터럴을 사용한 여러가지 예제 입니다.
var gifts = { // Key: Value 'first': 'partridge', 'second': 'turtledoves', 'fifth': 'golden rings'};var nobleGases = { 2: 'helium', 10: 'neon', 18: 'argon',};
Map 생성자를 사용해도 같은 객체를 생성할 수 있습니다.
var gifts = Map<String, String>();gifts['first'] = 'partridge';gifts['second'] = 'turtledoves';gifts['fifth'] = 'golden rings';var nobleGases = Map<int, String>();nobleGases[2] = 'helium';nobleGases[10] = 'neon';nobleGases[18] = 'argon';
Javascript에서 처럼 새로운 key-value 쌍을 map에 추가 할 수 있습니다.
var gifts = {'first': 'partridge'};gifts['fourth'] = 'calling birds'; // Add a key-value pair
Map으로 부터 value를 얻는 것도, Javascript와 같은 방법을 사용합니다.
var gifts = {'first': 'partridge'};assert(gifts['first'] == 'partridge');
만약 key가 map에 존재하지 않는다면, null 값을 반환합니다.
var gifts = {'first': 'partridge'};assert(gifts['fifth'] == null);
Map의 key-value 쌍의 수를 알고 싶다면, .length
를 사용하세요.
var gifts = {'first': 'partridge'};gifts['fourth'] = 'calling birds';assert(gifts.length == 2);
Map을 컴파일 타임 상수로 선언하고 싶다면, const
를 map 리터럴에 추가하세요.
final constantMap = const { 2: 'helium', 10: 'neon', 18: 'argon',};// constantMap[2] = 'Helium'; // This line will cause an error.
Maps에 대한 더 자세한 정보를 원한다면, generics 섹션과 Maps API를 참고하세요.
Runces and grapheme clusters
Dart에서 runes는 string의 유니코드 코드 포인트를 나타냅니다. characters package를 사용하여 Unicode (extended) grapheme clusters라고도 하는, 사용자가 인식하는 문자를 보거나 조작할 수 있습니다.
유니코드는 세상의 모든 문자, 숫자, 기호 시스템에 대해 고유한 숫자 값을 정의합니다. Dart의 문자열은 UTF-16 코드 단위의 시퀀스이기 때문에 문자열 내에서 유니코드 코드 포인트를 표현하려면 특별한 구문이 필요합니다. 유니코드 코드 포인트를 표현하는 가장 흔한 방법은 \uXXXX
형태로 나타내는 것 이고, XXXX는 16진수 4-digit 값 입니다. 예를 들면 하트 문자(♥)는 \u2665
입니다. 4개의 16진수 보다 적거나, 많이 사용하고 싶다면, 중괄호 안에 값을 넣으면 됩니다. 예를 들면 웃는 이모지(😆)는 \u{1f606}
으로 나타냅니다.
만약 유니코드 문자 각각을 읽고 써야한다면, characters package에 의해 String에 정의 되어 있는 characters
getter를 사용하세요. 반환된 Characters객체는 graphem clusters의 시퀀스로 이루어진 string 입니다. 아래는 characters API를 사용한 예제 입니다:
import 'package:characters/characters.dart';...var hi = 'Hi 🇩🇰';print(hi);print('The end of the string: ${hi.substring(hi.length - 1)}');print('The last character: ${hi.characters.last}\n');
실행 환경에 따라서 결과는 다음과 같을 것 입니다:
$ dart run bin/main.dartHi 🇩🇰 The end of the string: ???The last character: 🇩🇰
String 조작을 위한 characters 패키지 사용에 대해 더 자세히 알고 싶다면, example과 API reference 를 참고하세요.
Symbols
Symbol 객체는 Dart 프로그램에 선언된 연산자나 식별자를 나타냅니다.
아마도 symbol을 사용할 필요가 없을 수도 있지만, 축소(minification)를하면 식별자의 이름은 변경되지만 식별자의 symbol은 변경되지 않기 때문에 이름으로 식별자를 참조하는 API에는 매우 유용합니다.
식별자에 대한 symbol를 가져오려면 symbol 리터럴을 사용하면 됩니다. symbol 리터럴은 # 뒤에 식별자를 위치시키면 됩니다.
#radix #bar
Symbol 리터럴은 컴파일 타임 상수입니다.
Functions
Dart는 객체지향 언어입니다. 그렇기에 함수또한 객체이고 타입을 가집니다. 함수 이것은 함수가 변수에 할당되고, 다른 함수의 매개변수로 전달 될 수 있다는 것을 의미합니다. Dart 클래스의 인스턴스를 함수처럼 호출하는 것 또한 가능합니다. 더 자세한 정보를 원한다면, Callable class를 참고하세요.
다음은 함수를 구현하는 예제 입니다.
bool isNoble(int atomicNumber) { return _nobleGases[atomicNumber] != null;}
Effective Dart는 public APIs의 타입 명시를 추천하지만, 타입을 생략해도 여전히 잘 동작합니다:
isNoble(atomicNumber) { return _nobleGases[atomicNumber] != null;}
하나의 식만 가지고 있는 함수라면, 다음과 같은 생략된 문법을 사용 할 수 있습니다.
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
=> 수식
문법은 { return 수식; }
의 약칭 입니다. =>
표기법은 화살표(arrow) 문법이라고 불립니다.
Parameters
함수는 required positional 매개변수를 가질 수 있습니다. 이 과정은 named 매개변수나 optional positinal 매개변수로 이루어 집니다. (한 매개변수가 동시에 둘의 속성을 가지는 것은 불가능 합니다.)required positional과 optional positinal의 차이를 알고 싶다면, Named and positional parameters in Dart를 참고하세요.
Named parameters
Named 매개변수는 required
로 마크되지 않는 이상 선택사항 입니다.
함수를 호출 할 때, paramName: value
을 사용하여 named 매개변수를 정의 할 수 있습니다. 다음의 예를 보시죠.
enableFlags(bold: true, hidden: false);
함수를 정의 할 때 {param1, param2, …}
을 사용하여 named 매개변수를 정의 할 수도 있습니다.
/// Sets the [bold] and [hidden] flags ...void enableFlags({bool? bold, bool? hidden}) {...}
named 매개변수도 optinal 매개변수의 일종이지만, 두 속성 모두 사용자가 꼭 전달해야하는 매개변수라는 것을 나타내는 required
를 명시 할 수 있습니다.
const Scrollbar({Key? key, required Widget child)
위 코드에서의 child
를 전달하지 않고, Scrollbar
를 생성하려고 한다면, analyer가 이슈를 리포트합니다.
Optional positinal parameters
함수 매개변수 셋을 []
로 감싸면 optional positional 매개변수가 됩니다.
String say(String from, String msg, [String? device]) { var result = '$from says $msg'; if (device != null) { result = '$result with a $device'; } return result;}
아래는 위의 함수에서 optional 매개변수를 넘기지 않고 호출하는 예제 입니다.
assert(say('Bob', 'Howdy') == 'Bob says Howdy');
아래는 세번째 매개변수(optional)을 넘겨주며 호출하는 예제 입니다.
assert(say('Bob', 'Howdy', 'smoke signal') == 'Bob says Howdy with a smoke signal');
Default parameter values
named와 positinal의 optional 매개변수 초기 값을 할당하려면 =
를 사용하면 됩니다. 초기 값은 컴파일 타임 상수입니다. 만약 초기 값이 주어지지 않는다면, 초기 값은 null
값이 됩니다.
다음은 named 매개변수의 초기 값을 설정하는 예제 입니다.
/// Sets the [bold] and [hidden] flags ...void enableFlags({bool bold = false, bool hidden = false}) {...}// bold will be true; hidden will be false.enableFlags(bold: true);
다음은 positional 매개변수의 초기 값을 설정하는 예제 입니다.
String say(String from, String msg, [String device = 'carrier pigeon']) { var result = '$from says $msg with a $device'; return result;}assert(say('Bob', 'Howdy') == 'Bob says Howdy with a carrier pigeon');
list나 map을 초기 값으로 넘겨줄 수 있습니다. 다음 예제는 doStuff()
함수를 정의하고 list
매개변수로 list 초기 값을, gifts
매개변수로 map 초기 값을 받습니다.
void doStuff( {List<int> list = const [1, 2, 3], Map<String, String> gifts = const { 'first': 'paper', 'second': 'cotton', 'third': 'leather' }}) { print('list: $list'); print('gifts: $gifts');}
The main() function
모든 앱은 앱의 진입점 역할을 하는, top-level main()
함수가 필요합니다. main()
함수는 void
를 반환하고 optional List<String>
매개변수를 인자로 전달 받습니다.
다음은 간단한 main()
함수의 예제 입니다.
void main() { print(&39;Hello, World!&39;);}
다음은 인자를 받는 커맨드 라인 앱의 main()
함수 예제 입니다.
// Run the app like this: dart args.dart 1 testvoid main(List<String> arguments) { print(arguments);assert(arguments.length == 2); assert(int.parse(arguments[0]) == 1); assert(arguments[1] == 'test');}
커맨드 라인 인자를 정의하고, 받아오기 위해서 args library를 사용 수도 있습니다.
일급 객체로서의 함수
다음과 같이 함수는 다른 함수의 매개 변수로 전달 될 수 있습니다.
void printElement(int element) { print(element);}var list = [1, 2, 3];// Pass printElement as a parameter.list.forEach(printElement);
다음과 같이 변수에 함수를 할당 할 수도 있습니다.
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';assert(loudify('hello') == '!!! HELLO !!!');
위에 예제에서는 다음 섹션에 나올 익명(anonymous)함수를 사용합니다.
익명 함수
대부분의 함수들은 main()
나 printElement()
처럼 이름이 있습니다. 하지만 익명(anonymous) 함수, 람다(lambda), 클로져(closure) 같이 이름이 없는 함수가 있습니다. 익명 함수를 변수에 선언해서 collection에 추가하고 제거하는 것도 가능합니다.
익명 함수는 괄호 안에 콤마로 분리된 매개변수들, optional 타입 표기까지, 이름이 있는 함수들과 비슷합니다.
다음 코드 블럭은 함수 바디를 포함합니다.
([[Type] param1[, …]]) {
codeBlock;
};
다음 예제는 타입을 명시하지 않은 item
으로 익명 함수를 정의했습니다. 이 함수는 list의 모든 아이템을 순회하며, 인덱스에 해당하는 string 값을 출력합니다.
const list = ['apples', 'bananas', 'oranges'];list.forEach((item) { print('${list.indexOf(item)}: $item');});
만약 함수가 하나의 수식이나 구문을 반환한다면, 화살표 표기법을 사용하여 코드를 간결하게 만들 수 있습니다. 다음 코드는 위의 코드와 같은 결과를 출력합니다.
list.forEach((item) => print('${list.indexOf(item)}: $item'));
렉시컬 스코프(Lexical scope)
Dart는 렉시컬 스코프를 따르는 언어입니다. 그것은 변수의 유효 범위가 정적으로 정해진다는 것이고 더 간단하게 코드의 구조에 의해 결정된다는 뜻입니다. 변수의 유효 범위를 확인하고 싶다면, 중괄호를 안팎으로 확인하세요.
다음은 각각의 스코프 레벨에 변수가 있는 중첩 함수의 예제입니다.
bool topLevel = true;void main() { var insideMain = true;void myFunction() { var insideFunction = true;void nestedFunction() { var insideNestedFunction = true;assert(topLevel); assert(insideMain); assert(insideFunction); assert(insideNestedFunction); } }}
nestedFunction()
는 가장 위의 레벨까지, 모든 레벨의 변수 사용이 가능하다는 것을 알아두세요.
렉시컬 클로져(Lexical closures)
클로져는 함수가 이것의 원래 스코프의 밖에서 쓰여졌다고 해도, 해당 함수 렉시컬 스코프의 변수에 접근 할 수 있는 함수 객체입니다. (기억을 한다는 의미)
함수는 주위 스코프에 정의된 변수를 포함합니다. 다음의 예제에서, makeAdder()
는 addBy
변수를 기억합니다.
nestedFunction()
는 가장 위의 레벨까지, 모든 레벨의 변수 사용이 가능하다는 것을 알아두세요.
/// Returns a function that adds [addBy] to the/// function's argument.Function makeAdder(int addBy) { return (int i) => addBy + i;}void main() {// Create a function that adds 2. var add2 = makeAdder(2);// Create a function that adds 4. var add4 = makeAdder(4);assert(add2(3) == 5); assert(add4(3) == 7);}
동등성 테스트 함수
다음은 최상위 함수, 정적 메서드, 인스턴스 메서드의 동등성을 확인하는 테스트 코드 입니다.
void foo() {} // A top-level functionclass A { static void bar() {} // A static method void baz() {} // An instance method}void main() { Function x;// Comparing top-level functions. x = foo; assert(foo == x);// Comparing static methods. x = A.bar; assert(A.bar == x);// Comparing instance methods. var v = A(); // Instance #1 of A var w = A(); // Instance #2 of A var y = w; x = w.baz;// These closures refer to the same instance (#2), // so they're equal. assert(y.baz == x);// These closures refer to different instances, // so they're unequal. assert(v.baz != w.baz);}
반환 값
모든 함수는 값을 반환합니다. 만약 함수의 반환값이 명시되어 있지 않으면, return null;
이 암묵적으로 함수의 바디에 추가됩니다.
foo()assert(foo() == null);
연산자
Dart는 아래 표에 있는 연산자들을 지원합니다. 당신이 클래스 멤버로 이러한 연산자들을 구현하는 것이 가능합니다.
Description | Operator |
unary postfix | expr++ expr-- () [] ?[] . ?. ! |
unary prefix | -expr !expr ~expr ++expr --expr await expr |
multiplicative | * / % ~/ |
additive | + - |
shift | << >> >>> |
bitwise AND | & |
bitwise XOR | ^ |
bitwise OR | | |
relational and type test | >= > <= < as is is! |
equality | == != |
logical AND | && |
logical OR | || |
if null | ?? |
conditional | expr1 ? expr2 : expr3 |
cascade | .. ?.. |
assignment | = *= /= += -= &= ^= etc. |
연산자를 사용할 때, expression을 생성하게 됩니다. 다음은 연산자 표현의 예제 입니다.
a++a + b a = b a == b c ? a : b a is T
위의 연산자 테이블에 같은 행에 있는 연산자들 중에 왼쪽에서 오른쪽으로 갈 수록 우선순위가 낮아집니다. 예를 들면, %
연산자는 ==
연산자 보다 우선순위가 높습니다. 그리고 ==
는 논리 AND 연산자인 &&
보다 순위가 높습니다. 그러한 우선 순위에 따라 다음 두 줄의 코드는 같은 방식으로 작동합니다.
// 가독성이 좋음.if ((n % i == 0) && (d % i == 0)) ...// 결과는 같지만, 가독성이 떨어짐.if (n % i == 0 && d % i == 0) ...
산술 연산자
Dart는 다음 표와 같이 기본 산술 연산자를 지원합니다.
Operator | Meaning |
+ | Add |
– | Subtract |
-expr | Unary minus, also known as negation (reverse the sign of the expression) |
* | Multiply |
/ | Divide |
~/ | Divide, returning an integer result |
% | Get the remainder of an integer division (modulo) |
Example:
assert(2 + 3 == 5);assert(2 - 3 == -1);assert(2 * 3 == 6);assert(5 / 2 == 2.5); // Result is a doubleassert(5 ~/ 2 == 2); // Result is an intassert(5 % 2 == 1); // Remainderassert('5/2 = ${5 ~/ 2} r $1' == '5/2 = 2 r 1');
Dart는 prefix(접두사), postfix(접미사) 증가,감소 연산자를 지원합니다.
Operator | Meaning |
++var | var = var + 1 (expression value is var + 1 ) |
var++ | var = var + 1 (expression value is var ) |
--var | var = var – 1 (expression value is var – 1 ) |
var-- | var = var – 1 (expression value is var ) |
Example:
int a;int b;a = 0;b = ++a; // Increment a before b gets its value.assert(a == b); // 1 == 1a = 0;b = a++; // Increment a AFTER b gets its value.assert(a != b); // 1 != 0a = 0;b = --a; // Decrement a before b gets its value.assert(a == b); // -1 == -1a = 0;b = a--; // Decrement a AFTER b gets its value.assert(a != b); // -1 != 0
항등, 관계 연산자
다음 표는 Dart의 항등 연산자와 관계 연산자 입니다.
Operator | Meaning |
== | 같음 |
!= | 같지 않음 |
> | 더 큼 |
< | 더 작음 |
>= | 크거나 같음 |
<= | 작거나 같음 |
x와 y 두 객체가 같은 값을 대표하는지 테스트 하고 싶다면, ==
연산자를 사용하세요.(드물게 두 객체가 정확하게 같은 객체인지 확인하고 싶다면, identical() 함수를 사용하세요.) ==
연산자는 이렇게 동작합니다:
x 와 y 둘 다 null이면 true를 반환, 둘 중 하나만 null 이라면 false를 반환합니다.
Return the result of invoking the
==
method on x with the argument y. (That’s right, operators such as==
are methods that are invoked on their first operand. For details, see Operators.)
인자 y와 x에 대해 ==
메서드를 호출한 결과를 반환합니다. (==
같은 연산자들은 첫 번째 피연산자가 호출한 메서드로 봐도 됩니다. 더 자세히, Operators)
다음은 항등 연산자와 관계 연산자 예제입니다.
assert(2 == 2);assert(2 != 3);assert(3 > 2);assert(2 < 3);assert(3 >= 3);assert(2 <= 3);
타입 테스트 연산자
as
, is
그리고 is!
연산자들은 런타임에서 타입을 확인 할 때 유용합니다.
Operator | Meaning |
as | Typecast (also used to specify library prefixes) |
is | True if the object has the specified type |
is! | True if the object doesn’t have the specified type |
obj
가T
로 specified된 interface의 구현체라면obj is T
의 결과는 true 입니다. 얘를 들면,obj is Object?
는 항상 true 입니다.
어떤 객체가 캐스팅을 원하는 타입으로의 변환이 가능할 때 as
연산자를 사용해서 캐스팅을 할 수 있습니다. Example:
(employee as Person).firstName = 'Bob';
만약 객체가 타입 T라는 것을 확실하지 못한다면, 객체를 사용하기 전에 is T
로 타입을 확인하세요.
if (employee is Person) { // Type checkemployee.firstName = 'Bob';}
할당 연산자(Assignment operators)
앞서 봤다시피, =
연산자를 사용해 값을 할당 할 수 있습니다. 할당을 받는 변수가 null 일 때만 할당을 하고 싶다면, ??=
연산자를 사용하면 됩니다.
// Assign value to aa = value;// Assign value to b if b is null; otherwise, b stays the sameb ??= value;
+=
같은 복합(compound) 할당 연산자는 할당과 연산을 결합합니다.(+ 는 연산, = 는 할당)
= | *= | %= | >>>= | ^= |
+= | /= | <<= | &= | |= |
-= | ~/= | >>= |
복합 할당 연산자는 다음과 같이 작동합니다:
Compound assignment | Equivalent expression | |
For an operator op: | a op= b | a = a op b |
Example: | a += b | a = a + b |
다음은 할당 연산자와 복합 할당 연산자의 예제 입니다:
var a = 2; // Assign using =a *= 3; // Assign and multiply: a = a * 3assert(a == 6);
논리 연산자(Logical operators)
논리 연산자를 사용하여 boolean 표현식을 뒤집거나(false to true, true to false) 결합하는 것이 가능합니다.
Operator | Meaning |
!expr | inverts the following expression (changes false to true, and vice versa) |
|| | logical OR |
&& | logical AND |
다음은 논리 연산자 예제 입니다:
if (!done && (col == 0 || col == 3)) { // ...Do something...}
Bitwise와 shift 연산자
Dart에서는 숫자를 이루는 각각의 비트를 조작하는 것이 가능합니다. 주로 bitwise와 shift 연산자는 정수와 함께 쓰입니다.
Operator | Meaning |
& | AND |
| | OR |
^ | XOR |
~expr | Unary bitwise complement (0s become 1s; 1s become 0s) |
<< | Shift left |
>> | Shift right |
>>> | Unsigned shift right |
다음은 bitwise와 shift 연산자의 예제입니다:
final value = 0x22;final bitmask = 0x0f;assert((value & bitmask) == 0x02); / ANDassert((value & ~bitmask) == 0x20); / AND NOTassert((value | bitmask) == 0x2f); / ORassert((value ^ bitmask) == 0x2d); / XORassert((value << 4) == 0x220); / Shift leftassert((value >> 4) == 0x02); / Shift rightassert((value >>> 4) == 0x02); / Unsigned shift rightassert((-value >> 4) == -0x03); / Shift rightassert((-value >>> 4) > 0); / Unsigned shift right
조건 표현식(conditional expressions)
Dart는 if-else 구문을 간결하게 표현 할 수 있는 두 개의 연산자가 있습니다.
condition ? expr1 : expr2
만약 condition이 참이라면, expr1의 값을 반환하고, 아니라면 expr2의 값을 반환합니다.
expr1 ?? expr2
만약 expr1이 null이 아니라면, expr1의 값을 반환하고 null이라면, expr2의 값을 반환합니다.
boolean 표현식으로 어떤 값을 할당하는 상황이라면, ?
와 :
를 사용해보세요.
var visibility = isPublic ? 'public' : 'private'; (isPublic이 참이라면 visibility는 'public'이 되고 아니라면 'private'이 됩니다.)
boolean 표현식이 null인지 확인하고 싶다면, ??
를 사용하세요.
String playerName(String? name) => name ?? 'Guest';
위의 예는 다음과 같이 두 개의 방법으로 표현 될 수 있지만, 깔끔하진 않습니다.
// Slightly longer version uses ?: operator.String playerName(String? name) => name != null ? name : 'Guest';// Very long version uses if-else statement.String playerName(String? name) { if (name != null) { return name; } else { return 'Guest'; }}
Cascade notation
Cascades (..
, ?..
)는 같은 객체에 대해 연속적인 operations을 적용할 수 있게 해줍니다. 함수를 호출 또한 가능합니다. 이런 기능은 임시 변수를 만드는 과정을 줄이고 코드를 더 유동적으로 만들어 줍니다.
다음 예를 봅시다.
var paint = Paint() ..color = Colors.black ..strokeCap = StrokeCap.round ..strokeWidth = 5.0;
Paint()
생성자는 Paint
객체를 반환합니다. casade notaition 뒤에 오는 코드들은 반환 될 값들을 무시하며, 해당 객체에 대해 작동합니다.
위의 코드는 아래의 예제와 같은 코드입니다.
var paint = Paint();paint.color = Colors.black;paint.strokeCap = StrokeCap.round;paint.strokeWidth = 5.0;
만약 cascade를 사용하려는 객체의 field가 null 일 수도 있다면 null-shorting cascade (?..
)를 첫 번째 operation에 사용하세요. ?..
로 시작하는 것은 뒤에 오는 cascade operations이 null 객체 일 수도 있는 객체에 대해 실행되지 않을 것을 보장합니다.
querySelector('#confirm') // Get an object. ?..text = 'Confirm' // Use its members. ..class.add('important') ..onClick.listen((e) => window.alert('Confirmed!'));
Version note:The ?..
문법은 최소 2.12.버젼을 요구합니다.
위의 코드는 다음의 코드와 동일합니다.
var button = querySelector('#confirm');button?.text = 'Confirm';button?.class.add('important');button?.onClick.listen((e) => window.alert('Confirmed!'));
다음과 같이 중첩 cascades를 사용하는 것도 가능합니다.
final addressBook = (AddressBookBuilder() ..name = 'jenny' ..email = 'jenny@example.com' ..phone = (PhoneNumberBuilder() ..number = '415-555-0100' ..label = 'home') .build()) .build();
실제 객체를 반환하는 함수에 대해서만 cascade를 사용해야 합니다. void를 반환하는 함수에는 사용 할 수 없습니다. 다음과 같은 코드는 에러가 발생합니다.
var sb = StringBuffer();sb.write('foo') ..write('bar'); // Error: method 'write' isn't defined for 'void'.
sb.write()
호출은 void를 반환하고, void
에는 cascade를 사용 할 수 없습니다.
이 외의 연산자들
아마 다른 예제들에서 나머지 연산자들을 본 경험이 있을 것 입니다.
Operator | Name | Meaning |
() | Function application | Represents a function call |
[] | Subscript access | Represents a call to the overridable [] operator; example: fooList[1] passes the int 1 to fooList to access the element at index 1 |
?[] | Conditional subscript access | Like [] , but the leftmost operand can be null; example: fooList?[1] passes the int 1 to fooList to access the element at index 1 unless fooList is null (in which case the expression evaluates to null) |
. | Member access | Refers to a property of an expression; example: foo.bar selects property bar from expression foo |
?. | Conditional member access | Like . , but the leftmost operand can be null; example: foo?.bar selects property bar from expression foo unless foo is null (in which case the value of foo?.bar is null) |
! | Null assertion operator | Casts an expression to its underlying non-nullable type, throwing a runtime exception if the cast fails; example: foo!.bar asserts foo is non-null and selects the property bar , unless foo is null in which case a runtime exception is thrown |
.
, ?
, ..
연산자에 대해 더 자세히 알고 싶다면, class를 참고하세요.
Control flow statements
다음의 statements들을 사용해서 Dart 코드의 흐름을 제어 할 수 있습니다.
if
andelse
for
loopswhile
anddo
-while
loopsbreak
andcontinue
switch
andcase
assert
Exceptions의 설명 처럼, try-catch
와 throw
를 사용해 흐름을 제어 할 수도 있습니다.
If and else
Dart는 if
else
구문을 지원합니다. conditional expressions 또한 참고하세요.
if (isRaining()) { you.bringRainCoat();} else if (isSnowing()) { you.wearJacket();} else { car.putTopDown();}
Javascript와 다르게 조건에는 무조건 boolean 값이 사용되어야 합니다. Booleans를 확인해보세요.
For 반복
for
문을 활용해 반복을 수행 할 수 있습니다. 예:
var message = StringBuffer('Dart is fun');for (var i = 0; i < 5; i++) { message.write('!');}
Dart의 for
loops 안의 클로저는, Javascript에서 흔히 발생하는 위험을 피하면서, 해당 인덱스의 값을 기억합니다.
var callbacks = [];for (var i = 0; i < 2; i++) { callbacks.add(() => print(i));}callbacks.forEach((c) => c());
실행 결과는 0
과 1
입니다. 대조적으로, 위의 예는 Javascript에서 2
와 2
를 출력합니다.
만약 반복하려는 객체가 List나 Set 처럼 반복 될 수 있고, 현재 반복 카운터를 알 필요가 없다면, for-in
형태의 iteration 을 사용하세요.
for (final candidate in candidates) { candidate.interview();}
반복 가능한 클래스들은 forEach() 함수를 지원합니다.
While, do-while
while
loop는 loop을 돌기 전에 조건을 따져봅니다.
while (!isDone()) { doSomething();}
do-while
llop는 loop를 돈 후에 조건을 따집니다.
do { printLine();} while (!atEndOfPage());
Break 와 continue
Looping을 멈추고 싶다면 break
를 사용하세요.
while (true) { if (shutDownRequested()) break; processIncomingRequests();}
다음 loop 반복을 건너 뛰고 싶다면, continue
를 사용하세요.
for (int i = 0; i < candidates.length; i++) { var candidate = candidates[i]; if (candidate.yearsExperience < 5) { continue; } candidate.interview();}
만약 list와 set 같은 Iterable 을 사용한다면, 위의 예를 다음과 같이 바꿀 수 있습니다.
candidates .where((c) => c.yearsExperience >= 5) .forEach((c) => c.interview());
Switch와 case
Dart의 Switch문은 정수, string, 컴파일 타임 상수를 ==
를 사용해서 비교합니다. 비교 되는 두 객체는 반드시 같은 클래스의 인스턴스여야 합니다. 그리고 그 클래스는 ==
를 override 해서는 안 됩니다. Enumerated types은 switch
문에서 효과적입니다.
비어있지 않은 case
는 break
로 끝이 납니다. 비어있지 않은 case
를 끝내는 다른 방법으로는 continue
, throw
, return
이 있습니다.
default
는 매치되는 case
가 없을 때 실행됩니다.
var command = 'OPEN';switch (command) { case 'CLOSED': executeClosed(); break; case 'PENDING': executePending(); break; case 'APPROVED': executeApproved(); break; case 'DENIED': executeDenied(); break; case 'OPEN': executeOpen(); break; default: executeUnknown();}
다음 예제에서 case
안에서 break
를 생략하고, 에러가 발생합니다.
var command = &339;OPEN&339;;switch (command) &3123; case 'OPEN': executeOpen(); // ERROR: Missing break case 'CLOSED': executeClosed(); break;}
그러나, Dart는 "구현되지 않음"을 나타내기 위해, case
를 비울 수 있습니다.
var command = 'CLOSED';switch (command) { case 'CLOSED': // Empty case falls through. case 'NOW_CLOSED': // Runs for both CLOSED and NOW_CLOSED. executeNowClosed(); break;}
만약 "구현되지 않음"을 정말 사용하고 싶다면, contiune
를 라벨과 함께 사용하면 됩니다.
var command = 'CLOSED';switch (command) { case 'CLOSED': executeClosed(); continue nowClosed; // Continues executing at the nowClosed label.nowClosed: case 'NOW_CLOSED': // Runs for both CLOSED and NOW_CLOSED. executeNowClosed(); break;}
case
는 해당 법위 안에서만 존재하는 지역 변수를 가질 수 있습니다.
Assert
개발 도중에 boolean 조건이 false 일 때 코드 진행을 멈추고 싶다면 assert(condition, optionalMessage);
를 사용하세요. 이 페이지에서 많은 assert의 예제를 볼 수 있을 겁니다.
// Make sure the variable has a non-null value.assert(text != null);// Make sure the value is less than 100.assert(number < 100);// Make sure this is an https URL.assert(urlString.startsWith('https'));
assertion에 메시지를 추가하고 싶다면, assert
의 두번째 인자에 string을 넘겨주세요.
assert(urlString.startsWith('https'), 'URL ($urlString) should start with "https".');
assert
의 첫 번째 인자는 boolean 값을 내놓는 표현식이 전해집니다. 만약 표현식이 참이라면, assertion은 성공하고 코드가 계속 진행됩니다. 만약 false라면, assertion은 실패하고 exception(AssertionError) 이 발생합니다.
Assertions은 언제 효과적일까? 그건 어떤 툴과 프레임워크를 사용하는 지에 달려있습니다.
- 플러터는 assertions을 debug mode. 에서 사용 할 수 있습니다.
- dartdevc 같은 오직 개발만을 위한 툴은 보통 assertions이 기본적으로 가능합니다.
dart run
이나 dart2js 같은 툴들은--enable-asserts
커맨드를 이용해 assertion 사용이 가능합니다. .
실제 사용하는 소스코드(production code)에서는 assertion이 무시되고, assert
의 첫번째 인자는 평가되지 않습니다.
예외(Exceptions)
Dart 코드는 예외를 발생(throw)시키고, 캐치(catch) 할 수 있습니다. 예외는 예상하지 못한 상황이 발생했다는 것을 의미합니다. 만약 예외가 발생하지 않았다면, 예외를 발생시키는 isolate는 동작이 연기되고, 보통 isolate와 이것의 프로그램은 종료됩니다.
자바와 대조적으로, Dart의 모든 exceptions는 unchecked exceptions 입니다. 메서드는 어떤 예외를 발생 시킬 것인지 선언하지 못하고, 예외를 캐치할 필요도 없습니다.
Dart는 다양한 서브 타입들을 제공하는 Exception와 Error를 제공합니다. 물론 예외를 정의하는 것이 가능합니다. 그러나 Dart 프로그램은 Exception이나 Error 객체 이 외에도 null이 아닌(non-null) 객체를 예외로 발생(throw)시킬 수 있습니다.
Throw
다음은 예외를 발생시키는 예제입니다.
throw FormatException('Expected at least 1 section');
임의의 객체를 예외로 발생시키는 것 또한 가능합니다.
throw 'Out of llamas!';
예외를 발생시키는 것은 표현식(expression)이기 때문에 표현식이 가능한 어떤 곳이든 => 구문을 사용해서 예외를 발생시킬 수 있습니다.
void distanceTo(Point other) => throw UnimplementedError();
Catch
예외를 캐칭, 캡쳐링 하는 것은 예외가 더 진행되는 것을 막습니다.(그렇지 않으면 예외를 다시 발생 시킵니다.). 예외를 캐칭하는 것은 그 예외를 처리 할 수 있는 기회를 줍니다.
try { breedMoreLlamas();} on OutOfLlamasException { buyMoreLlamas();}
둘 이상의 예외를 발생 시킬 수 있는 코드를 처리하고 싶다면, 여러개의 catch 문을 사용하면 됩니다. 발생된 객체의 타입을 매칭하는 첫 번째 catch 문이 예외를 처리합니다. 만약 catch 문이 타입을 특정하지 않았다면, 그 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');}
앞의 코드가 보여주듯이, on
이나 catch
을 사용하면 됩니다. 예외의 타입을 명시할 필요가 있다면, on
을 사용하세요. 예외 핸들러가 예외 객체(위에서는 e를 말한다)를 필요로한다면, catch
를 사용하세요.
catch()
는 두개의 파라미터를 받을 수 있습니다. 첫 번째는 발생될 예외이고, 두 번째는 StackTrace 입니다.
try { // ···} on Exception catch (e) { print('Exception details:\n $e');} catch (e, s) { print('Exception details:\n $e'); print('Stack trace:\n $s');}
예외가 계속 진행되는 것을 허락하면서, 부분적으로 처리하고 싶다면, rethrow
키워드를 사용하세요.
void misbehave() { try { dynamic foo = true; print(foo++); // Runtime error } catch (e) { print('misbehave() partially handled ${e.runtimeType}.'); rethrow; // Allow callers to see the exception. }} void main() { try { misbehave(); } catch (e) { print('main() finished handling ${e.runtimeType}.'); }}
Finally
예외가 발생했던 아니던 어떤 코드를 실행하고 싶다면, finally
문을 사용하세요. 만약, 어떠한 catch
도 예외를 처리하지 못했다면, finally
문이 실행된 이후에 예외가 전파됩니다.
try { breedMoreLlamas();} finally { // Always clean up, even if an exception is thrown. cleanLlamaStalls();}
finally
문은 catch
문의 매칭을 시도 한 뒤 실행됩니다.
try { breedMoreLlamas();} catch (e) { print('Error: $e'); // Handle the exception first.} finally { cleanLlamaStalls(); // Then clean up.}
예외에 대해 더 자세히 알고 싶다면, Exceptions를 참고하세요.
클래스
Dart는 mixin 기반 상속을 지원하는 객체지향언어 입니다. 모든 객체는 클래스의 인스턴스이고, Null
을 제외한 클래스는 모두 Object에서 비롯합니다. Mixin 기반 상속이란 말은, 모든 클래스가 하나의 superclass를 갖고 있지만(top class인 Object?
를 제외한) 클래스의 바디는 다양한 클래스 계층에서 재사용 될 수 있음을 의미합니다. Extension methods는 서브 클래스를 추가하거나, 클래스를 바꾸지 않고 클래스에 기능을 추가하는 방법입니다.
클래스 멤버 사용하기
객체들은 함수와 데이터(각각 메서드, 인스턴스 변수)로 이루어진 멤버를 가집니다. 메서드를 호출 할 때, 객체에서 함수를 호출합니다: 메서드는 해당 객체의 함수와 데이터에 접근 할 수 있습니다.
(.
)를 사용햐여 인스턴스 변수나, 함수를 사용합니다.
var p = Point(2, 2);// Get the value of y.assert(p.y == 2);// Invoke distanceTo() on p.double distance = p.distanceTo(Point(4, 4));
만약 왼쪽 피연산자가 null 일 수도 있다면, .
대신 ?.
을 사용하세요.
// If p is non-null, set a variable equal to its y value.var a = p?.y;
생성자 사용
생성자를 사용하여 객체를 생성 할 수 있습니다. 생성자의 이름은 ClassName
, ClassName.identifier
이 될 수 있습니다. 예를 들면, 다음의 예제에서 Point
객체를 Point()
와 Point.fromJson()
생성자를 사용하여 생성합니다:
var p1 = Point(2, 2);var p2 = Point.fromJson({'x': 1, 'y': 2});
몇몇 클래스는 constant constructors를 제공합니다. 상수 생성자를 사용하여 컴파일 타임 상수를 생성하고 싶다면, 생성자 이름 앞에 const
를 사용하세요.
var p = const ImmutablePoint(2, 2);
다음과 같이 두개의 동일한 컴파일 타임 상수를 생성하는 것은, 하나의 동일한 인스턴스를 생성합니다.
var a = const ImmutablePoint(1, 1);var b = const ImmutablePoint(1, 1);assert(identical(a, b)); // They are the same instance!
상수 컨텍스트 안에서, 생성자나 리터럴 뒤의 const
는 생략이 가능합니다. 다음과 같은 코드가 있습니다.
// Lots of const keywords here.const pointAndLine = const { 'point': const [const ImmutablePoint(0, 0)], 'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],};
const
를 선언 할 때 첫 번째 const
를 제외하고 다른 const
는 생략 할 수 있습니다.
// Only one const, which establishes the constant context.const pointAndLine = { 'point': [ImmutablePoint(0, 0)], 'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],};
객체의 타입 검출
런타임에서 객체의 타입을 얻고 싶다면, Type 객체를 반환하는 Object
의 프로퍼티인 runtimeType
을 사용하세요.
print('The type of a is ${a.runtimeType}');
객체의 타입을 테스트하려면, runtimeType
대신 타입 테스트 연산자를 사용하세요. 프로덕션 환경에서, object is Type
테스트가 object.runtimeType == Type
테스트 보다 더 안전합니다.
여기까지 클래스를 어떻게 사용 하는지에 대해 알아보았습니다. 나머지 섹션에서는 어떻게 구현 하는지에 대해 알아보겠습니다.
인스턴스 변수
인스턴스 변수는 다음과 같이 선언합니다.
class Point { double? x; // Declare instance variable x, initially null. double? y; // Declare y, initially null. double z = 0; // Declare z, initially 0.}
초기화 되지 않은 인스턴스 변수는 null
값을 가집니다.
모든 인스턴스 변수는 내부적으로 getter 메서드를 실행합니다. initializers가 없는 final이 아닌 변수 그리고 late final
인스턴스 변수 또한 내부적으로 setter 메서드를 실행합니다. 더 자세히 알고 싶다면, Getters and setters를 참고하세요.
late
가 아닌 인스턴스 변수가 선언 된 동시에 초기화 되면, 생성자와 해당 initializer 목록이 실행되기 전에 값이 설정됩니다.
class Point { double? x; // Declare instance variable x, initially null. double? y; // Declare y, initially null.}void main() { var point = Point(); point.x = 4; // Use the setter method for x. assert(point.x == 4); // Use the getter method for x. assert(point.y == null); // Values default to null.}
인스턴스 변수는 final
로 선언 될 수 있고, 그런 경우에는 단 한 번만 값이 정확하게 할당됩니다. final
과 non-late
인스턴스 변수를 선언 할 때 생성자 매개변수나, 생성자의 initializer list 를 사용해 초기화 하세요.
class ProfileMark { final String name; final DateTime start = DateTime.now();ProfileMark(this.name); ProfileMark.unnamed() : name = '';}
생성자 바디가 시작된 후에 final
인스턴스 변수의 값을 할당하고 싶다면, 다음 중 하나를 사용하세요.
- Use a factory constructor.
late final
를주의해서 사용하세요: initializer가 없는late final
는 API에 setter를 추가합니다.
생성자
해당 클라스와 동일한 이름을 가지는 함수를 생성하면서 생성자를 선언 할 수 있습니다.(선택적으로 Named constructors에 명시되어 있는 식별자를 사용해도 됩니다.)
class Point { double x = 0; double y = 0;Point(double x, double y) { // See initializing formal parameters for a better way // to initialize instance variables. this.x = x; this.y = y; }}
현재 인스턴스를 참조하고 싶다면, this
키워드를 사용하세요.
Initializing formal parameters
생성자 인수를 인스턴스 변수에 할당하는 패턴은 자주 쓰입니다. Dart에서는 그것을 더 쉽게 수행합니다.
매개변수로 인스턴스 변수를 초기화 하는 것은, 무조건 초기화 되어야만 하거나 기본 값이 주어져야하는, null이 아니거나 final
인스턴스 변수만 가능합니다.
class Point { final double x; final double y;// Sets the x and y instance variables // before the constructor body runs. Point(this.x, this.y);}
initializing formals로 주어진 변수는 초기화 리스트 범위에서 암묵적으로 final 입니다.
Default constructors
생성자를 선언하지 않았다면, 기본 생성자가 주어집니다. 기본 생성자는 인수가 없고, superclass의 인수가 없는 생성자를 호출합니다.
생성자는 상속되지 않는다.
Subclass는 superclass로 부터 생성자를 상속받지 않습니다. 생성자를 선언하지 않은 subclass는 기본 생성자만을 가집니다.
이름이 있는(Named) 생성자
다수의 생성자를 구현하거나, 코드의 명확성을 더하고 싶다면 이름이 있는 생성자를 사용하세요.
const double xOrigin = 0;const double yOrigin = 0; class Point { final double x; final double y; Point(this.x, this.y); // Named constructorPoint.origin() : x = xOrigin, y = yOrigin;}
superclass's의 생성자는 subclass로 상속되지 않는 다는 것을 꼭 기억하세요. 만약 subclass에서 superclass와 같은 생성자를 사용하고 싶다면, subclass에서도 똑같이 구현해야 합니다.
Superclass의 Non-default 생성자 호출
디폴트로, sublcass의 생성자는 superclass의 이름이 없고(unnamed), 인수가 없는(no-argument) 생성자를 호출합니다. superclass의 생성자는 생성자 바디의 처음에 호출됩니다. 만약 initializer list가 사용되면, superclass가 호출되기 전에 실행됩니다. 요약하자면, 실행 순서는 다음과 같습니다.
- initializer list
- superclass’s no-arg(인수가 없는) constructor
- main class’s no-arg constructor
만약 superclass가 이름이 없고, 인수가 없는 생성자가 없다면, 반드시 superclass의 생성자 중 하나를 선택해서 호출해야 합니다. 생성자 바디에 콜론(:
)를 붙혀서 선택한 superclass의 생성자를 명시하세요.
superclass의 생성자로 전해지는 인수가, 생성자가 실행되기 전에 평가되기 때문에 인수는 함수 호출에서 처럼 표현식이 될 수 있습니다.
class Employee extends Person { Employee() : super.fromJson(fetchDefaultData()); // ···}
수동으로 superclass의 생성자 매개변수를 넘겨주는 것을 피하기 위해서, super-initializer 매개변수를 superclass의 생성자로 넘겨주면 됩니다. 이 피쳐를 리다이랙팅 생성자와 사용하는 것은 불가능합니다. Super-initializer 매개변수는 initializing formal 매개변수 와 비슷한 문법과 의미를 가집니다.
class Vector2d { final double x; final double y;Vector2d(this.x, this.y);}class Vector3d extends Vector2d { final double z;// Forward the x and y parameters to the default super constructor like: // Vector3d(final double x, final double y, this.z) : super(x, y); Vector3d(super.x, super.y, this.z);}
만약 super 생성자 호출에 이미 선택(optional positional) 인수가 있는 경우, super-initializer 매개변수는 선택 인수가 될 수 없습니다. 하지만, 명명된(named) 매개변수로 선언하는 것은 가능합니다.
class Vector2d { // ...Vector2d.named({required this.x, required this.y});}class Vector3d extends Vector2d { // ...// Forward the y parameter to the named super constructor like: // Vector3d.yzPlane({required double y, required this.z}) // : super.named(x: 0, y: y); Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);}
Initializer list
superclass 생성자를 호출할 뿐만 아니라 생성자 바디가 실행되기 전에 인스턴스 변수를 초기화할 수도 있습니다. 이니셜라이저는 쉼표로 구분합니다.
// Initializer list sets instance variables before// the constructor body runs.Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! { print('In Point.fromJson(): ($x, $y)');}
개발하는 동안, assert
를 초기화 리스트 안에 넣어서 input에 조건을 추가 할 수 있습니다.
Point.withAssert(this.x, this.y) : assert(x >= 0) { print('In Point.withAssert(): ($x, $y)');}
리디렉팅 생성자(Redirecting constructors)
가끔 생성자의 목적이 같은 클래스 내의 다른 생성자로 리디렉트하는 겨우 일 때가 있습니다. 리디렉팅 생성자의 바디는 비어있고, 콜론(:
) 뒤에 나오는, 클래스 이름 대신에 this
를 사용한 생성자 호출로 구성됩니다.
class Point { double x, y;// The main constructor for this class. Point(this.x, this.y);// Delegates to the main constructor. Point.alongXAxis(double x) : this(x, 0);}
상수(Constant) 생성자
어떤 클래스가 절대 바뀌지 않는 객체를 생성한다면, 이 객체를 컴파일 타임 상수로 만들 수 있습니다. 생성자를 const
로 정의하고 모든 인스턴스 변수를 final
로 선언하면 됩니다.
class ImmutablePoint { static const ImmutablePoint origin = ImmutablePoint(0, 0);final double x, y;const ImmutablePoint(this.x, this.y);}
상수 생성자가 항상 상수를 생성하는 건 아닙니다. 더 자세히 알고 싶다면, using constructors를 참고하세요.
Factory 생성자
항상 어떤 클래스의 새로운 인스턴스를 생성하지 않는 생성자를 구현하고 싶다면, factory
키워드를 사용하세요. 예를 들면, factory 생성자는 인스턴스를 캐쉬나 서브타입에서 반환 할 수 있습니다. Factory 생성자는, final 변수를 초기화 리스트에서 다루지 않는 로직을 사용하여 초기화 하는 방법으로도 사용 할 수 있습니다.
다음 예제에서 Logger
factory 생성자는 캐쉬에서 객체를 반환하고, Logger.fromJson
factory 생성자는 final 변수를 JSON 객체로 부터 초기화 합니다.
class Logger { final String name; bool mute = false;// _cache is library-private, thanks to // the _ in front of its name. static final Map<String, Logger> _cache = <String, Logger>{};factory Logger(String name) { return _cache.putIfAbsent(name, () => Logger._internal(name)); }factory Logger.fromJson(Map<String, Object> json) { return Logger(json['name'].toString()); }Logger._internal(this.name);void log(String msg) { if (!mute) print(msg); }}
다른 생성자를 호출 할 때 처럼, factory 생성자를 호출하세요.
var logger = Logger('UI');logger.log('Button clicked');var logMap = {'name': 'UI'};var loggerJson = Logger.fromJson(logMap);
Methods
인스턴스 메서드
객체의 인스턴스 메서드는 인스턴스 변수와 this
에 접근 할 수 있습니다. 다음 예제의 distanceTo()
메서드가 인스턴스 메서드의 예입니다.
import 'dart:math';class Point { final double x; final double y;Point(this.x, this.y);double distanceTo(Point other) { var dx = x - other.x; var dy = y - other.y; return sqrt(dx * dx + dy * dy); }}
연산자
연산자는 특별한 이름을 가진 인스턴스 메소드 입니다. Dart는 클래스 내에서 다음의 연산자들을 재정의 할 수 있습니다.
< | + | | | >>> |
> | / | ^ | [] |
<= | ~/ | & | []= |
>= | * | << | ~ |
– | % | >> | == |
연산자 선언은 operator
식별자를 사용합니다. 다음의 예제는 vector 덧셈(+
)과 뺄셈(-
)를 정의합니다.
class Vector { final int x, y;Vector(this.x, this.y);Vector operator +(Vector v) => Vector(x + v.x, y + v.y); Vector operator -(Vector v) => Vector(x - v.x, y - v.y);// Operator == and hashCode not shown. // ···}void main() { final v = Vector(2, 3); final w = Vector(2, 2);assert(v + w == Vector(4, 5)); assert(v - w == Vector(0, 1));}
Getter 와 Setter
Getter와 setter는 객체의 프로퍼티를 읽고(get) 쓰는(set) 함수 입니다. 모든 인스턴스 변수는 암묵적으로 getter와 setter를 가진다는 것을 기억하세요. get
와 set
키워드를 사용하여 getter와 setter를 구연하므로써 추가 프로퍼티를 생성 할 수 있습니다.
class Rectangle { double left, top, width, height;Rectangle(this.left, this.top, this.width, this.height);// Define two calculated properties: right and bottom. double get right => left + width; set right(double value) => left = value - width; double get bottom => top + height; set bottom(double value) => top = value - height;}void main() { var rect = Rectangle(3, 4, 20, 15); assert(rect.left == 3); rect.right = 12; assert(rect.left == -8);}
(번역 부정확) getters 및 setters를 사용하면, 클라이언트 코드를 변경하지 않고, 인스턴스 변수부터 시작하여 나중에 메서드로 래핑할 수 있습니다.
추상 메서드(Abstract methods)
인스턴스, getter, setter 메서드는 추상화 될 수 있습니다. 추상화란 인터페이스만 구현한 상태로 나머지 부분은 다른 클래스들에게 맡기는 것을 의미합니다. 추상 메서드는 오직 추상 클래스(abstract classes)에 존재 할 수 있습니다.
메서드를 추상화 하려면, 메서드 바디 대신에 세미콜론(;)을 사용하세요.
abstract class Doer { // Define instance variables and methods...void doSomething(); // Define an abstract method.}class EffectiveDoer extends Doer { void doSomething() { // Provide an implementation, so the method is not abstract here... }}
추상 클래스(Abstract classes)
abstract
수식어를 사용하여, 추상 클래스(인스턴스화 될 수 없는)를 선언하세요. 추상 클래스는 인터페이스를 정의 할 때 유용하며, 종종 일부 구현과 함께 사용됩니다
추상 클래스는 추상 메서드를 가질 수 있습니다. 다음은 추상 메서드를 가지는 추상 클래스의 예제입니다.
// This class is declared abstract and thus// can't be instantiated.abstract class AbstractContainer { // Define constructors, fields, methods...void updateChildren(); // Abstract method.}
암묵적 인터페이스(Implicit interfaces)
모든 클래스는 암묵적으로 클래스의 인스턴스 멤버를 포함하는 인터페이스를 정의합니다. 만약 B 클래스를 상속받지 않은 A 클래스가 B의 API를 사용하고 싶다면 B 인터페이스를 구현해야 합니다.
하나의 클래스는 implements
문 안에 하나 혹은 여러개의 인터페이스를 구현하고, 인터페이스에 필요한 API들을 제공합니다.
// A person. The implicit interface contains greet().class Person { // In the interface, but visible only in this library. final String _name;// Not in the interface, since this is a constructor. Person(this._name);// In the interface. String greet(String who) => 'Hello, $who. I am $_name.';}// An implementation of the Person interface.class Impostor implements Person { String get _name => '';String greet(String who) => 'Hi $who. Do you know who I am?';}String greetBob(Person person) => person.greet('Bob');void main() { print(greetBob(Person('Kathy'))); print(greetBob(Impostor()));}
다음은 여러개의 인터페이스를 가지는 클래스 입니다.
class Point implements Comparable, Location {...}
클래스 확장(Extending a class)
Subclass를 만들고 싶다면, extends
를 사용하세요. 그 클래스 안에서 superclass를 참조하고 싶다면 super
를 사용하면 됩니다.
class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); } // ···} class SmartTelevision extends Television { void turnOn() { super.turnOn(); _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); } // ···}
extends
, 의 다른 사용법을 알고 싶다면, parameterized types의 generics를 참고하세요.
Overriding members
Subclasses는 operators를 포함한 인스턴스 메서드, getter, setter를 오버라이드 하는 것이 가능합니다. @override
표기를 사용하여 의도적으로 멤버를 오버라이딩 할 수 있습니다.
class Television { // ··· set contrast(int value) {...}} class SmartTelevision extends Television { @override set contrast(num value) {...} // ···}
오버라이딩 메서드 선언은 그 메서드가 오버라이드 하는 메서드와 여러가지 방법으로 매치되어야 합니다.
- 리턴 타입은 반드시 오버라이딩 되는 함수의 리턴 타입(서브 타입도 가능)과 동일해야 합니다.
- 인수의 타입은 오버라이딩 되는 함수의 인수 타입(supertype도 가능)과 반드시 동일해야 합니다. 앞선 예제에서,
SmartTelevision
의 setter인contrast
는 인수의 타입을int
의 supertype인num
으로 변경합니다. - 만약 오버라이딩 되는 함수가 n개의 선택 매개변수를 가진다면, 오버라이딩 하는 함수 또한 n 개의 선택 매개변수를 가져야 합니다.
- Generic method는 generic이 아닌 것을 오버라이드 할 수 없고, 그 반대도 마찬가지 입니다.
메서드의 매개변수나 인스턴스 변수의 타입을 축소하고 싶은 때가 있을겁니다. 이런 행동은 보통의 룰을 어기는 행위이고, 런타임에서 에러를 발생 시킬 수도 있는 다운캐스트와 비슷합니다. 여전히, 코드가 타입 에러를 발생시키지 않는다고 확신 할 수 있다면, 타입을 축소하는 것은 가능합니다. 이런 경우에, covariant keyword
를 매개변수 선언 할 때 사용하면 됩니다. 자세한 정보를 원한다면, Dart language specification를 참고하세요.
noSuchMethod()
코드가 존재하지 않는 함수나, 인스턴스 변수를 접근하는 것을 감지하거나, 그것에 대해 처리하고 싶다면 noSuchMethod()
함수를 오버라이드 하면 됩니다.
class A { // Unless you override noSuchMethod, using a // non-existent member results in a NoSuchMethodError. @override void noSuchMethod(Invocation invocation) { print('You tried to use a non-existent member: ' '${invocation.memberName}'); }}
구현되지 않은 함수가 다음 중 하나라도 만족한다면, 그 함수는 호출 할 수 없습니다.
- 리시버가 static 타입
dynamic
일 때. - 리시버는 구현되지 않은 메서드(추상 메스드는 가능)를 정의하는 static 타입을 가지며, 리시버의 dynamic 타입은 클래스
Object
와 다른noSuchMethod()
를 구현 했을 떄.
확장 메서드(Extension methods)
확장 메서드는 이미 존재하는 라이브러리에 기능을 추가하는 방법 입니다. 우리는 확장 메서드가 무엇인지 모른채 사용 할 수도 있습니다. 예를 들면, 만약 IDE를 사용해서 코드를 구현하면, IDE는 regular 메서드가 아닌 확장 메서드를 추천 할 수도 있습니다.
다음은 string_apis.dart
에 정의되어 있는 String
의 확장 메서드인 parseInt()
의 예제 입니다.
import 'string_apis.dart';...print('42'.padLeft(5)); // Use a String method.print('42'.parseInt()); // Use an extension method.
확장 메서드의 구현과 활용을 더 자세히 알고 싶다면, extension methods page를 참고하세요.
열거 타입(Enumerated types)
열거 타입(Enumerated types), 종종 enumerations 나 enums로도 불립니다, 는 정해진 수의 상수 값을 가지는 특별한 종류의 클래스 입니다.
Enums 선언하기
enum Color { red, green, blue }
더 발전된 Enums 사용하기
Dart는 필드, 메서드, 상수 생성자 같이 수가 정해져 있는 상수 인스턴스가 있는 클래스를 선언하는 데 enum을 사용하는 것이 가능합니다.
더 발전된 enum을 선언하려면, 클래스와 비슷하지만 몇가지 다른 문법을 따라야 합니다.
- including those added by mixins으로 추가되는 변수를 포함한 인스턴스 변수들은 모두
final
로 선언되어야만 합니다. - 모든 generative constructors는 상수어야만 합니다.
- Factory constructors는 고정된 enum 인스턴스 중 하나를 반환 할 수 있습니다.
- 다른 클래스들은
Enum
으로 확장 될 수 없습니다. index
,hashCode
, 항등 연산자인==
는 오버라이드 할 수 없습니다.values
로 named된 멤버는 enum에 선언 될 수 없습니다. 만약 enum에 선언한다면, 자동으로 생성된 정적values
getter와 충돌합니다.- Enum의 모든 인스턴스들은 선언의 처음 부분에 선언되어야 하고, 반드시 한 개 이상의 인스턴스가 선언되어야 합니다.
다음은 다수의 인스턴스, 인스턴스 변수, getter 그리고 인터페이스를 가지는 enum의 선언 예제 입니다.
enum Vehicle implements Comparable<Vehicle> { car(tires: 4, passengers: 5, carbonPerKilometer: 400), bus(tires: 6, passengers: 50, carbonPerKilometer: 800), bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0);const Vehicle({ required this.tires, required this.passengers, required this.carbonPerKilometer, });final int tires; final int passengers; final int carbonPerKilometer;int get carbonFootprint => (carbonPerKilometer / passengers).round(); @override int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint;}
발전된 enums를 선언하는 것을 더 자세히 알고 싶다면, Classes를 참고하세요.
Enums 사용하기
정적 변수에 접근하는 것 처럼 열거 값에 접근하면 됩니다.
final favoriteColor = Color.blue;if (favoriteColor == Color.blue) { print('Your favorite color is blue!');}
Enum 각각의 값들은, 0 부터 시작해서 enum의 값을 가져오는, index
getter를 가집니다. 예를 들면, 첫 번째 값은 index 0을 가지고 두 번째 값은 index 1을 가집니다.
assert(Color.red.index == 0);assert(Color.green.index == 1);assert(Color.blue.index == 2);
열거 값의 리스트를 얻고 싶다면, enum의 values
상수를 사용하세요.
List<Color> colors = Color.values;assert(colors[2] == Color.blue);
Switch 구문에 enum을 사용해도 됩니다. 하지만 enum의 모든 값들을 처리하지 않으면 경고가 발생합니다.
var aColor = Color.blue;switch (aColor) { case Color.red: print(''Red as roses!''); break; case Color.green: print(''Green as grass!''); break; default: // Without this, you see a WARNING. print(aColor); // 'Color.blue'}
만약 이름이 있는 열거 값에 접근하고 싶다면, Color.blue
의 'blue'
처럼 .name
프로퍼티를 사용하면 됩니다.
print(Color.blue.name); //'blue'
클래스에 피쳐(features) 추가하기:Mixins
Mixins은 다수의 클래스 계층에서 클래스의 코드를 재사용 할 수 있는 방법입니다.
Mixin을 사용하려면, 밑의 코드처럼 with
키워드와 사용 할 mixin의 이름을 명시하면 됩니다.
class Musician extends Performer with Musical { // ···} class Maestro extends Person with Musical, Aggressive, Demented { Maestro(String maestroName) { name = maestroName; canConduct = true; }}
Mixin을 구현하려면, 객체를 확장하며, 생성자가 없는 클래스를 생성하세요. Mixin을 일반 클래스로 사용할 수 있도록 하려면 class
대신 mixin
키워드를 사용하십시오.
mixin Musical { bool canPlayPiano = false; bool canCompose = false; bool canConduct = false; void entertainMe() { if (canPlayPiano) { print('Playing piano'); } else if (canConduct) { print('Waving hands'); } else { print('Humming to self'); } }}
때때로 mixin으로 사용 할 수 있는 타입을 제한합니다. 예를 들면, mixin은 mixin은이 정의하지 않은 메서드를 호출할 수 있는가에 따라 달라질 수 있습니다. 다음 예제 처럼 on
키워드를 사용해 사용할 수 있는 superclass를 제한하므로써 mixin의 사용을 제한 할 수 있습니다.
class Musician { // ...}mixin MusicalPerformer on Musician { // ...}class SingerDancer extends Musician with MusicalPerformer { // ...}
위의 코드에서 Musician
클래스를 확장, 구현하는 클래스들만 MusicalPerformer
mixin을 사용 할 수 있습니다. SingerDancer
가 Musician
을 확장하기 때문에, SingerDancer
는 MusicalPerformer
mixin을 사용 할 수 있습니다.
클래스 변수와 메소드
static
키워드를 사용해 class-wide한 변수와 메소드를 선언하세요.
Static variables
Static 변수(클래스 변수)는 class-wide한 상수와, 상태를 정의 할 때 유용합니다.
class Queue &123; static const initialCapacity = 16; // ···&125;void main() &123; assert(Queue.initialCapacity == 16);&125;
Static 변수는 사용되기 전에는 초기화되지 않습니다.
Static methods
Static 메소드(클래스 메소드)는 인스턴스 위에서 실행되지 않기 때문에 this
에 접근 할 수 없습니다. 그러나 static 변수에 대한 접근은 가능합니다. 다음 예제에서 코드는 클래스에서 직접 static 메소드를 실행합니다.
import 'dart:math';class Point { double x, y; Point(this.x, this.y);static double distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); }}void main() { var a = Point(2, 2); var b = Point(4, 4); var distance = Point.distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance);}
Static 메소드를 컴파일 타임 상수로 사용 할 수 있습니다. 예를 들면, 상수 생성자의 매개변수로 static 메소드를 넘겨 줄 수 있습니다.
제네릭(Generics)
기본 배열 타입의 API 문서를 보면,List
, 타입이 사실 List<E>
로 되어 있는 걸 볼 수 있다. <…> 표시는 List를 제네릭 (or 매개변수화)-타입을 매개변수로 받는-타입으로 지정한다.관례상, 대부분의 타입 변수는 E, T, S, K, V 같은 single-letter 이름을 가집니다.
왜 제네릭을 쓸까?
보통 타입 세이프티 때문에 제네릭을 사용하지만, 사실 더 많은 기능을 수행합니다.
- 제네릭 타입을 정확하게 명시한 코드는 더 잘 작성된 코드 입니다.
- 코드 중복를 줄이기 위해 제네릭을 사용 할 수 있습니다.
만약 리스트가 string 값만 가지게 하고 싶다면, List<String>
로 리스트를 선언하면 된다. 그렇게 하므로써, 당신의 동료들, 그리고 툴들이 string이 아닌 값들은 list에 추가 될 수 없음을 바로 알 수 있습니다.
var names = <String>[];names.addAll(['Seth', 'Kathy', 'Lars']);names.add(42); // Error
제네릭을 사용하는 또 다른 이유는 코드 중복을 줄이기 위함입니다. 제네릭은 정적인 분석의 이점을 챙기면서, 많은 타입 중에 단일 인터페이스와 구현을 공유 할 수 있게 합니다. 예를 들면, 객체를 캐싱하는 인터페이스를 생성한다고 해봅시다.
abstract class ObjectCache { Object getByKey(String key); void setByKey(String key, Object value);}
만약 위 인터페이스의 string 버젼이 필요하다면 다음과 같이 선언하면 됩니다.
abstract class StringCache { String getByKey(String key); void setByKey(String key, String value);}
나중에 만약 number 버젼이 필요해 졌다면... 어떻게 하는 게 좋을까요?
제네릭 타입은 위처럼 모든 인터페이스를 생성해야하는 문제를 해결해줍니다. 타입 파라미터를 가지는 하나의 단일 인터페이스만을 구현하면 됩니다.
abstract class Cache<T> { T getByKey(String key); void setByKey(String key, T value);}
위의 코드에서, T는 stand-in 타입입니다. 이것은 개발자가 추후에 타입을 마음대로 정의 할 수 있게 해주는 플레이스 홀더입니다.
콜렉션 리터럴 사용하기
List, set 그리고 map 리터럴은 매개변수화 될 수 있습니다. 매개변수화된 리터럴은, <type>
(list, set) <keyType, valueType>
(map)를 시작 괄호에 추가하는 것만 빼면, 일반 리터럴과 비슷하게 생겼습니다. 다음은 타입이 있는 리터럴의 예제입니다.
var names = <String>['Seth', 'Kathy', 'Lars'];var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};var pages = <String, String>{ 'index.html': 'Homepage', 'robots.txt': 'Hints for web robots', 'humans.txt': 'We are people, not machines'};
생성자에 매개변수화된 타입 사용하기
생성자를 사용 할 때 하나 혹은 다수의 타입을 특정하고 싶다면, 타입을 클래스 이름 다음의 <...>
(angle brackets) 안에 넣으세요.
var nameSet = Set<String>.from(names);
다음 예제에서는 정수 키와 View 타입의 값을 가지는 map을 생성합니다.
var views = Map<int, View>();
제네릭 콜렉션과 그것이 가지는 타입들
Dart 제네릭 타입은 구체화 되어있습니다. 그것은 런타임에 타입들에 대한 정보를 가져온다는 것을 의미합니다. 예를 들면, 콜렉션의 타입을 다음과 같이 테스트 할 수 있습니다.
var names = <String>[];names.addAll(['Seth', 'Kathy', 'Lars']);print(names is List<String>); // true
매개변수화 된 타입을 제한하기
제네릭 타입을 구현 할 때, 인자로 제공되는 타입을 제한 해야하여 인수가 특정 타입의 서브타입이 되게 해야할 경우가 발생합니다. Extends
를 사용하면 가능합니다.
가장 자주 사용되는 방법은 non-nullalbe인 것을 보장하기 위해, Object
(디폴트인 Object? 대신)의 서브 타입으로 만드는 것 입니다.
class Foo<T extends Object> { Any type provided to Foo for T must be non-nullable.}
Object
이외의 타입들과 함께 extends
를 사용 할 수 있습니다. 다음은 SomBaseClass
를 확장하는 예로, SomeBaseClass
의 멤버들은 타입 T
의 객체로 볼 수 있습니다.
class Foo<T extends SomeBaseClass> { // Implementation goes here... String toString() => "Instance of 'Foo<$T>'";} class Extender extends SomeBaseClass {...}
SomeBaseClass
이나 이것의 서브 타입을 제네릭 인자로 사용하는 것도 가능합니다.
var someBaseClassFoo = Foo<SomeBaseClass>();var extenderFoo = Foo<Extender>();
제네릭 인자를 특정하지 않는 것도 가능합니다.
var foo = Foo();print(foo); // Instance of 'Foo<SomeBaseClass>'
non-SomeBaseClass
타입으로 특정하는 것은 에러를 발생시킵니다.
var foo = Foo<Object>();
제네릭 메소드 사용하기
초창기에 Dart의 제네릭은 클래스만 지원했습니다. 새로운 문법에서 제네릭 메소드라는 메소드는 메소드와 함수에 타입 인자를 추가 할 수 있습니다.
T first<T>(List<T> ts) { // Do some initial work or error checking, then... T tmp = ts[0]; // Do some additional checking or processing... return tmp;}
라이브러리와 가시성(Libraries and visibility)
import
와 library
디렉티브는 코드를 모듈화하고 공유하는 것을 도와줍니다. 라이브러리는 API들을 제공 할 뿐만 아니라, 관리(privacy)의 단위가 됩니다. 언더스코어(_
)로 시작하는 식별자들은 오직 그 라이브러리 안에서만 보입니다(visible). library
디렉티브를 사용하지 않았다고 해도, 모든 Dart 앱은 라이브러리입니다.
라이브러리들은 packages를 사용해 분산 될 수 있습니다.
라이브러리 사용하기(Using libraries)
어떤 라이브러리의 네임스페이스가 다른 라이브러리에서 사용된다면, import
를 사용하세요.
예를 들면, Dart 웹앱은 보통 dart:html 라이브러리를 사용합니다. 다음 예제 처럼 말이죠.
import 'dart:html';
import
가 필요한 인자는 라이브러리를 특정 지을 수 있는 URI 뿐입니다. 내장 라이브러리를 사용하기 위해서는, dart:
라는 특별한 규칙을 따릅니다. 이외의 라이브러리를 사용하고 싶다면, 파일 시스템 경로나 package:
를 사용하면 됩니다. package:
는 pub 같은 패키지 매니저들이 제공하는 라이브러리를 특정 지을 때 사용합니다.
import 'package:test/test.dart';
라이브러리 프리픽스 특정하기(Specifying a library prefix)
같은 식별자를 가지는 두 개의 라이브러리를 import 하면 충돌이 발생합니다. 그럴 때 프리픽스를 특정하면 문제가 해결됩니다. 예를 들면, 라이브러리1 과 라이브러리2가 Element 클래스를 가진다고 하면, 코드는 다음과 같을 것 입니다.
import 'package:lib1/lib1.dart';import 'package:lib2/lib2.dart' as lib2;// Uses Element from lib1.Element element1 = Element();// Uses Element from lib2.lib2.Element element2 = lib2.Element();
라이브러리의 일부만 가져오기(Importing only part of a library)
라이브러리의 일부만 필요하다면, 다음과 같이 라이브러리를 선택적으로 import 할 수 있습니다.
// Import only foo.import 'package:lib1/lib1.dart' show foo;// Import all names EXCEPT foo.import 'package:lib2/lib2.dart' hide foo;
라이브러리를 느리게 가져오기(Importing only part of a library)
지연된 로딩(lazy loading이라고도 불린다.)은 웹앱이 라이브러리가 필요할 때 로드하게 해줍니다. 다음은 지연된 로딩을 사용해야할 케이스 입니다.
- 웹앱의 초기 로딩 시간을 줄이기 위해
- A/B 테스팅을 진행하기 위해 — 예를 들어, 대안이 되는 알고리즘들의 구현을 시험해 볼 때가 있다.
- To load rarely used functionality, such as optional screens and dialogs.
라이브러리를 필요 할 때 로드하고 싶다면, deferred as
를 사용해 import 하세요.
import 'package:greetings/hello.dart' deferred as hello;
라이브러리를 사용해야 한다면, loadLibrary()
를 라이브러리의 식별자에 사용해 호출하세요.
Future<void> greet() async { await hello.loadLibrary(); hello.printGreeting();}
앞선 코드에서, await
키워드는 라이브러리가 로드 될 때 까지 실행을 멈춥니다. async
와 await
에 대해 더 자세히 알고 싶다면, asynchrony support를 참고하세요.
loadLibrary()
를 한 라이브러리에 여러번 호출해도 한 번만 로드되기 때문에 에러가 발생하지 않습니다.
로딩을 지연시킬 때 다음을 꼭 기억해두세요.
- 지연된 라이브러리의 상수는 importing 파일에서는 상수가 아닙니다. 꼭 기억하세요, 이 상수는 지연된 라이브러리가 로드 되기 전에는 존재하지 않는 상수 입니다.
- Importing 파일에서 지연된 라이브러리에 타입을 사용 할 수 없습니다. 대신, 지연된 라이브러리와 importing 파일 모두에서 가져온 라이브러리로 인터페이스 타입을 변경하는 것을 고려하세요.
- Dart는 암묵적으로
loadLibrary()
를deferred as namespace
를 사용하여 정의한 네임스페이스에 삽입합니다.LoadLibrary()
는Future
를 반환합니다 .
라이브러리 구현하기(Implementing libraries)
라이브러리 구현에 대한 자세한 방법은 Create Library Packages과 다음을 고려하세요.
- 라이브러리 소스 코드를 어떻게 짤 것인가.
export
directive를 어떻게 사용 할 것인가.part
directive를 언제 사용 할 것인가.library
directive를 언제 사용 할 것인가.- 다수의 플랫폼을 지원하는 라이브러리를 구현 할 때 어떻게 조건적인 imports 와 exports를 사용 할 것인가.
비동기 지원
Dart의 라이브러리에는 Future
와 Stream
객체를 반환하는 함수가 매우 많습니다. 이런 함수들을 비동기(asynchronous) 함수라고 합니다. 이 함수들은 I/O 같이 시간이 오래 걸릴 수도 있는 작업이 완료되기를 기다리지 않고, 값을 반환 할 수 있게 해줍니다.
async
나 await
같은 키워드들은 동기적인 코드처럼 보이는 비동기적인 코드를 이용해 비동기 프로그래밍을 가능하게 합니다.
Futures 다루기
당신이 만약 완료된 Future의 결과를 원한다면, 두가지 옵션이 있습니다.
async
와await
을 asynchronous programming codelab에 나와있는 것 처럼 사용하세요.- Future API를 library tour에 나와있는 것 처럼 사용하세요. .
async
나 await
을 사용하는 코드는 비동기적이지만, 외관상 동기적인 코드와 비슷합니다. 예를 들면, 다음은 await
을 사용해 비동기 함수의 결과를 기다리는 코드 입니다.
await lookUpVersion();
await
을 사용하려면, 해당 코드는 async
로 마크된, async
함수 안에 있어야 합니다.
Future<void> checkVersion() async { var version = await lookUpVersion(); // Do something with version}
try
, catch
, finally
를 사용하여 await
을 사용한 코드의 에러를 다루고, 깔끔하게 정리하세요.
try { version = await lookUpVersion();} catch (e) { // React to inability to look up the version}
async
함수 안에 여러개의 await
를 사용해도 됩니다. 예를 들면, 다음의 코드는 3번 함수의 결과를 기다립니다.
var entrypoint = await findEntryPoint();var exitCode = await runExecutable(entrypoint, args);await flushThenExit(exitCode);
await
expression
에서 expression
의 값은 보통 Future 입니다. 만약 아니라면, 자동으로 Future가 값을 감싸게 됩니다. 이 Future 객체는 객체를 반환하는 promise 를 나타냅니다. await
expression
의 값은 그 반환된 객체 입니다. await 표현은 그 객체가 사용 가능해질 때까지 실행을 멈춥니다.
If you get a compile-time error when using await
을 사용하면서 컴파일 타임 에러가 발생했다면,await
이async
함수 안에 있는지 확인해보세요.예를 들면, 앱의 main()
함수의 바디에 await
함수를 사용한다면, main()
는 async
로 마크되어 있어야 합니다.
void main() async { checkVersion(); print('In main: version is ${await lookUpVersion()}');}
Future, async
, await
, 의 사용을 더 자세히 배우고 싶다면, asynchronous programming codelab를 참고하세요.
Async 함수 선언하기
async
함수는 바디가 async
식별자로 마크된 함수 입니다.
asnyc
키워드를 함수 앞에 마크하는 것은, 함수가 Future를 반환하게 합니다. 예를 들면, String을 반환하는 다음과 같은 동기 함수가 있습니다.
String lookUpVersion() => '1.0.0';
만약 이 함수를 async
함수로 만들면 Future 값을 반환합니다.
Future<String> lookUpVersion() async => '1.0.0';
함수의 바디에서는 Future API를 사용할 필요가 없다는 것을 알아두세요. Dart는 필요 할 때 Future 객체를 생성합니다. 만약 함수가 쓸모 있는 값을 반환하지 않는다면, 반환 타입을 Future<void>
로 만드세요.
스트림 다루기
스트림에서 값을 가져오고 싶다면, 두 가지 옵션이 있습니다.
async
와 비동기 for문 (await for
)을 사용하세요.- Library tour에 나와있는 것 처럼, Stream API를 사용하세요.
비동기 for문은 다음과 같은 형태를 가집니다.
await for (varOrType identifier in expression) { // Executes each time the stream emits a value.}
위 expression
의 결과는 Stream 타입이어야 합니다. 실행의 흐름은 다음과 같습니다.
- 스트림이 값을 내놓을 때 까지 기다립니다.
- 도출된 값을 변수로 설정하여 for문의 바디를 실행합니다.
- 스트림이 끝날 때까지 1과 2를 반복합니다.
스트림 listening을 끝내고 싶다면, for문을 끝내고 stream을 unsubscribes하는 break
나 return
을 사용하면 됩니다.
비동기 for문을 구현 할 때 런타임 에러가 발생하면,await for
이 async
함수 안에 있는지 확인하세요.예를 들면, 비동기 for문을 앱의 main()
함수에 사용하고 싶다면, main()
함수의 바디는 async
로 마크되어야 합니다.
void main() async { // ... await for (final request in requestServer) { handleRequest(request); } // ...}
비동기 프로그래밍에 대해 더 자세히 알고 싶다면 library tour의 dart:async 섹션을 참고하세요.
제네레이터
시퀀스의 값을 지연된 상태에서 사용하고 싶다면, 제네레이터 함수를 사용하세요. Dart는 두가지 내장 제네레이터 함수를 가지고 있습니다.
동기적인 제네레이터 함수를 구현하고 싶다면, sync*
로 함수의 바디를 표시하고, yield
문으로 값을 사용하세요.
Iterable<int> naturalsTo(int n) sync* { int k = 0; while (k < n) yield k++;}
만약 제네레이터가 재귀의 형태를 가진다면, yield*
를 사용하여 성능을 향상 시킬 수 있습니다.
Iterable<int> naturalsDownFrom(int n) sync* { if (n > 0) { yield n; yield* naturalsDownFrom(n - 1); }}
호출 가능한 클래스(Callable classes)
Dart 클래스의 인스턴스를 함수처럼 호출하고 싶다면, call()
메소드를 구현하세요.
다음의 예제에서, WannabeFunction
클래스는 3개의 string을 받아서 각 문자열을 공백으로 구분하고 느낌표를 추가하는 call() 함수를 정의합니다.
Isolates
대부분의 컴퓨터는, 모바일에서도, 멀티 코어 CPUs를 가집니다. 이 모든 코어를 적절히 사용하기 위해서, 개발자는 예로부터 공유 메모리 쓰레드들을 동시에 사용해왔습니다. 그러나 상태를 공유하며 동시 실행(shared-state concurrency) 하는 것은 에러를 발생 시킬 수 있고, 코드 또한 복잡해집니다.
쓰레드를 대체하기 위해, Dart는 isolates의 안에서 코드를 실행합니다. 각각의 Dart isoate는 하나의 실행 쓰레드를 가지고, 다른 isolates 들과 변할 수 있는 객체들에 대해 공유하지 않습니다.
더 자세히 알고 싶다면, 다음을 참고하세요.
- Concurrency in Dart
- dart:isolate API reference,including Isolate.spawn() andTransferableTypedData
- Background parsing cookbook on the Flutter site
- Isolate sample app
Typedefs
타입 앨리어스 - typedef
키워드로 선언 되기 때문에 typedef로도 불립니다. - 는 타입을 참조하는 간결한 방법입니다. 다음은 IntList
라는 타입 앨리어스를 선언하고, 사용하는 예제입니다.
typedef IntList = List<int>;IntList il = [1, 2, 3];
타입 앨리어스는 타입 매개변수를 가집니다.
typedef ListMapper<X> = Map<X, List<X>>;Map<String, List<String>> m1 = ; // Verbose.ListMapper<String> m2 = ; // Same thing but shorter and clearer.
우리(dart.dev)는 인라인 함수 타입를 함수의 typedefs 대신 사용하는 것을 추천합니다. 그러나 함수의 typedefs는 여전히 사용해도 됩니다.
typedef Compare<T> = int Function(T a, T b);int sort(int a, int b) => a - b;void main() { assert(sort is Compare<int>); // True!}
Metadata
코드에 추가적인 정보를 더하고 싶다면 메타데이터를 사용하세요. 메타데이터 표기는 @
문자로 시작하는 deprecated
같은 컴파일 타임 상수나 상수 생성자의 호출 입니다.
모든 Dart 코드에는 세가지 표기가 가능합니다.:@Deprecated, @deprecated, @override
. 예를 들면, @override
의 용례는 클래스 확장하기를 참고하세요. 다음은 @Deprecated
표기를 사용하는 예제 입니다.
class Television { /// Use [turnOn] to turn the power on instead.@Deprecated('Use turnOn instead') void activate() { turnOn(); } /// Turns the TV's power on. void turnOn() {...} // ···}
여러분들 만의 메타데이터 표기를 만들 수도 있습니다. 다음은 두개의 인자를 받는 @Todo
표기를 정의하는 예제입니다.
library todo;class Todo { final String who; final String what;const Todo(this.who, this.what);}
다음은 @Todo
표기를 사용하는 예제입니다.
import 'todo.dart';@Todo('seth', 'make this do something')void doSomething() { print('do something');}
메타데이터는 라이브러리, 클래스, typedef, 타입 매개변수, 생성자, factory, 함수, 필드, 매개변수, 변수 선언 뒤에 나올 수 있고 import나 export 뒤에도 나올 수 있습니다. Reflection을 이용해 런타임에 메타데이터를 회수 할 수 있습니다.
주석
Dart는 싱글 라인, 멀티 라인, 문서 주석을 지원합니다.
싱글 라인 주석
싱글 라인 주석은 //
로 시작합니다. //
와 해당 라인의 끝까지 Dart의 컴파일러가 무시합니다.
void main() { // TODO: refactor into an AbstractLlamaGreetingFactory? print('Welcome to my Llama farm!');}
멀티 라인 주석
멀티 라인 주석은 /*
로 시작해서 */
로 끝납니다. /*
와 */
사이에 있는 것들은(다음 섹션에서 보게 될 문서 주석이 아니라면) Dart 컴파일러에서 무시합니다.
void main() { /* * This is a lot of work. Consider raising chickens.Llama larry = Llama(); larry.feed(); larry.exercise(); larry.clean(); */}
문서 주석(Documentation comments )
문서 주석은 ///
나 /**
로 시작하는 멀티 라인 또는 싱글 라인 주석 입니다. 연이은 라인에 ///
를 사용하는 것은 멀티 라인 문서 주석과 같은 효과를 발휘합니다.
문서 주석 안에, 괄호로 감싸진 텍스트를 제외한 것은 모두 analyzer가 무시합니다. 괄호를 사용하여, 클래스, 메소드, 필드, 최상위 변수, 함수, 매개변수를 참조 할 수 있습니다. 괄호 안에 있는 이름은 문서화된 프로그램 요소의 렉시컬 스코프 안에서 해석됩니다.
다음은 클래스와 인자들에 대한 참조를 가지고 있는 문서 주석에 대한 예제입니다.
/// A domesticated South American camelid (Lama glama).////// Andean cultures have used llamas as meat and pack/// animals since pre-Hispanic times.////// Just like any other animal, llamas need to eat,/// so don't forget to [feed] them some [Food].class Llama { String? name;/// Feeds your llama [food]. /// /// The typical llama eats one bale of hay per week. void feed(Food food) { // ... }/// Exercises your llama with an [activity] for /// [timeLimit] minutes. void exercise(Activity activity, int timeLimit) { // ... }}
위 클래스의 생성된 문서에서, [feed]
는 문서의 feed
메소드로 링크가 되고, [Food]
는 Food
클래스로 링크가 됩니다.
Dart 코드를 parse하고 HTML 문서를 생성하고 싶다면, dart doc라는 Dart의 문서 생성 툴을 사용하면 됩니다. 생성된 문서의 예를 보고 싶다면, Dart API documentation 를 참고하세요. 주석을 어떻게 달아야하는지 조언을 얻고 싶다면, Effective Dart: Documentation를 참고하세요.
요약(Summary)
이 페이지는 Dart 언어에서 자주 사용하는 피쳐들을 요약했습니다. 더 많은 피쳐들이 개발되고 있지만, 그것들이 이미 존재하는 코드들을 해치지는 않을 것 입니다. Dart에 대해 더 자세히 알고 싶다면, Dart language specification 와 Effective Dart를 참고하세요.
Dart 코어 라이브러리에 대해 더 배우고 싶다면, A Tour of the Dart Libraries를 참고하세요.