在學習Dart
的時候,會遇到一些Java
中沒有的概念或者用法,這篇文章總結了Dart
和Java
中一些不同,但又經常會用到的知識點。
一、建構函式初始化列表
初始化列表定義在建構函式)
和{
之間,初始化列表的語句之間用,
分割,它的作用有如下幾點。
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
get
和set
是專門用來讀取和寫入物件的屬性的方法,每一個類的例項變數都有一個隱式的getter
和可能的setter
(final
或者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
中,常用的容器有List
和Map
,在使用上和Java
也有一些區別。可以參考下面的兩篇文章,就不列舉了: