本篇基于Dart 3.1.5版本进行学习

在上一篇文章中我们学习了Dart的基础类型知识,接下来本篇我们继续学习Dart中方法和类的相关知识。

系列文章

Flutter基建 – Dart基础类型

Flutter基建 – Dart方法和类

Flutter基建 - Dart方法和类

Dart中方法

定义方法

Dart中定义方法的形式和Java有点类似,但是Dart方法有很多是Java不支持的操作,下面我们逐一感受下。

bool isAdult(int age) {
  return age >= 18;
}
void main() {
  print('${isAdult(20)}');
}
输出:
true

上面代码定义了一个最简单的带返回值的方法,Dart中方法的返回值类型定义在方法的最前面,也是通过return进行返回值的输出。

对于上面这种方法体中只有一个表达式的函数,可以简化写法,无需采用{}的形式,简化后的写法如下:

bool isAdult(int age) => age >= 18;
void printName(String name) => print(name);

舍弃了{}直接采用=>这种形式将一个表达式引入即可,并且箭头后面可以跟一些条件表达式,如? :三目表达式:

String getPersonType(int age) => age >= 18 ? "Adult" : "Child";

方法参数类型

Dart在方法参数上提供了很多的可选项,包括可选参数、参数默认值、可选位置参数等。接下来我们一个一个的去了解下这几种类型。

如上面isAdult(age)方法,如果在调用的时候不传入age值,那么系统就会提示你1 positional argument expected by 'isAdult', but 0 found,有一个位置参数需要提供,但是未找到的错误。也就是说age这个参数是必传的,那么该如何定义一个可选参数呢?

void optionalParams({int age = 0}) {
  print('age: $age');
}
optionalParams();
optionalParams(age: 18);
输出:
age: 0
age: 18

这里定义了一个optionalParams方法,内部age参数使用{}包裹住了,通过此操作说明了age参数是一个可选参数,并且默认值为0,那么在调用的时候age参数就是非必传项了,如果未传入age参数,那么它就是默认值0,通过上面调用和输出就可以很清晰的看出来传入和不传age的区别。

这里还需要注意的一点就是可选参数age如果在定义的时候没有定义默认值,那么它的类型必须是可空类型,因为可选参数在未定义默认值的时候默认值为null。 下面看看定义为可空类型的情况:

void optionalParams({int? age}) {
  print('age: $age');
}
optionalParams();
optionalParams(age: 18);
输出:
age: null
age: 18

看到这的时候是不是有个以为,通过{}包裹起来的参数就是可选参数,那必传参数咋定义呢,写在{}外面么?如果写在外面会直接看到系统提示的报错,必传参数也是需要写在{}内部的,只是需要加上required修饰符就可以了,看下面示例代码:

void optionalParams({int? age, required String name}) {
  print('age: $age');
}
optionalParams(name: "tao");

这样就可以定义可选和必传参数类型的方法了~

除了使用required修饰符要定义必传参数之外,Dart还提供了一种方式来同时定义必传参数和可选参数,那就是采用[]来包裹可选位置参数,在[]内部的参数就是可选位置参数,[]外部的就是必传参数,此时必传参数无需使用required修饰符修饰。

void otherOptionalParams(String name, [int? age]) {
  print('name: $name, age: $age');
}
otherOptionalParams('tao', 18);
otherOptionalParams('tao');
输出:
name: tao, age: 18
name: tao, age: null

使用[]的方式同样可以达到定义可选参数的效果,但是它和{}还是有点区别的,二者区别在于调用的时候{}方法可以指定参数的名称,这样更有利于理解方法调用时传入参数的名称和对应的值,在这一点上,我更推荐{}方式定义方法,这知识个人的一些建议,按需采纳

Dart中的类

构造方法

Dart中的类也是采用class关键字修饰,这一点是熟悉的味道,废话不多说直接进入代码示例

class Person {
  final String name;
  final int age;
  String address = "";
  Person(this.name, this.age, this.address);
}

上述代码定义了一个Person类,内部有三个成员变量,其中name和age采用final修饰,表示只会赋值一次,而且它俩也是直接定义在构造方法中,如果将构造方法中的name和age去掉,此时系统会提示所有的final变量都需要进行初始化。定义好了Person类之后,我们就可以在需要定义进行创建并使用。

Person person = Person("tao", 18);
person.address = "SH";
print('Person name: ${person.name}, age: ${person.age}, address: ${person.address}');
输出:
Person name: tao, age: 18, address: SH

接下来我们再看看Dart类是如何实现接口定义的。

实现接口和extends继承

