자바 개발자를 위한 Dart 소개

Dart는 단일 코드베이스에서 멋진 네이티브 컴파일 모바일 앱, 웹 앱, 데스크톱 앱을 빌드할 수 있는 Google의 UI 도구 키트인 Flutter를 위한 프로그래밍 언어입니다.

이 Codelab에서는 자바 개발자가 예상치 못한 기능에 초점을 맞추고 Dart를 소개합니다. 1분 이내에 Dart 함수를, 5분 이내에 스크립트를, 10분 이내에 앱을 작성할 수 있습니다.

학습할 내용

  • 생성자를 만드는 방법
  • 매개변수를 지정하는 다양한 방법
  • getter 및 setter를 만드는 시기 및 방법
  • Dart에서 공개 설정을 처리하는 방식
  • 팩토리를 만드는 방법
  • Dart에서 함수 프로그래밍 작동 방식
  • 기타 핵심 Dart 개념

요구사항

이 Codelab을 완료하려면 브라우저만 있으면 됩니다.

Dart 언어 기능 및 핵심 라이브러리를 사용할 수 있는 대화형 브라우저 기반 도구인 DartPad에서 모든 예를 작성하고 실행합니다. 원하는 경우 WebStorm, Dart 플러그인이 있는 IntelliJ 또는 Dart 코드 확장이 있는 Visual Studio Code와 같은 IDE를 대신 사용할 수도 있습니다.

이 Codelab에서 학습하고 싶은 내용은 무엇인가요?

이 주제를 처음 접하므로 개요를 파악하고 싶습니다. 이 주제에 관해 약간 알고 있지만 한 번 더 확인하고 싶습니다. 프로젝트에 사용할 코드 예를 찾고 있습니다. 특정 항목에 관한 설명을 찾고 있습니다.

먼저, 자바 튜토리얼의 Bicycle 클래스와 동일한 기능이 있는 간단한 Dart 클래스를 빌드합니다. Bicycle 클래스에는 getter 및 setter가 있는 비공개 인스턴스 변수가 몇 가지 포함되어 있습니다. main() 메서드는 Bicycle을 인스턴스화하여 콘솔에 출력합니다.

99c813a1913dcc42.png c97a12197358d545.png

DartPad 실행

이 Codelab은 모든 연습 세트를 위한 새로운 DartPad 인스턴스를 제공합니다. 아래 링크를 클릭하면 기본 'Hello' 예가 포함된 새 인스턴스가 열립니다. Codelab 전체에서 동일한 DartPad를 계속 사용할 수 있지만, Reset을 클릭하는 경우 DartPad가 다시 기본 예로 돌아가며 작업한 내용을 잃게 됩니다.

b2f84ff91b0e1396.png DartPad 열기

Bicycle 클래스 정의

b2f84ff91b0e1396.png main() 함수 위에 3개의 인스턴스 변수가 있는 Bicycle 클래스를 추가합니다. 또한 다음 코드 스니펫과 같이 main()에서 내용을 삭제합니다.

class Bicycle {
  int cadence;
  int speed;
  int gear;
}

void main() {
}

cf1e10b838bf60ee.png 유용한 정보

  • Dart 기본 메서드의 이름은 main()입니다. 명령줄 인수에 액세스해야 하는 경우 main(List<String> args)와 같이 인수를 추가할 수 있습니다.
  • main() 메서드는 최상위 수준에 있습니다. Dart에서는 클래스 외부에 코드를 정의할 수 있습니다. 변수, 함수, getter, setter는 모두 클래스 외부에 있을 수 있습니다.
  • 원래의 자바 예에서는 Dart가 사용하지 않는 private 태그를 사용하여 비공개 인스턴스 변수를 선언합니다. 공개 설정에 관한 내용은 잠시 후 '읽기 전용 변수 추가'에서 자세히 알아봅니다.
  • 모든 식별자가 기본적으로 공개되므로 main()Bicyclepublic으로 선언하지 않습니다. Dart에는 public, private 또는 protected 키워드가 없습니다.
  • 이 예에서 Dart Analyzer는 변수가 null을 허용하지 않으므로 초기화되어야 한다고 알리는 오류를 생성합니다. 다음 섹션에서 이 오류를 수정합니다.
  • Dart는 규칙에 따라 4자가 아닌 2자 들여쓰기를 사용합니다. dartfmt라는 편리한 도구 덕분에 Dart의 공백 규칙에 관해 걱정할 필요가 없습니다. Dart 코드 규칙(효과적인 Dart)에 따르면 'Dart의 공식 공백 처리 규칙은 dartfmt가 생성하는 것'입니다.

