시리즈

[플러터] 다트에서 같다는 것

20220114
class
const
dart
equality
equatable
final
flutter
object
operator

 다트에서 같다는 것은 무엇일까?

num(int, double), String, bool 처럼 하나의 값을 가지는 타입에서는 간단하다.

test('test 1', () {
      // 1과 2는 다르다
      expect(1 == 2, false);
      // 1과 1은 같다.
      expect(1 == 1, true);
      // 1.0 과 1은 같다. (num 타입)
      expect(1.0 == 1, true);
      expect('Hello' == 'Hi', false);
      expect('Hello' == 'hello', false);
      // 대소문자까지 같아야 같다.
      expect('Hello' == 'Hello', true);

      // 다트에서 1은 true 가 아니다.
      expect(1 == true, false);
			// 0도 false가 아니다.
      expect(0 == false, false);
    });

Object 에서는 조금 다르게 행동한다.

/// Object
/// The base class for all Dart objects except `null`.
///
/// Because `Object` is a root of the non-nullable Dart class hierarchy,
/// every other non-`Null` Dart class is a subclass of `Object`.
class Point extends Object {
  Point(this.y, this.x);

  int y;
  int x;

  void change(int newY, int newX) {
    y = newY;
    x = newX;
  }
}

사용할 클래스 (Point)

모든 non-null 객체는 Object를 확장하기 때문에, extends Object를 빼주어도 똑같다.

test('object test', () {
      // (1,2) 와 (2,3)은 다르다.
      final point1 = Point(1, 2);
      expect(point1 == Point(2, 3), false);

      // point2는 그대로 복사했으니 같다
      final point2 = point1;
      expect(point1 == point2, true);

      /// 포인트 1의 값을 이용해 새로운 Point3을 만듬
      // 들어있는 y와 x의 값은 같지만 다르다?
      final newX = point1.x;
      final newY = point1.y;
      final point3 = Point(newY, newX);
      expect(point1 == point3, false);
      expect(identical(point1, point3), false);

      // 값을 바꾼다.
      // 값을 바꿨지만 point2는 point1과 같다?
      point2.change(2, 3);
      expect(point1 == point2, true);
    });

들어있는 값이 같다고 해서 객체가 같다고 판단하지 않는다.

오히려 마지막 예제에서 보듯 값이 달라도 객체는 같을 수 있다.

자바에서는 인스턴스라는 표현을 쓰지만 다트에서는 Object라고 한다. (Object 라는 이름의 클래스와는 구별해야 한다.)

클래스 이름과 같은 함수를 constructor 라고 하는데 이 함수로 Object를 만들어 낸다.

한 Object를 여러 개의 레퍼런스가 참조할 수 있다.

Object 의 레퍼런스를 만드는 법은 간단하다.

point1 = Point(1,2) 를 하면 point1이 Point(1,2) 의 레퍼런스가 된다.

위 코드를 그림으로 나타내면 이렇게 된다.

레퍼런스 point1과 레퍼런스 point2 는 모두 Point(1,2) 를 가리키고 있기 때문에 point1 == point2 는 true가 된다.

추가로, point2.change() 의 결과로 인해 Object Point(1,2) 의 값이 2,3 으로 바뀌게 되는데, point1 또한 같은 Object를 가리키고 있기 때문에

당연히 point1가 가리키는 Object 의 값도 2,3 으로 변경되었다.

결론 : Object 에서(정확히는 레퍼런스) 같다는 것은 두 레퍼런스가 같은 Object 를 가리키고 있다는 것이다.

나는 이런걸 원한게 아니야.

결국 우리는 이런 현실을 받아들여야 한다.

expect(Point(1, 2) == Point(1, 2), false);

이걸 같게 만드려면 2가지 방법이 있는데, 사실 한 가지의 방법이다.

  1. immutable 클래스로 만들어서 compile 시간에 비교하도록 만든다.
  2. 대입 연산자(operator ==)를 재정의한다.

왜 하나의 방법이냐면, 2를 하려면 immutable 한 클래스로 만들어야 하기 때문이다.

immutable class?

@immutable
class ImmutablePoint {
	// const constructor
  const ImmutablePoint(this.y, this.x);

