In this official Flutter example there is a class, which does not extend another class. So why int get hashCode has #override over it? I.e. there is nothing to override, no?
class Item {
final int id;
final String name;
final Color color;
final int price = 42;
Item(this.id, this.name)
// To make the sample app look nicer, each item is given one of the
// Material Design primary colors.
: color = Colors.primaries[id % Colors.primaries.length];
#override
int get hashCode => id;
#override
bool operator ==(Object other) => other is Item && other.id == id;
}
From hashCode property documentation:
All objects have hash codes. The default hash code represents only the identity of the object, the same way as the default operator == implementation only considers objects equal if they are identical (see identityHashCode).
And from Object class documentation
Because Object is the root of the Dart class hierarchy, every other Dart class is a subclass of Object.
Every class is a subclass of the Object class, therefore they will always have the same properties of it and this is what you are overriding.
And now if you are asking why to override both hashCode and equals, check this link
From the official docs
A hash code is a single integer which represents the state of the object that affects operator == comparisons. All objects have hash codes. The default hash code represents only the identity of the object, the same way as the default operator == implementation only considers objects equal if they are identical (see identityHashCode).
In other words every time you create an object of class Item, you generate a hashCode property for it as well.
So now if you want to check equality of two objects of class item, Dart (like Java) will check the equality by doing == of the hashcode.
Which means that Item object1 != Item Object2 since each object will have its own unique hashcode.
Hence, the hashcode has to be overriden, in this case so that Item object1 can be checked for equality with Item object2
Related
On dart tour page (https://dart.dev/guides/language/language-tour#getting-an-objects-type) these have a statement that testing variable type with "is" expression is more stable. Why is it so?
An is test is a subtype test.
If you do e is Iterable, you check whether the value of e implements Iterable<dynamic>, which includes any of List, Set and Queue, as well as all the subtypes of Iterable used internally by the runtime system, like _TakeIterable (which is used to implement Iterable.take).
It matches both values of type Iterable<Object> and Iterable<int>.
All of these are objects which can safely be used as an Iterable, and are intended to be used as such.
If you do e.runtimeType == Iterable, you are checking whether the Type object returned by e.runtimeType is equal to precisely the type Iterable<dynamic>. That will be false for any list, set or queue, for any actual iterable class which only implements Iterable, and even for something which returns the Type object of Iterable<int> or Iterable<Object?> from runtimeType.
I say that you check the object returned by e.runtimeType, not the run-time type of the value, because anyone can override the runtimeType getter.
I can make a class like:
class WFY {
Type get runtimeType => Iterable<int>;
}
void main() {
print(WFY().runtimeType == Iterable<int>); // True!
}
The value returned by runtimeType doesn't have to have any relation to the actual runtime type of the object.
Obviously it usually has, because there is no benefit in overriding runtimeType, because you shouldn't be using it for anything anyway,
Even if your code works today, say:
assert(C().runtimeType == C); // Trivial, right!
it might fail tomorrow if I decide to make C() a factory constructor which returns a subtype, _C implementing C.
That's a change that is usually considered non-breaking, because the _C class can do everything the C interface requires, other than having C as actual runtime type.
So, doing Type object checks is not stable.
Another reason using is is better than comparing Type objects for equality is that it allows promotion.
num x = 1;
if (x is int) {
print(x.toRadixString(16)); // toRadixString is on int, not on num
}
The is check is understod by the language, and trusted to actually guarantee that the value's runtime type implements the type you check against.
Comparing Type objects can mean anything, so the compiler can't use it for anything.
Some people like to use runtimeType in their implementation of ==, like;
class MyClass {
// ...
bool operator ==(Object other) =>
MyClass == other.runtimeType && other is MyClass && this.x == other.x;
}
This is intended to avoid subclass instance being equal to super-class instances when you ask the superclass, but not if you ask the subclass (the "ColorPoint problem", where ColorPoint extends Point with a color, and is equal to a another ColorPoint with the same coordinates and color, but if you ask a plain Point whether it's equal to a ColorPoint, it only checks the coordinates.)
This use of runtimeType "works", but is not without issues.
It means you cannot use mocks for testing.
It means you cannot create a subclass which doesn't extend the state, only the behavior, and which would want to be equal to the superclass instances with the same state.
And it means you do extra work, because you still need to cast the other object from Object to the surrounding type in order to access members, and Type object checks do not promote.
If possible, it's better to never allow subclasss of a concrete class that has a == method, and if you need to share other behavior, inherit that from a shared superclass.
(In other words: Don't extend classes that aren't intended to be extended, don't put == on classes which are intended to be extended.)
Lets say I want to refactor my code so it's easier to read and I have an object property that is type of Map<String,AnotherObject or dynamic> what is the best way to convert this property to another object? When it is a map I can call the relevant object using its String key. If it becomes another object how would I call the one I want?
for example:
class A1{
Map<String,B1> property;
}
to:
class A1{
List<B2> property;
}
class B2{
String key;
B1 property;
}
In the example above in order to get the property I want I would have to filter the list where key = keyIwant, while if it is a map I can just call map[key]. Is there any effective way to convert a map to an object? Dart is the technology I use.
I suppose you're trying to make your code more maintainable by replacing your current Map with something else, which you refer to as object.
I also suppose that by object you mean Dart classes with typed fields.
If you want to continue to be able to find "objects" by their String names, you cannot avoid using a Map.
If the "keys" are all known at compile-time (ie. before your program actually runs) then you can used typed objects, which is what you should prefer as it makes reasoning and organizing code much easier.
Let's say you know that your Map will only ever have keys a and b with types A and B, respectively. Then you can replace your Map easily:
class A {}
class B {}
class MyClass {
final A a;
final B b;
MyClass(this.a, this.b);
}
Simple.
If some "keys" may not be present, just turn them into nullable values:
class A {}
class B {}
class MyClass {
final A? a;
final B? b;
// passing a or b is now optional!
MyClass({this.a, this.b});
}
If you don't know what the keys will be at all, then there's no way around using a Map. That's what they are for.
With Dart support for dynamic typing, you could "assume" certain keys will have certain types, though. So, while this is normally bad for code maintenance due to the impossibility to analyze this before the program runs (ie. it may crash at runtime), you could do something like this:
class Foo {
final String a;
Foo(this.a);
String toString() => 'Foo($a)';
}
class Bar {
final String a;
Bar(this.a);
String toString() => 'Bar($a)';
}
// example usage
void main() {
Map<String, dynamic> map = {'foo': Foo('a foo')};
Foo foo = map['foo'];
print(foo); // ok!
Bar? bar = map['bar'];
print(bar); // null
}
Hopefully this helps clarify when you should use an "object" and when you need to use a Map.
Why in named constructor not able to use datatypes and this keyword? It shows Error:
The parameter 'name' can't have a value of 'null' because of its type, but the implicit default value is 'null'.
Try adding either an explicit non-'null' default value or the 'required' modifier.dart(missing_default_value_for_parameter)
Your problem is that, if nothing else is specified, the default value for optional named parameters is null. So what your code actually does right now is setting the variable late String name to null which is not allowed because of the type String. If you want to allow null, the type should be String?.
The late keyword is used in situations where you cannot set the variable when initializing the object and is just a promise to the Dart compiler that the variable will have a value when you are going to use the variable at some point. (the compiler will add a check to make sure the variable is actually given a value before use).
But late is not a way to allow null as a valid value.
Another problem in your code is that it does not really make sense to do:
person({this.name}) {
this.name = name;
}
The reason is that {this.name} is already used for setting the variable name. When you later do: this.name = name then both name is pointing to the same variable in your object.
Based on the rest of your code I think you should remove the late keyword and instead use required to make sure the user of your class are specifying your named parameter. So something like this should work (also, please use Person instead of person for naming classes):
class Person {
String name;
int age;
Person({required this.name, required this.age});
void sayName() => print(name);
}
void main() {
final p = Person(name: 'Mathew', age: 24);
p.sayName();
print(p.age);
}
I removed the named constructor Person.age since I don't think it makes much sense to make a Person without a name. If name and age is truly optional, you can instead change the types to String? and int? and remove the required keyword.
This will make it clear when using your class that these two variable can be empty and the user of the class should remember to check for possible null values.
I recommend reading more about Dart null safety feature starting here: https://dart.dev/null-safety
I need a workaround or idiomatic way to access the static members defined in some type from a generic context.
Example:
enum E { first, second, third }
// no direct syntax to constrain to enum types
class EnumKeyList<TEnum> {
List<Object> _values;
// unable to access static member
EnumKeyList() : _values = List.filled(TEnum.values.length, Object());
// unable to access instance member
Object operator [](TEnum entry) => _values[entry.index];
}
Usage:
final list = EnumKeyList<E>(); // E.values.length would provide implicit fixed-size list instantiation
list[E.first] = 5; // can use enumeration entries as keys
I want to avoid the overhead of Map (hashing and additional memory). The real use case must index into the list in tight loops.
Having a fixed set of named keys is a useful requirement, but the example EnumKeyList should work with any generic type argument that provides an enumeration like interface.
Using enumerations provides the shortest way to declare valid 0-indexed keys and the count of the amount of entries through an enumeration's static values member.
Swift enumerations and protocols allow for static members. C# has constraints for enumeration types. C++ generics dwarf everything. Is there a simple way to achieve this in Dart?
I realize that I can declare my own class instead of an enumeration, but then I lose the implicitly generated members (having to manually assign a value to each constant in the class (bad for maintenance)) and I still can't provide access to a static member from the generic context.
See here for examples of how unmaintainable this is:
abstract class Enum {
final int rawValue;
const Enum(this.rawValue) : assert(rawValue >= 0);
// don't bother with a static 'values' member
}
class E extends Enum {
const E(int rawValue) : super(rawValue);
static const first = E(0);
static const second = E(1);
static const third = E(1); // repeated values
static const List<E> values = <E>[first, second]; // missed one
}
You cannot access static members through type variables.
Dart static members are really just declared in the namespace of the corresponding class/mixin/extension declaration, they are not part of the type. Type variables hold types, not declarations.
There is no idiomatic workaround.
You have to figure out which operations you need your class to support, then you can introduce a strategy object representing the class, and pass that to the function instead of (or alongside) the type argument.
In this case, you probably want the EnumKeyList constructor to take the list of values as an argument, so:
EnumKeyList(List<T> values) : _values = List.unmodifiable(values);
The workaround, in general, is to pass the values you'd want to read from a static member directly to the function needing them, along with the type.
You can't access them using the type alone.
The "cannot access index" problem could be fixed by the language adding an interface to all enums, like abstract class Enum { int get index; } and make all enum classes implement that interface.
There is no easy way to allow access to the values knowing only the type.
It might be possible to do something magical in the compiler and platform libraries, but it won't extend to user-written enums like this, and no viable way to emulate it.
Does the Map class in Dart have a way to ignore case if the key is a string?
Eg.
var map = new Map<String, int>(/*MyComparerThatIgnoresCase*/);
map["MyKey"] = 42;
var shouldBe42 = map["mykey"];
In C# the Dictionary constructor takes a comparer like the comment above. What is the canonical way to do this in Dart?
Maps in Dart have an internal method that compares keys for equality. So far as I know, you can't change this for the default Map class. However, you can use the very similar core LinkedHashMap class, which not only allows, but requires that you specify a key equality method. You can check out more about LinkedHashMaps at https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart:collection.LinkedHashMap
LinkedHashMap<String, String> map = new LinkedHashMap(
(a, b) => a.toLowerCase() == b.toLowerCase(),
(key) => key.toLowerCase().hashCode
);
map['Foo'] = 'bar';
print(map['foo']); //bar
The way to create a HashMap with a custom equals function (and corresponding custom hashCode function) is to use the optional parameters on the HashMap constructor:
new HashMap<String,Whatever>(equals: (a, b) => a.toUpperCase() == b.toUpperCase(),
hashCode: (a) => a.toUpperCase().hashCode);
I really, really recommend finding a way to not do the toUpperCase on every operation!
You can also do this using package:collection's CanonicalizedMap class. This class is explicitly designed to support maps with "canonical" versions of keys, and is slightly more efficient than passing a custom equality and hash code method to a normal Map.
Dart has a nifty
CaseInsensitiveEquality().equals(String a, String b)
in their
import 'package:collection/collection.dart';
It returns a bool and worked great for me when I was translating strings back to an enum. You do have to run dart pub add collection at the command line to install the package.