241
243

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JavaエンジニアがやってしまいがちなScalaプログラミング

Last updated at Posted at 2014-03-27

数値や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プログラミングにおいて、公開メソッドを以下のように宣言することはないと思います。

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}")
241
243
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
241
243

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?