0 概述
dart语言,一个类似于TypeScript的语言,上手很快,有自己独特的特性
- 可靠的Null Safe系统
- 强大的模式匹配
- 很多的OOP支持
- 并发里面的isolate
官网在这里
1 快速上手
1.1 安装
安装flutter全家桶以后,就会自动安装dart语言
1.2 创建项目
dart create helloworld
cd helloworld
dart run
比较简单,可以直接运行dart
1.3 目录结构
生成的项目目录结构
name: helloworld
description: A sample command-line application.
version: 1.0.0
# repository: https://github.com/my_org/my_repo
environment:
sdk: ^3.2.4
# Add regular dependencies here.
dependencies:
# path: ^1.8.0
dev_dependencies:
lints: ^2.1.0
test: ^1.24.0
pubspec.yaml,项目的配置文件,修改该配置文件,需要注意删掉.dart_tool,重新编译后才能生效。其中,name是项目名称,也是bin文件中的入口名称。
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
analysis_options.yaml,静态分析的配置文件
import 'package:helloworld/helloworld.dart' as helloworld;
void main(List<String> arguments) {
'Hello world: ${helloworld.calculate()}!');
print(}
bin/helloworld.dart,入口文件
int calculate() {
return 6 * 7;
}
lib/helloworld.dart,库文件夹
import 'package:helloworld/helloworld.dart';
import 'package:test/test.dart';
void main() {
'calculate', () {
test(, 42);
expect(calculate()});
}
test/helloworld.dart,单元测试文件夹
2 基础
代码在这里
2.1 类型
//只有int与double两种数值类型
//没有short,long,byte
//也没有float
{
testNumber()var a = 1; // 推导为int类型
var b = 2.2; // 推导为double类型
const c = 1; //常量声明
double d = 1; //显式类型声明
int e = 12;//显式类型声明
= 1e12;//声明num类型
num f += 1;
a += 2;
b += 3;
e ,b,c,d,e,f]);
print([a}
{
testString() var a = 'aA'; // 推导为string类型
const b = 'b'; // 推导为string为b的类型
+= '1';
a var c = 'Single quotes work well for string literals.';
var d = 'a:[$a]';//变量插入
var e = 'a:[${a.toUpperCase()}]';//调用变量的函数
var f = '''
You can create
multi-line strings like this one.
''';//多行文本
var g = r'In a raw string, not even \n gets special treatment.';//\n不转义,因为有r
,b,c,d,e,f,g]);
print([a}
{
testBoolean()var a = true;
var b = !false;
,b]);
print([a}
{
testDynamic()//dynamic编译时不检查,动态时检查,相当于typescript的any类型
dynamic c = 3;
//dynamic在编译时可以调用任意的方法,运行时进行匹配,下面代码运行时会报错
//c.toUppercase(2,3);
//这一句可以执行;
'c is $c and isEven:${c.isEven}');
print(
//dynamic 可以赋值为null类型
= null;
c 'c is $c when c is null');
print(}
{
testObject()//Object编译时检查,动态时不需检查,相当于typescript的unknown类型
Object d = 4;
//Object在编译时会进行检查,所有的类型都是Object类型,下面代码编译时会报错
//d.isEven
//有了is操作符判断以后就可以了
if( d is int){
'd is $d and isEven:${d.isEven}');
print(}
//Object不能赋值为null类型,但Object?可以
//d = null;//报错
Object? d2 = 88;
= null;
d2 'd2 is $d2 when d2 is null');
print(}
{
testTypeCheckAndConvert()Object a = 123;
//is和is!操作符
'a is number = ${a is num}');
print('a is double = ${a is double}');
print('a is not string = ${a is! String}');
print(
/* 以下代码报错,因为as类型转换失败
Unhandled exception:
type 'int' is not a subtype of type 'String' in type cast
*/
Object aStr = a as String;
'a is str !');
print(}
{
testType()
testNumber();
testString();
testBoolean();
testDynamic();
testObject();
testTypeCheckAndConvert();}
要点:
- 只有int与double两种数值类型,没有short,long,byte,char,也没有float
- 字符串支持插值,多行文本
- 有dynamic和Object两种类型
- 类型操作符有三个,as是强制类型转换,is和is!是类型判断
2.2 集合
{
testList()//推导为List<int>类型
var list = [1, 2, 3];
print(list);
//创建空数组
var list2 = List<int>.empty();
print(list2);
//增,删,改,查
.add(4);
list.removeAt(0);
list2] = 33;
list['list index 1 is ${list[1]}');
print('list length is ${list.length}');
print(
//遍历
for( final item in list){
'list item is :$item');
print(}
//list的控制流操作符
var mm = 44;
var list3 = [
...list,
for(var i = 0 ;i != 10;i++) i, //在集合加入for
if(mm<10) mm + 1, //在集合加入if
];'list3 $list3');
print(}
{
testSet()//推导为Set<string>类型
var set1 = {'a1', 'a2', 'a3'};
print(set1);
//创建空数组
var set2 = <int>{};
print(set2);
//增,删,改,查
.add('a4');
set1.remove('a2');
set1'set has a1 is: ${set1.contains('a1')}');
print('set length is ${set1.length}');
print(
//遍历
for( final item in set1){
'set item is :$item');
print(}
//set的控制流操作符
var mm = 44;
var set3 = {
...set1,
for(var i = 0 ;i != 10;i++) 'a${i}', //在集合加入for
if(mm>10) '${mm + 1}', //在集合加入if
};
'set3 $set3');
print(}
{
testMap()//推导为Set<string>类型
var map1 = {
'first1':'a1',
'first2':'a2',
'first3':'a3',
};
print(map1);
//创建空数组
var map2 = <int,String>{};
print(map2);
//增,删,改,查
'first4'] = 'a4';
map1[.remove('first2');
map1'first1'] = 'a1111';
map1['map has key first3 is: ${map1.containsKey('first3')}');
print('map length is ${map1.length}');
print('map value in key [first1] is ${map1['first1']}');
print(
//遍历
for( final item in map1.entries){
'map item is :${item.key} value:${item.value}');
print(}
//map的控制流操作符
var mm = 44;
var map3 = {
...map1,
for(var i = 0 ;i != 10;i++) 'first${i}':'b${i}', //在集合加入for
if(mm>10) 'mm':'${mm + 1}', //在集合加入if
};
'map3 $map3');
print(}
{
testPositionRecord()'-------- testPositionRecord ------');
print(//position record
var a = (1,'cc2');
int,String) b;
(= a;
b ,b,a==b]);
print([a
//record是immutable的,所有值不可修改
//a.$1 = 22
'first is ${a.$1}, second is ${a.$2}');
print(}
{
testNameRecord()'-------- testNameRecord ------');
print(//name record
var a = (age:11,name:'fish');
{int age,String name}) b;
(= a;
b ,b,a==b]);
print([a
//name record比较的时候,名称也是一部分
var c = (age2:11,name:'fish');
== c);
print(a
//record是immutable的,所有值不可修改
//a.$1 = 22
'name is ${a.name}, age is ${a.age}');
print(}
{
testMixRecord()'-------- testMixRecord ------');
print(
var record = ('first', a: 2, b: true, 'last');
'first : ${record.$1}, second : ${record.$2}');
print('a : ${record.a}, b : ${record.b}');
print(}
{
testCollection()
testList();
testSet();
testMap();
testPositionRecord();
testNameRecord();
testMixRecord();}
要点如下:
- 四种集合类型,list, set , map和record。list用[],set和map用{},record用()
- 注意一下在list,set和map的初始化列表中,都支持spread operator,和嵌入if和for表达式
2.3 函数
//position 参数,[]是可选标记,里面可以填写默认值,对于nullable类型默认值就是null
int maxNumber(int a, int b,[bool? enableNegation,int defaultPlus=0]){
if(enableNegation != null && enableNegation ){
= -a;
a = -b;
b }
int result;
if( a> b){
= a;
result }else{
= b;
result }
return result + defaultPlus;
}
{
testPositionFunction()'-------testPositionFunction---------');
print('${maxNumber(1,2)}');
print('${maxNumber(1,2,true)}');
print('${maxNumber(1,2,false,100)}');
print(}
//name参数,nullable参数可以不填写默认值,非nullable参数必须填写默认值。一种特殊情况是,非nullable参数不需填写默认值,但需要required标记。
int maxNumber2({required int left, required int right,bool? enableNegation,int defaultPlus=0}){
if(enableNegation != null && enableNegation ){
= -left;
left = -right;
right }
int result;
if( left> right){
= left;
result }else{
= right;
result }
return result + defaultPlus;
}
{
testNameFunction()'-------testNameFunction---------');
print('${maxNumber2(left:1,right:2)}');
print('${maxNumber2(left:1,right:2,enableNegation:true)}');
print('${maxNumber2(left:1,right:2,enableNegation:false,defaultPlus:100)}');
print(}
//定义函数参数
int combineNumber(int left,int right,int Function(int left,int right) handler){
return handler(left,right);
}
//可以用typedef来定义函数参数类型
typedef HandlerType = int Function(int a, int b);
int combineNumber2(int left,int right,HandlerType handler){
return handler(left,right);
}
{
testLambada()'-------testLambada---------');
print(//普通括号lambda是需要return
var c1 = combineNumber(1,2,(left,right){
return left+right;
});
//箭头lambda是立即value
var c2 = combineNumber(1,2,(left,right)=>left-right);
'c1 = $c1,c2 = $c2');
print(}
{
testFunction()
testPositionFunction();
testNameFunction();
testLambada();}
要点如下:
- position 参数,[]是可选标记,里面可以填写默认值,对于nullable类型默认值就是null
- name参数,nullable参数可以不填写默认值,非nullable参数必须填写默认值。一种特殊情况是,非nullable参数不需填写默认值,但需要required标记。
- 函数类型需要Function来标记,常用typedef来做类型别名。
- 创建lambda函数的时候,不需要=>符号,需要=>符号的话就是立即value的结构。
2.4 控制流
String testWhile(){
var out = "";
var a = 0;
while( a <= 10 ){
= out + a.toString() +" ";
out ++;
a}
return out;
}
String testDoWhile(){
var out = "";
var a = 0;
do{
= out + a.toString() +" ";
out ++;
a}while(a<=10);
return out;
}
String testFor(){
var out = "";
for( var a = 0 ;a<=10;a++){
= out+a.toString() +" ";
out }
return out;
}
String testForIn(){
var out = "";
for( var a in [1,2,3]){
= out + a.toString() +" ";
out }
return out;
}
{
testAlwaysCaptureVarInLoop()var callbacks = [];
for (var i = 0; i < 2; i++) {
.add(() => print('call $i'));
callbacks}
for (final c in callbacks) {
c();}
}
{
testLoop()'testWhile: ${testWhile()}');
print('testDoWhile: ${testDoWhile()}');
print('testFor: ${testFor()}');
print('testForIn: ${testForIn()}');
print('testAlwaysCaptureVarInLoop: ${testAlwaysCaptureVarInLoop()}');
print(}
int testIf(int a,int b){
if( a > b ){
return a;
}else{
return b;
}
}
String testIfCase(Object input){
//if与模式匹配的组合,if case
if( input case (1,String _)){
return '(int 1 and String)';
}else if(input case(int _,String _)){
return '(int and String)';
}else{
return 'other';
}
}
{
testCondtion()'testIf: ${testIf(33,44)}');
print('testIfCase (1,2): ${testIfCase((1,2))}');
print('testIfCase (\'cc\'): ${testIfCase('cc')}');
print('testIfCase (2,\'c2\'): ${testIfCase((2,'c2'))}');
print('testIfCase (1,\'c3\'): ${testIfCase((1,'c3'))}');
print(}
{
testSwitch()//switch表达式,可以获取值,而且自动进行Exhaustiveness checking
var a = 33;
var b = switch(a){
>10 => 'big',
==10 => 'middle',
<10 => 'little',
=>'unknown'
_ };
'switch $a is $b');
print(
//switch语句,可以带有独特的Guard clause,when语句,不匹配的话会fall through
var c = (111,33);
switch(c){
case (int a, int b) when a>b:
'switch c is (a,b) and a > b');
print(case (int a,int b):
'switch c is (a,b)');
print(}
}
{
testFlow()
testLoop();
testCondtion();
testSwitch();}
要点:
- while/do while/for/ for in/if都是和js一样的做法。
- for的变量是always caputre的
- if case用来做模式匹配
- switch有两种用法,switch表达式,和switch语句,跟kotlin类似。这里的switch还带有独特的Guard clause语法。
2.5 异常
import 'dart:async';
Function() handler){
testExceptionInner2(try {
handler();} on TimeoutException {
//只指定了捕捉的类型,不获取异常变量
'timeout exception');
print(} on Exception catch (e) {
//既指定了捕捉的类型,也获取异常变量
//e 是Exception类型
'Unknown exception: $e');
print(} catch (e,stack) {
//e 是Object类型,第二个参数固定为stack
'Something really unknown: $e, stack: $stack');
print(//重新抛出异常
rethrow;
}finally{
'finish');
print(}
}
{
testExceptionInner()'-----------testExceptionInner-----------');
print({
testExceptionInner2(()throw new TimeoutException('cc');
});
{
testExceptionInner2(() throw new Exception('jj');
});
try{
{
testExceptionInner2(()throw 'kk';
});
}catch(e){
}
}
{
testException()
testExceptionInner();}
要点:
- 异常可以捕捉,或者取值两种操作,只是捕捉的话,用on就可以了。需要取值的话,还要加一个catch
- rethrow操作比较好,不影响原有的异常堆栈
- 任可以抛出任何类型的异常,但是建议只抛出Exception类型的异常。
2.6 模式匹配
{
testPatternUseCase()'-----------testPatternUseCase-----------');
print(//变量声明
var (a1, [b1, c1]) = ('str', [1, 2]);
'$a1 $b1 $c1');
print(
//变量赋值
var (a2, b2) = ('left', 'right');
, a2) = (a2, b2); // Swap.
(b2'$a2 $b2');
print(
//switch表达式
dynamic obj = 1;
switch (obj) {
// Matches if 1 == obj.
case 1:
'one');
print(
// Matches if the value of obj is between the
// constant values of 'first' and 'last'.
case >= 10 && <= 20:
'in range');
print(
// Matches if obj is a record with two fields,
// then assigns the fields to 'a' and 'b'.
case (var a, var b):
'a = $a, b = $b');
print(
default:
}
//for表达式
Map<String, int> hist = {
'a': 23,
'b': 100,
};
for (var MapEntry(key: key, value: count) in hist.entries) {
'$key occurred $count times');
print(}
//if case表达式
Object input = (1,'33');
if( input case (1,String _)){
'(int 1 and String)');
print(}else if(input case(int _,String _)){
'(int and String)');
print(}else{
'other');
print(}
//也能用于json校验
Object json = {'user':['fish',123],'user2':['bb',456]};
if (json case {'user': [String name, int age]}) {
'User $name is $age years old.');
print(}
}
{
testPatternMatchValue()'-----------testPatternMatchValue-----------');
print(var handler1 = ((Object? a){
const jj = (1,'cc');
switch(a){
case 10:
'10');
print(case 'cc':
'cc');
print(case null:
'null');
print(case jj:
'match constant jj');
print(case _:
'other');
print(}
});
10);
handler1('cc');
handler1(null);
handler1(1,'cc'));
handler1(({'c'});
handler1(}
{
testPatternMatchCast()'-----------testPatternMatchCast-----------');
print(var handler1 = ((dynamic a){
switch(a){
//只有非空时才匹配,空的时候不匹配,也不抛异常
case var a1?:
'a1 is not null');
print(case _:
'other');
print(}
});
10);
handler1(null);
handler1(
var handler2 = ((dynamic a){
switch(a){
//非空与空时都匹配,空的时候会抛异常,相当于cast为非空类型。
case var a1!:
'a1 must nullable');
print(case _:
'other');
print(}
});
10);
handler2(//以下语句会抛出异常,因为a1!匹配遇到null,会抛出异常
//handler2(null);
var handler3 = ((dynamic a){
switch(a){
//非空与空时都匹配,空的时候不抛异常
case var a1:
'a1 is null or not-null');
print(case _:
'other');
print(}
});
10);
handler3(null);
handler3(
var handler4 = ((dynamic a){
switch(a){
//非空时,匹配a1,且cast为String类型,如果cast失败,会抛出异常
case var a1 as String:
'a1 must String');
print(case _:
'other');
print(}
});
//以下语句会抛出异常,因为a1匹配遇到到非string,会抛出异常
//handler4(10);
'bb');
handler4(}
class MyRect{
int x;
int y;
int width;
int height;
{required this.x,required this.y, required this.width, required this.height});
MyRect(}
{
testPatternMatchType()'-----------testPatternMatchType-----------');
print(var handler1 = ((Object object){
switch(object){
case int a:
'int $a');
print(case [var a, var b]:
'lsit: two [$a,$b]');
print(case [var a, ...var rest, var b]:
'list: three or more, inner is $rest');
print(case {"name":String name}:
'map: name with String value: $name');
print(case {"name":var name}:
'map: name with any value: $name');
print(case (var first,var second):
'record: position (first,second)');
print(case (name1:var name1,name2:var name2):
'record: name($name1,$name2)');
print(case MyRect(width:var width,height: var height)://通过object的getter来抽取变量
'myRect: width = $width,height = $height');
print(case _:
'other');
print(}
});
123);
handler1(1,2]);
handler1([1,2,3,4,5,6,7]);
handler1([{"name":"fish","age":123});
handler1({"name":520});
handler1('fish',123));
handler1(('fish',123,true));//不能匹配(var first,var second)
handler1((:'jj',name2:'kk'));
handler1((name1:'jj',name2:'kk',name3:'uu'));//不能匹配(name1:var name1,name2:var name2)
handler1((name1:1,y:1,width:100,height:200));
handler1(MyRect(x}
{
testPatternMatchRelation()'-----------testPatternMatchRelation-----------');
print(var handler1 = ((int char){
const space = 32;
const zero = 48;
const nine = 57;
const a = 97;
const A = 65;
switch (char) {
case < space:
'control');
print(case == space:
'space');
print(case > space && < zero:
'punctuation');
print(case >= zero && <= nine:
'digit');
print(case == a || == A:
'alpha A/a');
print(default:
'other');
print(};
});
' '.codeUnitAt(0));
handler1('\t'.codeUnitAt(0));
handler1('2'.codeUnitAt(0));
handler1('a'.codeUnitAt(0));
handler1('你'.codeUnitAt(0));
handler1(}
//pattern是类型与值的组合匹配工具,
//匹配以后还能进行cast操作,cast为非空类型,或者cast为指定类型,如果cast失败就会抛出异常
//匹配与cast以后还能解构取值
{
testPattern()
testPatternUseCase();
testPatternMatchValue();
testPatternMatchType();
testPatternMatchCast();
testPatternMatchRelation();}
dart的模式匹配是看家本领,主要用于:
- 变量声明
- 变量赋值
- switch表达式
- for表示解构,严格来说这个也属于变量声明
- if case表达式,这个用来做输入数据校验,简直一流。NICE。
dart的模式匹配有如下几种目的:
- 匹配常量,只有常量才能匹配,任何const的值。
- 匹配类型,list,set,map,record的组合类型,相当强大方便。当然也包括空与非空的匹配,
- 组合匹配,包括,且,或,子匹配。
- 类型cast,匹配成功以后,可以进行类型cast。包括强行指定非空,强行转换到目标类型。
- 解构变量,匹配成功以后,可以解构取出一部分的value,以便进行下一步的操作。
3 类
代码在这里
3.1 基础
//默认是不可以被继承的
class Person {
//默认为public,自动有getter和setter
var name = "fish";
var age = 123;
//final变量,只能在赋值,或者构造函数赋值,一旦赋值以后就无法更改
final int height;
this.height);
Person(
//下划线开头的是私有变量,没有getter,也没有setter
//自定义getter与setter
//getter与setter的名字不能与filed的名字一致
var _color = "red";
String get color{
"color getter run");
print(return _color;
}
set color(String value) {
"color setter run");
print(= value;
_color }
//late变量在运行时检查null方式
late int width;
//late + final,运行时检查null方式,运行时检查仅赋值一次
late final int kk;
{
eat()"$name is eating. He is $age years old. height = $height, width = $width, color = $_color ");
print(}
}
classBasicInner.dart文件。
import './classBasicInner.dart';
{
testClassBasic()var p = Person(33);
'person: name = ${p.name} , age = ${p.age} ,height = ${p.height} color = ${p.color}');
print(
.age = 121;
p.name = "Fish";
p
//以下语句在编译时报错,因为_color是私有变量
//print('${p._color}');
//以下语句在编译时保存,因为height是final变量,不能修改
//p.height = 33;
//color是setter方法,不是真实的变量
.color = 'green';
p
//以下语句在运行时报错,因为width是late变量,width还没初始化就执行getter
//print(p.width);
.width = 100;
p'width = ${p.width}');
print(
.kk = 8;
p//以下语句在运行时报错,因为kk是late + final变量,不能二次赋值
//p.kk = 9;
//调用方法
.eat();
p}
要点如下:
- 默认的公共变量,自动生成getter与setter
- 私有变量,是约定以下划线开头的变量。只能在同一个文件访问,不能在外部访问。依然会生成getter与setter。
- const是编译时常量。
- final是运行时常量,一旦赋值就不能修改。
- 默认是编译是检查变量的null方式,变量只能在声明处,构造函数参数列表,初始化参数列表,这三个地方赋值。注意,不在构造函数的body进行赋值检查。
- late是运行时检查null的方式,和kotlin的类似。
- late + final,是运行时检查常量 + 运行时检查null。
3.2 构造函数
class Shape{
String _name;
{String name = 'default'}):_name = name;
Shape(}
String prefixName(String name){
return "prefix_$name";
}
double addTen(double a){
return a+10;
}
//继承
class Point extends Shape {
double x = 0;
double y = 0;
//参数上直接用this来指定变量,无名称的是默认构造函数
//这里使用了position parameter
this.x, this.y);
Point(
//默认构造函数只有一个,第二个构造函数,需要名字,例如这个origin名字
//这里使用了name parameter
.origin({required this.x, required this.y});
Point
//使用初始化列表来初始化参数
.origin2({required double x1, required double y1})
Point: x = x1,
= y1;
y
//调用同级的构造函数
.diag(double x): this(x,x);
Point
//调用父级的构造函数
.withName(String name,this.x,this.y):super(name:name);
Point
//调用父级的构造函数,初始化列表允许使用函数
.withName2(String name,this.x,this.y):super(name:prefixName(name));
Point
//私有的构造函数,外部不能调用
._my(this.x,this.y):super(name:'_myPoint'){
Point'_my constructor init');
print(}
//工厂构造函数,有完善的构造体,可以对参数进行任意转换,返回值必须是Point,不能为null
factory Point.shiftTen(double x,double y){
return Point._my(x+10,y+10);
}
//覆盖方法
@override
String toString() {
return 'Point(x:$x,y:$y,name:${super._name})';
}
}
//不可变的类型
class ImmutablePoint{
//不可变类型的变量都必须是final
final double x;
final double y;
const ImmutablePoint(this.x,this.y);
//不可变类型的初始化列表,不能使用函数,以下语句编译时报错
//const ImmutablePoint.gg(x2,y2):x = x2,y = addTen(y2);
}
{
testClassConstruct()//构造函数可以不需要new关键字
var a = new Point(10,10);
var b = new Point.origin(x: 20, y: 20);
var c = Point.origin2(x1: 30, y1: 30);
var d = Point.withName("g1", 40,50);
var e = Point.withName2("g2",60,70);
var f = Point.diag(80);
//工厂构造函数的调用,和普通构造函数是一样的
var g = Point.shiftTen(90, 90);
'testClassConstruct a = $a, b = $b , c = $c, d = $d, e = $e , f = $f , g = $g');
print(}
要点如下:
- 默认构造函数只有一个,其他构造函数都需要带有名字。
- 成员变量默认是编译时检查变量的null方式,变量只能在声明处,构造函数参数列表,初始化参数列表,这三个地方赋值。注意,不在构造函数的body进行赋值检查。
- 工厂构造函数,可以做更多复杂的构造方式。
- 常量的类,成员变量必须是final,并且初始化构造函数不能使用函数。
- 有很多多样的变量赋值方式,具体看Demo。
3.3 方法
import 'dart:math';
abstract interface class IMap{
dynamic operator[](String name);
void operator[]=(String name ,dynamic target);
}
class Point implements IMap{
double x;
double y;
static int _instanceCount = 0 ;
static incInstanceCount(){
++;
_instanceCount}
static getInstanceCount(){
return _instanceCount;
}
this.x, this.y){
Point(
incInstanceCount();}
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
//操作符重载
operator +(Point v) => Point(x + v.x, y + v.y);
Point operator -(Point v) => Point(x - v.x, y - v.y);
Point
Map<String,dynamic> myMap = {};
@override
dynamic operator[](String name){
'get operator $name');
print(return myMap[name];
}
@override
void operator[]=(String name ,dynamic target){
'set operator [] $name = $target');
print(= target;
myMap[name] }
//override接口
@override
bool operator ==(Object other) =>
is Point && x == other.x && y == other.y;
other
//getter与setter
double get sum{
return x + y;
}
set sum(double value){
if( value != 0 ){
throw Exception('must be zero');
}
= 0;
x = 0;
y }
@override
int get hashCode => Object.hash(x, y);
@override
String toString(){
return 'Point(x=$x,y = $y)';
}
}
{
testClassMethodBasic()var point = Point(1,2);
.sum = 0;
point.x = 10;
point.y = 20;
pointvar point2 = point+point;
= point2;
IMap map 'name'] = 'fish';
map['map name is ${map['name']}');
print(
'point = $point, point2 = $point2, if point == point2 : ${point==point2}');
print(
'point to point2 distance = ${point.distanceTo(point2)}');
print(
'point instance count: ${Point.getInstanceCount()}');
print(}
extension isBlankString on String {
bool get isBlank => trim().isEmpty;
}
{
testClassMethodExtension()var mm = 'cc';
'$mm is blank = ${mm.isBlank}');
print(}
class WannabeFunction {
//直接用call来创建对象即可
String call(String a, String b, String c) => '$a $b $c!';
}
{
testClassMethodCallable()var wf = WannabeFunction();
var out = wf('Hi', 'there,', 'gang');
'WannabeFunction is $out');
print(}
{
testClassMethod()
testClassMethodBasic();
testClassMethodExtension();
testClassMethodCallable();}
要点有:
- 有static的变量和方法,以及普通的成员方法
- 有getter/setter的方法
- 有操作符重载的方法,支持加减乘除,方括号索引和设置
- 覆盖父级方法的话,可以加入@override标记
其他特性:
3.4 接口
//定义抽象类
abstract class Square{
//定义抽象方法,无需abstract前缀
String getName();
}
//每个类,都是一个接口
//extends继承
class Circle extends Square{
//override 覆盖方法
@override
String getName(){
return 'circle';
}
double _radius;
this._radius);
Circle(
double getRadius(){
return _radius;
}
}
//实现接口,用implements,建议不要使用class直接用interface,这样连私有变量的getter/setter都需要实现。
class MCricle extends Square implements Circle{
//私有变量_radius的getter/setter都需要实现。
@override
double get _radius{
return _radius2;
}
@override
set _radius(double value){
= value;
_radius2 }
double _radius2;
this._radius2);
MCricle(
@override
double getRadius(){
return _radius2;
}
@override
String getName(){
return 'mcircle';
}
}
//需要用abstract interface,才能实现Kotlin中的interface中意义。
abstract interface class ShapeInterface{
double getRadius();
String getName();
}
class MCircle2 implements ShapeInterface{
double _radius2;
this._radius2);
MCircle2(
@override
double getRadius(){
return _radius2;
}
@override
String getName(){
return 'mcircle2';
}
}
//定义一个mixin类,并且要求使用这个mixin的类都需要实现SquareInterface接口,on是可选操作
//相当于kotlin中的by,有自己的变量和方法
mixin ColorShape on ShapeInterface{
String color = "red";
{
debugInfo()'Square name is ${getName()} , radius is ${getRadius()}, color is $color');
print(}
}
//实现shape接口,并且使用ColorShape的with
class ColorSquare extends ShapeInterface with ColorShape{
@override
double getRadius(){
return 50;
}
@override
String getName(){
return 'colorSquare';
}
}
{
testClassInterface()var circle = Circle(11);
'circle radius is ${circle.getRadius()},name is ${circle.getName()}');
print(
var circle2 = MCricle(12);
'circle2 radius is ${circle2.getRadius()},name is ${circle2.getName()}');
print(
= MCircle2(10);
ShapeInterface circle3 'circle3 radius is ${circle3.getRadius()},name is ${circle3.getName()}');
print(
var square = ColorSquare();
.color = 'blue';
square.debugInfo();
square}
要点:
- 每个类,都是一个接口
- extends是继承,implements是实现接口。建议不要使用class直接用interface,这样连私有变量的getter/setter都需要实现。推荐使用abstract interface,才能实现Kotlin中的interface中意义。
- mixin是相当于Kotlin里面的by委托。mixin类有自己的成员变量和成员方法,而且可以指定使用者必须满足的类型要求,on约束。
3.5 类修饰符
//abstract, 抽象类,没啥好说的
abstract class Vehicle {
void moveForward(int meters);
}
//base, 只能继承,不能实现接口
class Vehicle2 {
base void moveForward(int meters) {
}
}
//interface,只能实现接口,不能继承
interface class Vehicle3 {
void moveForward(int meters) {
// ...
}
}
//final,不能继承,也不能实现接口
final class Vehicle4 {
void moveForward(int meters) {
// ...
}
}
//sealed类,和kotlin的一样
sealed class Vehicle5 {}
class Car extends Vehicle5 {}
class Truck implements Vehicle5 {}
class Bicycle extends Vehicle5 {}
{
testClassSealed()// ERROR: Cannot be instantiated
//Vehicle myVehicle = Vehicle5();
// Subclasses can be instantiated
= Car();
Vehicle5 myCar
//sealed类的好处是,switch的时候,可以进行exhaustively matched检查
/*
String getVehicleSound(Vehicle vehicle) {
// ERROR: The switch is missing the Bicycle subtype or a default case.
return switch (vehicle) {
Car() => 'vroom',
Truck() => 'VROOOOMM',
};
}
*/
}
{
testClassModifier()
testClassSealed();}
要点如下:
- abstract, 抽象类,没啥好说的
- base, 只能继承,不能实现接口
- interface,只能实现接口,不能继承
- final,不能继承,也不能实现接口
- sealed类,和kotlin的sealed是一样
3.6 枚举
enum Color {
'红色'),
red('绿色'),
green('蓝色');
blue(
//构造函数必须是const的,因此成员变量也必须是final的
final String label;
const Color(this.label);
@override
String toString(){
//每个枚举都有一个index
return 'color:$label, index $index';
}
}
{
testClassEnum()var color1 = Color.red;
'color1 = $color1');
print(
//默认有values,可以获取所有枚举值
var allColors = Color.values;
'allColors = $allColors');
print(}
要点如下:
- 枚举必须是const 构造函数的。
- FIXME,枚举作为输入变量,反序列化不存在的时候怎么办,会报错吗?
4 泛型
//泛型函数
<T>(T a){
T printTemp'${a}_temp');
print(return a;
}
//extends指定了泛型的上界
<T extends num>(T a){
T printTemp2'${a}_temp2');
print(var c = a+1;
//dart的泛型是运行时泛型,所以可以用is T的操作
if( c is T){
return c;
}
throw Exception('invalid $c');
}
//默认泛型参数为T的时候,允许传入nullable类型
//使用T extends Object的时候,不允许传入nullable类型
class SimpleData<T extends Object>{
? _data;
T
set(T t){
= t;
_data }
? get(){
Treturn _data;
}
}
class Person{
String name;
int age;
this.name,this.age);
Person(}
class Man extends Person{
super.name,super.age);
Man(}
{
testGeneric()null);
printTemp(1);
printTemp('abc');
printTemp(12);
printTemp2(12.3);
printTemp2(
var data1 = SimpleData<int>();
.set(123);
data1"data1 ${data1.get()}");
print(
var data2 = SimpleData<Person>();
.set(Person("cat",879));
data2"data2 ${data2.get()}");
print(//这里会报错,因为类型不匹配
//data2.set(123)
//这里会报错,因为不能使用nullable类型
//var data3 = SimpleData<Person?>();
//dart中没有协变,和逆变的说法,所以以下代码
//编译时没有问题,但是运行时报错
//在Kotlin中,不可变数组才是协变,才能允许将List<Man>赋值到List<Person>的。可变数组不是协变,不允许这样赋值的,在编译时确定错误
List<Man> list = List.empty();
List<Person> list2 = list;
.add(Person('cc',33));
list2}
要点:
- 支持泛型方法和泛型类
- 使用extends关键字来做泛型约束,这点和TypeScript是一样的。
- 运行时泛型实现,不是Java的编译时实现(运行时擦除泛型)
- 没有协变和逆变系统,这点略有缺失,运行时可能会产生错误。Kotlin中有协变和逆变系统
5 异步
import 'dart:async';
import 'dart:isolate';
import 'package:http/http.dart' as http;
//async和await执行,相当简单
async {
testHttpRequest() final response = await http.get(Uri.parse('http://www.baidu.com/'));
if (response.statusCode == 200) {
// 请求成功,你可以处理响应的数据
'Response data: ${response.body}');
print(} else {
// 请求失败,处理错误信息
'Failed to load data. Status code: ${response.statusCode}');
print(}
}
//在async中切换到其他线程执行,将结果发射回来
int slowFib(int n) => n <= 1 ? 1 : slowFib(n - 1) + slowFib(n - 2);
void testIsolateRun() async {
var result = await Isolate.run(() => slowFib(10));
'Fib(10) = $result');
print(}
//参考这里的范例,https://dart.dev/language/isolates
void testCompleter() async {
//completer,需要在同一个isolate中使用,不能在不同isolate之间传递
final completer = Completer<int>();
//不同isolate之间只能使用SendPort和ReceivePort来传递信息
final receivePort = ReceivePort();
//启动Isolate,不同Isolate之间内存也是不共享的
await Isolate.spawn(_isolatedFunction, receivePort.sendPort);
//侦听结果信息
.listen((data) {
receivePortif (data is int) {
.complete(data);
completer} else if (data is String) {
.completeError(data);
completer}
});
//等待completer的结果
var result = await completer.future;
'Fib2(20) = $result');
print(
//关闭receivePort,从而让listen关闭。并且isolate停止,才能让程序退出
.close();
receivePort}
void _isolatedFunction(SendPort sendPort) {
// This is executed in the spawned isolate
try {
var result = slowFib(20);
.send(result); // Sending the result back to the main isolate
sendPort} catch (e) {
.send('Error: $e'); // Sending an error message back to the main isolate
sendPort}
}
{
testAsyncBasic()//testHttpRequest();
testIsolateRun();
testCompleter();}
要点如下:
- isolate是独特的异步系统,每个isolate的内存是隔绝的。使用ReceivePort和SendPort来传递。各自的isoltate之间有事件循环。
- 异步方法使用async/await来标记就可以了,这点和js是一样的。
- 自定义异步方法的话,可以用Completer来实现。
- 使用Isolate.run来切换到后台线程来执行代码。
6 非空安全性
import 'dart:math';
bool random(){
double randomDouble = Random().nextDouble();
return randomDouble<0.5;
}
class Person{
String name;
int age;
this.name,this.age);
Person(}
{
testNullBasic()List<int>? a;
if( random()){
= [1,2,3];
a }
? b;
Personif( random()){
= Person('fish',123);
b }
'a is $a and a[0] is ${a?[0]}');
print('b is $b and a.name is ${b?.name}');
print(
//两个句号组成的,可以让返回值变为b本身,方便串联多个操作
?..name = 'cat'
b..age = 234;
'after b is $b and a.name is ${b?.name}');
print(}
Dart是sound null safety系统,为此,它作出了很多努力,可以看这里
7 工具
dart pub add xxx
添加依赖
dart pub remove xxx
删除依赖
dart run
启动
具体可以看这里
10 总结
dart语言中优秀的地方:
- 优秀的sound null safety类型系统
- 优秀的模式匹配工具,可以简洁精确地表达类型
- 严谨的final和const的区分,final是运行时不可变,const是编译时不变。只有const变量才能放在switch表达式中进行匹配。(暂时没有在其他语言中看到对final和const区分如何严谨的特性)
- for和if放在集合表达式中,语法糖挺好
- isolate的设计很适合UI系统,而且不容易产生并发修改同一个变量的情况,UI系统用这个设计非常好。
- 编译时泛型系统
- 同时支持AOT和JIT
不太好的地方:
- 所有的类默认都是一个interface,真是糟糕透了。
- 所有成员变量,公开的都自动生成getter/setter方法,甚至无法屏蔽其中之一。这个设计最差,类最大优点就是封装性,默认生成setter毫无封装性可言。而且,私有变量,在外部文件无法访问它的getter/setter,我还得手写一次getter,也很麻烦。(为啥没有只生成getter,不生成setter的默认配置)
- 没有反射, toString, hashCode, json序列化都不好做。
- 没有TypeScript的类型运算和判断操作。
性能比较在这里
- Kotlin = java ,约为3.67x
- node js,约为4.68x
- dart,约为6.42x
总体而言,dart的性能处于最后一档,作为强类型而言,这确实有点弱。再加上语言特性加持的话,我认为TypeScript其实比dart更好,性能更好,而且也支持热更新。
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!