본문 바로가기
Dart/기초

[Dart] Dart 배우기

by 붕어사랑 티스토리 2021. 10. 13.
반응형

https://dart.dev/guides/language/language-tour

 

A tour of the Dart language

A tour of all the major Dart language features.

dart.dev

해당문서는 위 공식페이지 내용을 번역하였습니다.

 

 

Dart의 주요 컨셉

  • Dart의 모든 variable은 객체(Object) 입니다. 심지어 숫자, 함수 그리고 null까지 객체입니다. 모든 객체들은 자바와 같이 Object 클래스를 상속받습니다(null클래스 제외).
  • Dart의 클래스들은 모드 인터페이스로 사용될 수 있습니다.
  • Dart는 타입을 명시해주어야 하는 언어이지만 Dart는 타입 추론이 가능합니다. number라는 type은 대표적으로 숫자를 추론할 수 있는 자료형입니다.
  • Dart는 null safety를 지원합니다. 퀘스쳔 마크 ? 를 이용하여 해당 변수가 null을 가질 수 있을지 아닐지를 결정합니다. 예를들어 int? 라고 적으면 해당 변수가 int이거나 null이 될 수 있습니다. 만약 변수가 null을 절대 가지지 않을경우 느낌표 ! 를 이용해 표시 할 수 있습니다. 예를들어 int x = nullableButNotNullInt! 라고 적으면 x에 변수를 할당할때 절대 null이 아니다 라고 표시할 수 있습니다. 만약 null이면 exception을 일으킵니다.
  •  만약 아무 타입이나 받고 싶으면 Object? 또는 Object를 사용하여 받습니다. 단 런타임때 타입체크를 꼭 해주셔야합니다.
  • Dart는 generic type을 지원합니다. ex) List<int> 또는 List<Object>
  • 자바와 달리 Dart는 public, protected, private 키워드가 없습니다. 대신 underscore (_) 로 시작하면 private를 나타냅니다. ex) int _privateValue
  • Dart의 private 범위는 라이브러리 범위입니다. 즉 같은 클래스 내에서 private이 아니라 같은 파일 내에서 private 입니다.
  • 자바와 달리 클래스 생성에 new 키워드를 생략 할 수 있습니다.

 

변수선언

var name = 'Bob';

var은 reference를 저장하는 자료형입니다. 위 예제에서 name이라는 var이 String object를 담고 있습니다.

Object name = 'Bob';

컨셉에서 배운것 처럼 Object를 통하여 Dynamic하게 받아 줄 수 있습니다.

String name = 'Bob';

또는 위처럼 명시적으로 자료형을 지정해 줄 수 있고요.

 

 

 

 

var 과 dynamic

var은 타입추론이 가능합니다. 허나 한번 타입추론을 수행하면 이후 타입변경이 불가능 합니다.

var a= 123;
//a = "456"; // error!

dynamic은 var과 같이 타입추론이 가능한 변수입니다. 타입이 정해져도 이후 타입변경이 가능합니다.

dynamic a = 123
a = "456"; // OK

 

 

 

 

 

 

 

초기값

nullable 타입을 초기화 해주지 않고 선언하면 초기값으로 null을 가집니다.

int? lineCount;
assert(lineCount == null);

assert 함수는 괄호안에 조건문이 false일 경우 exception을 일으킵니다. flutter 개발중 debug모드에서는 assertion이 동작하고 debug모드가 아닐경우 assert 함수는 ignored 됩니다.

 

 

 

Non-nullable 타입의 경우 조금 다릅니다.

아래의 케이스 경우에는 non-nullable타입은 선언과 동시에 반스시 초기화 해 주어야 합니다.

  • 정적변수
  • 전역변수
  • 클래스의 필드일 경우 생성자에 초기화 하는 내용이 없을경우
  • non-nuallble타입이 함수의 optional타입으로 들어갈 경우 default value 필요

위 케이스를 제외하곤 선언 이후 사용전에만 초기화 해 주면 됩니다.

int global = 10; // 전역변수는 선언과 동시에 초기화 해 주어야 한다.

