数値やbooleanは==で、文字列はequalsで同値比較してしまう
before
正しく同値比較できる。ただScalaだと同値比較は==を使ったほうが良いと考える。
b == true
n == 2
x.equals(y)
after
b == true
n == 2
x == y
==を使った方がいい理由は以下2点。
- 統一感
- equalsのレシーバがnullだった場合NullPointerExceptionが出るが、==ならレシーバがnullでもfalseという判定になり例外が出ない
String#splitはいつもJava版のものを使っている
before
JavaのString#splitの引数はString型。
しかもこれは正規表現パターンとして扱われるので以下の様なケースではエスケープが必要になる。
val msg = "hello.world"
val ary = msg.split("\\.") // Array("hello", "world")
after
引数がChar型なsplitがStringLike.scalaで用意されている。
今回のような場合はこちらを使うことでエスケープが不要になる。
val msg = "hello.world"
val ary = msg.split('.') // Array("hello", "world")
ちなみにExtractorを利用することで、変数宣言と代入を一度に行うことができます。
val Array(v1, v2) = msg.split('.')
リスト要素の文字列変換でループ処理を書いてしまう
before
val sb = new StringBuilder()
val l = List("one", "two", "three")
for(i <- 0 until l.size) {
sb.append(l(i))
if (i != l.size-1) {
sb.append(", ")
}
}
println(sb.toString)
after
mkStringを使います。
※Java8だとString#joinを使います。
val l = List("one", "two", "three")
println(l.mkString(", "))
添字が欲しい時ついfor文を使ってしまう
before
普段はforeach使ったりmapやfilterなどの関数をバリバリ使うようになってきたのに添字が欲しい時は昔ながらのfor文を書いてしまう。
for(i <- 0 until members.size) {
println(i + " : " + members(i).name)
}
after1
zipWithIndexを使うことで要素と添字のタプルのリストを得ることが出来ます。後続メソッドはforeachでもmapでも必要に応じて。
members.zipWithIndex.foreach { case (member, i) =>
println(i + " : " + member.name)
}
after2
for文スタイルでも。
for((member, i) <- members.zipWithIndex) {
println(i + " : " + member.name)
}
mapメソッドをループ処理メソッドと勘違いしている
beforeもafterも標準出力される結果は同じではあるのですが。
「map」という単語を辞書で引くと4番目くらいに「写像(数学)」と出てきます。写像処理、分かりやすく言えば変換処理といったところです。map関数は値だけでなく型も変えてよいことになっています。map関数は変換した戻り値を使って後続の処理を継続するというのが基本となります。
※Java8でも同様のmapメソッドが追加
before1
mapメソッドを使っているのに戻り値を取っていないコードを見かけたらリファクタリングの合図です。
List(1,9,10,3).map { n =>
if ( n % 3 == 0 ) {
println(n + "は3の倍数です。")
}
}
after
このケースでは3の倍数の値をprintlnしたいだけなのでいわゆる副作用のある処理となります。こういう場合はforeach(戻り値はUnit型)を使います。
List(1,9,10,3).foreach { n =>
if ( n % 3 == 0 ) {
println(n + "は3の倍数です。")
}
}
scala.collection.immutable.Listをjava.util.List相当と思っている
Javaプログラミングにおいて、公開メソッドを以下のように宣言することはないと思います。
public String foo(ArrayList<User> users) {
通常、Listインタフェースを使って宣言し、利用側に具象クラスの(つまりアルゴリズムの)強制は行わないようにするでしょう。
Javaではこういうところを気をつけている人でもScalaになると次のように書く人が多いように感じます。
def foo(users: List[User]): String = {
冒頭にあるようにscalaのListをjava.util.List相当と思っている、もしくは色んな本やblogでListがよく使われているからなんとなく使っている、のどちらかではないかと思いますが、scalaのListは、java.util.LinkedList相当です(厳密にはjava.util.LinkedListは双方向リストで、scalaのListは単方向リスト)。ゆえにhead/tail関数の実行はO(1)で速いのですが、ランダムアクセスには弱い構造となっています。
再帰で処理する際はhead/tailメソッドを多用するので関数型スタイルのプログラミングとは相性が良い構造といえます。
よって実際のリスト構造を生成する際、通常はListを使用するようにし、foo関数のような公開関数の型の宣言部にはSeqで定義しておくのが良いでしょう。
def foo(users: Seq[User]): String = {
rubyなどでお馴染みの式展開の存在を知らない
Scalaでは文字列中の式展開がサポートされています。これをs補完子と呼びます。
before
Logger.debug("No." + (i + 1) + " is " + user.name)
もしくは
Logger.debug("No.%d is %s".format(i+1, user.name))
after
こっちの方がスッキリ書けることが多く知っていると重宝します。
s補完子内部ではStringBuilderが使用されています。
Logger.debug(s"No.${i + 1} is ${user.name}")