当前位置: 首页 > 科技观察

深入理解Scala---核心规则

时间:2023-03-16 23:52:23 科技观察

ReadEvalPrintLoop(REPL)Scala中的REPL是指直接运行scala.exe进入的交互式命令行模式。广义上也指那些在线编程工具。核心规则1:请使用REPL熟悉Scala语言。Scala的REPL的一个优点是它可以反馈我们键入的每一行代码的内部表示。例如:scala>defadd(a:Int,b:Int):Int=a+badd:(a:Int,b:Int)Int我们定义一个函数来完成两个数的相加。Scala回显给我们的东西帮助我们编写代码。表达式和语句表达式和语句的区别在于语句用于执行,而表达式用于求值。在程序员的世界里,表达式就是返回值,而语言就是没有返回值的程序的执行。Scala是一种面向表达式的编程语言。但它并不是100%成立的。Scala代码中仍然存在控制语言块。毕竟,我们编写程序来控制各种实体为我们服务。核心规则2:使用表达式,而不是语句。这个规则主要是帮助我们简化代码,就像前面加法的例子一样,a+b是一个表达式。与我们用C语言编写的相同实现相比,简单性不好。在代码中,这样的例子肯定还有很多。不要使用Return当我们使用表达式时,不需要Return。因为表达式本身是用来求值的,所以我们需要明确的说出我现在要返回什么。Scala编译器自动使用最后一个表达式的返回值作为函数的返回值。我们应该记住,编程指南是函数在同一个地方返回。如果我们现在没有Return语句,就像在Scala中一样,是否有类似的编程指南?看一个例子:objectNoReturnextendsscala.App{defcreateErrorMessage1(errorCode:Int):String={valresult=errorCodematch{case1=>"NetworkFailure"case2=>"I/OFailure"case3=>"UnknownError"}returnresult}defcreateErrorMessage2(errorCode:Int):String={varresult:String=null//notvalerrorCodematch{case1=>result="NetworkFailure"case2=>result="I/OFailure"case_=>result="UnknownError"}returnresult;}defcreateErrorMessage3(errorCode:Int):String={errorCodmatch{case1=>"NetworkFailure"case2=>"I/OFailure"case3=>"UnknownError"}}println(createErrorMessage1(1))println(createErrorMessage2(2))println(createErrorMessage3(3))println(1match{case1=>"NetworkFailure"case2=>3})println(2match{case1=>"NetworkFailure"case2=>3})}createErrorMessage2应该是我们之前的写法。定义一个局部变量,然后匹配errorCode并赋值。createErrorMessage1是Scala推荐的写法(虽然不够简洁),它使用val而不是var来声明临时变量。val表示值,赋值后不允许改变;var是一个变量,可以重复赋值。createErrorMessage1的结果后跟一个表达式。评估后直接赋值。createErrorMessage3就更简洁了,几乎是***的形式。该函数直接返回一个表达式,没有临时对象。注意:匹配大小写支持每个分支返回的不同类型。这个特性在函数式编程中非常有用。虽然Scala支持所有三种写法,但还是推荐最后一种。因为它有助于简化代码的复杂性并增加程序的不变性。不可变是指在程序执行过程中,所有的状态(变量)都是不变的。不可变代码比可变代码更容易理解、调试和维护。面向表达的语言倾向和不可变对象的使用可以减少程序中的可变对象。使用不可变对象核心规则3:使用不可变对象来大幅减少运行时故障。当面临可变和不可变的选择时,选择不可变对象无疑是最稳妥的选择。对象等价Scala提供了##和==来判断一个对象是否等价,它们可以作用于AnyRef(引用)和AnyVal(值)。Objecthash和equal应该成对出现。因为等价经常使用哈希值。importcollection.immutable.HashMapclassPoint2(varx:Int,vary:Int)extendsEquals{defmove(mx:Int,my:Int):Unit={x=x+mxy=y+my}overridedefhashCode():Int=y+(31*x)defcanEqual(that:Any):Boolean=thatmatch{casep:Point2=>truecase_=>false}overridedefequals(that:Any):Boolean={defstrictEquals(other:Point2)=this.x==other.x&&this复制代码.y==other.ythatmatch{casea:AnyRefifthiseqa=>truecasep:Point2=>(pcanEqualthis)&&strictEquals(p)case_=>false}}objectObjecteEqualityextendsscala.App{valx=newPoint2(1,1)valy=newPoint2(1,2)valz=newPoint2(1,1)println(x==y)//假println(x==z)//真valmap=HashMap(x->"HAI",y->"ZOMG")println(map(x))//HAIprintln(map(y))//ZOMGprintln(map(z))//HAI,ifremovehashCode,therewillbeanexceptionx.move(1,1)//println(map(x))//Exceptioninthread"main"java.util.NoSuchElementException:keynotfound:Point2@40println(map.find(_._1==x))}3-22行定义了一个Point2类,它继承自等于。traitEqualsextendsAny{defcanEqual(that:Any):Booleandefequals(that:Any):Boolean}定义了自己的move方法和hashCode方法。canEqual用于判断是否可以对对象应用equal方法,这里只是检查类型是否匹配。equal包含一个内部函数strictEquals,用于判断对象的成员是否相等。equal首先检查是否引用了同一个Point2对象,如果是则直接返回true。否则检查类型是否匹配,如果匹配则使用strictEquals判断对象成员是否相等。第36行:println(map(z)),它的正确执行取决于是否定义了hashCode。Map在寻找指定key的值时,会调用key.##。在第38行,由于移动改变了x的内部状态,所以将hashCode计算出的新值作为key在Map中进行搜索。如果找不到对应的值,就会报NoSuchElementException。第40行比较奇怪。看一下find的定义:traitIterableLike:override/*TraversableLike*/deffind(p:A=>Boolean):Option[A]=iterator.find(p)objectIterator:deffind(p:A=>Boolean):Option[A]={varres:Option[A]=Nonewhile(res.isEmpty&&hasNext){vale=next()if(p(e))res=Some(e)}res}传递给find的是谓词.迭代器迭代集合中的每个元素并将该元素作为参数传递给谓词。我们这里传递给谓词的所有参数都是一个键值对[A,B]。_是传递给谓词的参数。_1指的是键值对中的第一个元素(实际上是元组中的第一个元素),即A,以Point2为key。现在这句话的意思就很容易理解了,它和x的hashCode是同一个元素。_1的定义位于:traitProduct2:/**Aprojectionofelement1ofthisProduct。*@returnAprojectionofelement1。*/def_1:T1我们在实现对象的等价判断时,请遵循:如果两个对象相等,那么它们的hashCode应该相同。对象的hashCode在其生命周期内不会改变。如果一个对象被发送到另一个JVM,应该保证等价性检查依赖于对象的属性在两个JVM中都可用。主要用于序列化。如果我们的对象是不可变的,那么上面的条件2本身就满足了,这就简化了等价判断。另外,不变性不仅简化了等价判断,也简化了并行数据访问,因为没有同步互斥。#p#用None代替null还是被大家诟病。null强制每个人添加额外的处理代码。Scala使用Option包裹了空值处理,我们不再需要判断变量是否为空。我们可以把Option看成一个通用的容器,里面包含一些对象容器(Some),或者空容器(None)。两个容器都需要对象的类型。同样,Scala也有空列表Nil。核心规则4:使用None而不是null在Java中,我们经常会遇到null异常。如果我们学会正确使用Option,完全可以避免空异常的发生。Scala的Option伴随对象包含一个工厂方法,可以自动将Java的null转换为None:varx:Option[String]=Option(null)。等同于varx:Option[String]=Null。Scala中更高级的用法是将其视为集合。这意味着,您可以在Option上使用map、flatMap、foreach甚至for表达式。使用Null的一些高级实例:classHttpSessionclassConnectionobjectDriverManager{defgetConnection(url:String,user:String,pw:String):Connection={println("getConnection")newConnection}}objectAdvancedNullextendsscala.App{//CREATEANOBJECTORRETURNADEFAULTdefgetTemporary:Option([字符串]):java.io.File={tmpArg.map(名称=>newjava.io.File(名称))。过滤器(_.isDirectory)。getOrElse(newjava.io.File(System.getProperty("java.io.tmpdir")))}//EXECUTEBLOCKOFCODEIFVARIABLEISINITIALIZEDvalusername1:Option[String]=Option("Sulliy")for(uname<-username1){println("User:"+uname)}valusername2:Option[String]=Nonefor(uname<-username2){println("User:"+uname)}defcanAuthenticate(username:String,password:Array[Char]):Boolean={println("canAuthenticate")true}defprivilegesFor(username:String):Int={println("privilegesFor")0}definjectPrivilegesIntoSession(session:HttpSession,privileges:Int):Unit={println("injectPrivilegesIntoSession")}defauthenticateSession(session:HttpSession,username:Option[String],password:Option[Array[Char]])={for(u<-用户名;p<-密码;ifcanAuthenticate(u,p)){valprivileges=privilegesFor(u)injectPrivilegesIntoSession(session,privileges)}}authenticateSession(newHttpSession,None,None)//USINGPOTENTIALUNINITIALIZEDVARIABLESTOCONSTRUCTANOTHERVARIABLEdefcreateConnection(conn_url:Option[String],conn_user:Option[String],conn_pw:Option[String]):Option[Connection]=for{url<-conn_urluser<-conn_userpw<-conn_pw}yieldDriverManager.getConnection(url,user,pw)createConnection(None,Option("sully"),无)deflift3[A,B,C,D](f:Function3[A,B,C,D]):Function3[选项[A],选项[B],选项[C],选项[D]]={(oa:Option[A],ob:Option[B],oc:Option[C])=>for(a<-oa;b<-ob;c<-oc)yieldf(a,b,c)}lift3(DriverManager.getConnection)(选项("127.0.0.1"),选项("sulliy"),Option("sulliy"))}第11-16行,通过文件名获取File对象的例子由于入参为Option[String],所以该函数可以接受None,为null。,filter,getOrElse是Option的成员函数:@inlinefinaldefmap[B](f:A=>B):Option[B]=if(isEmpty)NoneelseSome(f(this.get))@inlinefinaldeffilter(p:A=>Boolean):Option[A]=if(isEmpty||p(this.get))thiselseNone@inlinefinaldefgetOrElse[B>:A](default:=>B):B=if(isEmpty)defaultelsethis.get前两个functions都是返回Option[],所以我们可以级联的写。map返回None(Option的子类),filterofNone返回None,getOrElseofNone返回默认值,即newjava.io.File(System.getProperty("java.io.tmpdir")。当我们需要创建一个对象或者返回一个默认对象时,我们可以使用这个方法。第18-21行说明将Option放在for循环中。由于username1被赋值,username2为空,所以第20行会被执行,第24行不会被执行。第37行-47举一个更复杂的例子。第49-56行说明了通过可能未初始化的对象Object创建新对象。使用产量。第58-62行提供了一个通用方法,将普通的Java方法封装到支持Option的新Scala方法中。这样,我们就不需要自己处理所有参数的空检查。多态环境等价判断的核心规则5:使用多态时,使用scala.Equals提供的模板。Scala.Equals上面已经提到了,需要注意的是:请同时重写canEqual和equal。原文链接:httphttps://my.oschina.net/sulliy/blog/220081