Bicycle 생성자 정의

b2f84ff91b0e1396.png 다음 생성자를 Bicycle 클래스에 추가합니다.

Bicycle(this.cadence, this.speed, this.gear);

cf1e10b838bf60ee.png 유용한 정보

  • 이 생성자에는 본문이 없으며, 이는 Dart에서 유효합니다.
  • 본문이 없는 생성자의 끝부분에 세미콜론(;)을 추가하는 것을 잊어버린 경우 DartPad는 'A function body must be provided.'라는 오류를 표시합니다.
  • 생성자의 매개변수 목록에 this를 사용하면 인스턴스 변수에 값을 할당하는 편리한 단축어 역할을 합니다.
  • 위 코드는 다음 코드와 같습니다.
Bicycle(int cadence, int speed, int gear) {
  this.cadence = cadence;
  this.speed = speed;
  this.gear = gear;
}

코드 형식 지정

DartPad UI 상단의 Format을 클릭하여 언제든지 Dart 코드의 형식을 다시 지정할 수 있습니다. 형식 다시 지정은 특히 DartPad에 코드를 붙여넣을 때 유용하며 양쪽 맞춤은 꺼져 있습니다.

b2f84ff91b0e1396.png Format을 클릭합니다.

Bicycle 인스턴스의 인스턴스화 및 출력

b2f84ff91b0e1396.png 다음 코드를 main() 함수에 추가합니다.

void main() {
  var bike = new Bicycle(2, 0, 1);
  print(bike);
}

b2f84ff91b0e1396.png 다음과 같이 선택사항인 new 키워드를 삭제합니다.

var bike = Bicycle(2, 0, 1);

cf1e10b838bf60ee.png 유용한 정보

  • Dart 2에서는 new 키워드가 선택사항이 되었습니다.
  • 변수의 값이 변경되지 않는다는 것을 알고 있다면 var 대신 final을 사용할 수 있습니다.

예 실행

b2f84ff91b0e1396.png DartPad 창 상단의 Run을 클릭하여 예를 실행합니다. Run이 사용 설정되어 있지 않으면 이 페이지의 뒷부분에 있는 문제 섹션을 참고하세요.

다음과 같은 출력이 표시됩니다.

Instance of 'Bicycle'

cf1e10b838bf60ee.png 유용한 정보

  • 유형 추론이 작동하고 있으며 Analyzer가 var bike =로 시작하는 문이 Bicycle 인스턴스를 정의한다고 추론함을 나타내는 오류 또는 경고가 표시되면 안 됩니다.

출력 개선

출력(Instance of 'Bicycle')은 정확하지만 그다지 유용하지는 않습니다. 모든 Dart 클래스에는 더 유용한 출력을 제공하기 위해 재정의할 수 있는 toString() 메서드가 있습니다.

b2f84ff91b0e1396.png Bicycle 클래스의 아무 곳에나 다음 toString() 메서드를 추가합니다.

@override
String toString() => 'Bicycle: $speed mph';

cf1e10b838bf60ee.png 유용한 정보

  • @override 주석은 의도적으로 멤버를 재정의한다는 것을 Analyzer에 알립니다. 적절하게 재정의하지 못하면 Analyzer가 오류를 발생시킵니다.
  • Dart는 문자열 지정 시 작은따옴표 또는 큰따옴표를 지원합니다.
  • 문자열 보간 유형을 사용하여 문자열 리터럴, 즉 ${expression} 내에 표현식 값을 넣을 수 있습니다. 표현식이 식별자인 경우 $variableName과 같이 중괄호를 생략할 수 있습니다.
  • 이중 화살표(=>) 표기법을 사용하여 한 줄 함수 또는 메서드를 줄일 수 있습니다.

예 실행

b2f84ff91b0e1396.png Run을 클릭합니다.

다음과 같은 출력이 표시됩니다.

Bicycle: 0 mph

문제가 있나요? 코드를 확인하세요.

읽기 전용 변수 추가

원래의 자바 예에서는 speed를 읽기 전용 변수로 정의합니다. 그리고 이 변수를 비공개로 선언하고 getter만 제공합니다. 이제 Dart에서 동일한 기능을 제공하겠습니다.

b2f84ff91b0e1396.png DartPad에서 bicycle.dart를 엽니다(또는 자체 복사본을 계속 사용).

라이브러리에 Dart 식별자를 비공개로 표시하려면 이름을 밑줄(_)로 시작해야 합니다. 이름을 변경하고 getter를 추가하여 speed를 읽기 전용으로 변환할 수 있습니다.

speed를 비공개, 읽기 전용 인스턴스 변수로 설정

b2f84ff91b0e1396.png 다음과 같이 Bicycle 생성자에서 speed 매개변수를 삭제합니다.

Bicycle(this.cadence, this.gear);

b2f84ff91b0e1396.png 다음과 같이 main()Bicycle 생성자 호출에서 두 번째 (speed) 매개변수를 삭제합니다.

var bike = Bicycle(2, 1);

b2f84ff91b0e1396.png 나머지 speed 항목을 _speed로 변경합니다. (2번 나옴)

b2f84ff91b0e1396.png 다음과 같이 _speed를 0으로 초기화합니다.

int _speed = 0;

b2f84ff91b0e1396.png 다음 getter를 Bicycle 클래스에 추가합니다.

int get speed => _speed;

cf1e10b838bf60ee.png 유용한 정보

  • 각 변수(숫자인 경우도 포함)는 유형 선언에 ?를 추가하여 null을 허용하는 유형으로 선언하거나 초기화해야 합니다.
  • Dart 컴파일러는 이름이 밑줄로 시작하는 모든 식별자에 라이브러리 공개 설정을 적용합니다. 라이브러리 공개 설정은 일반적으로 식별자가 정의된 파일(클래스만이 아니라) 내에서만 식별자가 표시됨을 의미합니다.
  • 기본적으로 Dart는 모든 공개 인스턴스 변수에 암시적 getter 및 setter를 제공합니다. 읽기 전용 또는 쓰기 전용 변수를 적용하거나 값을 계산 또는 확인하거나 다른 곳에서 값을 업데이트하려는 경우가 아니라면 자체 getter 또는 setter를 정의할 필요가 없습니다.
  • 원래의 자바 샘플에서는 cadencegear에 getter 및 setter를 제공했습니다. Dart 샘플은 이러한 변수에 명시적인 getter 및 setter가 필요하지 않으므로 인스턴스 변수만 사용합니다.
  • bike.cadence와 같은 간단한 필드부터 시작하여 나중에 getter 및 setter를 사용하도록 리팩터링할 수 있습니다. API는 동일하게 유지됩니다. 달리 말하면 필드에서 getter 및 setter로 이동하는 것은 Dart에서 브레이킹 체인지가 아닙니다.

읽기 전용 인스턴스 변수로 speed 구현 완료

b2f84ff91b0e1396.png 다음 메서드를 Bicycle 클래스에 추가합니다.

void applyBrake(int decrement) {
  _speed -= decrement;
}

void speedUp(int increment) {
  _speed += increment;
}

최종적인 Dart 예는 원래의 자바와 비슷하지만 40줄이 아닌 23줄로 더 간결합니다.

class Bicycle {
  int cadence;
  int _speed = 0;
  int get speed => _speed;
  int gear;

  Bicycle(this.cadence, this.gear);

  void applyBrake(int decrement) {
    _speed -= decrement;
  }

  void speedUp(int increment) {
    _speed += increment;
  }

  @override
  String toString() => 'Bicycle: $_speed mph';
}

void main() {
  var bike = Bicycle(2, 1);
  print(bike);
}

문제가 있나요? 코드를 확인하세요.

다음 연습에서는 자바 튜토리얼의 또 다른 예인 Rectangle 클래스를 정의합니다.

자바 코드는 생성자가 이름은 동일하지만 매개변수의 수 또는 유형은 다른 자바의 일반적인 방식인 오버로딩 생성자를 보여줍니다. Dart는 오버로딩 생성자를 지원하지 않으며 이 섹션에서 확인할 수 있듯이 이 상황을 다르게 처리합니다.

b2f84ff91b0e1396.png DartPad에서 Rectangle 예를 엽니다.

Rectangle 생성자 추가

b2f84ff91b0e1396.png 다음과 같이 자바 예에 있는 4개의 생성자를 모두 대체하는 하나의 빈 생성자를 추가합니다.

Rectangle({this.origin = const Point(0, 0), this.width = 0, this.height = 0});

이 생성자는 이름이 지정된 선택적 매개변수를 사용합니다.

cf1e10b838bf60ee.png 유용한 정보

  • this.origin, this.width, this.height는 생성자의 선언 내에 인스턴스 변수를 할당하는 데 약칭 기법을 사용합니다.
  • this.origin, this.width, this.height는 이름이 지정된 선택적 매개변수입니다. 이름이 지정된 매개변수는 중괄호({})로 묶여 있습니다.
  • this.origin = const Point(0, 0) 구문은 origin 인스턴스 변수의 기본값 Point(0,0)을 지정합니다. 지정된 기본값은 컴파일 시간 상수여야 합니다. 이 생성자는 인스턴스 변수 3개 모두의 기본값을 제공합니다.

출력 개선

b2f84ff91b0e1396.png 다음 toString() 함수를 Rectangle 클래스에 추가합니다.

@override
String toString() =>
      'Origin: (${origin.x}, ${origin.y}), width: $width, height: $height';

생성자 사용

b2f84ff91b0e1396.png main()을 다음 코드로 대체함으로써 필요한 매개변수만 사용하여 Rectangle을 인스턴스화할 수 있는지 확인합니다.

main() {
  print(Rectangle(origin: const Point(10, 20), width: 100, height: 200));
  print(Rectangle(origin: const Point(10, 10)));
  print(Rectangle(width: 200));
  print(Rectangle());
}

cf1e10b838bf60ee.png 유용한 정보

  • Rectangle의 Dart 생성자는 한 줄의 코드입니다. 이는 자바 버전의 동등한 생성자에 16줄의 코드를 사용하는 것과 비교됩니다.

예 실행

다음과 같은 출력이 표시됩니다.

Origin: (10, 20), width: 100, height: 200
Origin: (10, 10), width: 0, height: 0
Origin: (0, 0), width: 200, height: 0
Origin: (0, 0), width: 0, height: 0

문제가 있나요? 코드를 확인하세요.

자바에서 흔히 사용되는 설계 패턴인 팩토리는 인스턴스화의 세부정보를 숨기고 팩토리 반환 유형의 하위유형을 반환하는 기능을 제공하며 선택적으로 새 객체가 아닌 기존 객체를 반환하는 등 직접 객체 인스턴스화에 비해 여러 이점이 있습니다.

이 단계에서는 다음과 같이 도형 생성 팩토리를 구현하는 두 가지 방법을 보여줍니다.

  • 옵션 1: 최상위 함수 만들기
  • 옵션 2: 팩토리 생성자 만들기

이 연습에서는 도형을 인스턴스화하고 계산된 영역을 출력하는 Shapes 예를 사용합니다.

import 'dart:math';

abstract class Shape {
  num get area;
}

class Circle implements Shape {
  final num radius;
  Circle(this.radius);
  num get area => pi * pow(radius, 2);
}

class Square implements Shape {
  final num side;
  Square(this.side);
  num get area => pow(side, 2);
}

main() {
  final circle = Circle(2);
  final square = Square(2);
  print(circle.area);
  print(square.area);
}

b2f84ff91b0e1396.png DartPad에서 Shapes 예를 엽니다.

콘솔 영역에 다음과 같이 원 및 정사각형의 계산된 영역이 표시되어야 합니다.

12.566370614359172
4

cf1e10b838bf60ee.png 유용한 정보

  • Dart는 추상 클래스를 지원합니다.
  • 하나의 파일에 여러 클래스를 정의할 수 있습니다.
  • dart:math는 Dart의 핵심 라이브러리 중 하나입니다. 다른 핵심 라이브러리로는 dart:core, dart:async, dart:convert, dart:collection이 있습니다.
  • 규칙에 따라 Dart 라이브러리 상수는 lowerCamelCase(예: PI) 대신 pi)입니다. 그 이유에 관해 궁금한 경우 스타일 가이드라인, 상수 이름에 lowerCamelCase 사용 선호를 참고하세요.
  • 다음 코드는 값을 계산하는 2개의 getter를 보여줍니다. num get area => pi * pow(radius, 2); // Circle num get area => pow(side, 2); // Square

