다트의 모든 변수는 객체이다
sound null safety : null이라고 정의하지 않는한 null이 될 수 없음
그럼 어떻게 null이라고 정의해줄 수 있을까? -> ? 연산자를 이용한다
int? a = null;
? 연산자를 이용하여 nullable(null이 가능하도록)로 정의하면 initial value가 null이 된다.
null일 수도 있는 값들을 다룰 때는 항상 주의해야한다.
유용한 연산자 중 하나는 ??=
왼쪽 변수가 null이 아닐때에만 값을 할당한다.
예를 들어
a ??= 5
라는 식이 있을 때, a가 3일 때에는 a의 값이 변하지 않지만 a가 null이었다면 5로 바뀌게 된다.
왼쪽 expression의 값이 null이 아니면 왼쪽 값을, null이면 오른쪽 값을 return 한다.
print(1 ?? 3);
// print(1);
print(null ?? 12);
// print(12);
myObject?.someProperty
null일지도 모르는 변수를 다루는 또다른 방법.
만약 myObject가 null 이 아니라면 someProperty
에 접근하고, null이라면 null을 return 한다.
Dart는 list, map, set을 literal로 지원한다.
final aListOfStrings = ['one', 'two', 'three'];
final aSetOfStrings = {'one', 'two', 'three'};
final aMapOfStringsToInts = {
'one': 1,
'two': 2,
'three': 3,
};
또는,
final aListOfInts = <int>[];
final aSetOfInts = <int>{};
final aMapOfIntToDouble = <int, double>{};
하나의 객체에 여러 작업을 하고 싶을 때가 있다.
var button = querySelector('#confirm');
button.text = 'Confirm';
button.classes.add('important');
button.onClick.listen((e) => window.alert('Confirmed!'));
이런식으로.
모든 작업은 button(querySelector('#confirm')) 에 대한 작업이기에 우리는 ..
이라는 새로운 연산자를 도입할 수 있다.
..
은 .
을 통해 객체의 맴버변수(또는 함수)에 접근하는 것과 굉장히 유사하다.
예를 들어 우리는 someObject.someFunction()
처럼 .
을 이용하여 someObject의 함수에 접근할 수 있다. 차이점이 있다면 위 표현은 someFunction()의 return 값을 가질 것이다.
대신, someObject..someFunction()
은 여전히 someObject의 someFunction을 작동시키지만 여전히 someObject의 참조값을 가진다.
그러므로 ..
을 이용하여 아래의 식처럼 계속해서 연쇄적으로 querySelector에 접근할 수 있다.
querySelector('#confirm')
..text = 'Confirm'
..classes.add('important')
..onClick.listen((e) => window.alert('Confirmed!'));
class MyClass {
int _aProperty = 0;
int get aProperty => _aProperty;
set aProperty(int value) {
if (value >= 0) {
_aProperty = value;
}
}
}
dart의 함수는 두가지 형태의 parameter를 가진다 : positional과 named
positional은 그냥 기본적인 형태의 parameter이다. 위치에 따라 구분한다.
int sumUpToFive(int a, [int? b, int? c, int? d, int? e]) {
int sum = a;
if (b != null) sum += b;
if (c != null) sum += c;
if (d != null) sum += d;
if (e != null) sum += e;
return sum;
}
// ···
int total = sumUpToFive(1, 2);
int otherTotal = sumUpToFive(1, 2, 3, 4, 5);
dart에서는 []
대괄호를 이용하여 positional parameter들을 optional하게 만들 수 있다.
int sumUpToFive(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {
// ···
}
// ···
int newTotal = sumUpToFive(1);
print(newTotal); // <-- prints 15
default value를 줄 수도 있다. Optional value들은 parameter가 대입되지 않더라도 함수에서 살아있다. default value가 미리 정의되어 있었다면 해당값으로, 아니라면 null로 초기화된다.
Optional named
{}
중괄호를 이용하여 named parameter를 정의할 수 있다.
void printName(String firstName, String lastName, {String? suffix}) {
print('$firstName $lastName ${suffix ?? ''}');
}
// ···
printName('Avinash', 'Gupta');
printName('Poshmeister', 'Moneybuckets', suffix: 'IV');
역시 default value를 정의해줄 수 있다.
optional하지 않은 named parameter를 정의해주고 싶다면 required 를 붙여준다.
함수는 optional positional parameter와 optional named parameter를 동시에 가질 수 없다.는 것을 기억하자.
class MyColor {
int red;
int green;
int blue;
MyColor(this.red, this.green, this.blue);
}
final color = MyColor(80, 80, 128);
constructor에서 this.
를 이용하여 값을 바로 할당할 수 있다!!
named parameter를 이용할수도 있다.
class MyColor {
...
MyColor({required this.red, required this.green, required this.blue});
}
final color = MyColor(red: 80, green: 80, blue: 80);
위 코드에서는 this.green, this.red, this.blue가 모두 required
로 표시된다. 세 변수가 모두 필요하다는 것! default value를 넣는다면 required를 뺼 수도 있다.
MyColor([this.red = 0, this.green = 0, this.blue = 0]);
// or
MyColor({this.red = 0, this.green = 0, this.blue = 0});
이름이 없는 constructor는 하나만 정의할 수 있다.
여러 개의 constructor를 만들기 위해서는 이름이 존재하는 named constructor를 이용하자.
class Point {
double x, y;
Point(this.x, this.y);
Point.origin()
: x = 0,
y = 0;
}
final myPoint = Point.origin();
:
생성과 동시에 멤버 변수를 초기화하는데 쓰인다
생성자 : 초기화 리스트 {
}
Point.fromJson(Map<String, num> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
constructor의 body 부분이 실행되기 전에 변수들을 초기화시키기 위해 이용한다.
class Square extends Shape {}
class Circle extends Shape {}
class Shape {
Shape();
factory Shape.fromTypeName(String typeName) {
if (typeName == 'square') return Square();
if (typeName == 'circle') return Circle();
print('I don\'t recognize $typeName');
throw Error();
}
}
factory 라는 키워드를 이용하여 factory constructor를 정의할 수 있다.
Factory constructor는 subtype이나 null을 리턴할 수 있다.
다른 constructor와 연결하는 용도로만 이용되는 constructur가 있을 수 있다. redirecting constructor의 body는 항상 비어있으며, :(colon) 뒤에서 다른 constructor를 호출한다.
class Automobile {
String make;
String model;
int mpg;
// The main constructor for this class.
Automobile(this.make, this.model, this.mpg);
// Delegates to the main constructor.
Automobile.hybrid(String make, String model) : this(make, model, 60);
// Delegates to a named constructor
Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');
}
변화가 없는 object를 만들고 싶다면 const constructur를 만들 수도 있다. 단, 모든 instance 변수는 final이 돼야 한다.
class ImmutablePoint {
const ImmutablePoint(this.x, this.y);
final int x;
final int y;
static const ImmutablePoint origin = ImmutablePoint(0, 0);
}
dart에서 Iterable은 추상 클래스다. Iterable로 instance를 생성할 수는 없다는 말이다.
하지만 new List나 Set을 통해 new Iterable을 생성하는 것은 가능하다.
Iterable<int> iterable = [1, 2, 3];
그럼 그냥 List로 정의하는 거랑 똑같지 않냐고? 그렇진 않다. Iterable로 정의하는 순간 우리는 index로 값을 불러올 수 없다. 즉 iterable[1]
과 같은 접근이 불가능해진다는 것. 이는 이러한 접근이 Iterable에는 정의되지 않고 List에만 정의되어 있기 때문이다
대신 쓸 수 있는 것은 iterable.elementAt(1)
과 같은 표현이다.
var iterable = ['Salad', 'Popcorn', 'Toast'];for (var element in iterable) { print(element);}
iterable 내의 원소들에 차례대로 접근한다.
그럼 이제 iterable class에서 이용할 수 있는 것들을 알아보자.
첫 원소와 마지막 원소에 접근하기 위해서 각각 iterable.first
, iterable.last
를 이용할 수 있다.
특정 조건을 만족하는 첫번째 원소를 가져온다.
parameter로 bool을 return하는 condition함수를 넣어준다.
String element = iterable.firstWhere((element) => element.length > 5);
위와 같은 expression은 길이가 5보다 큰 첫번째 element를 찾아내겠지.
// You can also use an `orElse` function in case no value is found!
var element4 = items.firstWhere(
(element) => element.length > 10,
orElse: () => 'None!',
);
print(element4);
만약 condition을 만족하는 value가 하나도 없는데 orElse가 정의되지 않았다면 StateError
가 생긴다.
모든 원소들이 해당 조건을 만족하는 지 확인한다.
return items.every((element) => element.length >= 5);
하나의 원소라도 해당 조건을 만족하면 true
조건을 만족시키는 모든 원소들을 찾기 위해서는?
where
을 이용한다.
var evenNumbers = numbers.where((number) => number.isEven);
-> numbers중 짝수인 원소들만 모아 새로운 iterable을 만들어준다.
조건을 만족하는 원소가 없으면 그냥 빈 iterable를 리턴한다.
각 원소들을 받아 함수를 적용하여 대체한다.
Iterable<int> output = numbers.map((number) => number * 10);
모든 원소들에 10을 곱한 새로운 iterable을 만든다.
import 'dart:async';
Future는 실행 결과가 끝나는 미래에 그 값을 반환하는 계산을 가진다. -> Promise 개념
HttpServer.bind('127.0.0.1', 4444)
.then((server) => print('${server.isBroadcast}'))
.catchError(print);
위 예제에서 bind()
함수는 Future 타입을 리턴한다.
-> Future.then()
: Future가 성공적으로 값을 반환했을 때의 콜백 함수이다.
-> Future.catchError()
: Future가 성공적으로 값을 반환하지 않았을 때의 콜백함수이다.
결과의 시퀀스