https://dart.dev/guides/language/language-tour
위 문서로 공부하며 기억해둘만한 내용만 정리
실습 사이트
부가적인 내용이나 다른 코드가 섞여있을 수 있습니다.
- Control flow statements
Dart에서는 다음 코드르 사용하여 흐름을 제어할 수 있다.
- if and else
- for loops
- while and do-while loops
- break and continue
- switch and case
- assert
또, try-catch throw를 사용하여 제어 흐름에 영향을 줄 수 있다.
- if and else
if (isRaining()) {
you.bringRainCoat();
} else if (isSnowing()) {
you.wearJacket();
} else {
car.putTopDown();
}
조건문은 반드시 boolean 값으로 평가되어야 한다.
- For loops
표준 for loop를 이용하여 반복할 수 있다.
var message = StringBuffer('Dart is fun');
for (var i = 0; i < 5; i++) {
message.write('!');
}
Dart의 for loop 내부의 클로저는, 인덱스 값을 캡처하여 자바스크립트에서 발견되는 함정을 방지한다.
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.add(() => print(i));
}
callbacks.forEach((c) => c()); // 0 1
위 코드는 0과 1을 출력한다.
이와 반대로 자바스크립트에서는 2와 2를 출력한다.
// javascript
var callbacks = [];
for (var i = 0; i < 2; i++) {
callbacks.push(() => console.log(i));
}
callbacks.forEach((c) => c()); // 2 2
반복하는 객체가 Iterable(List 또는 Set 등)이고, 현재 반복 카운터 값을 알 필요가 없는 경우 for - in 형태의 반복문을 사용할 수 있다.
for (final candidate in candidates) {
candidate.interview();
}
https://dart.dev/codelabs/iterables
Iterable 클래스는 forEach()를 사용할 수도 있다.
var collection = [1, 2, 3];
collection.forEach(print); // 1 2 3
- while and do-while
while (!isDone()) {
doSomething();
}
do {
printLine();
} while (!atEndOfPage());
- break and continue
while (true) {
if (shutDownRequested()) break;
processIncomingRequests();
}
반복을 멈추기 위해 사용한다.
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 and case
Dart의 switch 문은 ==를 사용하여 정수, 문자열, 컴파일 타임 상수를 비교한다.
비교된 객체는 모두 동일한 클래스의 인스턴스(서브타입도 아닌)여야 하며, ==를 재정의해서는 안된다.
Enumerated 타입은 switch 문에서 잘 동작한다.
비어있지 않은 각 case 절은 원칙적으로 break문으로 끝난다. 다른 유효한 방법으로 끝내려면, 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 = 'OPEN';
switch (command) {
case 'OPEN':
executeOpen();
// ERROR: Missing break
case 'CLOSED':
executeClosed();
break;
}
다음과 같이 비어있는 case 절을 지원하기도 한다.
var command = 'CLOSED';
switch (command) {
case 'CLOSED': // Empty case falls through.
case 'NOW_CLOSED':
// Runs for both CLOSED and NOW_CLOSED.
executeNowClosed();
break;
}
비어있지 않은 case 절에서 continue 문과 label을 사용할 수도 있다.
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를 사용한다.
assert(condition, optionalMessage);
// 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'));
assert(urlString.startsWith('https'),
'URL ($urlString) should start with "https".'); // 선택적으로 후행 쉼표 포함 가능
assert의 첫번째 인자는 boolean 값 표현식이 될 수 있다.
표현식이 false라면 AssertionError가 던져진다.
assertion은 사용하는 툴과 프레임워크에 따라 동작한다.
플러터는 디버그 모드에서 활성화한다.
dartdevc와 같은 개발 전용 도구는 기본적으로 활성화한다.
dart run, dart2js와 같은 도구는 커맨드 라인 플래그(--enable-asserts)로 활성화한다.
프로덕션 코드에서는 무시된다.
- Exceptions
Dart 코드는 예외를 throw하고 catch할 수 있다.
예외는 예상치 못한 일이 일어났음을 알리는 에러다.
예외가 catch되지 않으면, 예외가 발생한 isolate가 중단되고, 일반적으로 isolate와 프로그램이 종료된다.
https://dart.dev/guides/language/language-tour#isolates
자바와 달리, Dart의 모든 예외는 unchecked exception이다.
메소드는 어떤 예외가 던져질지 선언하지 않으며, 예외를 잡을 필요도 없다.
Dart는 Exception과 Error 타입과 이미 정의된 수많은 하위 타입을 제공한다.
나만의 예외를 정의할 수도 있다.
Dart 프로그램은 Exception, Error 객체 뿐만 아니라 null이 아닌 모든 객체를 예외로 던질 수 있다.
- Throw
throw FormatException('Expected at least 1 section');
throw 'Out of llamas!'; // 임의의 객체를 던질 수도 있다. 보통 Error나 Exception을 구현한 예외를 던진다.
예외를 던지는 것은 표현식이기 때문에, => 문과 표현식을 허용하는 모든 곳에서 예외를 던질 수 있다.
void distanceTo(Point other) => throw UnimplementedError();
- Catch
예외를 catch하면, 예외가 다시 발생하지 않는 한 전파되는 것이 중단되고, catch한 예외를 처리할 수 있다.
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
}
여러 타입의 예외를 처리하기 위해 여러 catch 절을 지정할 수도 있다.
던져진 예외 타입과 일치하는 첫번째 catch 절이 예외를 처리하게 된다.
catch 절이 타입을 지정하지 않으면, 모든 타입의 throw된 객체를 처리할 수 있다.
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은 예외 타입을 지정할 때 사용한다.
catch는 예외 핸들러에서 예외 객체가 필요할 때 사용한다.
catch에는 하나 또는 두 개의 파라미터를 지정할 수 있다.
첫번째는 던져진 예외, 두번째는 stack trace(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.
}
- Classes
Dart는 클래스와 믹스인 기반 상속이 있는 객체지향 언어다.
모든 클래스는 클래스의 인스턴스고, Null을 제외한 모든 객체는 Object의 자손이다.
믹스인 기반 상속은, 모든 클래스(Object? 제외)가 정확히 하나의 수퍼클래스를 가지지만, 클래스 바디는 여러 클래스 계층에서 재사용될 수 있음을 의미한다.
Extension 메소드는, 클래스를 변경하거나 하위클래스를 만들지 않고 클래스에 기능을 추가하는 방법이다.
- Using class members
객체는 함수(메소드)와 데이터(인스턴스 변수)로 구성된 멤버를 가진다.
메소드를 호출하면, 객체에서 이를 호출한다. 메소드는 객체의 함수와 데이터에 접근한다.
.을 사용하여 인스턴스 변수 또는 메소드를 참조한다.
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;
- Using constructors
생성자를 이용하여 객체를 만들 수 있다.
생성자의 이름은 <클래스명> 또는 <클래스명>.<식별자>가 될 수 있다.
다음 코드는 Point 객체를 생성한다.
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
생성자 명 이전에 new 키워드는 선택 사항이다.
일부 클래스는 constant 생성자를 제공한다. constant 생성자를 이용하여 컴파일 타임 상수를 만들기 위해, 생성자 명 앞에 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!
constant 컨텍스트 내에서, 생성자 또는 리터럴 앞에 const를 생략할 수 있다.
다음 코드에서는 const map을 만든다.
// 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 키워드의 첫번째 사용을 제외하고 모두 생략할 수 있다.
// Only one const, which establishes the constant context.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
constant 생성자가 const context 외부에 있고, const 없이 호출되면 non-constant 객체를 생성한다.
var a = const ImmutablePoint(1, 1); // Creates a constant
var b = ImmutablePoint(1, 1); // Does NOT create a constant
assert(!identical(a, b)); // NOT the same instance!
- 객체 타입 가져오기
런타임에 객체의 타입을 가져오기 위해, Object의 runtimeType 속성을 사용할 수 있다. 이는 Type 객체를 리턴한다.
print('The type of a is ${a.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 메소드를 생성한다.
initializer가 없는 non-final, late final 인스턴스 변수도 암시적 setter 메소드를 생성한다.
선언된 non-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일 수 있고, 정확히 한 번 설정되어야 한다.
생성자 파라미터 또는 생성자의 initializer list를 사용하여, 선언 시에 final, non-late 인스턴스 변수를 초기화하자.
class ProfileMark {
final String name;
final DateTime start = DateTime.now();
ProfileMark(this.name);
ProfileMark.unnamed() : name = '';
}
생성자 바디가 실행된 이후에 final 인스턴스 변수 값을 할당하고 싶은 경우, 생성자나 late final을 사용하자.
단, initilizer가 없는 late final은 API에 setter를 추가한다.
- Constructors
클래스명과 동일한 함수를 통해 생성자를 선언한다.(또는, Named constructors에서 설명되는 추가적인 식별자)
생성자의 가장 일반적인 형태인 generative constructor는 클래스의 새 인스턴스를 생성한다.
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 키워드는 현재 인스턴스를 참조한다.
이름 충돌이 있는 경우에만 this를 사용하자. Dart 스타일에서는 this를 생략한다.
Dart에서는 formal parameters를 초기화하기 위한 간단한 방법이 있다.
파라미터 초기화는 non-nullable 또는 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);
}
언급된 변수는 암시적으로 final이며 initializer list 범위 내에만 있다.
만약 생성자를 선언하지 않는다면, 디폴트 생성자가 제공된다. 디폴트 생성자는 인자가 없고 수퍼클래스에서 인자가 없는 생성자를 호출한다.
생성자는 상속되지 않는다. 하위 클래스의 생성자는 상위 클래스의 생성자를 상속하지 않는다.
생성자를 선언하지 않은 서브 클래스에는 디폴트 생성자만 있다.
named constructor를 사용하여 여러 생성자를 구현하거나 명확함을 제공할 수 있다.
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
final double x;
final double y;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
생성자는 상속되지 않는다는 점을 기억하자. 즉, 수퍼클래스의 named constructor도 하위 클래스로 상속되지 않는다.
수퍼클래스에 정의된 named constructor를 사용하여 하위 클래스를 생성하고 싶다면, 해당 생성자를 하위 클래스에 구현해야한다.
기본적으로 하위 클래스의 생성자는, 수퍼 클래스의 이름 없는, 또는 인자가 없는 생성자를 호출한다.
수퍼 클래스의 생성자는 생성자 바디의 시작 부분에서 불려진다.
initializer list도 사용되는 경우, 수퍼 클래스가 불려지기 전에 실행된다.
요약하면 실행 순서는 다음과 같다.
1. initializer list
2. 수퍼 클래스의 인자 없는 생성자
3. 메인 클래스의 인자 없는 생성자
수퍼 클래스가 이름없는, 또는 인자 없는 생성자를 가지고 있지 않다면, 수퍼 클래스의 생성자를 수동으로 호출해야 한다.
콜론(:) 뒤, 생성자 바디 바로 앞에 수퍼 클래스의 생성자를 지정한다.
다음 코드에서는 Employee 클래스의 생성자는 수퍼 클래스 Person의 named constructor를 호출한다.
class Person {
String? firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson().
Employee.fromJson(super.data) : super.fromJson() {
print('in Employee');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
생성자를 호출하기 전에 수퍼클래스 생성자에 대한 인자가 평가되기 때문에, 인자는 함수 호출과 같은 표현식이 될 수 있다.
class Employee extends Person {
Employee() : super.fromJson(fetchDefaultData());
// ···
}
수퍼 클래스에 대한 인자는 this에 접근할 수 없다. 예를 들어, 인자는 정적 메소드를 호출할 수 있지만, 인스턴스 메소드는 호출할 수 없다.
생성자의 수퍼 호출에 각 파라미터를 수동으로 전달하는 것을 피하기 위해, super-initializer parameter를 사용하여 지정된 또는 디폴트 수퍼클래스 생성자에 파라미터를 전달할 수 있다.
이 기능은 생성자를 redirection하는데 사용할 수 없다.
super-initializer parameter는 formal parameter 초기화와 유사한 문법 및 의미를 가진다.
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-initializer parameter는 수퍼 생성자 호출이 위치 인자(positional parameter)를 가지는 경우에 위치를 지정할 수 없지만, 이름이 지정될 순 있다.
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 : 수퍼 클래스 생성자를 호출하는 것 외에, 생성자 바디가 실행되기 전에 인스턴스 변수를 초기화할 수 있다. initializer는 콤마로 구분된다.
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
initialize의 오른쪽은 this에 접근할 수 없다.
개발하는 동안, initializer list에서 assert를 사용하여 입력 값을 검증할 수 있다.
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
initializer list는 final 필드를 설정할 때 편리하다.
import 'dart:math';
class Point {
final double x;
final double y;
final double distanceFromOrigin;
Point(double x, double y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
void main() {
var p = Point(2, 3);
print(p.distanceFromOrigin);
}
단지 동일한 클래스의 다른 생성자로 리다이렉트하는 것이 생성자의 목적이 될 수도 있다.
redirection 생성자의 바디는 비어있고, 생성자 호출(클래스 이름 대신 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);
}
클래스가 결코 변하지 않는 객체들을 제공한다면, 이 객체들을 컴파일 타임 상수로 만들 수 있다.
const 생성자를 정의하고, 모든 인스턴스 변수가 final인지 확인한다.
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
const 생성자가 항상 상수를 만드는 것은 아니다.
항상 클래스의 새로운 인스턴스를 만들지 않는 생성자를 구현하려면, factory 키워드를 사용한다.
예를 들어, 팩토리 생성자는 캐시에서 인스턴스를 반환할 수도 있거나 서브타입의 인스턴스를 반환할 수 있다.
팩토리 생성자는 initializer list로 다룰 수 없는 로직을 이용하여 final 변수를 초기화 하는데 사용할 수도 있다.
다음 코드에서 Logger 클래스의 팩토리 생성자는 캐시에서 객체를 반환하고,
Logger.fromJson 팩토리 생성자는 JSON 값으로부터 final 변수를 초기화한다.
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);
}
}
팩토리 생성자는 this에 접근할 수 없다.
팩토리 생성자는 다른 생성자처럼 호출된다.
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
'플러터' 카테고리의 다른 글
Dart 문법 공부 - 6 (0) | 2022.06.01 |
---|---|
Dart 문법 공부 - 5 (0) | 2022.05.30 |
Dart 문법 공부 - 4 (0) | 2022.05.29 |
Dart 문법 공부 - 2 (0) | 2022.05.28 |
Dart 문법 공부 - 1 (0) | 2022.05.28 |