{
    static int staticVar = 10; // 정적변수는 선언과 동시에 초기화 해 주어야 한다.
    int lineCount;// 로컬변수는 선언문에 초기화를 해주지 않아도 된다.

    if (weLikeToCount) {
      lineCount = countLines();
    } else {
      lineCount = 0;
    }
    // print함수 사용전에 lineCount에 값을 부여
    print(lineCount);
}

 

 

 

Late 변수

Dart 2.12 버전에서 late라는 키워드가 추가되었습니다.

 

사용처는 다음과 같습니다

 

  • non nullable변수를 선언하되, 초기화는 나중에 할 때
  • 초기화 값을 lazily하게 할 때

 

앞서 non-nullable타입은 전역변수일 때 선언과 동시에 초기화 해 주어야 한다 했습니다.

하지만 late키워드를 붙이면 아래 처럼 가능합니다.

late String description;

void main() {
  description = 'Feijoada!';
  print(description);
}

 

 

다음으로 lazily에 대한 예제입니다. 아래 코드의 결과물이 이 어떻게 될 지 예상해 보세요!

 

int func(){
  print("func called");
  return 100;
}

void main() {
  late int a = func();
  print("hello world");
  print(a);
}

결과

hello world
func called
100

hello world가 가장 먼저 출력되었죠? 이유는 다음과 같습니다.

 

late키워드를 붙이고 선언과 동시에 초기화를 선언할 경우, 이니셜라이저는 변수 초기화를 변수가 처음 사용될때 초기화 합니다. 이는 아래 두가지 경우에 유용합니다.

 

  • 변수가 필요없거나 이니셜라이징 하는데 시간이 오래 걸릴때
  • 인스턴스 변수를 초기화 할 때, 그리고 이니셜라이저가 this를 통해 변수에 접근이 필요하다면

아래의 코드로 예를 들면 temperature라는 변수가 사용되지 않으면 _readTherometer() 함수는 영원히 사용되지 않습니다.

// This is the program's only call to _readThermometer().
late String temperature = _readThermometer(); // Lazily initialized.

 

 

 

 

 

Final과 Const

값을 변경을 원하지 않을경우 var 대신 finalconst를 이용합니다.

final과 const의 차이는 아래와 같습니다.

 

final : runtime 시점에서 값이 결정됨

const : compile 시점에서 값이 결정됨

 

즉 아래와 같이 함수로 값을 결정할경우 const는 compile 시점에서 값을 모르기에 오류가 납니다.

final time1 = DateTime.now(); // OK
const time2 = DateTime.now(); // 에러!

 

또한 아래와 같이 자료형을 명시해줄 수 있습니다.

 

const bar = 1000000; // Unit of pressure (dynes/cm2)
const double atm = 1.01325 * bar; // Standard atmosphere

 

 

Built-in 자료형

Dart에는 아래와 같은 built-in 자료형이 있습니다.

 

  • num, int, double
  • String
  • bool
  • List
  • Set
  • Map
  • Runes
  • Symbol
  • Null

Dart의 클래스 상속 구조는 아래와 같습니다.

 

자료형을 확인하는 방법

runtimeType 을 활용한다.

  int p=10;
  print(p.runtimeType);
int

 

 

Num 자료형

num 자료형은 int도 될 수 있고 double도 될 수 있는 자료형입니다.

dart에서 int와 double은 8바이트(64bit 입니다)

num x = 1; // x can have both int and double values
x += 2.5;

 

 

int형 자료형은 플랫폼에 따라 범위가 달라집니다.

Native 플랫폼(모바일이나 데스크탑) :  -2^63 ~ 2^63 -1

Web 플랫폼(자바스크립트로 돌아가는 플랫폼) : -2^53  ~ 2^53 -1

 

String 자료형

말그대로 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} 을 사용합니다. 중괄호{} 는 생략가능합니다.

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!');

 

Dart에 스트링이 같은지 비교할 때 == 연산자를 사용합니다.

 

 

