Dart語言——45分鐘快速入門(下)

程式設計之路從0到1發表於2019-05-15

類和物件

類的定義

// Dart中定義一個類
class Person {
  String name;
  int age;

  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }
}
複製程式碼

Dart中的類與Java中的相似,不同的是,Dart中沒有privatepublic這些成員訪問修飾符。如果是類私有的成員,不希望外面訪問,只需要在成員變數之前加上一個下劃線_變為私有即可。

以上程式碼,在Dart中還有一種簡化寫法,可以自動在構造方法中對成員變數初始化。

// Dart中定義一個類
class  Person {
    String name;
    int age;

    // 在構造方法中初始化成員變數時,可使用如下寫法簡化
    Person(this.name, this.age);

    // 如需處理其他變數時,也可單獨對其操作
    // Person(this.name, this.age, String address){
    //     print(address);
    // }
    // 注意,構造方法不能過載,以上註釋掉
}
複製程式碼

另外還需要注意一點,Dart中沒有構造方法的過載,不能寫兩個同名的構造方法。

Getters 和 Setters方法

在Java中,一般不會直接在類的外部去訪問類成員,通常使用setter和getter方法來操作類的成員變數。而在Dart語言中,所有類中都包含隱式的getter方法,對於非final修飾的成員,類中還包含隱式的setter方法。這就意味著,在Dart中,你可以直接在類外部通過.操作符訪問類成員。這一特點使得Dart語法更加簡潔,不會寫出滿屏的setXXX、getXXX方法。

當然,很多時候我們呼叫setter和getter方法並不僅僅是為了賦值和訪問,而是為了一些額外的處理,這時候我們只需要使用setget關鍵字實現setter和getter方法即可。

class  Person {
    String userName;

    Person(this.userName);

    // 方法名前加get關鍵字
    String get name{
        return  "user:"  +  this.userName;
    }

    // 方法名前加set關鍵字
    set name(String name){
        // do something
        this.userName = name;
    }
}

void  main() {
    var p = new Person("zhangsan");
    print(p.name);   // user:zhangsan
    p.name = "Jack";
    print(p.name);   // user:Jack
}
複製程式碼

要注意,在建立物件時,new關鍵字並不是必須的,可以省略不寫。在寫Flutter介面時,不建議寫new關鍵字例項化物件,因為Flutter框架中沒有類似的xml語言來描述UI介面,介面也是使用Dart語言來寫,在使用Dart寫UI時,要保持程式碼的簡潔和結構化,省略new會更友好。

構造方法

如果沒有定義構造方法,則會有一個預設的無參構造方法,並且會呼叫超類的無參構造方法。

命名構造方法

上面已經說過,Dart類中兩個同名構造方法不能過載,但是Dart語言為類新增了一種稱為命名構造方法的東西。

class  Person {
    String userName;
    int age;

    Person(this.userName, this.age);

    // 命名構造方法
    Person.fromData(Map data) {
        this.userName = data['name'];
        this.age = data['age'];
    }
}

void  main() {
    // 使用命名構造方法建立物件
    var p = new Person.fromData({
        "name":"Bob",
        "age":19
    });
    print(p.userName);
}
複製程式碼

注意,使用命名構造方法可以為一個類實現多個構造方法,也可以更清晰的表明意圖。

常量構造方法

如果想提供一個狀態永遠不變的對像,在Dart中,我們可以建立一個編譯時常量物件,節省開銷。

class  ConstPoint {
    final num x;
    final num y;

    // 使用const修構造方法
    const ConstPoint(this.x, this.y);

    // 編譯時常量物件,需使用const來建立物件
    static final ConstPoint origin = const  ConstPoint(0, 0);
}

void  main() {
    print(ConstPoint.origin.x);
    print(ConstPoint.origin.y);
}
複製程式碼

工廠構造方法

當我們需要建立一個新的物件或者從快取中取一個物件時,工廠構造方法就派上了用場。

class  Logger {
    final String name;

    // 建立一個靜態Map做為快取
    static final Map<String, Logger> _cache =  <String, Logger>{};

    // 定義一個命名構造方法,用下劃線"_"修飾,將構造方法私有化
    Logger._internal(this.name);

    // 使用關鍵字factory修飾類同名構造方法
    factory Logger(String name) {
        if (_cache.containsKey(name)) {
            return _cache[name];
        } else {
            // 呼叫命名構造方法建立新物件
            final logger= new  Logger._internal(name);
            _cache[name] = logger; // 存入快取
            return logger;
        }
    }
}

void  main() {
    var uiLog = new Logger('UI');
    var eventLog = new Logger('event');
}
複製程式碼

構造方法重定向

有時候一個構造方法會調動類中的其他構造方法來例項化,這時候可以使用構造方法重定向,

class Point {
  num x;
  num y;

  // 同名構造方法
  Point(this.x, this.y);

  // 命名構造方法重定向到同名構造方法,中間使用一個冒號
  Point.alongXAxis(num x) : this(x, 0);
}
複製程式碼

類的初始化列表

熟悉C++的朋友應該對初始化列表很瞭解了,Java中是沒有這個特性的。

class  Point {
    final  num x;
    final  num y;
    final  num distance;

    Point(x, y)
        : x = x,
          y = y,
          distance =  sqrt(x * x + y * y){
             print("這是構造方法");
          }
}

void  main() {
    var p =  new  Point(2, 3);
    print(p.distance);
}
複製程式碼

在這裡插入圖片描述

  • 初始化列表位於構造方法的小括號與大括號之間,在初始化列表之前需新增一個冒號。
  • 初始化列表是由逗號分隔的一些賦值語句組成。
  • 它適合用來初始化 final修飾的變數
  • 初始化列表的呼叫是在構造方法之前,也就是在類完成例項化之前,因此初始化列表中是不能訪問 this

運算子過載

這個特性,又很類似於C++中的運算子過載,在Java中是沒用這種概念的。

class Point {
  int x;
  int y;

  Point(this.x, this.y);

  // 使用operator關鍵字,為該類過載"+"運算子
  Point operator +(Point p) {
    return new Point(this.x + p.x, this.y + p.y);
  }

  // 為該類過載"-"運算子
  Point operator -(Point p) {
    return new Point(this.x - p.x, this.y - p.y);
  }
}

void main(){
   var p1 = new Point(1,5);
   var p2 = new Point(7,10);

   // 過載運算子後,類可以使用“+”、“-” 運算子操作
   var p3 = p1 + p2;
   var p4 = p2 - p1;

   print("${p3.x}, ${p3.y}");
   print("${p4.x}, ${p4.y}");
}
複製程式碼

列印結果:

8, 15
6, 5
複製程式碼

Dart中允許過載的運算子如下:

+ * ~/ / % ^
< > <= >= == [] []=
& ~ << >> |

類的繼承

Dart中的繼承,與Java中相似,可以使用關鍵字extends繼承父類,使用關鍵字super引用父類

class Father {
    myFunction(){
        // do something
    }
}

class Son extends Father {

    @override
    myFunction(){
        super.myFunction();
        // do something
    }
}
複製程式碼

我們都知道,Java中的類僅支援單繼承,而Dart中的類可以實現多繼承。要實現多繼承,需要使用with關鍵字。

// 首先定義三個父類
class Father1 {
    a(){
      print("this is a func");
    }

    common(){
        print("common Father1");
    }
}

class Father2 {
    b(){
      print("this is b func");
    }

    common(){
        print("common Father2");
    }
}

class Father3 {
    c(){
      print("this is c func");
    }

    common(){
        print("common Father3");
    }
}

//定義子類
class Son extends Father1 with Father2,Father3{

}

void main() {
  var obj = new Son();
  obj.common();
  obj.a();
  obj.b();
  obj.c();
}
複製程式碼

列印結果:

common Father3
this is a func
this is b func
this is c func
複製程式碼

要注意,以上繼承寫法中,也可以直接使用with,等價於如下寫法

class Son with Father1,Father2,Father3{

}
複製程式碼

介面抽象

抽象類

Dart語言沒有提供interface關鍵字來定義介面,但是Dart語言中保留了抽象類,同Java,使用abstract關鍵字來修飾抽象類。而Dart中的抽象類,實際上就相當於Java中的介面。

abstract class Base {
    // 省略函式體即可定義抽象方法,不需加關鍵字
    func1();
    func2();
}
複製程式碼

注意,抽象類是不能被例項化的,子類繼承抽象類時,必須實現全部抽象方法。

隱式介面

實際上在Dart中,每個類都隱式的定義了一個包含所有例項成員的介面, 並且該類實現了這個介面。

因此,如果我們想實現某個介面,但有又不想繼承,則可以使用這種隱式介面機制。我們需要用到關鍵字implements

class People {
  void greet(){
    print("Hello");
  }
}

class Student implements People{
  @override
  void greet(){
    print("Hi,I'm Alice.");
  }
}

greet(People p){
  p.greet();
}

void main() {
  greet(new Student());
}
複製程式碼

泛型

Dart中也支援泛型,用法與Java中類似。

// 泛型
var names = new List<String>();
names.add("zhangsan")

var maps = new Map<int, String>();
maps[1]="value";

// 字面量寫法
var infos = <String>['Seth', 'Kathy', 'Lars'];

