溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解Scala的核心規(guī)則

發(fā)布時(shí)間:2021-09-18 17:23:52 來源:億速云 閱讀:114 作者:柒染 欄目:編程語言

這篇文章將為大家詳細(xì)講解有關(guān)如何理解Scala的核心規(guī)則,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。

Read Eval Print Loop (REPL)

REPL在Scala里面指的是直接運(yùn)行scala.exe進(jìn)入的交互式命令行模式。廣義上講,也泛指那些在線編程工具。

核心規(guī)則1:請(qǐng)使用REPL來熟悉Scala語言。

Scala的REPL有個(gè)好處是能夠?qū)⑽覀冚斎氲拿啃写a的內(nèi)部表示反饋出來。比如:

scala> def add(a:Int, b:Int):Int = a + b

add: (a: Int, b: Int)Int

我們定義一個(gè)函數(shù),完成兩個(gè)數(shù)的加法。Scala回顯給我們的內(nèi)容可以幫助我們寫代碼。

表達(dá)式與語句

表達(dá)式與語句的區(qū)別是:語句是用來執(zhí)行的,而表達(dá)式是用來求值的。在程序員的世界里,表達(dá)式就是返回值,語言就是沒有返回值執(zhí)行程序。

Scala是表達(dá)式導(dǎo)向的編程語言。但并不是100%成立,Scala代碼中還是有控制語塊,畢竟我們寫程序就是為了控制各種實(shí)體為我們服務(wù)的。

核心規(guī)則2:使用表達(dá)式,而不是語句。

這條規(guī)則主要是幫助我們簡(jiǎn)化代碼,就像前面加法的例子,a+b就是一個(gè)表達(dá)式。相比于我們C語言寫的相同實(shí)現(xiàn),簡(jiǎn)單不好。代碼里面,像這樣的例子肯定還是存在很多的。

不要使用Return

當(dāng)我們使用表達(dá)式的時(shí)候,就不需要Return了。因?yàn)楸磉_(dá)式本身就是用來求值的,我們必要再去顯式地說我現(xiàn)在要返回什么。Scala編譯器自動(dòng)使用***一個(gè)表達(dá)式的返回值作為函數(shù)的返回值。

我們應(yīng)該記得一個(gè)編程指導(dǎo)意見就是函數(shù)在同一個(gè)地方返回。如果我們現(xiàn)在沒有Return語句了,像在Scala中,有沒有類似的編程指導(dǎo)呢?看個(gè)例子:

object NoReturn extends scala.App {    def createErrorMessage1(errorCode : Int) : String = {      val result = errorCode match {        case 1 => "Network Failure"       case 2 => "I/O Failure"       case 3 => "Unknown Error"     }      return result    }    def createErrorMessage2(errorCode: Int) : String = {      var result : String = null            // not val      errorCode match {        case 1 =>          result = "Network Failure"       case 2 =>          result = "I/O Failure"       case _ =>          result = "Unknown Error"     }      return result;    }    def createErrorMessage3(errorCode : Int) : String = {      errorCode match {        case 1 => "Network Failure"       case 2 => "I/O Failure"       case 3 => "Unknown Error"     }    }    println(createErrorMessage1(1))    println(createErrorMessage2(2))    println(createErrorMessage3(3))    println(1 match{case 1 => "Network Failure" case 2 => 3})    println(2 match{case 1 => "Network Failure" case 2 => 3})  }

createErrorMessage2應(yīng)該是我們以往的寫法。定義一個(gè)局部變量,然后匹配errorCode,對(duì)其進(jìn)行賦值。createErrorMessage1是Scala推薦的寫法(雖然還不夠簡(jiǎn)潔),它使用的是val而不是var,來聲明臨時(shí)變量。val表示值,賦值后就不允許再更改;var是變量,可以重復(fù)賦值。createErrorMessage1的的result之后是一個(gè)表達(dá)式。求值之后直接就賦值了。createErrorMessage3就更加簡(jiǎn)潔了,差不多到了***形態(tài)了。函數(shù)直接就返回一個(gè)表達(dá)式,少了臨時(shí)對(duì)象。

注:match case支持每個(gè)分支返回的類型不同。這個(gè)特性在函數(shù)式編程中非常有用。

Scala雖然支持所有的3中寫法,但是推薦***一種。因?yàn)樗鼛椭?jiǎn)化了代碼的復(fù)雜度,增加了程序的不可變性。不可變指的是程序在執(zhí)行過程中,所有的狀態(tài)(變量)都是常量。不可變的代碼比可變代碼更加容易理解、調(diào)試和維護(hù)。

表達(dá)式導(dǎo)向的語言傾向與使用不可變的對(duì)象,能減少程序中的可變對(duì)象。

使用不可變對(duì)象

核心規(guī)則3:使用不可變對(duì)象可以大幅減少運(yùn)行時(shí)故障。當(dāng)面對(duì)可變與不可變的選擇時(shí),選擇不可變對(duì)象無疑是最安全的。