abstract class Car {
  final String name;
  Car(this.name);
  void getCarInfo() {
    print('Car name: $name');
  }
}
class BMW implements Car {
  @override
  String get name => "BMW 330";
  @override
  void getCarInfo() {
    print('BMW name: $name');
  }
}
class BYD implements Car {
  @override
  String get name => "BYW 汉";
  @override
  void getCarInfo() {
    print('BYD name: $name');
  }
}
BMW bmw = BMW();
bmw.getCarInfo();
BYD byd = BYD();
byd.getCarInfo();
输出:
BMW name: BMW 330
BYD name: BYW 汉

这里我们在最上方定义了一个抽象类Car,并且内部定义了一个name成员变量和getCarInfo()成员方法,然后将BMW和BYD类去实现Car类,通过Implements方式的实现之后,定义在Car中的name和getCarInfo()也需要在字类中去复写,否则系统就会提示我们有变量或者方法未进行实现。

如果我们将implements替换成extends之后,再看看实现有什么区别:

class BMW extends Car {
  BMW(super.name);
  @override
  void getCarInfo() {
    print('BMW name: $name');
  }
}
class BYD extends Car {
  BYD(super.name);
  @override
  void getCarInfo() {
    print('BYD name: $name');
  }
}

改为extends继承之后,name这个变量的实现就有所区别了,implements需要我们去实现get name来复写name变量的获取途径,而extends则需要我们保证字类和父类的构造方法保持一致,并且可以通过super.name来引用父类的构造方法。

枚举类

枚举类基本上在个语言中都会出现,它可以帮助我们很好的定义一些类型值。

enum Size { big, middle, small }
Size size = Size.big;
print('size: $size');
List<Size> sizeList = Size.values;
print('Size list: $sizeList');
输出:
size: Size.big
Size list: [Size.big, Size.middle, Size.small]

在Dart中,枚举类有个很人性化的地方就是可以直接通过枚举类的.values将定义的所有枚举值转换为一个列表对象,这样我们就可以很轻松的获取到枚举中所有的值。

类的扩展方法

类的扩展方法在Kotlin中可谓是开发者非常喜欢的一个特性,在日常开发中可以帮助我们很大程度上优化一些代码,Dart中也引入了扩展方法特性,值得称赞哇,下面我们来看看如何自己定义扩展方法。

我们以最常用的判断一个字符串是否符合手机号规则的例子来认识下扩展方法:

extension StringExtension on String {
  bool isPhoneNum() {
    RegExp exp = RegExp(r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))d{8}$');
    return exp.hasMatch(this);
  }
}
print("18711111111".isPhoneNum());
print("1871111111".isPhoneNum());
输出:
true
false

定义扩展方法使用的是extension关键字,后面的on表示作用在哪个类上,然后就可以在内部定义我们需要的方法实现。

这里有一个需要注意的就是跟在extension后面的名称小伙伴们是否觉得多余了,因为我们在调用的时候根本没有用到此名称,那么它有啥作用呢?

扩展名是Dart为了解决冲突使用的,我们在上面调用isPhoneNum()的时候,还可以使用StringExtension(”).isPhoneNum()这个方式来调用,只是变得繁琐了,此时试想一下如果有两处定义了相同的isPhoneNum()扩展方法之后,系统怎么会知道该调用哪个方法呢,这就到了扩展名发挥作用的时候了,具体如何解决冲突看下面示例代码:

Flutter基建 - Dart方法和类

我们在test和test2中都定义了isPhoneNum()的扩展方法

# test.dart
extension StringExtension on String {
  bool isPhoneNum() {
    RegExp exp = RegExp(r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))d{8}$');
    return exp.hasMatch(this);
  }
}
void main() {
  StringExtension('').isPhoneNum();
}
#test2.dart
extension StringExtension on String {
  bool isPhoneNum() {
    RegExp exp = RegExp(r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))d{8}$');
    return exp.hasMatch(this);
  }
}

上面在test.dart文件的main()方法中直接使用StringExtension(”).isPhoneNum()调用自身的扩展方法是没有问题的,如果此时我们想调用test2.dart中相同名称的扩展方法就需要通过import区分了。

# test.dart
import 'inner/test2.dart' as test2;
extension StringExtension on String {
  bool isPhoneNum() {
    RegExp exp = RegExp(r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))d{8}$');
    return exp.hasMatch(this);
  }
}
void main() {
  StringExtension('').isPhoneNum();
  test2.StringExtension('').isPhoneNum();
}

将test.dart中代码增加import指定文件并采用as重命名的方式就可以指定对应的扩展方法了。

扩展方法在我们日常开发中可以极大的增加开发效率和便捷性,大家可以多多体验下~

写在最后

本篇文章主要介绍了Dart中方法和类的相关知识,希望可以帮助大家了解和巩固下Dart的基础知识,后续会循序渐进逐步接触Dart更多的知识。

我是Taonce,如果觉得本文对你有所帮助,帮忙关注、赞或者收藏三连一下,谢谢~