미새문지

dart 객체 학습 본문

개발 TIL

dart 객체 학습

문미새 2024. 12. 6. 21:11
728x90
class A {
  int value;

  A(this.value);
}

void main() {
  print(A(1) == A(1));
  print(A(1));
}

 

결과:

false
Instance of 'A'
Exited.

dart언어는 참조 비교를 사용하기 때문에, 가변 객체인 A 인스턴스는 생성할 때마다 새로운 메모리에 할당되기 때문에 주소값이 다르다.

A클래스의 인스턴스를 출력하게 되면, 클래스의 속성이 아닌 어떤 클래스의 인스턴스인지만 모호하게 알려준다.

해당 클래스의 속성을 출력하려면 변수명에 $를 붙여($변수명) 출력해야 하며, 이는 리액트의 변수값을 가져오는 ${변수명}과 동일하다.

class A {
  int value;

  A(this.value);

  @override
  String toString() {
    return "$value";
  }
}

void main() {
  print(A(1) == A(1));
  print(A(1));
}

 

결과:

false
1
Exited.

값을 기반으로 같은지 비교하고 싶으면 연산자(==)를 수행하는 방식도 있고, 이를 @override하여 값 비교를 수행하도록 할 수 있다.

class A {
  int value;

  A(this.value);

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is A && runtimeType == other.runtimeType && value == other.value;
  }

  @override
  int get hashCode => value.hashCode;

  @override
  String toString() {
    return "$value";
  }
}

void main() {
  print(A(1) == A(1));
  print(A(1));
}

 

  • identical(this, other): 두 객체의 참조가 같은지 확인한다. 참조가 같다면 비교할 필요 없이 true를 반환한다.
  • other is A: other의 타입이 다르면 비교할 수 없기 때문에, 비교하려는 객체가 A 클래스인지 확인한다. 
  • runtimeType == other.runtimeType: A 클래스를 상속받은 다른 클래스가 있을 경우를 대비한 검사이며, 같은 타입인지 보장한다.
  • value == other.value: 마지막으로 두 객체의 value 값이 같은지 확인한다.

 

class A {
  int value;

  A(this.value);

  @override
  bool operator ==(Object other) {
    return identical(this, other) ||
        other is A && runtimeType == other.runtimeType && value == other.value;
  }

  @override
  int get hashCode => value.hashCode;
}

void main() {
  Map<A, int> map = {A(1): 1};
  print(map[A(1)]);
}

hashCode를 사용하는 방법도 있다.

hashCode는 Map, Set 등의 해시 기반 자료형에서 값을 찾을 때 사용하며, 비교 연산자(==)를 수정하는 경우 함께 수정해야 한다.

hashCode를 수정하는 오버라이딩 코드를 사용하지 않을 경우 print(map[A(1)]);은 의도와 다르게 동작하게 되어 null값을 반환한다.


Equatable 패키지를 이용하면 값 비교를 쉽게 구현할 수 있는데, 터미널에 "dart pub add equatable" 명령어를 실행해 패키지를 설치해주면 된다.(flutter 프로젝트에선 dart 대신 flutter를 입력하면 됨)

import 'package:equatable/equatable.dart';

class A extends Equatable {
  int value;

  A(this.value);

  @override
  List<Object?> get props => [value];
}

void main() {
  print(A(1) == A(1));
  print(A(1));
}

 

결과:

true
A(1)
Exited.

 

이 패키지는 operator ==와 hashCode를 자동으로 생성해서 값 기반 비교를 제공해준다.

equatable 패키지의 장점은 props만 정의하면 알아서 처리되기 때문에 매우 간결하다. 그리고 자동으로 생성해주는 operator와 hashCode의 동작이 항상 같게 관리하기 때문에 일관성이 있고 유지보수성이 좋다는 장점이 있다.


객체 복사는 얕은 복사와 깊은 복사가 있다.

void main() {
  List<int> a = [1];
  List<int> b = a; // 얕은 복사 (메모리 주소만 전달)
  
  print(a == b); // true (a와 b가 동일한 메모리 주소를 가짐)
  
  a.add(2); // a와 b가 바라보는 메모리의 값을 수정
  print(a); // [1, 2]
  print(b); // [1, 2]
}

얕은 복사는 메모리 주소인 참조만 전달하는 것을 의미한다.

b에 a의 메모리 주소만 전달했기 때문에 동일한 주소값을 가져 둘이 비교했을 때 true값이 나온다. 이는 포인터 개념과 유사하며 해당 값의 메모리 주소를 그대로 가져와 사용할 수 있게 한다.

 

void main() {
  List<int> a = [1];
  List<int> b = a.toList(); // 깊은 복사 (새로운 배열 생성)
  
  print(a == b); // false (a와 b가 다른 메모리 주소를 가짐)
  
  a.add(2);
  print(a); // [1, 2]
  print(b); // [1]
}

깊은 복사는 값이 동일한 객체를 새롭게 생성하는 것을 의미한다. 

a의 값을 b에 메모리 주소를 넘기는게 아니라 같은 값으로 새로 생성하기 때문에, 값은 같으나 메모리 주소를 다르게 가지면서 false값이 나온다. 이는 포인터를 사용하지 않고 값을 넣었을 때와 유사하다.

728x90