Flutter 知識梳理 (Dart) - Dart 和 Java 有哪些不同?

澤毛發表於2019-07-10

在學習Dart的時候,會遇到一些Java中沒有的概念或者用法,這篇文章總結了DartJava中一些不同,但又經常會用到的知識點。

一、建構函式初始化列表

初始化列表定義在建構函式){之間,初始化列表的語句之間用,分割,它的作用有如下幾點。

1.1 為final變數賦值

我們希望類中的final變數初始值能由外部傳入,但是在建構函式中為final變數賦值是不允許的,例如下面這樣:

class ConstField {
  
  final value;
  
  ConstField(int y) {
    value = y;
  }
  
}
複製程式碼

此時可以採用Dart提供的初始化列表的方式:

class ConstField {
  
  final value;
  
  ConstField(int input) : value = input;
  
}
複製程式碼

只有初始化列表,沒有建構函式體的時候,初始化列表最後用;結尾。

1.2 對輸入引數進行判斷

class ConstField {
  
  final value;
  
  ConstField(int input) : assert(input > 0), value = input;
  
}
複製程式碼

1.3 呼叫父類的建構函式

class Parent {
  
  int value;
  
  Parent(int input);
  
}

class Child extends Parent {
  
  Child(int input) : assert(input > 0), super(input);
  
}
複製程式碼

在有初始化列表的情況下,函式呼叫的先後順序為:

  • 子類初始化列表
  • 父類建構函式
  • 子類建構函式

二、const 建構函式

const建構函式可以保證你的類稱為永遠不會更改的物件,並使這些物件稱為編譯時常量:

  • 定義const建構函式確保所有例項變數都是final的。
  • 建構函式不能有函式體,但是可以有初始化列表。
class ConstConstructor {
  
  final value;
  
  const ConstConstructor(int input) : value = input;
  
}
複製程式碼

三、例項變數的賦值

3.1 使用this簡化賦值

class Subject {
  
  int value;
  int value2;
  
  Subject(this.value, this.value2);
  
  log() {
    print('value=$value, value2=$value2');
  }
  
}

void main() {
  Subject(2,3).log();
}
複製程式碼

3.2 使用可選引數列表賦值或提供預設值

當我們希望 只為某些變數賦值,其餘的採用初始值 時,在Java中只能採用定義多個建構函式或者使用builder模式,在Dart中採用可選引數列表的方式實現就很簡單,呼叫的時候只需要採用 引數名:值 的形式賦值即可。

class Subject {
  
  int value;
  int value2;
  
  Subject({this.value, this.value2 = 3});
  
  log() {
    print('value=$value, value2=$value2');
  }
  
}

void main() {
  Subject(value : 2).log();
}
複製程式碼

四、getter & setter

getset是專門用來讀取和寫入物件的屬性的方法,每一個類的例項變數都有一個隱式的getter和可能的setterfinal或者const的只有getter)。

setter/getter使得我們在外部可以像對待普通成員變數進行操作,但是在內部進行額外的處理。

set/get後跟的是成員變數的名字,不可以與其它成員變數重名。

class Person {
  
  String _name;
      
  set name(String value) {
    assert(value != null);
    _name = value;
  }
  
  get name => 'Person name is $_name';
  
}

void main() {
  Person p = Person();
  p.name = "tom";
  print(p.name);
}
複製程式碼

五、factory 修飾的建構函式

factory 用於修飾類的建構函式

例如,我們有一個Shape類,當我們用factory修飾:

factory Shapre() { return ...; }
複製程式碼

再通過Shape()或者new Shape()的方式呼叫該函式時,不會真正地建立Shape,而是由該建構函式返回一個Shape或者Shape的子類。

它相當於為類提供了一個靜態的建立方法,該方法的返回值是它本身或它的子類物件,但是外部並不知道這個方法的存在,只需要按照普通建立物件的方式建立即可。

根據這一特點,目前factory的應用有兩類:

  • 實現工廠模式。
  • 單例模式。

5.1 工廠模式

Java中工廠模式的實現可以參考這裡,工廠模式,下面我們Dart的實現版本。

class Shape {
  
  factory Shape(String shapeType) {
    if (shapeType.compareTo('CIRCLE') == 0) {
      return Square();
    } else {
      return Circle();
    }
  }
  
  void draw() {}
}

class Square implements Shape {
  
  @override
  void draw() {
    print('Inside Square::draw() method.');
  }
}

class Circle implements Shape {
  
  @override
  void draw() {
    print('Inside Circle::draw() method.');
  }
}

void main() {
  Shape shape = Shape('CIRCLE');
  shape.draw();
}
複製程式碼

5.2 單例模式

回想一下,在Java中實現單例模式時,一般會將它的構造方法宣告為private,再定義一個getInstance()的靜態方法來返回單例物件。

而在Dart中,利用factory的特性,我們可以仍然按普通的方式建立物件,但是在多個地方獲得的是同一個例項。

class Singleton {

  //單例物件。
  static Singleton _instance;

  //建構函式。
  Singleton._internal() {
    print("_internal");
  }

  static Singleton _getInstance() {
    //僅在 _instance 為空時才呼叫。
    if (_instance == null) {
      _instance = Singleton._internal();
    }
    return _instance;
  }
  
  //呼叫方法。
  void method() {
    print("method");
  }

  //1.通過 new Singleton() 呼叫。
  factory Singleton() => _getInstance();
  //2.通過 Singleton.instance 呼叫。
  static Singleton get instance => _getInstance();

}
複製程式碼

呼叫方式有如下兩種,_instance都會只建立一次:

Singleton.instance.method();
Singleton().method();
複製程式碼

六、命名建構函式

Java中,可以宣告多個建構函式,但是會遇到一個問題,如果有多個建構函式接受的引數個數和型別都相同,那怎麼辦呢?Dart的命名引數就解決了這一個問題。

除此之外,Dart的命名建構函式可以使得含義更加直觀。

class Point {
  
  int x1;
  int y1;
  
  Point({this.x1, this.y1});
  
  Point.coordinate100() {
    x1 = 100;
    y1 = 100;
  }
    
  Point.coordinate50() {
    x1 = 50;
    y1 = 50;
  }
  
  log() {
    print('x=$x1, y=$y1');
  }
  
}

void main() {
  Point p100 = Point.coordinate100();
  p100.log();
  Point p50 = Point.coordinate50();
  p50.log();
}
複製程式碼

這裡需要注意,當我們宣告瞭命名建構函式,那麼就會 覆蓋預設的無參建構函式,這點和宣告瞭普通的有參建構函式是一樣的。也就是說,在它子類的建構函式中,需要通過super.xxx顯示地呼叫父類中的建構函式。

七、操作符

Dart中有一些操作符和Java不同。

7.1 型別操作符

  • 判斷物件是否是指定的型別,使用is或者!is,類似Java中的instanceof
void main() {
   int n = 2;
   print(n is int);
}

void main() {
   double  n = 2.20;
   var num = n is! int;
   print(num);
}
複製程式碼
  • a as T:將a轉換稱為指定的型別T,類似於Java中的(T)a

7.2 賦值運算子

  • ??=:僅在變數為null時才分配。
void main() {
  int a;
  a ??= 1;
  a ??= 2;
  print(a);
}
複製程式碼

執行結果:

1
複製程式碼

7.3 條件表示式

  • expr1 ?? expr2:如果expr1不為null,那麼返回expr1的值,否則返回expr2的值。
void main() {
  String a;
  String b = a ?? "default";
  print(b);
}
複製程式碼

執行結果:

default
複製程式碼

7.4 條件成員訪問:判空+呼叫

  • ?.:僅當物件不為空時才執行後面的操作,省去了Java中先判空,再去呼叫方法的步驟。
class Person {
  
  String name;
  
  Person(this.name);
      
  log() {
    print(name);
  }
  
}

void main() {
  Person p;
  String name = p?.name;
  p?.log();
  p = Person('a');
  p.log();
}
複製程式碼

7.5 級聯操作符

..允許對同一物件執行一系列操作,可以省去建立臨時變數的步驟。

class Person {
  
  String name;
  
  Person(this.name);
      
  log() {
    print(name);
  }
  
}

void main() {
  Person p = Person('a');
  p..name = 'b'
    ..log();
}
複製程式碼

八、函式也是一種型別

Flutter中,有很多將Function也就是方法作為成員變數的使用,其作用類似於Java當中的回撥,當我們的回撥介面中只有一個抽象函式時,可以使用這種方法代替。

typedef CalBuilder = int Function(int x, int y);

class Cal {
  
  CalBuilder builder;
  
  Cal(this.builder);
      
  log() {
    print('result=${builder(1, 2)}');
  }
  
}

void main() {
  Cal cal = Cal((a, b) => a + b);
  cal.log();
}
複製程式碼

九、函式的可選引數

當函式執行時不需要強制傳遞引數時,可以使用可選引數,可選引數時函式中的最後一個引數。

9.1 可選位置引數

可選引數的語法為:

void function_name(param1, [param1, param2]) { }
複製程式碼

示例如下:

void main() {
  func(1);
  func(1,'2');
}

void func(int a, [String b, int c]) {
  print(a);
  print(b);
  print(c);
}
複製程式碼

注意:如果當賦值給可選引數的實引數目不夠時,那麼必須按照形參型別定義的順序依次賦值,否則無法編譯通過。

9.2 可選命名引數

與可選位置引數不同,可選命名引數要求必須在傳遞值時指定引數名稱。

void function_name(a, {param1, param2}) { }
複製程式碼

示例:

void main() {
  func(1);
  func(2, c:3);
}

void func(int a, {String b, int c}) {
  print(a);
  print(b);
  print(c);
}
複製程式碼

9.3 帶有預設值的可選引數

無論是可選位置引數還是可選命名引數,都可以定義預設值。語法為:

function_name(param1, {param2 = default_value}) { }
複製程式碼

示例:

void main() {
  func(1);
  func(1,'2');
}

void func(int a, [String b = 'default', int c]) {
  print(a);
  print(b);
  print(c);
}
複製程式碼

輸出結果:


1
default
null
1
2
null
複製程式碼

十、容器

Dart中,常用的容器有ListMap,在使用上和Java也有一些區別。可以參考下面的兩篇文章,就不列舉了:

參考文章

相關文章