Dart에서는 multi-line String을 지원합니다. 사용법은 따음표 세개를 쓰면 됩니다. ''', """ 둘다 사용가능합니다.

 

var s1 = '''
You can create
multi-line strings like this one.
''';

var s2 = """This is also a
multi-line string.""";

 

 

 

 

List

Dart에서는 배열을 List라고 부릅니다. List는 파이썬처럼 여러 자료형을 받을 수 있고 제네릭을 사용하면 리스트 안의 타입을 강제화 시킬 수 있습니다.

List list = [1,2.2,'3'];  // JSArray<dynamic>
List<int> list = [1, 2, 3]; // JSArray<int>, 제네릭을 이용한 타입 강제화

 

Dart에서는 spread operator(...)null-aware spread operator(...?) 을 제공합니다.

이 연산자는 여러값을 입력하는데 사용합니다.

이를 활용하여 아래의 예제처럼 list안에 list를 붙여넣을 수 있습니다.

 

var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);

만약 입력해주는 list가 null이 될 수 있을경우 아래처럼 null-aware spread operator(...?)  을 사용하여 붙여 줄 수 있습니다.

 

var list;
var list2 = [0, ...?list];
assert(list2.length == 1);

 

Dart는 collection ifcollection for 기능을 제공합니다. 이 기능은 list안에 if문과 for문을 사용 가능하게 해줍니다.

 

var nav = [
  'Home',
  'Furniture',
  'Plants',
  if (promoActive) 'Outlet'
];
var listOfInts = [1, 2, 3];
var listOfStrings = [
  '#0',
  for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');

 

 

 

Set

파이선의 집합 자료형과 동일합니다. 중괄호를 사용합니다 {}

add()addAll() 함수를 사용해 데이터를 입력합니다.

Set<String> halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};

var elements = <String>{};
elements.add('fluorine');
elements.addAll(halogens);
assert(elements.length == 5);

 

 

Map

파이썬의 사전형과 동일합니다. 중괄호를 사용하며 key, value로 저장합니다.

Map<String,String> gifts = {
  // Key:    Value
  'first': 'partridge',
  'second': 'turtledoves',
  'fifth': 'golden rings'
};
var nobleGases = {
  2: 'helium',
  10: 'neon',
  18: 'argon',
};

아래와 같이 데이터를 입력 해 줄 수 있습니다.

 

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';

 

 

 

 

 

 

Set, Map에서 spread operator, collection if, collection for

Set, Map에서도 spread operatorcollection if, collection for 기능을 사용 할 수 있습니다.

void main() {
  var items;
  items = [2, 3, 4];
  var set = { 1, 2, ...items };
  print(set);
  //{1, 2, 3, 4}
  
  set = {...set, if(true) 5};
  print(set);
  //{1, 2, 3, 4, 5}

  set = {...set, for(int i =5;i<10;i++)i};
  print(set);
  //{1, 2, 3, 4, 5, 6, 7, 8, 9}
  
  items = {'c':1};
  var dictionary = {'a':1, 'b':2, ...items};
  print(dictionary);
  //{a: 1, b: 2, c: 1}
  
  dictionary = {...dictionary, if(true) 'd':4};
  print(dictionary);
  //{a: 1, b: 2, c: 1, d: 4}
  
  dictionary = {...dictionary, for(int i=5;i<10;i++)String.fromCharCode('a'.codeUnits[0]+i):i};
  print(dictionary);
  //{a: 1, b: 2, c: 1, d: 4, f: 5, g: 6, h: 7, i: 8, j: 9}
  
}

 

 

 

 

함수

Dart는 true object-oriented 언어입니다. 심지어 함수조차 객체이며 Function이라는 type을 가집니다. 이는 함수가 값을 assign 받을 수 있고 또는 arguments로 다른 함수에 pass 될 수도 있습니다.

 

아래는 기본적인 함수 예제입니다.

bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

 

함수의 리턴형은 생략 될 수 있습니다. 허나 권장하진 않습니다.

 

isNoble(atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}

 

함수가 한줄이라면 아래와 같이 shorthand로 작성 할 수 있습니다.

 

bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

=> expr 의 의미는 { return expr; } 입니다.

 

 

아래와 같이 함수는 변수로도 사용 가능합니다.

loveBoungUh(){
  print("I love loveBoungUh");
}

Function fun;

fun = loveBoungUh();

fun();
//I love loveBoungUh

 

 

 

 

 

 

Optional Parameter

Dart는 함수 input을 받을때 일부는 optional로 받아도 그만 안받아도 그만인걸로 설정 할 수 있습니다.

 

사용법은 인풋을 대괄호로 묶어주면 됩니다.

 

이를통해 함수오버로딩을 엄청나게 생략 할 수 있을지도..!

 

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');

 

Named Parameter

Dart의 핵심적인 기능입니다.

 

함수를 선언할때 아래와 같이 선언했다고 가정해봅시다.

void fun(int input1,int input2, int input3){}

그럼 우리가 함수에 input1부터 input3까지 순서대로 입력해주려면 함수에 순서대로 입력해야 합니다.

 

fun(인풋1, 인풋2, 인풋3)

 

이렇게요. 이를 required positional parameters 라고 합니다.

 

 

여기서 named parameter란 이러한 순서에 상관없이 인풋을 입력하도록 해주는 기능입니다.

 

사용법은 아래와 같습니다.

  • 함수를 선언할 때 named parameter로 사용할 변수들은 중괄호로 묶어준다
  • 함수에 인풋을 입력할때 인풋명을 명시해준다.
//함수 선언
void enableFlags({bool? bold, bool? hidden}) {...}

//사용
enableFlags(bold: true, hidden: false);

 

 

 

required 키워드

한가지 기억해주실게 있습니다. named parameter은 optional parameter입니다!

 

//함수 선언
void enableFlags({bool? bold, bool? hidden}) {}

//사용, optional 파라미터이기에 인풋이 생략 가능하다
enableFlags(bold: true);

만약 named parameter들 중 반드시 요구되는 인풋이 있다면 required 키워드를 사용하면 됩니다.

const Scrollbar({Key? key, required Widget child})

 

 

 

Type test operators

아래는 dart의 type에 관한 operator입니다

 

as : 타입캐스팅

is : object가 특정한 타입일때

is! : object가 특정한 타입이 아닐때(파이선은 is not 과 동일)

 

 

(employee as Person).firstName = 'Bob';
if (employee is Person) {
  // Type check
  employee.firstName = 'Bob';
}

 

 

 

 

조건연산

Dart의 조건연산은 아래와 같습니다

  • condition ? expr1 : expr2  // 삼항연산
  • expr1 ?? expr2  // expr1이 null이 아니면 expr1 리턴, null일경우 expr2 리턴, 코틀린의 엘비스 연산자와 동일

 

 

?. 연산

클래스의 멤버에 접근할때 보통 . 연산자를 사용합니다. 만약 클래스가 null이 될 가능성이 있는경우 ?. 로 접근합니다.

 

class?.member;

 

 

 

Cascase 표시법

Dart에서는 cascades 연산자 ....? 을 이용하여 같은 객체에 연속으로 연산을 할 수 있습니다.

동일안 객체의 멤버변수에 연속적으로 접근할 수 있고 심지어 함수도 호출 가능합니다.

 

var paint = Paint();
paint.color = Colors.black;
paint.strokeCap = StrokeCap.round;
paint.strokeWidth = 5.0;

위 코드는 아래와 같이 간결하게 적을 수 있습니다.

var paint = Paint()
  ..color = Colors.black
  ..strokeCap = StrokeCap.round
  ..strokeWidth = 5.0;

 

 

만약에 cascade를 하려는 object가 null이 될 수 있는 경우 ?.. 연산자를 이용하여 cascade 연산을 할 수 있습니다.

querySelector('#confirm') // Get an object.
  ?..text = 'Confirm' // Use its members.
  ..classes.add('important')
  ..onClick.listen((e) => window.alert('Confirmed!'));

위 코드는 아래와 동일합니다.

var button = querySelector('#confirm');
button?.text = 'Confirm';
button?.classes.add('important');
button?.onClick.listen((e) => window.alert('Confirmed!'));

 

 

 

또한 cascade는 nesting 하여 사용 가능합니다.

 

final addressBook = (AddressBookBuilder()
      ..name = 'jenny'
      ..email = 'jenny@example.com'
      ..phone = (PhoneNumberBuilder()
            ..number = '415-555-0100'
            ..label = 'home')
          .build())
    .build();

 

 

 

주의하실점은 함수가 객체를 리턴하는지 안하는지 꼭 확인해야 한다는 점입니다. 아래와 같은경우는 에러가 납니다.

var sb = StringBuffer();
sb.write('foo')
  ..write('bar'); // Error: void객체에는 write함수가 없습니다.

 

 

 

Switch문

Dart의 switch문의 사용법은 다른 언어들과 비슷합니다. 단 몇가지 차이점이 있습니다.

 

  • switch문에 정수형 자료뿐만 아니라 string, 또는 compile-time constants까지 사용가능합니다.
  • 자료형 비교는 == 연산자를 사용합니다.
  • 비교되는 객체는 반드시 동일한 class의 객체이여야 하고 == 연산자를 override 하면 안됩니다.
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();
}

 

 

아래예제는 break를 생략한 예제입니다. 에러를 일으킵니다.

 

var command = 'OPEN';
switch (command) {
  case 'OPEN':
    executeOpen();
    // ERROR: Missing break

  case 'CLOSED':
    executeClosed();
    break;
}

 

하지만 Dart에서는 empty case 를 지원합니다. 아래같은경우 에러가 나지 않습니다.

var command = 'CLOSED';
switch (command) {
  case 'CLOSED': // Empty case falls through.
  case 'NOW_CLOSED':
    // Runs for both CLOSED and NOW_CLOSED.
    executeNowClosed();
    break;
}

 

만약에 fall-through를 하고싶다면 아래와 같이 continue 문을 이용하여 사용 가능합니다.

 

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;
}

 

 

 

 

Assert문

 

앞서 말씀드린대로 개발중에 Assert를 활용하면 많은 에러를 잡으실 수 있습니다.

flutter의 debug 모드에서 Assert함수는 주어진 조건이 false이면 exception을 일으킵니다.

 

 

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 함수가 활성화 되려면 다음과 같은 조건이 있습니다.

  • Flutter에서 디버깅모드를 수행하면 assert가 활성화 됩니다.
  • dartdevc같은 development-only tools들은 assertion을 활성화 시킵니다.
  • dart run, dart2js 같은 tools은 --enable-asserts 라는 flag를 주면 assertion이 활성화 됩니다.

 

Typedefs

 

Dart에도 typedef가 있습니다. 사용법은 아래와 같습니다/

typedef IntList = List<int>;
IntList il = [1, 2, 3];

 

 

아래와 같이 typedef는 typeparameter도 가질 수 있습니다.

 

typedef ListMapper<X> = Map<X, List<X>>;
Map<String, List<String>> m1 = {}; // Verbose.
ListMapper<String> m2 = {}; // Same thing but shorter and clearer.

 

 

 

Metadata

자바의 annotation과 같은 기능입니다.

 

자바와 마찬가지로 기본적으로 @Deprecated, @deprecated, @override 등이 있습니다.

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() {...}
  // ···
}

 

아래와 같이 커스텀 Metadata도 만들 수 있습니다.

library todo;

class Todo {
  final String who;
  final String what;

  const Todo(this.who, this.what);
}
import 'todo.dart';

@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}

 

반응형

'Dart > 기초' 카테고리의 다른 글

[Dart] var과 dynamic  (0) 2021.10.15
[Dart] mixin 클래스  (0) 2021.10.15
[Dart] Dart 클래스 배우기  (0) 2021.10.14
[Dart] 리스트이어 붙이기. spread operator ...  (0) 2021.07.26
Dart의 선택적 매개변수  (0) 2021.07.20

댓글