Marcelo Glasberg
Marcelo Glasberg

Reputation: 30879

In Dart, what's the difference between List.from and .of, and between Map.from and .of?

In Dart, what's the difference between List.from and List.of, and between Map.from and Map.of? Their documentation is not totally clear:

/**
* Creates a [LinkedHashMap] instance that contains all key/value pairs of
* [other].
*
* The keys must all be instances of [K] and the values of [V].
* The [other] map itself can have any type.
*
* A `LinkedHashMap` requires the keys to implement compatible
* `operator==` and `hashCode`, and it allows `null` as a key.
* It iterates in key insertion order.
*/
factory Map.from(Map other) = LinkedHashMap<K, V>.from;

/**
* Creates a [LinkedHashMap] with the same keys and values as [other].
*
* A `LinkedHashMap` requires the keys to implement compatible
* `operator==` and `hashCode`, and it allows `null` as a key.
* It iterates in key insertion order.
*/
factory Map.of(Map<K, V> other) = LinkedHashMap<K, V>.of;

/**
* Creates a list containing all [elements].
*
* The [Iterator] of [elements] provides the order of the elements.
*
* All the [elements] should be instances of [E].
* The `elements` iterable itself may have any element type, so this
* constructor can be used to down-cast a `List`, for example as:
* ```dart
* List<SuperType> superList = ...;
* List<SubType> subList =
*     new List<SubType>.from(superList.whereType<SubType>());
* ```
*
* This constructor creates a growable list when [growable] is true;
* otherwise, it returns a fixed-length list.
*/
external factory List.from(Iterable elements, {bool growable: true});

/**
* Creates a list from [elements].
*
* The [Iterator] of [elements] provides the order of the elements.
*
* This constructor creates a growable list when [growable] is true;
* otherwise, it returns a fixed-length list.
*/
factory List.of(Iterable<E> elements, {bool growable: true}) =>
  new List<E>.from(elements, growable: growable);

Is the difference related to generics? Maybe the .from factories let you change the type of the list, while the .of ones do not? I come from a Java background, which works with type erasure, and maybe types are reified in Dart and you cannot use casts or raw types to change list/map types?

Upvotes: 40

Views: 13767

Answers (3)

CopsOnRoad
CopsOnRoad

Reputation: 267614

List.of() and toList()

They are used to create a new list of the same type as the original, but List.of() can be used to upcast:

var ints = <int> [0];
var newList1 = ints.toList(); // List<int>
var newList2 = List<num>.of(ints); // List<num>

You can also copy a list by doing:

var newList3 = [...ints]; // List<int>
var newList4 = [for (var v in ints) v]; // List<int>

List.from()

Use this if you want to downcast and therefore it is important that the subtype is a type of supertype.

var ints = List<int>.from(<num>[0, 1]); // Good as all elements are of type `int`
var ints = List<int>.from(<num>[0, 1.5]); // Bad as some elements are of type `double`

Upvotes: 5

Suragch
Suragch

Reputation: 511846

Whenever possible it is better to use collection literals now rather than the .from or .of constructors. Apparently there are some performance benefits to this. (See link at bottom.)

Examples:

  • something.toList()
  • [...something]

Exception:

  • Can use .from if you need to downcast.

If you do use them, though, you should always include the type.

Source: Dart team engineer's post

Upvotes: 4

Jonah Williams
Jonah Williams

Reputation: 21441

The important difference between the from and of methods are that the latter have type annotations and the former do not. Since Dart generics are reified and Dart 2 is strongly typed, this is key to both ensuring the List/Map is correctly constructed:

List<String> foo = new List.from(<int>[1, 2, 3]); // okay until runtime.
List<String> bar = new List.of(<int>[1, 2, 3]); // analysis error

And ensuring that the types are inferred correctly:

var foo = new List.from(<int>[1, 2, 3]); // List<dynamic>
var bar = new List.of(<int>[1, 2, 3]); // List<int>

In Dart 1 types were completely optional, so many APIs were untyped or partially typed. List.from and Map.from are good examples, since the Iterable/Map passed into them doesn't have a type parameter. Sometimes Dart can figure out what the type of this object should be, but sometimes it just ended up as List<dynamic> or Map<dynamic, dynamic>.

In Dart 2 the type dynamic was changed from being both a top (Object) and bottom (null) type to only being a top type. Thus if you created a List<dynamic> accidentally in Dart 1 you could still pass it to a method which required a List<String>. But in Dart 2 List<dynamic> is almost the same as List<Object>, so this would fail.

If you are using Dart 2, you should always use the typed version of these APIs. Why do the old ones still exist, and what are the plans there? I don't really know. I would guess they would be phased out over time, along with the rest of the Dart 1.

Upvotes: 50

Related Questions