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 클래스를 정의하고, 숫자를 다루는 기본 유틸리티를 일부 제공해요.
int와 double의 parse() 메서드로 문자열을 각각 정수나 double로 변환할 수 있어요.
assert(int.parse('42') == 42);
assert(int.parse('0x42') == 66);
assert(double.parse('0.50') == 0.5);
또는 num의 parse() 메서드를 사용할 수도 있어요. 이 메서드는 가능하면 정수를, 그렇지 않으면 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를 사용할 수 있어요. StringBuffer는 toString()을 호출할 때까지 새 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은 많은 컬렉션에서 찾을 수 있는 공통 기능을 공유해요. 이 공통 기능 중 일부는 List와 Set이 구현하는 Iterable 클래스가 정의해요.
참고:
Map은Iterable을 구현하지 않지만,Map의keys와values프로퍼티를 사용해서 그로부터Iterable을 얻을 수 있어요.
isEmpty나 isNotEmpty로 리스트·셋·맵에 항목이 있는지 확인할 수 있어요.
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();
조건에 맞는 모든 항목을 얻으려면 Iterable의 where() 메서드를, 일부 또는 모든 항목이 조건에 맞는지 확인하려면 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);
some과 message 사이의 공백만 인코딩된 걸 볼 수 있어요.
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.http나 Uri.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 날짜를 사용하세요.
전체 메서드 목록은 DateTime과 Duration의 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를 오버라이드해서 사용자 정의 해시 코드를 만들 수도 있어요. 그렇게 한다면 == 연산자도 오버라이드하고 싶을 거예요. (==로) 같은 객체들은 동일한 해시 코드를 가져야 해요. 해시 코드가 고유할 필요는 없지만 잘 분산되어 있어야 해요.
팁:
hashCodegetter를 일관되고 쉽게 구현하려면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)
Iterable과 Iterator 클래스는 값 컬렉션에 대한 순차 접근을 지원해요. 이 컬렉션들을 연습하려면 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는 객체가 더 이상 참조되지 않은 후 콜백 함수를 실행하는 데 사용할 수 있어요. 하지만 이 콜백의 실행은 보장되지 않아요.
NativeFinalizer는 dart:ffi로 네이티브 코드와 상호작용할 때 더 강한 보장을 제공해요. 이 콜백은 객체가 더 이상 참조되지 않은 후 적어도 한 번은 호출돼요. 또한 데이터베이스 연결이나 열린 파일 같은 네이티브 리소스를 닫는 데도 사용할 수 있어요.
객체가 너무 일찍 가비지 컬렉션되고 파이널라이즈되는 것을 막으려면, 클래스가 Finalizable 인터페이스를 구현할 수 있어요. 지역 변수가 Finalizable이면, 그것이 선언된 코드 블록을 빠져나갈 때까지 가비지 컬렉션되지 않아요.
버전 참고: 약한 참조와 파이널라이저 지원은 Dart 2.17에서 추가됐어요.
더 알아보기
- dart:core API 레퍼런스에서 이 라이브러리의 전체 클래스·메서드 목록을 확인할 수 있어요.
- 언어 투어에서 Dart 언어의 기본 타입과 기능을 자세히 살펴보세요.