對(duì)象等價(jià)性

Scala提供了##和==來判斷對(duì)象是不是等價(jià),它們可以作用于AnyRef(引用)和AnyVal(值)。

對(duì)象的哈希值和equal應(yīng)該成對(duì)出現(xiàn)。因?yàn)榈葍r(jià)性經(jīng)常使用到了hash值。

import collection.immutable.HashMap  class Point2(var x: Int, var y: Int) extends Equals {    def move(mx: Int, my: Int) : Unit = {      x = x + mx      y = y + my    }    override def hashCode(): Int = y + (31*x)    def canEqual(that: Any): Boolean = that match {      case p: Point2 => true     case _ => false   }    override def equals(that: Any): Boolean = {      def strictEquals(other: Point2) =        this.x == other.x && this.y == other.y      that match {        case a: AnyRef if this eq a => true       case p: Point2 => (p canEqual this) && strictEquals(p)        case _ => false     }    }  }  object ObjecteEquality extends scala.App  {    val x = new Point2(1,1)    val y = new Point2(1,2)    val z = new Point2(1,1)    println(x == y) // false    println(x == z) // true    val map = HashMap(x -> "HAI", y -> "ZOMG")    println(map(x)) // HAI    println(map(y)) // ZOMG    println(map(z)) // HAI, if we remove hashCode, there will be an exception    x.move(1,1)  // println(map(x)) //Exception in thread "main" java.util.NoSuchElementException: key not found: Point2@40    println(map.find(_._1 == x))  }

3-22行定義了一個(gè)Point2類,它繼承自Equals。

trait Equals extends Any {     def canEqual(that: Any): Boolean     def equals(that: Any): Boolean   }

定義了自己的move方法和hashCode方法。canEqual用來判斷是否可以在對(duì)象上應(yīng)用equal方法,這里只是檢查是否類型匹配。equal包含一個(gè)內(nèi)部函數(shù)strictEquals用來判斷對(duì)象的成員是否相等。equal首先檢查是不是引用了同一個(gè)Point2對(duì)象,如果是,直接返回true。否則,檢查類型是不是匹配,如果是,用strictEquals用來判斷對(duì)象的成員是否相等。

第36行:println(map(z)),它的正確執(zhí)行依賴于hashCode是否定義。Map在尋找指定key的值的時(shí)候,會(huì)調(diào)用key.##。

第38行,由于move改變了x的內(nèi)部狀態(tài),hashCode計(jì)算出來的新值當(dāng)做key去Map里面查找,找不到對(duì)應(yīng)的值,就會(huì)報(bào)NoSuchElementException異常。

第40行,比較奇特。看下find的定義:

trait IterableLike:

override /*TraversableLike*/ def find(p: A => Boolean): Option[A] = iterator.find(p)

object Iterator:

def find(p: A => Boolean): Option[A] = {    var res: Option[A] = None    while (res.isEmpty && hasNext) {      val e = next()      if (p(e)) res = Some(e)    }    res  }

傳給find的是一個(gè)predicate。迭代器遍歷集合中的每個(gè)元素,并將該元素作為參數(shù)傳給predicate。所有我們這里傳給predicate的參數(shù)是一個(gè)鍵值對(duì)[A,B]。_就是傳給predicate的參數(shù)。_1指的是鍵值對(duì)中的***個(gè)元素(實(shí)際上是元組中的***個(gè)元素),即A,也就是作為key的Point2?,F(xiàn)在很容易明白這句的意思了,就是與x的hashCode一樣的元素。_1的定義位于:

trait Product2:

/** A projection of element 1 of this Product.     * @return A projection of element 1.     */   def _1: T1

在我們實(shí)現(xiàn)對(duì)象的等價(jià)判斷的時(shí)候,請(qǐng)遵循:

  • 如果兩個(gè)對(duì)象相等,那它們應(yīng)該有相同的hashCode。

  • 對(duì)象的hashCode在其生命周期內(nèi)不會(huì)改變。

  • 如果將一個(gè)對(duì)象發(fā)送給其他的JVM,應(yīng)該保證等價(jià)判斷依賴于對(duì)象在兩個(gè)JVM都可用的屬性。主要用于序列化。

如果我們的對(duì)象是不可變的,那么上面的條件2自行就滿足了,這會(huì)簡(jiǎn)化等價(jià)判斷。另外,不可變性不僅僅簡(jiǎn)化等價(jià)判斷,也會(huì)簡(jiǎn)化并行數(shù)據(jù)的訪問,因?yàn)椴淮嬖谕交コ狻?/p>

使用None而不是null

null的使用還是很受大家詬病的。null迫使大家添加了額外的處理代碼。Scala使用Option來包裝了null的處理,我們不在需要去判斷變量是否為空。我們可以將Option看成一個(gè)通用的容器,包含了一些對(duì)象的容器(Some),或者是空容器(None)。這兩周容器都需要對(duì)象的類型。