var pages = <String, String>{
  'index.html': 'Homepage',
  'robots.txt': 'Hints for web robots'
};
複製程式碼

異常處理

如果關心具體異常,針對不同異常進行不同處理,可以使用try...on處理異常,finally是可選的,用於最後的處理。

  try {
      // 使除數為0
      print(11~/0);
  } on IntegerDivisionByZeroException {
      print("除數為0");
  }on Exception{
      print("Exception");
  }finally {
      print("finally");
  }
複製程式碼

不關心具體異常,只想捕獲,避免異常繼續傳遞,則可以使用try...catch處理

  try {
      print(11~/0);
  } catch(e){
      // 列印報錯資訊
      print(e);
  }finally {
      print("finally");
  }
複製程式碼

如果想獲取更多異常資訊,可以使用兩個引數的catch,第二個引數是異常的呼叫棧資訊

  try {
      print(11~/0);
  } catch(e,s){
      print(s);
  }
複製程式碼

如果你既想針對不同異常進行不同處理,還想列印呼叫棧資訊,那就將兩種結合起來使用

  try {
      print(11~/0);
  } on IntegerDivisionByZeroException catch(e,s){
      print(s);
  } on Exception catch(e,s){
      print(s);
  }
複製程式碼

庫與匯入

Dart使用import語句用來匯入一個庫,後面跟一個字串形式的Uri來指定表示要引用的庫。

// 指定dart:字首,表示匯入標準庫,如dart:io
import 'dart:math';

// 也可以用相對路徑或絕對路徑來引用dart檔案
import 'lib/student/student.dart';

// 指定package:字首,表示匯入包管理系統中的庫
import 'package:utils/utils.dart';
複製程式碼

匯入庫時,可以使用as關鍵字來給庫起別名,避免名稱空間衝突。

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;

// 使用lib1中的Element
Element element1 = new Element();
// 使用lib2中的Element
lib2.Element element2 = new lib2.Element(); 
複製程式碼

使用showhide關鍵字控制庫中成員的可見性

// 僅匯入foo,遮蔽庫中其他成員
import 'package:lib1/lib1.dart' show foo;

// 遮蔽foo,庫中其他成員都可見
import 'package:lib2/lib2.dart' hide foo;
複製程式碼

為了減少 APP 的啟動時間,載入很少使用的功能,我們還可以延遲匯入庫。使用 deferred as關鍵字延遲匯入

import 'package:deferred/hello.dart' deferred as hello;

// 當需要使用時,再通過庫識別符號呼叫 loadLibrary函式載入
hello.loadLibrary();
複製程式碼

非同步程式設計

Dart與JavaScript一樣,是一個單執行緒模型。但這並不意味著Dart中不能進行非同步程式設計,只是這種非同步程式設計區別於傳統的多執行緒非同步方式。

Dart中的所有程式碼都只在一個執行緒上執行,但Dart程式碼可以執行在多個isolate上。isolate可以看做一個微小的執行緒,isolate由虛擬機器排程,isolate之間沒有共享記憶體,因此它們之間沒有競爭,不需要鎖,不用擔心死鎖,因此開銷小,效能高。由於沒有共享記憶體,所以它們之間唯一的通訊只能通過Port進行,而且Dart中的訊息傳遞也總是非同步的。

Dart中兩種方式可以使用Future物件來進行非同步程式設計

  • 使用 asyncawait關鍵字
  • 使用 Future API

使用asyncawait編寫程式碼非常簡單,而且編寫的程式碼看起來有點像同步程式碼,實際上是非同步的。

// 匯入io庫,呼叫sleep函式
import 'dart:io';

// 模擬耗時操作,呼叫sleep函式睡眠2秒
doTask() async{
  await sleep(const Duration(seconds:2));
  return "Ok";
}

// 定義一個函式用於包裝
test() async {
  var r = await doTask();
  print(r);
}

void main(){
  print("main start");
  test();
  print("main end");
}
複製程式碼

執行結果:

main start
main end
Ok
複製程式碼

在函式簽名中加入async關鍵字,表示該函式非同步執行,await表示等待非同步結果執行完成返回Future物件。但有一點需要注意,await只能在async函式中出現,因此往往需要再定義一個async函式,用於包裝。上述程式碼中test函式就是用於包裝。

關於Dart非同步程式設計的更多詳細知識,請跳轉本人另一篇部落格 Dart 非同步程式設計詳解之一文全懂

以上是本篇的全部內容,關於博主的更多技術文章,請跳轉到以下連結,謝謝!

我的個人部落格

GitHub

Dart語言——45分鐘快速入門

關注我的公眾號:程式設計之路從0到1

程式設計之路從0到1

相關文章