옵션 1: 최상위 함수 만들기

b2f84ff91b0e1396.png 최상위 수준(모든 클래스 외부)에 다음 함수를 추가하여 팩토리를 최상위 함수로 구현합니다.

Shape shapeFactory(String type) {
  if (type == 'circle') return Circle(2);
  if (type == 'square') return Square(2);
  throw 'Can\'t create $type.';
}

b2f84ff91b0e1396.png main() 메서드의 처음 두 줄을 다음 코드로 대체하여 팩토리 함수를 호출합니다.

  final circle = shapeFactory('circle');
  final square = shapeFactory('square');

예 실행

출력은 이전과 동일하게 표시됩니다.

cf1e10b838bf60ee.png 유용한 정보

  • 'circle' 또는 'square' 이외의 문자열로 함수를 호출하면 예외가 발생합니다.
  • Dart SDK는 많은 일반적인 예외의 클래스를 정의합니다. 그러나 개발자가 직접 Exception 클래스를 구현하여 더 구체적인 예외를 만들거나 이 예에서와 같이 마주한 문제를 설명하는 문자열을 발생시킬 수 있습니다.
  • 예외가 발생하면 DartPad는 Uncaught를 보고합니다. 더 유용한 정보를 보려면 try-catch 문으로 코드를 래핑하고 예외를 출력합니다. 선택적 연습으로 이 DartPad 예를 확인해 보세요.
  • 문자열 내에 작은따옴표를 사용하려면 슬래시를 사용하여 삽입된 따옴표를 이스케이프 처리하거나('Can\'t create $type.') 큰따옴표로 문자열을 지정합니다("Can't create $type.").

문제가 있나요? 코드를 확인하세요.

옵션 2: 팩토리 생성자 만들기

Dart의 factory 키워드를 사용하여 팩토리 생성자를 만듭니다.

b2f84ff91b0e1396.png 다음과 같이 추상 Shape 클래스에 팩토리 생성자를 추가합니다.

abstract class Shape {
  factory Shape(String type) {
    if (type == 'circle') return Circle(2);
    if (type == 'square') return Square(2);
    throw 'Can\'t create $type.';
  }
  num get area;
}

b2f84ff91b0e1396.png 도형을 인스턴스화하는 다음 코드로 main()의 처음 두 줄을 대체합니다.

  final circle = Shape('circle');
  final square = Shape('square');

b2f84ff91b0e1396.png 이전에 추가한 shapeFactory() 함수를 삭제합니다.

cf1e10b838bf60ee.png 유용한 정보

  • 팩토리 생성자의 코드는 shapeFactory() 함수에 사용된 코드와 동일합니다.

문제가 있나요? 코드를 확인하세요.

모든 클래스가 인터페이스를 정의하므로 Dart 언어에는 interface 키워드가 포함되어 있지 않습니다.

b2f84ff91b0e1396.png DartPad에서 Shapes 예를 엽니다(또는 자체 복사본을 계속 사용합니다).

b2f84ff91b0e1396.png 다음과 같이 Circle 인터페이스를 구현하는 CircleMock 클래스를 추가합니다.

class CircleMock implements Circle {}

b2f84ff91b0e1396.png CircleMockCircle구현을 상속하지 않고 인터페이스만 사용하므로 'Missing concrete implementations' 오류가 표시됩니다. 다음과 같이 arearadius 인스턴스 변수를 정의하여 이 오류를 수정합니다.

class CircleMock implements Circle {
  num area = 0;
  num radius = 0;
}

cf1e10b838bf60ee.png 유용한 정보

  • CircleMock 클래스가 어떤 동작도 정의하지 않더라도 유효한 Dart입니다. Analyzer는 오류를 발생시키지 않습니다.
  • CircleMockarea 인스턴스 변수는 Circlearea getter를 구현합니다.

문제가 있나요? 코드를 확인하세요.

함수 프로그래밍에서는 다음과 같은 작업을 할 수 있습니다.

  • 함수를 인수로 전달합니다.
  • 변수에 함수를 할당합니다.
  • 여러 인수를 사용하는 함수를 각각 단일 인수를 사용하는 함수 시퀀스로 분해합니다(커링이라고도 함).
  • 상숫값으로 사용할 수 있는 이름 없는 함수를 만듭니다(람다 표현식이라고도 하며, 람다 표현식은 JDK 8 출시에서 자바에 추가되었음).

Dart는 이러한 모든 기능을 지원합니다. Dart에서는 함수도 객체이며 Function 유형을 가집니다. 즉, 함수를 변수에 할당하거나 다른 함수에 인수로 전달할 수 있습니다. 또한 이 예와 같이 Dart 클래스의 인스턴스를 함수인 것처럼 호출할 수도 있습니다.

다음 예에서는 명령형(함수형이 아님) 코드를 사용합니다.

String scream(int length) => "A${'a' * length}h!";

main() {
  final values = [1, 2, 3, 5, 10, 50];
  for (var length in values) {
    print(scream(length));
  }
}

b2f84ff91b0e1396.png DartPad에서 Scream 예를 엽니다.

출력은 다음과 같이 표시됩니다.

Aah!
Aaah!
Aaaah!
Aaaaaah!
Aaaaaaaaaaah!
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaah!

cf1e10b838bf60ee.png 유용한 정보

  • 문자열 보간 유형을 사용할 때 ${'a' * length} 문자열은 ''a' 문자를 length회 반복함'으로 평가됩니다.

명령형 코드를 함수형으로 변환

b2f84ff91b0e1396.png 다음과 같이 main()에서 명령형 for() {...} 루프를 삭제하고 메서드 체이닝을 사용하는 한 줄의 코드로 바꿉니다.

  values.map(scream).forEach(print);

예 실행

함수형 방식은 명령형 예와 동일한 6개의 스크림을 출력합니다.

문제가 있나요? 코드를 확인하세요.

추가 Iterable 기능 사용

핵심 ListIterable 클래스는 fold(), where(), join(), skip() 등을 지원합니다. 또한 Dart는 매핑 및 세트도 기본적으로 지원합니다.

b2f84ff91b0e1396.png main()values.map() 줄을 다음 코드로 대체합니다.

  values.skip(1).take(3).map(scream).forEach(print);

예 실행

출력은 다음과 같이 표시됩니다.

Aaah!
Aaaah!
Aaaaaah!

cf1e10b838bf60ee.png 유용한 정보

  • skip(1)values 목록 리터럴에서 첫 번째 값인 1을 건너뜁니다.
  • take(3)values 목록 리터럴에서 다음 3개 값(2, 3, 5)을 가져옵니다.
  • 나머지 값은 건너뜁니다.

문제가 있나요? 코드를 확인하세요.

이 Codelab을 완료함으로써 자바와 Dart의 차이점을 몇 가지 알게 되었습니다. Dart는 학습하기 쉽습니다. 또한 Dart의 핵심 라이브러리사용 가능한 다양한 패키지 세트를 통해 생산성을 높일 수 있습니다. Dart는 큰 앱으로도 원활하게 확장됩니다. 수백 명의 Google 엔지니어가 Dart를 사용하여 Google의 수익 창출에 기여하는 미션 크리티컬 앱을 작성합니다.

다음 단계

20분 분량의 Codelab으로는 자바와 Dart의 차이점을 모두 보여주기에 충분하지 않습니다. 예를 들어 이 Codelab은 다음 사항에 관해서는 다루지 않았습니다.

  • async/await - 이 패턴을 사용하면 비동기 코드를 동기 코드인 것처럼 작성할 수 있습니다. π의 소수점 이하 처음 5자리 계산을 애니메이션으로 보여주는 이 DartPad 예를 확인해 보세요.
  • 메서드 캐스케이드 - 모든 것이 빌더입니다!
  • Null 인식 연산자

Dart 기술이 실제로 작동하는 것을 보고 싶다면 Flutter Codelab을 사용해 보세요.

자세히 알아보기

다음 자료, 리소스, 웹사이트에서 Dart에 관해 더 자세히 알아볼 수 있습니다.

자료

리소스

웹사이트