Built-in types and their methods
Like Ruby, Dart is a purely object-oriented (OO) language, so every variable in Dart points to an object and there are no primitive types as in Java or C#. Every variable is an instance of a class (that inherits from the base class Object
) and has a type, and when uninitialized has the value null
. But for ease-of-use Dart has built-in types for numbers, Booleans, and strings defined in dart:core
, that look and behave like primitive types; that is, they can be made with literal values and have the basic operations you expect (to make it clear, we will use full typing in builtin_types.dart
, but we could have used var
as well).
A String (notice the capital) is a sequence of Unicode (UTF-16) characters, for example:
They can be indicated by paired '
or "
(use ""
when the string contains '
and vice versa). Adjacent string literals are concatenated. If you need multiline strings, use triple quotes '''
or """
(handy for defining chunks of HTML!).
Escape characters are not interpreted when the string is prefixed by r
, a so-called raw string, invaluable when defining regular expressions. The empty string ''
or ""
is not the same as null
. The following are all legal strings:
String q = "What's up?"; String s1 = 'abc' "def"; print(s1); // abcdef String multiLine = ''' <h1> Beautiful page </h1> <div class="start"> This is a story about the landing on the moon </div> <hr> '''; print(multiLine); String rawStr = r"Why don't you \t learn Dart!"; // output: Why don't you \t learn Dart! print(rawStr); var emptyStr = ''; // empty string
The numeric types are num
(number), int
(integer), and double
; the last two are subtypes of num
:
int n = 42; double pi = 3.14;
Integers can use hexadecimal notation preceding with 0x
, as shown in the following code:
int hex = 0xDEADBEEF;
And they can be of arbitrary precision, as shown in the following code:
int hugePrimeNumber = 4776913109852041418248056622882488319;
Doubles are of a 64-bit precision and can use scientific notation:
double d1 = 12345e-4;
The num
type has the usual abs()
, ceil()
, and floor()
methods as well as round()
for rounding. Use either int
or num
, but double
only in the specific case you need a decimal number that cannot be an integer.
Booleans can only have the value true
or false
:
bool selected = false;
In contrast to JS where any variable can be evaluated as true or false, Dart does not permit these strange behaviors in checked mode; in production mode every value different from true
is treated as false
.
Conversions
To use numbers as strings use the toString()
method and to convert a String
to an int
use int.parse()
:
String lucky = 7.toString();
int seven = int.parse
('7');
Likewise, converting a String
to a double
is done with double.parse()
:
double pi2 = double.parse('3.1415');
If you want to retain only a certain amount of decimal numbers from a double
, use toStringAsFixed()
:
String pi2Str = pi2.toStringAsFixed(3); // 3.142 (notice rounding occurs)
To convert between num
types use toDouble()
for int to double and toInt()
for double to int (truncation occurs!).
Operators
All operators in Dart follow the normal priority rules; when in doubt or for clarity, use ()
around expressions that must be executed first.
We have our familiar number operators (+
, -
, *
, /
, and %
) in Dart, and assignments with these can be shortened as +=
. Use ~/
to get an integer result from a division. Likewise, we have pre- and postfix ++
and --
to add or subtract 1 to or from a number, and <
, <=
, >
, and >=
to check the order of numbers or Strings.
Numbers have also bitwise and shift operators for manipulating individual bits.
To see if two variables have the same content use ==
or !=
for different content. These expressions result in Boolean values, such as b1
and b2
in this snippet (brackets are only used for clarity):
var i = 100; var j = 1000; var b1 = (i == j); var b2 = (i!= j); print('$b1'); // false print('$b2'); // true
For numbers and strings ==
is true when both variables have the same value.
For Strings both hold true; the same String is only one object in memory, and if the string variable gets a new value, it references another address in memory. Strings are immutable.
var s = "strings are immutable"; var t = "strings are immutable"; print(s == t); // true, they contain the same characters print(identical(s, t)); // true, they are the // same object in memory
Boolean values or expressions can be combined with an AND operator (&&
) or an OR operator (||
), or negated with a NOT operator (!
).
Because we will be working a lot with objects and types in Dart code, it is important to be able to test if an object is
or is!
(not) of a certain type (class):
var b3 = (7 is num); // () are not necessary print('$b3'); // true var b4 = (7 is! double); print('$b4'); // true, it's an int
A very useful built-in function that can be used for micro unit testing is assert
. Its parameter is a Boolean expression. When this is true, nothing happens at runtime; but when it is false, the program stops with an AssertionError
. You can sprinkle them around in your code to test certain conditions that must be true; in the production mode, assert statements are ignored. So for the last example we could have written:
assert(b4 == true) or shorter assert(b4)
We will use these throughout the example code, but will not print them in the text for brevity.
The []
indexing operator is used to obtain an element from a collection (a group of variables) at a certain index, the first element has index 0
.
To convert or cast a variable v
to a certain type T
, use the as
operator: v as T
If v
is indeed of that type, all is well and you can access all methods of T
, but if this fails an error is generated.
Some useful String methods
Strings are all pervasive and Dart provides handy methods to work with them, for details refer to the documentation at the following link:
http://api.dartlang.org/docs/releases/latest/dart_core/String.html
We show some examples in string_methods.dart
:
- You can test that the owner of a bank account (a
String
) is not filled in withowner.isEmpty
, which returns a Boolean value:assert("".isEmpty);
length()
returns the number of UTF-16 characters:assert('Google'.length == 6);
- Use
trim()
to remove the leading and trailing whitespace:assert('\thello '.trim() == 'hello');
- Use
startswith()
,endswith()
, andcontains()
to detect the presence of subwords:var fullName = 'Larry Page'; assert(fullName.startsWith('La')); assert(fullName.endsWith('age')); assert(fullName.contains('y P'));
- Use
replaceAll()
to replace a substring; notice that the original string was not changed (strings are immutable!):var composer = 'Johann Sebastian Bach'; var s = composer.replaceAll('a', '-'); print(s); // Joh-nn Seb-sti-n B-ch assert(s != composer); // composer didn't change
- Use the
[]
operator to get the character at indexi
in the string:var lang = "Dart"; assert(lang[0] == "D");
- Find the location of a substring inside a string with
indexOf()
:assert(lang.indexOf("ar") == 1);
- Extract a part of a string with
substring()
:assert("20000 rabbits".substring(9, 13) == 'bits');
When printing any object the toString()
method, which returns a String, is automatically called. If no particular version of this method was provided, the toString()
method from class Object
is called, which prints the type of the object, as shown in the following code:
print('$ba'); // produces Instance of 'BankAccount'
In banking_v2.dart
we provide the following method:
String toString() => 'Bank account from $owner withnumber $number and balance $balance';
Now print('$ba');
produces the following output:
Bank account from John Gates with number 075-0623456-72and balance 1000.0
If you need many operations in building your strings, instead of creating new strings at each operation and thus using more memory, consider using a StringBuffer
object for better efficiency. A StringBuffer
doesn't generate a new String
object until toString()
is called. An example is given in the following code:
var sb = new StringBuffer(); sb.write("Use a StringBuffer "); sb.writeAll(["for ", "efficient ", "string ", "creation "]); sb.write("if you are "); sb.write("building lots of strings."); var fullString = sb.toString(); print('$fullString'); sb.clear(); // sb is empty again assert(sb.toString() == '');
Dates and times
Almost every app needs time info, so how can we do this in Dart? The dart:core
package has a class DateTime
for this. In our banking app we could add the attributes dateCreated
and dateModified
to our class BankAccount
. In the constructor, dateCreated
is initialized to the moment at which the account is created; in our deposit
and withdraw
methods we update dateModified
. This is shown in the following code (refer to banking_v2.dart
):
class BankAccount { String owner, number; double balance; DateTime dateCreated, dateModified; BankAccount(this.owner, this.number, this.balance) { dateCreated = new DateTime.now(); } deposit(double amount) { balance += amount; dateModified = new DateTime.now(); } // other code }
We can print this out with the following command:
print('Bank account created at: ${ba.dateCreated}');
The output produced is as follows:
Bank account created at: 2013-02-10 10:42:45.387
The method DateTime.parse(dateString)
produces a DateTime
object from a String in one of the suitable formats: 20130227 13:27:00
or 2010-01-17
. All weekdays and month names are defined as const
int
, such as MON
and JAN
. You can extract all date parts as an int with methods such as second
, day
, month
, year
, as shown in the following code:
ba.dateModified.weekday
A time span is represented by a Duration
object, difference()
gives the duration between two DateTime
objects, and you can add
and subtract
a duration from a DateTime
.
Lists
This is the basic collection type for making an ordered group of objects, it can be of fixed size (called an array in other languages) or it can grow dynamically. Again length
returns the number of elements in the List; the last element has index length – 1
. An empty List with length
equal to 0
and property isEmpty
equal to true
can be created in two ways: literal or with a constructor (refer to lists.dart
):
var empty = []; var empty2 = new List(); // equivalent assert(empty.isEmpty && empty2.isEmpty && empty.length == 0);
We can either define and populate a List with a literal by using []
as in the following code:
var langs = ["Java","Python","Ruby", "Dart"];
Or we can define a List with a constructor and an add()
method:
var langs2 = new List();
langs2.add("C");
langs2.add("C#");
langs2.add("D");
print(langs2); // [C, C#, D]
A read-only List with constant elements resulting in better performance can be defined as shown in the following code:
var readOnlyList = const ["Java","Python","Ruby", "Dart"];
The []
operator can be used to fetch and set List elements:
var langBest = langs[3]; assert(langBest=="Dart"); langs2[2] = "JavaScript";
But using an index greater than or equal to the List length provokes a RangeError
in runtime (with no compile-time check!):
langs[4] = "F#"; // RangeError !
To check if a List contains a certain item, use the method with that name:
print('${langs.contains("Dart")}'); // true
When you know the type of the list elements, the list itself can be typed; for example, langs
and langs2
are both of type List<String>
.
A String can be split
over a certain character or pattern (which could be a space " "
or even ""
) producing a List<String>
, which can then be further analyzed, as shown in the following code:
var number = "075-0623456-72"; var parts = number.split('-'); print('$parts'); // produces [075, 0623456, 72]
In simple scenarios data records are written line after line in text files, each line containing the data of one object. In each line the data fields are separated by a certain character, such as a ;
. We could read in and split each line of the file, and obtain a List of fields for each object to be shown on a screen or processed further. Conversely a List can be joined by concatenating all its elements in one String (here with a separator '-'
):
var str = parts.join('-'); assert(number==str);
A list with N elements is used mostly to support an efficient search of the whole list, or a large number of the list's elements. The time it takes to search a list grows linearly with N; it is of order O(N).
In summary, a List is an ordered collection of items that can be retrieved or changed by index (0-based, working via index is fast), and that can contain duplicates. You can find more useful functions in the API, but we will come back to List again in the The collection hierarchy and its functional nature section in Chapter 3, Structuring Code with Classes and Libraries. (For API docs, see the documentation at http://api.dartlang.org/docs/releases/latest/dart_core/List.html.)
Maps
Another very useful and built-in type is a Map, basically a dictionary of (key:value
) pairs where the value is associated with the key. The number of pairs is the length of the Map. Keys must be unique, they may not be null, and the lookup of the value from the key is fast; remember, however, that the order of the pairs is not guaranteed! Similar to a List, a Map can be created literally with {}
as shown in the following code:
Map webLinks = { 'Dart': 'http://www.dartlang.org/','HTML5': 'http://www.html5rocks.com/'};
The keys must be of type String
for a literal Map.
Or it can be created with a constructor (refer to maps.dart
):
Map webLinks2 = new Map(); webLinks2['Dart'] = 'http://www.dartlang.org/'; (1) webLinks2['HTML5'] = 'http://www.html5rocks.com/';
The empty Map created with var map = {}
or var map = new Map()
has length
as 0
; the length of a Map is not fixed. You can fetch the value corresponding to a certain key with:
var link = webLinks2['Dart']; // 'http://www.dartlang.org/'
If the key is not in the Map, you get null
(it is not an error):
var link2 = webLinks2['C']; // null
To check if your Map contains a certain key, use the containsKey()
method:
if (webLinks2.containsKey('C')) print("The map webLinks2 contains key 'C"); else print("The map webLinks2 does not contain key 'C'"); // prints: The map webLinks2 does not contain key 'C'
To obtain a List of the keys or values, use the methods with the same name:
var keys = webLinks2.keys.toList(); print('$keys'); // [Dart, HTML5, ASP.NET] // getting the values: var values = webLinks2.values.toList(); print('$values'); // printed output: // [http://www.learningdart.org/, http://www.html5rocks.com/, // http://www.asp.net/]
Setting a value is done with the syntax shown in line (1)
; this applies both to inserting a new key-value pair in the map, or changing the value for an existing key:
webLinks2['Dart'] = 'http://www.learningdart.org/'; // change webLinks2['ASP.NET'] = 'http://www.asp.net/'; // new key
A very handy method is putIfAbsent
, which makes your code a lot cleaner. It takes two parameters: a key and a function that returns a value. The method tests if the key already exists; if not, the function is evaluated and the resulting key-value pair is inserted in the map (for example, we use a very simple function that directly returns a value, but this could be a calculation or a database-lookup operation):
webLinks2.putIfAbsent('F#', () => 'www.fsharp.net');
assert(webLinks2['F#']=="www.fsharp.net");
Again for performance reasons, use const
maps when the keys and values are literals or constants:
var cities = const {'1':'London','2':'Tokyo','3':'Madrid'};
A Map can also be explicitly typed, for example, a Map with integer keys and String values:
Map<int, String>
A Map with N elements is used mostly to support an efficient direct access to a single element of the map based on its key. This will always execute in the same time regardless of the size of the input dataset; the algorithm is of order O(1).
Note
For the API docs for Map see the documentation at the following link:
https://api.dartlang.org/docs/channels/stable/latest/dart_core/Map.html