  final int y;
  final int x;

  // void change(int newY, int newX) {
  //   y = newY;
  //   x = newX;
  // }
}

immutable 클래스가 뭐냐면 모든 인스턴스 변수가 final 인 클래스다.

그러니 change 같은걸 할 수 없다.

constructor의 앞에 const 가 붙어있다. 이런 constructor를 constant constructor(https://dart.dev/guides/language/language-tour#constant-constructors) 라고 한다.

immutable 한 클래스는 constant constructor 를 만들어 주는 것이 좋다.

constant constructor 를 이용해서 만든 객체를 constant object 라고 한다.

이렇게 하면 여러 가지 장점이 있는데, 런타임에 속도가 빨라지는 이점이 있고, 안의 값이 같으면 같다고 판단한다.

test('immutable object test', () {
      const point1 = ImmutablePoint(1, 2);
      // 값이 다른 const object
      const point2 = ImmutablePoint(2, 3);
      // 당연히 다름
      expect(point1 == point2, false);

      // 값이 같은 const object
      const point3 = ImmutablePoint(1, 2);
			// GOOD!
      expect(point1 == point3, true);

      // non constant object
      var point4 = ImmutablePoint(1, 2);
      expect(point1 == point4, false);
    });

constant constructor 를 만들었지만 point4 의 경우에는 const 를 쓰지 않아서 non-constant object 가 만들어진 것을 확인할 수 있다.

non-constant object 라고 하니까 새로운 개념 같지만, 우리가 알던 그냥 object 를 말하는 것이다.

이제 우리가 생각한대로 된다.

expect(const ImmutablePoint(1, 2) == const ImmutablePoint(1, 2), true);
expect(ImmutablePoint(1, 2) == ImmutablePoint(1, 2), false);

const를 빼먹지 않을까 걱정된다면 linter를 사용하자.

https://dart.dev/guides/language/analysis-options#enabling-linter-rules

linter 옵션이 켜져 있다면 이렇게 알아서 const 로 바꾸라고 해준다.


연산자 오버로딩

연산자 오버로딩은 연산자의 작동 방식을 직접 정의한다는 뜻이다. 그냥 메소드 하나 새로 만든다고 생각하면 된다.

Point 클래스에 == 연산자 오버로딩을 시도하면 linter가 화를 낸다.



https://dart.dev/guides/language/effective-dart/design#avoid-defining-custom-equality-for-mutable-classes 여기에 이유가 있다.

간단하게 말하면 hash를 사용하는 collection 에서 예측 불가능한 동작을 한다고 되어 있다. 여기서 hash collection 을 당장 사용할 일이 없으니 오버로딩을 해도 되지만 Immutable 클래스에다가 하는 것이 좋다.

@immutable
class ImmutablePoint2 {
  // const constructor
  const ImmutablePoint2(this.y, this.x);

  final int y;
  final int x;

  @override
  bool operator ==(Object other) {
    return (other is ImmutablePoint2) && other.x == x && other.y == y;
  }

  // Map 과 Set 에서 key 역할을 한다.
  @override
  int get hashCode => x.hashCode & y.hashCode;
}

따로 예제는 없다. 어차피 constant object 에는 의미가 없고, non-constant object 에 대해서도 생각한대로 동작한다.

expect(ImmutablePoint2(1, 2) == ImmutablePoint2(1, 2), true);

Equatable

매번 연산자 오버로딩을 하기 귀찮으니 우리는 패키지를 쓴다.

아마 제일 만만한 게 equatable (https://pub.dev/packages/equatable) 이다.

@immutable
class ImmutablePoint3 extends Equatable {
  // const constructor
  const ImmutablePoint3(this.y, this.x);

  final int y;
  final int x;

  @override
  List<Object?> get props => [y, x];
}

코드가 매우 간단해 졌다. Equatable 을 확장하고 props 를 오버라이드 했다.

props 안에 있는 값이 모두 같으면 같다고 판단한다.

expect(ImmutablePoint3(1, 2) == ImmutablePoint3(1, 2), true);

Equatable 클래스 내부에 연산자 오버로딩이 되어 있기 때문에 가능한 것이다.

@override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Equatable &&
          runtimeType == other.runtimeType &&
          equals(props, other.props);

코드

https://gist.github.com/letyletylety/85bd9b5f85e3b19c2b87cafb240763ef


참고

다트 공식 투어 (클래스)

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

Effective dart (equality)

https://dart.dev/guides/language/effective-dart/design#equality

코드팩토리님 equatable

https://blog.codefactory.ai/flutter/equatable/

coflutter

https://coflutter.com/dart-how-to-compare-2-objects/

lint 사용하기

https://dart.dev/guides/language/analysis-options#enabling-linter-rules

dart linter

https://dart-lang.github.io/linter/lints/index.html

linter 규칙 : avoid_equals_and_hash_code_on_mutable_classes

https://dart-lang.github.io/linter/lints/avoid_equals_and_hash_code_on_mutable_classes.html

equatable 클래스

https://pub.dev/packages/equatable



.

piano (press key Q)

Categories

flutter ( 82 ) dart ( 34 ) android ( 32 ) kotlin ( 11 ) plugin ( 8 ) provider ( 8 ) vim ( 7 ) bloc ( 6 ) iOS ( 6 ) state management ( 6 ) 플러터 ( 6 ) PS ( 5 ) algorithm ( 5 ) architecture ( 5 ) async ( 5 ) getx ( 5 ) java ( 5 ) API ( 4 ) BOJ ( 4 ) class ( 4 ) daily ( 4 ) git ( 4 ) golang ( 4 ) memo ( 4 ) riverpod ( 4 ) state ( 4 ) stream ( 4 ) test ( 4 ) web ( 4 ) widget ( 4 ) windows ( 4 ) HTTP ( 3 ) androidX ( 3 ) app state ( 3 ) context ( 3 ) crash ( 3 ) db ( 3 ) editor ( 3 ) error ( 3 ) extension ( 3 ) github ( 3 ) hive ( 3 ) ide ( 3 ) package ( 3 ) pubspec ( 3 ) python ( 3 ) syntax ( 3 ) vscode ( 3 ) app icon ( 2 ) await ( 2 ) chocolatey ( 2 ) consumer ( 2 ) cp949 ( 2 ) deployment ( 2 ) dev ( 2 ) flavor ( 2 ) gesture ( 2 ) globalkey ( 2 ) go ( 2 ) google ( 2 ) hack ( 2 ) js ( 2 ) json ( 2 ) key ( 2 ) keystore ( 2 ) list ( 2 ) listview ( 2 ) lock ( 2 ) mac ( 2 ) map ( 2 ) navigation ( 2 ) nosql ( 2 ) project ( 2 ) pub ( 2 ) recyclerview ( 2 ) rxdart ( 2 ) sdk ( 2 ) selector ( 2 ) setting ( 2 ) size ( 2 ) soc ( 2 ) synchronized ( 2 ) tdd ( 2 ) tip ( 2 ) version ( 2 ) viewmodel ( 2 ) vundle ( 2 ) webview ( 2 ) xcode ( 2 ) yaml ( 2 ) ( 2 ) 플러터 단점 ( 2 ) 16.0 ( 1 ) 2.0 ( 1 ) 2023 ( 1 ) AATP2 ( 1 ) ChangeNotifierProvider ( 1 ) Example ( 1 ) Guava ( 1 ) ImageReader ( 1 ) Mo's algorithm ( 1 ) OAuth2 ( 1 ) OpenGL ( 1 ) Oreo ( 1 ) ProgressBar ( 1 ) REST API ( 1 ) Trie ( 1 ) activity ( 1 ) adaptive ( 1 ) android P ( 1 ) android context ( 1 ) android11 ( 1 ) apktool2 ( 1 ) app exit ( 1 ) append ( 1 ) appicon ( 1 ) arkit ( 1 ) array ( 1 ) asciidoc ( 1 ) async * ( 1 ) async* ( 1 ) audio ( 1 ) authorization ( 1 ) await for ( 1 ) behaviorsubject ( 1 ) beta ( 1 ) binary ( 1 ) binarysearch ( 1 ) blender ( 1 ) book ( 1 ) bottomsheet ( 1 ) break ( 1 ) broadcast ( 1 ) browser ( 1 ) bubbles ( 1 ) bug ( 1 ) build ( 1 ) buildcontext ( 1 ) buildnumber ( 1 ) bundle ( 1 ) button ( 1 ) bytecode ( 1 ) cache ( 1 ) camera2 ( 1 ) cameramanager ( 1 ) cd ( 1 ) chrome ( 1 ) ci ( 1 ) circle ( 1 ) clean ( 1 ) clean architecture ( 1 ) cli ( 1 ) clip ( 1 ) clipboard ( 1 ) cloud ide ( 1 ) cmdlet ( 1 ) code ( 1 ) coding test ( 1 ) command ( 1 ) comparator ( 1 ) complexity ( 1 ) concurrency ( 1 ) conditional ( 1 ) const ( 1 ) constraint ( 1 ) constraintlayout ( 1 ) controlc ( 1 ) controlv ( 1 ) converter ( 1 ) copy ( 1 ) copy project ( 1 ) coupling ( 1 ) coverage ( 1 ) cp ( 1 ) css ( 1 ) cupertino ( 1 ) cursor ( 1 ) cv ( 1 ) data class ( 1 ) data structure ( 1 ) dataBinding ( 1 ) database ( 1 ) debounce ( 1 ) decompile ( 1 ) delegate ( 1 ) deno ( 1 ) design pattern ( 1 ) development ( 1 ) device ( 1 ) di ( 1 ) dialog ( 1 ) dio ( 1 ) drawable ( 1 ) drug ( 1 ) emmet ( 1 ) encoding ( 1 ) english ( 1 ) entries ( 1 ) environment ( 1 ) equality ( 1 ) equatable ( 1 ) euc-kr ( 1 ) euckr ( 1 ) exit ( 1 ) expand ( 1 ) expanded ( 1 ) export ( 1 ) extension method ( 1 ) facade ( 1 ) fake ( 1 ) field ( 1 ) figma ( 1 ) final ( 1 ) fixed ( 1 ) flutter pub ( 1 ) flutter web ( 1 ) flutter_inappwebview ( 1 ) flutter_test ( 1 ) flutterflow ( 1 ) fold ( 1 ) fonts ( 1 ) form ( 1 ) frame ( 1 ) future ( 1 ) gestureDetector ( 1 ) gestureRecognizer ( 1 ) gesturearena ( 1 ) get-command ( 1 ) get_cli ( 1 ) getbuilder ( 1 ) getx단점 ( 1 ) gitignore ( 1 ) glut ( 1 ) google fonts ( 1 ) gopath ( 1 ) goto ( 1 ) gradient ( 1 ) graphics ( 1 ) gvim ( 1 ) hackaton ( 1 ) hash ( 1 ) hashmap ( 1 ) hot reload ( 1 ) how to ( 1 ) html ( 1 ) i18n ( 1 ) icon ( 1 ) id ( 1 ) impeller ( 1 ) implementation ( 1 ) import ( 1 ) indicator ( 1 ) inkwell ( 1 ) interrupt ( 1 ) intl ( 1 ) introduction ( 1 ) io ( 1 ) isar ( 1 ) iterable ( 1 ) iteration ( 1 ) javascript ( 1 ) julia ( 1 ) juno ( 1 ) jupyter ( 1 ) kakaomap ( 1 ) keytool ( 1 ) korean ( 1 ) kotlin syntax ( 1 ) l10n ( 1 ) lambda ( 1 ) language ( 1 ) layer ( 1 ) layout ( 1 ) lineageOS ( 1 ) localkey ( 1 ) localtoglobal ( 1 ) long list ( 1 ) ls ( 1 ) mac osx ( 1 ) markdown ( 1 ) markup ( 1 ) material ( 1 ) method ( 1 ) microtask ( 1 ) migrate ( 1 ) mintlify ( 1 ) mock ( 1 ) module ( 1 ) monitor ( 1 ) moor ( 1 ) mouse ( 1 ) mouseregion ( 1 ) multiplatform ( 1 ) multiset ( 1 ) multithread ( 1 ) mutable ( 1 ) mvvm ( 1 ) new ( 1 ) node ( 1 ) nodejs ( 1 ) nosuchmethod ( 1 ) null-safety ( 1 ) numberformat ( 1 ) nvim ( 1 ) object ( 1 ) objectbox ( 1 ) objectkey ( 1 ) obx ( 1 ) online ide ( 1 ) operator ( 1 ) orientation ( 1 ) parabeac ( 1 ) parse ( 1 ) paste ( 1 ) path ( 1 ) pattern ( 1 ) pitfall ( 1 ) play store ( 1 ) pod ( 1 ) podfile ( 1 ) pointer ( 1 ) pointers ( 1 ) powershell ( 1 ) private ( 1 ) programming ( 1 ) pull to refresh ( 1 ) puzzle ( 1 ) pycharm ( 1 ) realitykit ( 1 ) recursion ( 1 ) reduce ( 1 ) reference ( 1 ) regex ( 1 ) regular expression ( 1 ) release note ( 1 ) renderbox ( 1 ) renderobject ( 1 ) repl ( 1 ) repository ( 1 ) response ( 1 ) rm ( 1 ) rotue ( 1 ) round ( 1 ) run ( 1 ) scope ( 1 ) scroll ( 1 ) search ( 1 ) server ( 1 ) serverless ( 1 ) service ( 1 ) sharp ( 1 ) singlerepo ( 1 ) singleton ( 1 ) sketch ( 1 ) sliver ( 1 ) sliverlist ( 1 ) snippets ( 1 ) sogae ( 1 ) sorting ( 1 ) source ( 1 ) sparse ( 1 ) sparse array ( 1 ) spec ( 1 ) split ( 1 ) sqflite ( 1 ) sqlite ( 1 ) sqrt decomposition ( 1 ) stateful ( 1 ) statefulwidget ( 1 ) step ( 1 ) stepper ( 1 ) string ( 1 ) stringbuffer ( 1 ) stringbuilder ( 1 ) studio ( 1 ) study ( 1 ) sub-directory ( 1 ) svn ( 1 ) swiftui ( 1 ) swipe to refresh ( 1 ) system_alert_window ( 1 ) system_cache ( 1 ) systemnavigator ( 1 ) tail recursion ( 1 ) tailrec ( 1 ) tap test ( 1 ) text ( 1 ) texteditingcontroller ( 1 ) textfield ( 1 ) texttheme ( 1 ) themedata ( 1 ) then ( 1 ) thread ( 1 ) throttle ( 1 ) time ( 1 ) tool ( 1 ) tools ( 1 ) tooltip ( 1 ) ts ( 1 ) tutorial ( 1 ) typescript ( 1 ) ui ( 1 ) unittest ( 1 ) update ( 1 ) usb ( 1 ) utf8 ( 1 ) ux ( 1 ) valuekey ( 1 ) variable ( 1 ) vector ( 1 ) versioncode ( 1 ) very_good ( 1 ) view ( 1 ) vim plugin ( 1 ) vimrc ( 1 ) virtualenv ( 1 ) wasm ( 1 ) web app ( 1 ) webview_flutter ( 1 ) while ( 1 ) widget tree ( 1 ) window ( 1 ) wsl ( 1 ) yield ( 1 ) 강의 ( 1 ) 개발 ( 1 ) 개발 공부 ( 1 ) 공부법 ( 1 ) 그래픽스 ( 1 ) 꼬리재귀 ( 1 ) 꿀팁 ( 1 ) 데노 ( 1 ) 두줄 ( 1 ) 디노 ( 1 ) 번역 ( 1 ) 블록 ( 1 ) 상태관리 ( 1 ) 실험 ( 1 ) 안드로이드 ( 1 ) 안드로이드프로젝트 ( 1 ) 안드로이드프로젝트복사 ( 1 ) 어이없는 ( 1 ) 조건부 임포트 ( 1 ) 주절주절분노조절실패의식으흐름 ( 1 ) 패키지 ( 1 ) 프로젝트복사 ( 1 ) 플러그인 ( 1 )