对象具有由函数和数据(分别为方法和实例变量)组成的成员。调用方法时,可以在对象上调用它:该方法可以访问该对象的函数和变量。使用点(.)来引用实例变量或方法:
varp=Point(2,2);//Setthevalueoftheinstancevariabley.p.y=3;//Getthevalueofy.assert(p.y==3);//InvokedistanceTo()onp.numdistance=p.distanceTo(Point(4,4));当最左边的操作数有可能为null时,使用.而不是.避免异常:
//Ifpisnon-null,setitsyvalueto4.p.y=4;使用构造函数构造函数用来创建对象.构造函数的名称可以为类名或者其他类方法.比如你可以用Point的Point()构造函数或者Point.fromJson():
varp1=Point(2,2);varp2=Point.fromJson({'x':1,'y':2});以下代码具有相同的效果,但new在构造函数名称之前使用可选关键字:
varp=constImmutablePoint(2,2);构造两个相同的编译时常量会产生一个规范的实例:
常量构造函数如果传递相同的参数,仅仅会存在一个对象,不会重复创建!!!
vara=constImmutablePoint(1,1);varb=constImmutablePoint(1,1);//常量构造函数如果传递相同的参数,仅仅会存在一个对象,不会重复创建!!!assert(identical(a,b));//Theyarethesameinstance!在常量上下文中,您可以省略const构造函数或文字之前的内容。例如,查看此代码,该代码创建一个const映射:
//Lotsofconstkeywordshere.constpointAndLine=const{'point':const[constImmutablePoint(0,0)],'line':const[constImmutablePoint(1,10),constImmutablePoint(-2,11)],};您可以省略除const关键字的第一次使用之外的所有内容:
//Onlyoneconst,whichestablishestheconstantcontext.constpointAndLine={'point':[ImmutablePoint(0,0)],'line':[ImmutablePoint(1,10),ImmutablePoint(-2,11)],};如果常量构造函数在常量上下文之外并且在没有const它的情况下调用,则会创建一个非常量对象:
print('Thetypeofais${a.runtimeType}');到目前为止,您已经了解了如何使用类。本节的其余部分将介绍如何实现类。
classPoint{numx;//Declareinstancevariablex,initiallynull.numy;//Declarey,initiallynull.numz=0;//Declarez,initially0.}所有未初始化的实例变量都具有该值null。
classPoint{numx,y;Point(numx,numy){//There'sabetterwaytodothis,staytuned.this.x=x;this.y=y;}}//该this关键字是指当前实例。注意:有名称冲突时,才使用this。否则,Dart风格省略了this。
将构造函数参数赋值给实例变量的模式是如此常见,Dart具有语法糖,使其变得简单:classPoint{numx,y;
//Syntacticsugarforsettingxandy//beforetheconstructorbodyruns.Point(this.x,this.y);}
使用命名构造函数为类实现多个构造函数:
classPoint{numx,y;Point(this.x,this.y);//NamedconstructorPoint.origin(){x=0;y=0;}}请记住,构造函数不是继承的,这意味着超类的命名构造函数不会被子类继承。如果希望使用超类中定义的命名构造函数创建子类,则必须在子类中实现该构造函数。
如果超类没有未命名的无参数构造函数,则必须手动调用超类中的一个构造函数。在冒号(:)之后,在构造函数体(如果有)之前指定超类构造函数。
在下面的示例中,Employee类的构造函数为其超类Person调用命名构造函数。
classPerson{StringfirstName;Person.fromJson(Mapdata){print('inPerson');}}classEmployeeextendsPerson{//Persondoesnothaveadefaultconstructor;//youmustcallsuper.fromJson(data).Employee.fromJson(Mapdata):super.fromJson(data){print('inEmployee');}}main(){varemp=newEmployee.fromJson({});//Prints://inPerson//inEmployeeif(empisPerson){//Typecheckemp.firstName='Bob';}(empasPerson).firstName='Bob';}因为在调用构造函数之前会计算超类构造函数的参数,所以参数可以是一个表达式,例如函数调用:
classEmployeeextendsPerson{Employee():super.fromJson(getDefaultData());//···}警告:超类构造函数的参数无权访问this。例如,参数可以调用静态方法,但不能调用实例方法。
除了调用超类构造函数之外,还可以在构造函数体运行之前初始化实例变量。用逗号分隔初始化程序。
//Initializerlistsetsinstancevariablesbefore//theconstructorbodyruns.Point.fromJson(Map
在开发期间,您可以通过assert在初始化列表中使用来验证输入。
Point.withAssert(this.x,this.y):assert(x>=0){print('InPoint.withAssert():($x,$y)');}设置最终字段时,初始化程序列表很方便。以下示例初始化初始化列表中的三个最终字段。
import'dart:math';classPoint{finalnumx;finalnumy;finalnumdistanceFromOrigin;Point(x,y):x=x,y=y,distanceFromOrigin=sqrt(x*x+y*y);}main(){varp=newPoint(2,3);print(p.distanceFromOrigin);}重定向构造函数有时构造函数的唯一目的是重定向到同一个类中的另一个构造函数。重定向构造函数的主体是空的,构造函数调用出现在冒号(:)之后。
classPoint{numx,y;//Themainconstructorforthisclass.Point(this.x,this.y);//Delegatestothemainconstructor.Point.alongXAxis(numx):this(x,0);}常量构造函数如果您的类生成永远不会更改的对象,则可以使这些对象成为编译时常量。为此,请定义const构造函数并确保所有实例变量都是final。
factory关键字在实现不总是创建其类的新实例的构造函数时使用。例如,工厂构造函数可能从缓存中返回实例,或者它可能返回子类型的实例。
以下示例演示了从缓存中返回对象的工厂构造函数:
classLogger{finalStringname;boolmute=false;//_cacheislibrary-private,thanksto//the_infrontofitsname.staticfinalMap
像调用任何其他构造函数一样调用工厂构造函数:varlogger=Logger('UI');logger.log('Buttonclicked');
方法是为对象提供行为的函数。
对象的实例方法可以访问实例变量和this。在distanceTo()下面的示例中方法是一个实例方法的一个例子:
import'dart:math';classPoint{numx,y;Point(this.x,this.y);numdistanceTo(Pointother){vardx=x-other.x;vardy=y-other.y;returnsqrt(dx*dx+dy*dy);}}Gettersandsettersgetter和setter是提供对象属性的读写访问权限的特殊方法。回想一下,每个实例变量都有一个隐式getter,如果合适的话还有一个setter。您可以使用get和set关键字通过实现getter和setter来创建其他属性:
classRectangle{numleft,top,width,height;Rectangle(this.left,this.top,this.width,this.height);//Definetwocalculatedproperties:rightandbottom.numgetright=>left+width;setright(numvalue)=>left=value-width;numgetbottom=>top+height;setbottom(numvalue)=>top=value-height;}voidmain(){varrect=Rectangle(3,4,20,15);assert(rect.left==3);rect.right=12;assert(rect.left==-8);}使用getter和setter,您可以从实例变量开始,稍后使用方法包装它们,而无需更改客户端代码。
注意:无论是否明确定义了getter,增量(++)等运算符都以预期的方式工作。为避免任何意外的副作用,操作员只需调用一次getter,将其值保存在临时变量中。
//Thisclassisdeclaredabstractandthus//can'tbeinstantiated.abstractclassAbstractContainer{//Defineconstructors,fields,methods...voidupdateChildren();//Abstractmethod.}隐式接口每个类都隐式定义一个接口,该接口包含该类的所有实例成员及其实现的任何接口。如果要在不继承B实现的情况下创建支持B类API的A类,则A类应实现B接口。
/Aperson.Theimplicitinterfacecontainsgreet().classPerson{//Intheinterface,butvisibleonlyinthislibrary.final_name;//Notintheinterface,sincethisisaconstructor.Person(this._name);//Intheinterface.Stringgreet(Stringwho)=>'Hello,$who.Iam$_name.';}//AnimplementationofthePersoninterface.classImpostorimplementsPerson{get_name=>'';Stringgreet(Stringwho)=>'Hi$who.DoyouknowwhoIam';}StringgreetBob(Personperson)=>person.greet('Bob');voidmain(){print(greetBob(Person('Kathy')));print(greetBob(Impostor()));}这是一个指定类实现多个接口的示例:
classPointimplementsComparable,Location{...}扩展类使用extends创建一个子类,并super指超:
classTelevision{voidturnOn(){_illuminateDisplay();_activateIrSensor();}//···}classSmartTelevisionextendsTelevision{voidturnOn(){super.turnOn();_bootNetworkInterface();_initializeMemory();_upgradeApps();}//···}复写成员子类可以覆盖实例方法,getter和setter。您可以使用@override注释来指示您有意覆盖成员:
您可以覆盖下表中显示的运算符。例如,如果定义Vector类,则可以定义+添加两个向量的方法。
注意:您可能已经注意到,这!=不是可覆盖的运算符。表达e1!=e2只是语法糖!(e1==e2)。
这是一个覆盖+和-运算符的类的示例:
要在代码尝试使用不存在的方法或实例变量时检测或做出反应,您可以覆盖noSuchMethod():
classA{//UnlessyouoverridenoSuchMethod,usinga//non-existentmemberresultsinaNoSuchMethodError.@overridevoidnoSuchMethod(Invocationinvocation){print('Youtriedtouseanon-existentmember:'+'${invocation.memberName}');}}你不能调用一个未实现的方法除非满足一下至少一个条件:
assert(Color.red.index==0);assert(Color.green.index==1);assert(Color.blue.index==2);要获取枚举中所有值的列表,请使用枚举values常量。
varaColor=Color.blue;switch(aColor){caseColor.red:print('Redasroses!');break;caseColor.green:print('Greenasgrass!');break;default://Withoutthis,youseeaWARNING.print(aColor);//'Color.blue'}枚举类型具有以下限制:
Mixins是一种在多个类层次结构中重用类代码的方法。
要使用mixin,请使用with关键字后跟一个或多个mixin名称。以下示例显示了两个使用mixins的类:
mixinMusical{boolcanPlayPiano=false;boolcanCompose=false;boolcanConduct=false;voidentertainMe(){if(canPlayPiano){print('Playingpiano');}elseif(canConduct){print('Wavinghands');}else{print('Hummingtoself');}}}要指定只有某些类型可以使用mixin-例如,所以你的mixin可以调用它没有定义的方法-用于on指定所需的超类:
使用static关键字实现类范围的变量和方法。
静态变量(类变量)对于类范围的状态和常量很有用:
classQueue{staticconstinitialCapacity=16;//···}voidmain(){assert(Queue.initialCapacity==16);}静态变量在使用之前不会初始化。
静态方法(类方法)不对实例进行操作,因此无权访问this。例如:
import'dart:math';classPoint{numx,y;Point(this.x,this.y);staticnumdistanceBetween(Pointa,Pointb){vardx=a.x-b.x;vardy=a.y-b.y;returnsqrt(dx*dx+dy*dy);}}voidmain(){vara=Point(2,2);varb=Point(4,4);vardistance=Point.distanceBetween(a,b);assert(2.8 您可以使用静态方法作为编译时常量。例如,您可以将静态方法作为参数传递给常量构造函数。 类型安全通常需要泛型,但它们比仅允许代码运行有更多好处: 正确指定泛型类型会产生更好的生成代码。 varnames=List abstractclassObjectCache{ObjectgetByKey(Stringkey);voidsetByKey(Stringkey,Objectvalue);}您发现需要此接口的特定于字符串的版本,因此您需要创建另一个接口: abstractclassStringCache{StringgetByKey(Stringkey);voidsetByKey(Stringkey,Stringvalue);}之后,您决定要使用此接口的数字版本...您明白了。 通用类型可以省去创建所有这些接口的麻烦。相反,您可以创建一个带有类型参数的接口: abstractclassCache varnames= varnameSet=Set varviews=Map varnames=List 实现泛型类型时,您可能希望限制其参数的类型。你可以使用extends。 classFoo varsomeBaseClassFoo=Foo varfoo=Foo();print(foo);//Instanceof'Foo varfoo=Foo Tfirst 该import和library指令可以帮助您创建一个模块化的,可共享的代码库。库不仅提供API,还是隐私单元:以下划线(_)开头的标识符仅在库内可见。每个Dart应用程序都是一个库,即使它不使用library指令。 使用import指定如何从一个库中的命名空间在另一个库的范围内使用。 import'dart:html';唯一需要的参数import是指定库的URI。对于内置库,URI具有特殊dart:方案。对于其他库,您可以使用文件系统路径或package:方案。该package:方案指定由包管理器(如pub工具)提供的库。例如: import'package:test/test.dart';注意:URI代表统一资源标识符。URL(统一资源定位符)是一种常见的URI。 如果导入两个具有冲突标识符的库,则可以为一个或两个库指定前缀。例如,如果library1和library2都有一个Element类,那么你可能有这样的代码: import'package:lib1/lib1.dart';import'package:lib2/lib2.dart'aslib2;//UsesElementfromlib1.Elementelement1=Element();//UsesElementfromlib2.lib2.Elementelement2=lib2.Element();仅导入库的一部分如果只想使用库的一部分,则可以有选择地导入库。例如: //Importonlyfoo.import'package:lib1/lib1.dart'showfoo;//ImportallnamesEXCEPTfoo.import'package:lib2/lib2.dart'hidefoo;懒惰地加载一个库延迟加载(也称为延迟加载)允许应用程序根据需要加载库,如果需要的话。以下是您可能使用延迟加载的一些情况: import'package:greetings/hello.dart'deferredashello;当您需要库时,loadLibrary()使用库的标识符进行调用。 您可以loadLibrary()在库上多次调用而不会出现问题。该库只加载一次。 使用延迟加载时请记住以下内容: 在async和await关键字支持异步编程,让你写异步代码看起来类似于同步代码。 当您需要完成Future的结果时,您有两个选择: 使用async和await异步的代码,但它看起来很像同步代码。例如,这里有一些代码await用于等待异步函数的结果: awaitlookUpVersion();要使用await,代码必须在异步函数中-标记为的函数async: 使用try,catch和finally处理使用await以下代码的错误和清理: try{version=awaitlookUpVersion();}catch(e){//Reacttoinabilitytolookuptheversion}您可以await在异步功能中多次使用。例如,以下代码等待三次函数结果: varentrypoint=awaitfindEntrypoint();varexitCode=awaitrunExecutable(entrypoint,args);awaitflushThenExit(exitCode);在,await表达式中;表达式的值通常是一个Future类型,如果不是,那么这个值将会自动装箱在Future中。此Future对象象征返回object的承诺。await表达式的值是返回的object对象。await表达式阻塞直到返回object值为止。如果在使用时出现编译时错误await,请确保await处于异步功能中。例如,要await在您的应用程序的main()功能中使用,main()必须将正文标记为async: 如果将其更改为异步函数-例如,因为将来的实现将非常耗时-返回的值是Future:```dartFuture 当您需要从Stream获取值时,您有两个选择: 注意:在使用之前awaitfor,请确保它使代码更清晰,并且您确实希望等待所有流的结果。例如,你通常应该不使用awaitfor的UI事件侦听器,因为UI框架发送无尽的事件流。 异步for循环具有以下形式: awaitfor(varOrTypeidentifierinexpression){//Executeseachtimethestreamemitsavalue.}值expression必须具有Stream类型。执行过程如下: 要停止侦听流,可以使用breakorreturn语句,该for语句会从for循环中断开并从流中取消取消。 如果在实现异步for循环时遇到编译时错误,请确保awaitfor它处于异步函数中。例如,要在应用程序的main()函数中使用异步for循环,main()必须将正文标记为async: 当您需要懒惰地生成一系列值时,请考虑使用生成器函数。Dart内置支持两种发电机功能: 要实现同步生成器函数,请将函数体标记为sync*,并使用yield语句来传递值: Iterable Stream 要允许像函数一样调用Dart类,请实现该call()方法。 在下面的示例中,WannabeFunction该类定义了一个call()函数,它接受三个字符串并连接它们,用空格分隔每个字符串,并附加一个感叹号。 大多数计算机,即使在移动平台上,也有多核CPU。为了利用所有这些核心,开发人员传统上使用并发运行的共享内存线程。但是,共享状态并发容易出错,并且可能导致代码复杂化。 所有Dart代码都在隔离区内运行,而不是线程。每个隔离区都有自己的内存堆,确保不会从任何其他隔离区访问隔离区的状态。 请考虑以下代码,它不使用typedef: classSortedCollection{Functioncompare;SortedCollection(intf(Objecta,Objectb)){compare=f;}}//Initial,brokenimplementation.intsort(Objecta,Objectb)=>0;voidmain(){SortedCollectioncoll=SortedCollection(sort);//Allweknowisthatcompareisafunction,//butwhattypeoffunctionassert(coll.compareisFunction);}当分配类型信息丢失f到compare。类型f是(Object,Object)→int(其中→表示返回),但类型compare是功能。如果我们将代码更改为使用显式名称并保留类型信息,则开发人员和工具都可以使用该信息。 typedefCompare=intFunction(Objecta,Objectb);classSortedCollection{Comparecompare;SortedCollection(this.compare);}//Initial,brokenimplementation.intsort(Objecta,Objectb)=>0;voidmain(){SortedCollectioncoll=SortedCollection(sort);assert(coll.compareisFunction);assert(coll.compareisCompare);}注意:目前,typedef仅限于函数类型。我们希望这会改变。 因为typedef只是别名,所以它们提供了一种检查任何函数类型的方法。例如: typedefCompare classTelevision{///_Deprecated:Use[turnOn]instead._@deprecatedvoidactivate(){turnOn();}///TurnstheTV'spoweron.voidturnOn(){...}}您可以定义自己的元数据注释。这是一个定义带有两个参数的@todo注释的示例: librarytodo;classTodo{finalStringwho;finalStringwhat;constTodo(this.who,this.what);}以下是使用@todo注释的示例: Dart支持单行注释,多行注释和文档注释。 单行注释以//开头。//Dart编译器会忽略行之间和行尾的所有内容。 voidmain(){//TODO:refactorintoanAbstractLlamaGreetingFactoryprint('WelcometomyLlamafarm!');}多行注释多行注释以.../结尾/。介于两者之间的/,并/用飞镖编译器忽略(除非该注释是一个文档注释;见下一节)。多行注释可以嵌套。 voidmain(){/**Thisisalotofwork.Considerraisingchickens.Llamalarry=Llama();larry.feed();larry.exercise();larry.clean();*/}文档注释文档注释是首先多行或单行注释///或/**。使用///连续的行上有一个多行文档注释同样的效果。 在文档注释中,Dart编译器忽略所有文本,除非它括在括号中。使用括号,您可以引用类,方法,字段,顶级变量,函数和参数。括号中的名称在已记录的程序元素的词法范围内得到解析。 以下是文档注释的示例,其中引用了其他类和参数: ///AdomesticatedSouthAmericancamelid(Lamaglama).//////Andeancultureshaveusedllamasasmeatandpack///animalssincepre-Hispanictimes.classLlama{Stringname;///Feedsyourllama[Food].//////Thetypicalllamaeatsonebaleofhayperweek.voidfeed(Foodfood){//...}///Exercisesyourllamawithan[activity]for///[timeLimit]minutes.voidexercise(Activityactivity,inttimeLimit){//...}}