dart:core

dart:core

Dart의 dart:core 라이브러리가 제공하는 주요 기능을 소개해 드릴게요. 작지만 아주 중요한 내장 기능들이 이 라이브러리에 담겨 있어요.

출처: dart:core

본문

dart:core 라이브러리(API 레퍼런스)는 작지만 아주 중요한 내장 기능들을 제공해요. 이 라이브러리는 모든 Dart 프로그램에 자동으로 import 돼요.

콘솔에 출력하기 (Printing to the console)

최상위 함수인 print()는 인자 하나(어떤 Object든)를 받아서 그 객체의 문자열 값(toString()이 반환한 값)을 콘솔에 표시해요.

print(anObject);
print('I drink $tea.');

기본 문자열과 toString()에 대한 자세한 내용은 언어 투어의 Strings 섹션을 참고하세요.

숫자 (Numbers)

dart:core 라이브러리는 num, int, double 클래스를 정의하고, 숫자를 다루는 기본 유틸리티를 일부 제공해요.

intdoubleparse() 메서드로 문자열을 각각 정수나 double로 변환할 수 있어요.

assert(int.parse('42') == 42);
assert(int.parse('0x42') == 66);
assert(double.parse('0.50') == 0.5);

또는 numparse() 메서드를 사용할 수도 있어요. 이 메서드는 가능하면 정수를, 그렇지 않으면 double을 만들어요.

assert(num.parse('42') is int);
assert(num.parse('0x42') is int);
assert(num.parse('0.50') is double);

정수의 진법(base)을 지정하려면 radix 매개변수를 추가하세요.

assert(int.parse('42', radix: 16) == 66);

toString() 메서드로 int나 double을 문자열로 변환할 수 있어요. 소수점 오른쪽 자릿수를 지정하려면 toStringAsFixed()를, 문자열의 유효 숫자(significant digit) 수를 지정하려면 toStringAsPrecision()을 사용하세요.

// Convert an int to a string.
assert(42.toString() == '42');

// Convert a double to a string.
assert(123.456.toString() == '123.456');

// Specify the number of digits after the decimal.
assert(123.456.toStringAsFixed(2) == '123.46');

// Specify the number of significant figures.
assert(123.456.toStringAsPrecision(2) == '1.2e+2');
assert(double.parse('1.2e+2') == 120.0);

자세한 내용은 int, double, num의 API 문서와 dart:math 섹션을 참고하세요.

문자열과 정규 표현식 (Strings and regular expressions)

Dart의 문자열은 UTF-16 코드 유닛(code unit)의 불변(immutable) 시퀀스예요. 언어 투어에 문자열에 대한 더 자세한 정보가 있어요. 정규 표현식(RegExp 객체)을 사용해서 문자열 안을 검색하고 문자열의 일부를 바꿀 수 있어요.

String 클래스는 split(), contains(), startsWith(), endsWith() 같은 메서드를 정의해요.

문자열 안에서 검색하기 (Searching inside a string)

문자열 안의 특정 위치를 찾고, 문자열이 특정 패턴으로 시작하는지·끝나는지도 확인할 수 있어요. 예를 들어:

// Check whether a string contains another string.
assert('Never odd or even'.contains('odd'));

// Does a string start with another string?
assert('Never odd or even'.startsWith('Never'));

// Does a string end with another string?
assert('Never odd or even'.endsWith('even'));

// Find the location of a string inside a string.
assert('Never odd or even'.indexOf('odd') == 6);

문자열에서 데이터 추출하기 (Extracting data from a string)

문자열에서 개별 문자를 각각 String이나 int로 얻을 수 있어요. 정확히는 개별 UTF-16 코드 유닛을 얻는 건데요, 높은 번호의 문자(예: 높은음자리표 기호 '\u{1D11E}')는 각각 코드 유닛 두 개로 구성돼요.

부분 문자열(substring)을 추출하거나 문자열을 부분 문자열들의 목록으로 나눌 수도 있어요.

// Grab a substring.
assert('Never odd or even'.substring(6, 9) == 'odd');

// Split a string using a string pattern.
var parts = 'progressive web apps'.split(' ');
assert(parts.length == 3);
assert(parts[0] == 'progressive');

// Get a UTF-16 code unit (as a string) by index.
assert('Never odd or even'[0] == 'N');

// Use split() with an empty string parameter to get
// a list of all characters (as Strings); good for
// iterating.
for (final char in 'hello'.split('')) {
  print(char);
}

// Get all the UTF-16 code units in the string.
var codeUnitList = 'Never odd or even'.codeUnits.toList();
assert(codeUnitList[0] == 78);

참고: 많은 경우 순수 코드 유닛보다는 유니코드 그래핌 클러스터(grapheme cluster)로 작업하고 싶을 거예요. 이는 사용자가 인지하는 문자예요(예: "🇬🇧"는 사용자가 보는 하나의 문자이지만 여러 UTF-16 코드 유닛으로 이뤄져 있어요). 이를 위해 Dart 팀은 characters 패키지를 제공해요.

대문자·소문자로 변환하기 (Converting to uppercase or lowercase)

문자열을 대문자·소문자 변형으로 쉽게 변환할 수 있어요.

// Convert to uppercase.
assert('web apps'.toUpperCase() == 'WEB APPS');

// Convert to lowercase.
assert('WEB APPS'.toLowerCase() == 'web apps');

참고: 이 메서드들은 모든 언어에서 동작하지 않아요. 예를 들어 터키어 알파벳의 점 없는 I(dotless I)는 잘못 변환돼요.

공백 자르기와 빈 문자열 (Trimming and empty strings)

trim()으로 앞뒤의 모든 공백을 제거할 수 있어요. 문자열이 비어 있는지(길이가 0인지) 확인하려면 isEmpty를 사용하세요.

// Trim a string.
assert('  hello  '.trim() == 'hello');

// Check whether a string is empty.
assert(''.isEmpty);

// Strings with only white space are not empty.
assert('  '.isNotEmpty);

문자열 일부 바꾸기 (Replacing part of a string)

문자열은 불변(immutable) 객체예요. 즉 만들 수는 있지만 바꿀 수는 없어요. String API 레퍼런스를 자세히 보면, 어떤 메서드도 String의 상태를 실제로 바꾸지 않는다는 걸 알 수 있어요. 예를 들어 replaceAll() 메서드는 원래 String을 바꾸지 않고 새 String을 반환해요.

var greetingTemplate = 'Hello, NAME!';
var greeting = greetingTemplate.replaceAll(RegExp('NAME'), 'Bob');

// greetingTemplate didn't change.
assert(greeting != greetingTemplate);

문자열 만들기 (Building a string)

프로그래밍 방식으로 문자열을 생성하려면 StringBuffer를 사용할 수 있어요. StringBuffertoString()을 호출할 때까지 새 String 객체를 만들지 않아요. writeAll() 메서드에는 구분자(separator)를 지정할 수 있는 선택적 두 번째 매개변수가 있어요. 이 예시에서는 공백이에요.

var sb = StringBuffer();
sb
  ..write('Use a StringBuffer for ')
  ..writeAll(['efficient', 'string', 'creation'], ' ')
  ..write('.');

var fullString = sb.toString();

assert(fullString == 'Use a StringBuffer for efficient string creation.');

정규 표현식 (Regular expressions)

RegExp 클래스는 JavaScript 정규 표현식과 같은 기능을 제공해요. 문자열의 효율적인 검색과 패턴 매칭에 정규 표현식을 사용하세요.

// Here's a regular expression for one or more digits.
var digitSequence = RegExp(r'\d+');

var lettersOnly = 'llamas live fifteen to twenty years';
var someDigits = 'llamas live 15 to 20 years';

// contains() can use a regular expression.
assert(!lettersOnly.contains(digitSequence));
assert(someDigits.contains(digitSequence));

// Replace every match with another string.
var exedOut = someDigits.replaceAll(digitSequence, 'XX');
assert(exedOut == 'llamas live XX to XX years');

RegExp 클래스로 직접 작업할 수도 있어요. Match 클래스는 정규 표현식 매치에 대한 접근을 제공해요.

var digitSequence = RegExp(r'\d+');
var someDigits = 'llamas live 15 to 20 years';

// Check whether the reg exp has a match in a string.
assert(digitSequence.hasMatch(someDigits));

// Loop through all matches.
for (final match in digitSequence.allMatches(someDigits)) {
  print(match.group(0)); // 15, then 20
}

더 자세히 (More information)

전체 메서드 목록은 String API 레퍼런스와 StringBuffer, Pattern, RegExp, Match의 API 레퍼런스를 참고하세요.

컬렉션 (Collections)

Dart는 리스트, 셋, 맵에 대한 클래스를 포함하는 핵심 컬렉션 API를 함께 제공해요.

팁: 리스트와 셋 모두에서 쓸 수 있는 API를 연습하고 싶다면 Iterable 컬렉션 튜토리얼을 따라 해보세요.

리스트 (Lists)

언어 투어에서 보여 주듯 리터럴로 리스트를 만들고 초기화할 수 있어요. 또는 List 생성자 중 하나를 사용하면 돼요. List 클래스는 리스트에 항목을 추가하고 제거하는 여러 메서드도 정의해요.

// Create an empty list of strings.
var grains = <String>[];
assert(grains.isEmpty);

// Create a list using a list literal.
var fruits = ['apples', 'oranges'];

// Add to a list.
fruits.add('kiwis');

// Add multiple items to a list.
fruits.addAll(['grapes', 'bananas']);

// Get the list length.
assert(fruits.length == 5);

// Remove a single item.
var appleIndex = fruits.indexOf('apples');
fruits.removeAt(appleIndex);
assert(fruits.length == 4);

// Remove all elements from a list.
fruits.clear();
assert(fruits.isEmpty);

// You can also create a List using one of the constructors.
var vegetables = List.filled(99, 'broccoli');
assert(vegetables.every((v) => v == 'broccoli'));

indexOf()를 사용해서 리스트 안 객체의 인덱스를 찾을 수 있어요.

var fruits = ['apples', 'oranges'];

// Access a list item by index.
assert(fruits[0] == 'apples');

// Find an item in a list.
assert(fruits.indexOf('apples') == 0);

sort() 메서드로 리스트를 정렬할 수 있어요. 두 객체를 비교하는 정렬 함수를 제공할 수 있는데, 이 함수는 작으면 < 0, 같으면 0, 크면 > 0을 반환해야 해요. 다음 예시는 Comparable이 정의하고 String이 구현하는 compareTo()를 사용해요.

var fruits = ['bananas', 'apples', 'oranges'];

// Sort a list.
fruits.sort((a, b) => a.compareTo(b));
assert(fruits[0] == 'apples');

리스트는 매개변수화된 타입(제네릭, generics)이므로, 리스트가 담아야 할 타입을 지정할 수 있어요.

// This list should contain only strings.
var fruits = <String>[];

fruits.add('apples');
var fruit = fruits[0];
assert(fruit is String);
fruits.add(5); // Error: 'int' can't be assigned to 'String'

참고: 많은 경우 제네릭 타입을 명시적으로 지정할 필요가 없어요. Dart가 알아서 추론해 주기 때문이에요. ['Dash', 'Dart'] 같은 리스트는 List<String>(문자열 리스트라고 읽어요)으로 이해돼요.

하지만 제네릭 타입을 지정해야 할 때도 있어요. 예를 들어 Dart가 추론할 대상이 없을 때처럼요: []는 어떤 것들의 조합이라도 담을 수 있는 리스트가 될 수 있어요. 그건 보통 원하는 게 아니니까 <String>[]이나 <Person>[] 같은 것을 쓰면 돼요.

전체 메서드 목록은 List API 레퍼런스를 참고하세요.

셋 (Sets)

Dart의 셋(set)은 고유한 항목들의 순서 없는 컬렉션이에요. 셋은 순서가 없으므로 인덱스(위치)로 셋의 항목을 얻을 수 없어요.

// Create an empty set of strings.
var ingredients = <String>{};

// Add new items to it.
ingredients.addAll(['gold', 'titanium', 'xenon']);
assert(ingredients.length == 3);

// Adding a duplicate item has no effect.
ingredients.add('gold');
assert(ingredients.length == 3);

// Remove an item from a set.
ingredients.remove('gold');
assert(ingredients.length == 2);

// You can also create sets using
// one of the constructors.
var atomicNumbers = Set.from([79, 22, 54]);

contains()containsAll()로 하나 이상의 객체가 셋에 있는지 확인할 수 있어요.

var ingredients = Set<String>();
ingredients.addAll(['gold', 'titanium', 'xenon']);

// Check whether an item is in the set.
assert(ingredients.contains('titanium'));

// Check whether all the items are in the set.
assert(ingredients.containsAll(['titanium', 'xenon']));

교집합(intersection)은 두 개의 다른 셋에 모두 있는 항목들로 이뤄진 셋이에요.

var ingredients = Set<String>();
ingredients.addAll(['gold', 'titanium', 'xenon']);

// Create the intersection of two sets.
var nobleGases = Set.from(['xenon', 'argon']);
var intersection = ingredients.intersection(nobleGases);
assert(intersection.length == 1);
assert(intersection.contains('xenon'));

전체 메서드 목록은 Set API 레퍼런스를 참고하세요.

맵 (Maps)

맵(map)은 흔히 딕셔너리(dictionary)나 해시(hash)라고도 하는데, 키-값 쌍(key-value pair)의 순서 없는 컬렉션이에요. 맵은 키를 어떤 값과 연결해서 쉽게 꺼내 쓸 수 있게 해줘요. JavaScript와 달리 Dart 객체는 맵이 아니에요.

간결한 리터럴 문법으로 맵을 선언하거나, 전통적인 생성자를 사용할 수 있어요.

// Maps often use strings as keys.
var hawaiianBeaches = {
  'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai': ['Hanalei', 'Poipu'],
};

// Maps can be built from a constructor.
var searchTerms = Map();

// Maps are parameterized types; you can specify what
// types the key and value should be.
var nobleGases = Map<int, String>();

대괄호 구문으로 맵 항목을 추가하고, 얻고, 설정할 수 있어요. 맵에서 키와 그 값을 제거하려면 remove()를 사용하세요.

var nobleGases = {54: 'xenon'};

// Retrieve a value with a key.
assert(nobleGases[54] == 'xenon');

// Check whether a map contains a key.
assert(nobleGases.containsKey(54));

// Remove a key and its value.
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));

맵에서 모든 값이나 모든 키를 가져올 수 있어요.

var hawaiianBeaches = {
  'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai': ['Hanalei', 'Poipu'],
};

// Get all the keys as an unordered collection
// (an Iterable).
var keys = hawaiianBeaches.keys;

assert(keys.length == 3);
assert(keys.contains('Oahu'));

// Get all the values as an unordered collection
// (an Iterable of Lists).
var values = hawaiianBeaches.values;
assert(values.length == 3);
assert(values.any((v) => v.contains('Waikiki')));

맵에 키가 있는지 확인하려면 containsKey()를 사용하세요. 맵의 값은 null이 될 수 있으므로, 키의 값을 얻어서 null인지 검사하는 것으로 키 존재 여부를 판단할 수는 없어요.

var hawaiianBeaches = {
  'Oahu': ['Waikiki', 'Kailua', 'Waimanalo'],
  'Big Island': ['Wailea Bay', 'Pololu Beach'],
  'Kauai': ['Hanalei', 'Poipu'],
};

assert(hawaiianBeaches.containsKey('Oahu'));
assert(!hawaiianBeaches.containsKey('Florida'));