類似地,Scala還有空的列表Nil。

核心規(guī)則4:使用None而不是null

Java中,我們經(jīng)常會(huì)碰到空異常。如果我們學(xué)會(huì)了正確使用Option,完全可以避免空異常的發(fā)生。

Scala的Option伴生對(duì)象(companion object)包含一個(gè)工廠方法,將Java的null自動(dòng)轉(zhuǎn)換為None:var x : Option[String] = Option(null)。等價(jià)于var x : Option[String] = Null。

Scala更加高級(jí)的用法是把它當(dāng)作一個(gè)集合。這意味著,你可以在Option上使用map、flatMap、foreach,甚至是for表達(dá)式。

使用Null的一些高級(jí)實(shí)例:

class HttpSession  class Connection  object DriverManager {    def getConnection(url: String, user: String, pw: String): Connection = {      println("getConnection")      new Connection    }  }  object AdvancedNull extends scala.App {    //CREATE AN OBJECT OR RETURN A DEFAULT    def getTemporaryDirectory(tmpArg: Option[String]): java.io.File = {      tmpArg.map(name => new java.io.File(name)).        filter(_.isDirectory).        getOrElse(new java.io.File(        System.getProperty("java.io.tmpdir")))    }    //EXECUTE BLOCK OF CODE IF VARIABLE IS INITIALIZED    val username1: Option[String] = Option("Sulliy")    for (uname <- username1) {      println("User: " + uname)    }    val username2: Option[String] = None    for (uname <- username2) {      println("User: " + uname)    }    def canAuthenticate(username: String, password: Array[Char]): Boolean = {      println("canAuthenticate")      true   }    def privilegesFor(username: String): Int = {      println("privilegesFor")      0   }    def injectPrivilegesIntoSession(session: HttpSession, privileges: Int): Unit = {      println("injectPrivilegesIntoSession")    }    def authenticateSession(session: HttpSession,                            username: Option[String],                            password: Option[Array[Char]]) = {      for (u <- username;           p <- password;           if canAuthenticate(u, p)) {        val privileges = privilegesFor(u)        injectPrivilegesIntoSession(session, privileges)      }    }    authenticateSession(new HttpSession, None, None)    //USING POTENTIAL UNINITIALIZED VARIABLES TO CONSTRUCT ANOTHER VARIABLE    def createConnection(conn_url: Option[String],                         conn_user: Option[String],                         conn_pw: Option[String]): Option[Connection] =      for {        url <- conn_url        user <- conn_user        pw <- conn_pw      } yield DriverManager.getConnection(url, user, pw)    createConnection(None, Option("sully"), None)    def lift3[A, B, C, D](f: Function3[A, B, C, D]): Function3[Option[A], Option[B], Option[C], Option[D]] = {      (oa: Option[A], ob: Option[B], oc: Option[C]) =>        for (a <- oa; b <- ob; c <- oc) yield f(a, b, c)    }    lift3(DriverManager.getConnection)(Option("127.0.0.1"), Option("sulliy"), Option("sulliy"))  }

11行-16行,示例了通過一個(gè)文件名獲取File對(duì)象。由于輸入?yún)?shù)是Option[String],該函數(shù)可以接受None,既null。map、filter、getOrElse都是Option的成員函數(shù):

@inline final def map[B](f: A => B): Option[B] =       if (isEmpty) None else Some(f(this.get))   @inline final def filter(p: A => Boolean): Option[A] =       if (isEmpty || p(this.get)) this else None   @inline final def getOrElse[B >: A](default: => B): B =       if (isEmpty) default else this.get

前兩個(gè)函數(shù)都又返回了Option[],所以我們可以進(jìn)行級(jí)聯(lián)的書寫。map返回None(Option的子類),None的filter返回None,None的getOrElse返回default,即new java.io.File( System.getProperty("java.io.tmpdir")。

我們?cè)谛枰獎(jiǎng)?chuàng)建一個(gè)對(duì)象或者返回一個(gè)缺省對(duì)象的時(shí)候,可以使用這種方法。

18行-21行,示例了將Option放在for循環(huán)里面。由于username1賦值了,username2為空,因此20行會(huì)被執(zhí)行,24行不會(huì)被執(zhí)行。37行-47行,給了一個(gè)更加復(fù)雜的例子。

49行-56行,示例了通過一個(gè)可能未初始化的對(duì)象來創(chuàng)建新對(duì)象。用到了yield。

58行-62行,提供了一個(gè)通用方法,將普通的Java方法封裝為支持Option的新的Scala方法。這樣,我們就不需要自己去處理所有參數(shù)的null檢查。

多態(tài)環(huán)境下的等價(jià)判斷

核心規(guī)則5:在使用多態(tài)情況下,使用scala.Equals提供的模板。

前面已經(jīng)提到了scala.Equals,需要注意的是:請(qǐng)同時(shí)重寫canEqual和equal。

關(guān)于如何理解Scala的核心規(guī)則就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI