- Learning Dart(Second Edition)
- Ivo Balbaert Dzenan Ridjanovic
- 2757字
- 2021-07-16 19:47:07
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 like in Java or C#. Every variable is an instance of a class (that inherits from the Object
base class) and has a type and, when uninitialized, has the null
value. However, for easy use, Dart has built-in types for the 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 that you would 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:
String country = "Egypt"; String chineseForWorld = '世界';
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, which is invaluable while 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 num
(number) types are int
(integer) and double
, which are both subtypes of num
:
int n = 42; double pi = 3.14;
Integers can use a hexadecimal notation preceding with 0x
, as shown in the following code:
int hex = 0xDEADBEEF;
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 when you need a decimal number that cannot be an integer.
Booleans can only have a true
or false
value:
bool selected = false;
In contrast to JavaScript, where any variable can be evaluated as true or false, Dart does not permit these strange behaviors in the checked mode; in the production mode, every value different from true
is treated as false
.
Conversions
To use numbers as strings, use the toString()
method and to convert String
into int
, use int.parse()
:
String lucky = 7.toString(); int seven = int.parse('7');
Likewise, converting String
into 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 double
, use toStringAsFixed()
:
String pi2Str = pi2.toStringAsFixed(3);// 3.142 (notice rounding occurs)
To convert between the num
types, use toDouble()
for int to double and toInt()
for double to int (truncation occurs!).
Operators
All the operators in Dart follow the normal priority rules, when in doubt or for clarity, use ()
around the 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 prefixed and postfixed ++
and --
to add or subtract 1
to or from a number, and <
, <=
, >
, and >=
to check the order of the numbers or strings.
Numbers have also bitwise- and shift-operators to manipulate individual bits.
To see if the two variables have the same content, use ==
. Or, to check for different content, use !=
. 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 the 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 the 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, which 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 AssertionError
. You can sprinkle them around in your code to test certain conditions that must hold; in the production mode, the 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 a 0
index.
To convert or cast a v
variable to a certain T
type, use the as
operator: v as T
.
If v
indeed is of this type, all is well and you will be able to access all of T
's methods, but if this fails, an error will be 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 http://api.dartlang.org/docs/releases/latest/dart_core/String.html.
We will show some examples in string_methods.dart
:
- You can test that the owner of a bank account (a string) is not filled in with
owner.isEmpty
, which returns a Boolean value:assert("".isEmpty);
length()
returns the number of UTF16 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');
While 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 the Object
class 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 with number $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 to build 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
object like sb
in the following code 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 information, so how can we do it in Dart? The dart:core
package has a DateTime
class for this. In our banking app, we could add the dateCreated
and dateModified
attributes to our BankAccount
class. In the constructor, dateCreated
would be initialized to the moment at which the account is created; in our deposit
and withdraw
methods, we could 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 DateTime.parse(dateString)
method produces a DateTime
object from a string in one of the suitable formats: 20130227 13:27:00
or 2010-01-17
. All the weekdays and month names are defined as const
int
, such as MON
and JAN
. You can extract all the date parts as an int with methods such as second
, day
, month
, and 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 DateTime
.
List
This is the basic collection type to make an ordered group of objects; it can be of a 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 a length – 1
index. An empty list with length equal to 0
and an isEmpty
property 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 []
like in the following code:
var langs = ["Java","Python","Ruby", "Dart"];
Or, we can define a List
object 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 length of the List, provokes RangeError
in runtime (with no compile-time check!):
langs[4] = "F#"; // RangeError !
To check whether a List contains a certain item, use the method with the 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 the List<String>
type.
A string can be split
over a certain character or pattern (which could be a space " "
or even ""
), producing a List<String>
named parts
in the following code, which can then be further analyzed:
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 ";
". 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 the o (n) order.
To summarize it, a List is an ordered collection of the 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 be coming back to the List again in Chapter 3, Structuring Code with Classes and Libraries, in The collection hierarchy and its functional nature section. (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 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
constructor. Keys must be unique, they may not be null and the lookup of the value from the key is fast; mind, however, that the order of the pairs is not guaranteed! Similar to List
, a Map
constructor 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 the string type 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 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
constructor, you will get null
(it is not an error):
var link2 = webLinks2['C']; // null
To check whether 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 whether the key already exists; if not, the function is evaluated and the value returned is assigned to the key (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 the const
maps when the keys and values are literals or constants:
var cities = const {'1':'London','2':'Tokyo','3':'Madrid'};
A Map
constructor can also be explicitly typed, for example, Map
with integer keys and String values:
Map<int, String>
A Map
constructor 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 the order O(1)
.
(For the API docs for Map
, see the documentation at http://api.dartlang.org/docs/releases/latest/dart_core/Map.html).