本節翻譯自
綜述:本節介紹了for推導式的使用;學習如何使用Scala特有的註解,以及如何與Java註解實現互操作。
for推導式
Scala提供了一個輕量級符號 for
表示序列推導。推導式的形式為 for (enumerators) yield e
,其中 enumerators
是指以分號分隔的列舉器列表。列舉器是一個引入新變數的生成器,或者是一個過濾器。推導式求解出由列舉器生成的每個繫結的主體 e
,並返回這些值的序列。
這裡給個例子:
case class User(val name: String, val age: Int)
val userBase = List(new User("Travis", 28),
new User("Kelly", 33),
new User("Jennifer", 44),
new User("Dennis", 23))
val twentySomethings = for (user <- userBase if (user.age >=20 && user.age < 30))
yield user.name // i.e. add this to a list
twentySomethings.foreach(name => println(name)) // prints Travis Dennis
for
迴圈實際上使用 yield
語句建立了一個 List
。因為我們說 yield user.name
是一個 List[String]
。user <- userBean
是我們的生成器,if(user.age >= 20 && user.age < 30
是一個過濾掉20多歲使用者 (filters out users who are in their 20s) 的守衛。
下面是更加複雜的例子,其使用了兩個生成器。它計算在 0
和 n-1
之間的所有數值對,它們的和等於給定的值 v
:
def foo(n: Int, v: Int) =
for (i <- 0 until n;
j <- i until n if i + j == v)
yield (i, j)
foo(10, 10) foreach {
case (i, j) =>
print(s"($i, $j) ") // prints (1, 9) (2, 8) (3, 7) (4, 6) (5, 5)
}
這裡 n == 10
、v == 10
。在第一次迭代過程中 i == 0
、j == 0
,所以 i + j != v
,因此沒有東西的產出。在 i
遞增到1之前,j
會遞增9次。如果沒有 if
守衛,該方法只會簡單地列印出以下內容:
(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (0, 5) (0, 6) (0, 7) (0, 8) (0, 9) (1, 1) ...
注意,推導式並不侷限於列表。每一個支援 withFilter
、map
和 flapMap
(用適當的型別)的資料型別,都可以使用序列推導式。
你可以在推導式中忽略 yield
。在這種情況下,推導式將返回 Unit
。如果你需要執行副作用,這可能很有用。下面這個程式的結果相當於上面的,但沒有使用 yield
:
def foo(n: Int, v: Int) =
for (i <- 0 until n;
j <- i until n if i + j == v)
print(s"($i, $j)")
foo(10, 10)
註解
註解將元資訊與定義關聯起來。例如,如果在方法之前有 @deprecated
註解,則方法被使用會導致編譯器列印警告。
object DeprecationDemo extends App {
@deprecated
def hello = "hola"
hello
}
這段程式將編譯,但是編譯器會列印警告:“there was one deprecation warning”。
註解子句適用於它後面的第一個定義或宣告。多個註解子句可能在定義和宣告之前出現。這些子句的順序無關緊要。
確保編碼正確性的註解
如果條件不滿足,某些註解實際上會導致編譯失敗。例如,@tailrec
的註解確保了一個方法是尾遞迴的。尾遞迴可以保持記憶體需求不變。下面展示瞭如何在計算階乘的方法中使用它:
import scala.annotation.tailrec
def factorial(x: Int): Int = {
@tailrec
def factorialHelper(x: Int, accumulator: Int): Int = {
if (x == 1) accumulator else factorialHelper(x - 1, accumulator * x)
}
factorialHelper(x, 1)
}
factorialHelper
方法有一個 @tailrec
註解,它確保了該方法確實是尾遞迴的。如果我們將 factorialHelper
的實現更改為以下內容,那麼它就會失敗:
import scala.annotation.tailrec
def factorial(x: Int): Int = {
@tailrec
def factorialHelper(x: Int): Int = {
if (x == 1) 1 else x * factorialHelper(x - 1)
}
factorialHelper(x)
}
我們會得到一個“Recursive call not in tail position”的訊息。
註解程式碼生成的影響
一些註解如 @inline
會影響生成的程式碼(也就是說,如果你沒有使用註解的話,你的jar檔案可能有不同的位元組)。內聯意味著將程式碼插入到呼叫地點的方法的主體中。結果位元組碼更長,但有希望執行得更快。使用 @inline
的註解並不能確保一個方法是內聯的,但是當且僅當滿足一些關於生成程式碼大小的啟發式規則時,才會導致編譯器做到這一點。
Java註解
在編寫與 Java 互動的 Scala 程式碼時,註解語法有一些不同之處。注意:確保使用了 -target:jvm-1.8
選項和 Java 註解。
Java 以註解的形式擁有使用者定義的後設資料。註釋的一個關鍵特徵是它們依賴於指定 name-value
對來初始化它們的元素。例如,如果我們需要一個註釋來跟蹤某個類的源頭,我們可以將它定義為
@interface Source {
public String URL();
public String mail();
}
然後把它應用到下面
@Source(URL = "http://coders.com/",
mail = "support@coders.com")
public class MyClass extends HisClass ...
Scala 中的註解應用程式看起來就像是建構函式呼叫,用於例項化一個 Java 註釋,它必須使用命名引數:
@Source(URL = "http://coders.com/",
mail = "support@coders.com")
class MyScalaClass ...
如果註解只包含一個元素(沒有預設值),那麼這個語法就很麻煩。因此,按照約定,如果名稱被指定為 value
,則可以在Java中使用類似於構造器的語法:
@interface SourceURL {
public String value();
public String mail() default "";
}
然後把它應用到下面
@SourceURL("http://coders.com/")
public class MyClass extends HisClass ...
在這種情況下,Scala 提供了同樣的可能性
@SourceURL("http://coders.com/")
class MyScalaClass ...
mail
元素是用預設值指定的,因此我們不需要顯式地為它提供一個值。但是,如果我們這樣做了,我們就不能在Java中混合使用兩種風格:
@SourceURL(value = "http://coders.com/",
mail = "support@coders.com")
public class MyClass extends HisClass ...
Scala在這方面提供了更多的靈活性
@SourceURL("http://coders.com/",
mail = "support@coders.com")
class MyScalaClass ...