本博客原地址:https://ntopic.cn/p/2023092401/


Dart官网代码实验室:https://dart.dev/codelabs/dart-cheatsheet

特别说明:为了更进一步验证Dart代码特性,下面示例的代码并非与官方代码完全一致(为了探究细节,默认比官方代码要复杂一些)。

字符串插值:${}

基础语法:字符串中,可以通过${}插入上下文中变量和变量运算值。

void main() {  // 1. 字符串插值  var a = 2;  var b = 3;  var c = 'Hello';  print('1. 字符串插值: ${c.toUpperCase()} Dart:a is ${a} and b is ${b}, so a>b is ${a > b}, a+b=${a + b}');  // 结果:1. 字符串插值: HELLO Dart:a is 2 and b is 3, so a>b is false, a+b=5}

变量赋值:?和null

基础语法:Dart是空安全(或者null安全)的语言,也就是说除非显示声明变量是可为null的,否则他们不能为null。默认情况下,变量默认是不能为null的。

void main() {  // 2. 变量赋值  // 非法声明:int d = null; String s;  int d = 4;  int? e;  String f = 'Hello';  String? g;  print('2. 变量赋值: d: ${d}, e:${e}, f:${f}, g:${g}');  // 结果:2. 变量赋值: d: 4, e:null, f:Hello, g:null}

空运算符:??和??=

基础语法:用于处理可能会为空值的变量,??判断是否为空,??=当为空时才运行赋值。

特别注意:??和??=这两个运算符中间不能有空格。

void main() {  // 3. 空运算符  int? h;  h ??= 3;  h ??= 5; // 不生效,因为此时h非空  int i = 1 ?? 3; // 1非空,所以3不生效  int j = null ?? 4;  print('3. 空运算符: h=${h}, i=${i}, j=${j}');  // 结果:3. 空运算符: h=3, i=1, j=4}

访问空对象属性:.?

基础语法:为了正常访问可能为空对象的属性。在Java中,通过if条件判断来访问属性,如:int a = (obj == null) ? 0 : obj.getA();

void main() {  // 4. 访问空对象属性  String? k;  String? l = 'Hello';  var m = k?.toLowerCase()?.toUpperCase();  var n = l?.toLowerCase()?.toUpperCase();  print('4. 访问空对象属性: m=${m}, n=${n}');  // 结果:4. 访问空对象属性: m=null, n=HELLO}

集合类型:[]和{}

基础语法:Dart存在内置的基础集合类型,包括list, set和map等,可以指定元素类型。list元素可以重复,set和map不允许重复。

特别注意:这里只是简单用例,集合类型的其他用法,我在下次学习并通过博客分享。

void main() {  // 5. 集合类型  var aList = ['a', 'b', 'b'];  var aSet = {'a', 'b', 'b'};  var aMap = {'a': 'Hello', 'b': 'World', 'b': 'Dart'};  print('5. 集合类型: aList=${aList}, aSet=${aSet}, aMap=${aMap}');  // 结果:5. 集合类型: aList=[a, b, b], aSet={a, b}, aMap={a: Hello, b: Dart}}

箭头语法函数:=>

基础语法:箭头是定义函数的一种简便方法,箭头右边的执行结果作为返回值。

void main() {  // 6. 箭头语法函数  String joinWithCommas(List values) => values.join(',');  final cList = [2, 5, 7];  print('6. 箭头语法函数: cList join=${joinWithCommas(cList)}');  // 结果:6. 箭头语法函数: cList join=2,5,7}

级联和空判断:..和?..

基础语法:为了简便对同一个对象连续执行多个方法,它每次执行均返回的是操作对象引用,而不是操作结果。

特别说明:常规情况下myObject.someMethod()返回的是方法的执行结果,但是级联操作myObject..someMethod()返回的是myObject引用本身,那么它就可以连续执行多个方法,如:myObject..someMethod()..otherMethod()等。

级联联合空判断,可以在连续执行多个方法的时候,无需担心操作对象为null,如以下代码样例:将 BigObject 的 anInt 属性设为 1、aString 属性设为 String!、aList 属性设置为 [3.0]、然后调用 allDone()。

class BigObject {  int anInt = 0;  String aString = '';  List aList = [];  bool _done = false;    void allDone() {    _done = true;  }}BigObject fillBigObject(BigObject obj) {  return obj?..anInt = 1    ..aString = 'String!'    ..aList = [3.0]    ..allDone();}

对象属性访问器:getters和setters

基础语法:按照类的封装原则,类属性不能直接暴露给外部访问或者设置,应该提供getters和setters方法。Dart提供了简单的实现方式。Dart的类属性没有public/private等可见于修饰符,如果以下划线_开头,则为private,否则为public公共域。

代码样例:有一个购物车类,其中有一个私有的 List 类型的 prices 属性;一个名为 total 的 getter,用于返回总价格。只要新列表不包含任何负价格, setter 就会用新的列表替换列表(在这种情况下,setter 应该抛出 InvalidPriceException)。

class InvalidPriceException {}class ShoppingCart {  // 下划线开头,私有属性  List _prices = [];    // total的getter方法,用户返回总价格值  double get total => _prices.fold(0, (e,t) => e+t);  // 设置_prices的值,如果存在负数,则抛出异常  set prices(List newPrices) {    if(newPrices.any((e) => e < 0)) {      throw InvalidPriceException();    }        _prices = newPrices;  }}

函数可选位置入参:[]

基础语法:函数的入参列表中,最后面的参数通过[]曝光起来,他们是可选的,即调用函数时可以不传入参数。除非指定了默认值,否则可选入参默的认值均为null。

void main() {  // 8. 函数可选位置入参  int sumUpToFive(int a, [int? b, int? c, int? d, int? e]) {    return a + (b ?? 0) + (c ?? 0) + (d ?? 0) + (e ?? 0);  }  int sumUpToFive2(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {    return a + b + c + d + e;  }  print('8. 函数可选位置入参: sumUpToFive(1,2)=${sumUpToFive(1, 2)}, sumUpToFive(1,2,3)=${sumUpToFive(1, 2, 3)}');  print('8. 函数可选位置入参: sumUpToFive2(1,1)=${sumUpToFive2(1, 1)}, sumUpToFive2(1,1,1)=${sumUpToFive2(1, 1, 1)}');  // 结果:  // 8. 函数可选位置入参: sumUpToFive(1,2)=3, sumUpToFive(1,2,3)=6  // 8. 函数可选位置入参: sumUpToFive2(1,1)=14, sumUpToFive2(1,1,1)=12}

函数命名入参:{}

基本语法:与位置参数类似,命名参数使用{}包裹,它也是可选的,除非有默认值,否则它的值也是null。在调用命名参数函数时,命名参数必须通过参数名来定位,且它的顺序是可随意(这2点是与位置参数的最大区别)。

代码样例:有个MyDataObject类,有3个属性和copyWith方法,方法的入参均可能为空,如果为空则使用原对象值,否则使用入参值:

class MyDataObject {  final int anInt;  final String aString;  final double aDouble;  MyDataObject({    this.anInt = 1,    this.aString = 'OLD',    this.aDouble = 2.0,  });  String toString() {    return 'MyDataObject: {anInt=$anInt, aString=$aString, aDouble=$aDouble}';  }  // 本方法的3个入参均为命名参数  MyDataObject copyWith({int? newInt, String? newString, double? newDouble}) {    return MyDataObject(      anInt: newInt ?? this.anInt,      aString: newString ?? this.aString,      aDouble: newDouble ?? this.aDouble,    );  }}void main() {  // 10. 函数命名入参  final myDataObject = MyDataObject();  final newDataObject = myDataObject.copyWith(newInt: 2, newString: 'NEW', newDouble: 4.0);  print('10. 函数命名入参: myDataObject=$myDataObject, newDataObject=$newDataObject');  // 结果:10. 函数命名入参: myDataObject=MyDataObject: {anInt=1, aString=OLD, aDouble=2.0}, newDataObject=MyDataObject: {anInt=2, aString=NEW, aDouble=4.0}}

异常:try,on,catch,rethrow和finally

基础语法:Dart可以抛出和捕获异常,所有异常都是未检测异常。函数或者方法无需声明可能抛出的异常。Dart提供了Exception和Error两种异常类型,但业务逻辑中,可以抛出任意非空的对象(如:throw ‘abc’)。通过rethrow关键字,可重新抛出异常。

代码样例:tryFunction不可靠方法,捕获不同的异常并打印日志。

typedef VoidFunction = void Function();class ExceptionWithMessage {  final String message;  const ExceptionWithMessage(this.message);}// Call logException to log an exception, and doneLogging when finished.abstract class Logger {  void logException(Type t, [String? msg]);  void doneLogging();}void tryFunction(VoidFunction untrustworthy, Logger logger) {  try {    untrustworthy();  } on ExceptionWithMessage catch(e) {    logger.logException(e.runtimeType, e.message);  } on Exception {    logger.logException(Exception);  } finally {    logger.doneLogging();  } }

构造方法:this,required,位置参数和命名参数

基础语法:在构造函数中,通过this关键字可以为成员变量快速赋值。构造函数的如此可以是位置参数,也可以是命名参数,如果参数是必选参数,则使用required关键字修饰,且该参数不能有默认值。

// 位置参数class MyColor1 {  int red;  int green;  int blue;  // 主构造函数  MyColor1(this.red, this.green, this.blue);  // 命名构造函数:默认值初始化  MyColor1.origin()      : red = 0,        green = 0,        blue = 0;  MyColor1.origin2() : this(0, 0, 0);}final color10 = MyColor1(80, 80, 128);final color11 = MyColor1.origin();final color12 = MyColor1.origin2();// 命名参数class MyColor2 {  int red;  int green;  int blue;  // 主构造函数  MyColor2({required this.red, required this.green, required this.blue});  // 命名构造函数:默认值初始化  MyColor2.origin()      : red = 0,        green = 0,        blue = 0;  MyColor2.origin2() : this(red: 0, green: 0, blue: 0);}final color20 = MyColor2(  red: 80,  green: 80,  blue: 128,);final color21 = MyColor2.origin();final color22 = MyColor2.origin2();

构造方法::初始化列表

基础语法:在执行构造函数体之前,需要进行一些初始化操作,比如校验参数合法性、初始化参数等。

代码样例:使用的初始化列表将 word 的前两个字符分配给 letterOne 和 LetterTwo 属性。

class FirstTwoLetters {  final String letterOne;  final String letterTwo;  // 初始化列表  FirstTwoLetters(String word)      : assert(word.length >= 2),        letterOne = word[0],        letterTwo = word[1];  String toString() {    return 'FirstTwoLetters: {letterOne=$letterOne, letterTwo=$letterTwo}';  }}void main() {  // 13. 构造方法:初始化列表  final firstTwoLetters = FirstTwoLetters('Dart');  print('13. 构造方法:初始化列表: firstTwoLetters=$firstTwoLetters');  // 结果:13. 构造方法:初始化列表: firstTwoLetters=FirstTwoLetters: {letterOne=D, letterTwo=a}}

构造方法:factory工厂

基础语法:父类根据入参,返回具体子类。

代码样例:一般父类方法提供一个无任何参数的构造函数。

class Square extends Shape {}class Circle extends Shape {}class Shape {  Shape();  factory Shape.fromTypeName(String typeName) {    if (typeName == 'square') return Square();    if (typeName == 'circle') return Circle();    throw ArgumentError('Unrecognized $typeName');  }}

构造方法::重定向

基本语法:构造方法中,通过:引用另外一个构造方法,可以是主构造函数,也可以是命名构造函数。

class Automobile {  String make;  String model;  int mpg;  // 类主构造函数  Automobile(this.make, this.model, this.mpg);  // 命名构造函数:重定向主构造函数  Automobile.hybrid(String make, String model) : this(make, model, 60);  // 命名构造函数:重定向命名构造函数  Automobile.fancyHybrid() : this.hybrid('Futurecar', 'Mark 2');}

构造方法:final,const常量

基础语法:如果类生成的对象永远都不会更改,则可以让这些对象成为编译时常量。为此,请定义 const 构造方法并确保所有实例变量都是 final 的。

class ImmutablePoint {  static const ImmutablePoint origin = ImmutablePoint(0, 0);  // 类属性必须用final修饰  final int x;  final int y;  // 构造函数更加const关键字  const ImmutablePoint(this.x, this.y);}

最后

Dart学习第2天,根据官方文档由浅入深学习,更多语法和技巧在后续研发中我在补充。

完整的测试用的实例代码,部分代码示例在小节中已经提供:

class MyDataObject {  final int anInt;  final String aString;  final double aDouble;  MyDataObject({    this.anInt = 1,    this.aString = 'OLD',    this.aDouble = 2.0,  });  String toString() {    return 'MyDataObject: {anInt=$anInt, aString=$aString, aDouble=$aDouble}';  }  // 本方法的3个入参均为命名参数  MyDataObject copyWith({int? newInt, String? newString, double? newDouble}) {    return MyDataObject(      anInt: newInt ?? this.anInt,      aString: newString ?? this.aString,      aDouble: newDouble ?? this.aDouble,    );  }}// 位置参数class MyColor1 {  int red;  int green;  int blue;  // 主构造函数  MyColor1(this.red, this.green, this.blue);  // 命名构造函数:默认值初始化  MyColor1.origin()      : red = 0,        green = 0,        blue = 0;  MyColor1.origin2() : this(0, 0, 0);}final color10 = MyColor1(  80,  80,  128,);final color11 = MyColor1.origin();final color12 = MyColor1.origin2();// 命名参数class MyColor2 {  int red;  int green;  int blue;  // 主构造函数  MyColor2({required this.red, required this.green, required this.blue});  // 命名构造函数:默认值初始化  MyColor2.origin()      : red = 0,        green = 0,        blue = 0;  MyColor2.origin2() : this(red: 0, green: 0, blue: 0);}final color20 = MyColor2(  red: 80,  green: 80,  blue: 128,);final color21 = MyColor2.origin();final color22 = MyColor2.origin2();class FirstTwoLetters {  final String letterOne;  final String letterTwo;  // 初始化列表  FirstTwoLetters(String word)      : assert(word.length >= 2),        letterOne = word[0],        letterTwo = word[1];  String toString() {    return 'FirstTwoLetters: {letterOne=$letterOne, letterTwo=$letterTwo}';  }}class ImmutablePoint {  static const ImmutablePoint origin = ImmutablePoint(0, 0);  // 类属性必须用final修饰  final int x;  final int y;  // 构造函数更加const关键字  const ImmutablePoint(this.x, this.y);}void main() {  // 1. 字符串插值  var a = 2;  var b = 3;  var c = 'Hello';  print(      '1. 字符串插值: ${c.toUpperCase()} Dart:a is ${a} and b is ${b}, so a>b is ${a > b}, a+b=${a + b}');  // 2. 变量赋值  // 非法声明:int d = null; String s;  int d = 4;  int? e;  String f = 'Hello';  String? g;  print('2. 变量赋值: d: ${d}, e:${e}, f:${f}, g:${g}');  // 3. 空运算符  int? h;  h ??= 3;  h ??= 5; // 不生效,因为此时h非空  int i = 1 ?? 3; // 1非空,所以3不生效  int j = null ?? 4;  print('3. 空运算符: h=${h}, i=${i}, j=${j}');  // 4. 访问空对象属性  String? k;  String? l = 'Hello';  var m = k?.toLowerCase()?.toUpperCase();  var n = l?.toLowerCase()?.toUpperCase();  print('4. 访问空对象属性: m=${m}, n=${n}');  // 5. 集合类型  var aList = ['a', 'b', 'b'];  var aSet = {'a', 'b', 'b'};  var aMap = {'a': 'Hello', 'b': 'World', 'b': 'Dart'};  print('5. 集合类型: aList=${aList}, aSet=${aSet}, aMap=${aMap}');  // 6. 箭头语法函数  String joinWithCommas(List values) => values.join(',');  final cList = [2, 5, 7];  print('6. 箭头语法函数: cList join=${joinWithCommas(cList)}');  // 8. 函数可选位置入参  int sumUpToFive(int a, [int? b, int? c, int? d, int? e]) {    return a + (b ?? 0) + (c ?? 0) + (d ?? 0) + (e ?? 0);  }  int sumUpToFive2(int a, [int b = 2, int c = 3, int d = 4, int e = 5]) {    return a + b + c + d + e;  }  print(      '8. 函数可选位置入参: sumUpToFive(1,2)=${sumUpToFive(1, 2)}, sumUpToFive(1,2,3)=${sumUpToFive(1, 2, 3)}');  print(      '8. 函数可选位置入参: sumUpToFive2(1,1)=${sumUpToFive2(1, 1)}, sumUpToFive2(1,1,1)=${sumUpToFive2(1, 1, 1)}');  // 10. 函数命名入参  final myDataObject = MyDataObject();  final newDataObject = myDataObject.copyWith(newInt: 2, newString: 'NEW', newDouble: 4.0);  print('10. 函数命名入参: myDataObject=$myDataObject, newDataObject=$newDataObject');  // 13. 构造方法:初始化列表  final firstTwoLetters = FirstTwoLetters('Dart');  print('13. 构造方法:初始化列表: firstTwoLetters=$firstTwoLetters');}

本文作者:奔跑的蜗牛,转载请注明原文链接:https://ntopic.cn