키가 맵에 아직 존재하지 않을 때만 그 키에 값을 할당하고 싶다면 putIfAbsent() 메서드를 사용하세요. 값을 반환하는 함수를 반드시 제공해야 해요.

var teamAssignments = <String, String>{};
teamAssignments.putIfAbsent('Catcher', () => pickToughestKid());
assert(teamAssignments['Catcher'] != null);

전체 메서드 목록은 Map API 레퍼런스를 참고하세요.

공통 컬렉션 메서드 (Common collection methods)

List, Set, Map은 많은 컬렉션에서 찾을 수 있는 공통 기능을 공유해요. 이 공통 기능 중 일부는 ListSet이 구현하는 Iterable 클래스가 정의해요.

참고: MapIterable을 구현하지 않지만, Mapkeysvalues 프로퍼티를 사용해서 그로부터 Iterable을 얻을 수 있어요.

isEmptyisNotEmpty로 리스트·셋·맵에 항목이 있는지 확인할 수 있어요.

var coffees = <String>[];
var teas = ['green', 'black', 'chamomile', 'earl grey'];
assert(coffees.isEmpty);
assert(teas.isNotEmpty);

리스트·셋·맵의 각 항목에 함수를 적용하려면 forEach()를 사용할 수 있어요.

var teas = ['green', 'black', 'chamomile', 'earl grey'];

teas.forEach((tea) => print('I drink $tea'));

맵에 forEach()를 호출하면 함수가 두 인자(키와 값)를 받아야 해요.

hawaiianBeaches.forEach((k, v) {
  print('I want to visit $k and swim at $v');
  // I want to visit Oahu and swim at
  // [Waikiki, Kailua, Waimanalo], etc.
});

Iterable은 모든 결과를 단일 객체로 제공하는 map() 메서드를 제공해요.

var teas = ['green', 'black', 'chamomile', 'earl grey'];

var loudTeas = teas.map((tea) => tea.toUpperCase());
loudTeas.forEach(print);

참고: map()이 반환한 객체는 지연 평가(lazily evaluated)되는 Iterable이에요. 반환된 객체에서 항목을 요청하기 전에는 함수가 호출되지 않아요.

각 항목에 함수가 즉시 호출되도록 강제하려면 map().toList()map().toSet()을 사용하세요.

var loudTeas = teas.map((tea) => tea.toUpperCase()).toList();

조건에 맞는 모든 항목을 얻으려면 Iterablewhere() 메서드를, 일부 또는 모든 항목이 조건에 맞는지 확인하려면 any()every() 메서드를 사용하세요.

var teas = ['green', 'black', 'chamomile', 'earl grey'];

// Chamomile is not caffeinated.
bool isDecaffeinated(String teaName) => teaName == 'chamomile';

// Use where() to find only the items that return true
// from the provided function.
var decaffeinatedTeas = teas.where((tea) => isDecaffeinated(tea));
// or teas.where(isDecaffeinated)

// Use any() to check whether at least one item in the
// collection satisfies a condition.
assert(teas.any(isDecaffeinated));

// Use every() to check whether all the items in a
// collection satisfy a condition.
assert(!teas.every(isDecaffeinated));

전체 메서드 목록은 Iterable API 레퍼런스와 List, Set, Map의 API 레퍼런스를 참고하세요.

URI

Uri 클래스는 URI(URL이라고도 해요)에서 사용할 문자열을 인코딩·디코딩하는 함수를 제공해요. 이 함수들은 &=처럼 URI에서 특별한 의미를 갖는 문자들을 처리해요. Uri 클래스는 URI의 구성 요소(host, port, scheme 등)도 파싱해서 노출해요.

완전한 URI 인코딩·디코딩하기 (Encoding and decoding fully qualified URIs)

URI에서 특별한 의미를 갖는 문자(예: /, :, &, #)를 제외한 문자를 인코딩·디코딩하려면 encodeFull()decodeFull() 메서드를 사용하세요. 이 메서드들은 완전한 URI를 인코딩·디코딩하기에 좋고, 특별한 URI 문자는 그대로 남겨 둬요.

var uri = 'https://example.org/api?foo=some message';

var encoded = Uri.encodeFull(uri);
assert(encoded == 'https://example.org/api?foo=some%20message');

var decoded = Uri.decodeFull(encoded);
assert(uri == decoded);

somemessage 사이의 공백만 인코딩된 걸 볼 수 있어요.

URI 구성 요소 인코딩·디코딩하기 (Encoding and decoding URI components)

URI에서 특별한 의미를 갖는 문자열의 모든 문자(예: /, &, :을 포함하되 여기에 한정되지 않아요)를 인코딩·디코딩하려면 encodeComponent()decodeComponent() 메서드를 사용하세요.

var uri = 'https://example.org/api?foo=some message';

var encoded = Uri.encodeComponent(uri);
assert(
  encoded == 'https%3A%2F%2Fexample.org%2Fapi%3Ffoo%3Dsome%20message',
);

var decoded = Uri.decodeComponent(encoded);
assert(uri == decoded);

모든 특수 문자가 인코딩된 걸 볼 수 있어요. 예를 들어 /%2F로 인코딩돼요.

URI 파싱하기 (Parsing URIs)

Uri 객체나 URI 문자열이 있으면 path 같은 Uri 필드를 사용해 그 구성 부분을 얻을 수 있어요. 문자열에서 Uri를 만들려면 parse() 정적 메서드를 사용하세요.

var uri = Uri.parse('https://example.org:8080/foo/bar#frag');

assert(uri.scheme == 'https');
assert(uri.host == 'example.org');
assert(uri.path == '/foo/bar');
assert(uri.fragment == 'frag');
assert(uri.origin == 'https://example.org:8080');

얻을 수 있는 다른 URI 구성 요소는 Uri API 레퍼런스를 참고하세요.

URI 만들기 (Building URIs)

Uri() 생성자를 사용해 개별 부분으로부터 URI를 만들 수 있어요.

var uri = Uri(
  scheme: 'https',
  host: 'example.org',
  path: '/foo/bar',
  fragment: 'frag',
  queryParameters: {'lang': 'dart'},
);
assert(uri.toString() == 'https://example.org/foo/bar?lang=dart#frag');

fragment를 지정할 필요가 없고 http나 https scheme의 URI를 만들 때는, 대신 Uri.httpUri.https 팩토리 생성자를 사용할 수 있어요.

var httpUri = Uri.http('example.org', '/foo/bar', {'lang': 'dart'});
var httpsUri = Uri.https('example.org', '/foo/bar', {'lang': 'dart'});

assert(httpUri.toString() == 'http://example.org/foo/bar?lang=dart');
assert(httpsUri.toString() == 'https://example.org/foo/bar?lang=dart');

날짜와 시간 (Dates and times)

DateTime 객체는 시간상의 한 지점이에요. 시간대는 UTC이거나 로컬 시간대예요.

여러 생성자와 메서드로 DateTime 객체를 만들 수 있어요.

// Get the current date and time.
var now = DateTime.now();

// Create a new DateTime with the local time zone.
var y2k = DateTime(2000); // January 1, 2000

// Specify the month and day.
y2k = DateTime(2000, 1, 2); // January 2, 2000

// Specify the date as a UTC time.
y2k = DateTime.utc(2000); // 1/1/2000, UTC

// Specify a date and time in ms since the Unix epoch.
y2k = DateTime.fromMillisecondsSinceEpoch(946684800000, isUtc: true);

// Parse an ISO 8601 date in the UTC time zone.
y2k = DateTime.parse('2000-01-01T00:00:00Z');

// Create a new DateTime from an existing one, adjusting just some properties:
var sameTimeLastYear = now.copyWith(year: now.year - 1);

경고: DateTime 연산은 일광 절약 시간(Daylight Savings Time) 및 기타 비표준 시간 조정과 관련해 예상치 못한 결과를 줄 수 있어요.

날짜의 millisecondsSinceEpoch 프로퍼티는 "Unix epoch"(1970년 1월 1일 UTC) 이후의 밀리초 수를 반환해요.

// 1/1/2000, UTC
var y2k = DateTime.utc(2000);
assert(y2k.millisecondsSinceEpoch == 946684800000);

// 1/1/1970, UTC
var unixEpoch = DateTime.utc(1970);
assert(unixEpoch.millisecondsSinceEpoch == 0);

Duration 클래스를 사용해서 두 날짜의 차이를 계산하고 날짜를 앞·뒤로 이동할 수 있어요.

var y2k = DateTime.utc(2000);

// Add one year.
var y2001 = y2k.add(const Duration(days: 366));
assert(y2001.year == 2001);

// Subtract 30 days.
var december2000 = y2001.subtract(const Duration(days: 30));
assert(december2000.year == 2000);
assert(december2000.month == 12);

// Calculate the difference between two dates.
// Returns a Duration object.
var duration = y2001.difference(y2k);
assert(duration.inDays == 366); // y2k was a leap year.

경고: 시계가 바뀌면(예: 일광 절약 시간으로) Duration으로 DateTime을 일 단위로 이동하는 데 문제가 생길 수 있어요. 날짜를 일 단위로 이동해야 한다면 UTC 날짜를 사용하세요.

전체 메서드 목록은 DateTimeDuration의 API 레퍼런스를 참고하세요.

유틸리티 클래스 (Utility classes)

핵심 라이브러리에는 정렬, 값 매핑, 반복에 유용한 다양한 유틸리티 클래스가 있어요.

객체 비교하기 (Comparing objects)

Comparable 인터페이스를 구현하면 객체가 (보통 정렬을 위해) 다른 객체와 비교될 수 있다는 것을 나타내요. compareTo() 메서드는 작으면 < 0, 같으면 0, 크면 > 0을 반환해요.

class Line implements Comparable<Line> {
  final int length;
  const Line(this.length);

  @override
  int compareTo(Line other) => length - other.length;
}

void main() {
  var short = const Line(1);
  var long = const Line(100);
  assert(short.compareTo(long) < 0);
}

맵 키 구현하기 (Implementing map keys)

Dart의 각 객체는 정수형 해시 코드(hash code)를 자동으로 제공하므로, 맵의 키로 쓸 수 있어요. 하지만 hashCode getter를 오버라이드해서 사용자 정의 해시 코드를 만들 수도 있어요. 그렇게 한다면 == 연산자도 오버라이드하고 싶을 거예요. (==로) 같은 객체들은 동일한 해시 코드를 가져야 해요. 해시 코드가 고유할 필요는 없지만 잘 분산되어 있어야 해요.

팁: hashCode getter를 일관되고 쉽게 구현하려면 Object 클래스가 제공하는 정적 해싱 메서드를 사용하는 걸 고려해 보세요.

객체의 여러 프로퍼티에 대한 단일 해시 코드를 만들려면 Object.hash()를 사용할 수 있어요. 컬렉션에 대한 해시 코드를 만들려면 Object.hashAll()(요소 순서가 중요할 때)이나 Object.hashAllUnordered()를 사용할 수 있어요.

class Person {
  final String firstName, lastName;

  Person(this.firstName, this.lastName);

  // Override hashCode using the static hashing methods
  // provided by the `Object` class.
  @override
  int get hashCode => Object.hash(firstName, lastName);

  // You should generally implement operator `==` if you
  // override `hashCode`.
  @override
  bool operator ==(Object other) {
    return other is Person &&
        other.firstName == firstName &&
        other.lastName == lastName;
  }
}

void main() {
  var p1 = Person('Bob', 'Smith');
  var p2 = Person('Bob', 'Smith');
  var p3 = 'not a person';
  assert(p1.hashCode == p2.hashCode);
  assert(p1 == p2);
  assert(p1 != p3);
}

반복 (Iteration)

IterableIterator 클래스는 값 컬렉션에 대한 순차 접근을 지원해요. 이 컬렉션들을 연습하려면 Iterable 컬렉션 튜토리얼을 따라 해보세요.

for-in 루프에서 사용할 Iterator를 제공할 수 있는 클래스를 만든다면 (가능하면) Iterable을 확장(extend)하거나 구현(implement)하세요. 실제 반복 능력을 정의하려면 Iterator를 구현하세요.

class Process {
  // Represents a process...
}

class ProcessIterator implements Iterator<Process> {
  @override
  Process get current => ...
  @override
  bool moveNext() => ...
}

// A mythical class that lets you iterate through all
// processes. Extends a subclass of [Iterable].
class Processes extends IterableBase<Process> {
  @override
  final Iterator<Process> iterator = ProcessIterator();
}

void main() {
  // Iterable objects can be used with for-in.
  for (final process in Processes()) {
    // Do something with the process.
  }
}

예외 (Exceptions)

Dart 핵심 라이브러리는 많은 흔한 예외와 오류를 정의해요. 예외(exception)는 미리 계획하고 잡을 수 있는 조건으로 간주돼요. 오류(error)는 예상하거나 계획하지 않은 조건이에요.

가장 흔한 오류 몇 가지를 살펴볼게요.

  • NoSuchMethodError — 수신 객체(받는 객체, null일 수도 있어요)가 메서드를 구현하지 않을 때 던져져요.
  • ArgumentError — 예상치 못한 인자를 만난 메서드가 던질 수 있어요.

애플리케이션 특정 예외를 던지는 것은 오류가 발생했음을 나타내는 흔한 방법이에요. Exception 인터페이스를 구현해서 사용자 정의 예외를 정의할 수 있어요.

class FooException implements Exception {
  final String? msg;

  const FooException([this.msg]);

  @override
  String toString() => msg ?? 'FooException';
}

자세한 내용은 언어 투어의 Exceptions 섹션Exception API 레퍼런스를 참고하세요.

약한 참조와 파이널라이저 (Weak references and finalizers)

Dart는 가비지 컬렉션(garbage-collected) 언어예요. 즉 참조되지 않는 어떤 Dart 객체든 가비지 컬렉터가 정리(dispose)할 수 있어요. 이 기본 동작은 네이티브 리소스가 관련된 시나리오나 대상 객체를 수정할 수 없는 경우에는 바람직하지 않을 수 있어요.

WeakReference는 대상 객체가 가비지 컬렉터에 의해 수집되는 방식에 영향을 주지 않는 참조를 저장해요. 또 다른 선택지로는 Expando를 사용해서 객체에 프로퍼티를 추가하는 방법이 있어요.

Finalizer는 객체가 더 이상 참조되지 않은 후 콜백 함수를 실행하는 데 사용할 수 있어요. 하지만 이 콜백의 실행은 보장되지 않아요.

NativeFinalizerdart:ffi로 네이티브 코드와 상호작용할 때 더 강한 보장을 제공해요. 이 콜백은 객체가 더 이상 참조되지 않은 후 적어도 한 번은 호출돼요. 또한 데이터베이스 연결이나 열린 파일 같은 네이티브 리소스를 닫는 데도 사용할 수 있어요.

객체가 너무 일찍 가비지 컬렉션되고 파이널라이즈되는 것을 막으려면, 클래스가 Finalizable 인터페이스를 구현할 수 있어요. 지역 변수가 Finalizable이면, 그것이 선언된 코드 블록을 빠져나갈 때까지 가비지 컬렉션되지 않아요.

버전 참고: 약한 참조와 파이널라이저 지원은 Dart 2.17에서 추가됐어요.

더 알아보기

  • dart:core API 레퍼런스에서 이 라이브러리의 전체 클래스·메서드 목록을 확인할 수 있어요.
  • 언어 투어에서 Dart 언어의 기본 타입과 기능을 자세히 살펴보세요.