Learning Dart
上QQ阅读APP看书,第一时间看更新

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:

Built-in types and their methods

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;

Note

You cannot use this feature if you rely on compilation to JS, because here we are restricted to JS integers!

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.

Tip

Strings a and b can be concatenated with + as a + b, but string interpolation such as '$a $b' executes faster, so prefer this.

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.

Note

== is an operator and can be redefined for any type; generally it will check whether both arguments have the same value. Use the identical(a,b) function to check whether variables a and b refer to the same object.

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 with owner.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(), and contains() 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 index i 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'

Tip

If you need a readable representation of an object, give its class a toString() method.

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