ScalaのListとJavaのListと配列(使い方や相互変換)


ScalaのListとJavaのListの使い方とかについてのメモです。

scala.List

scala.Listはイミュータブルです。Javaのようにaddしたりはできません。

addしたいのであれば以下のように再生成する必要があります。

package com.tsukaby.scala.sample

object Main {
  def main(args: Array[String]) {
    var scalaList: List[Int] = List(1, 2, 3)

    scalaList = scalaList ::: List(4)

    for (value <- scalaList) {
      println(f"value=$value%d")
    }
  }
}

:::はリストを連結して新しいリストを返却します。

scala.collection.mutable.ListBuffer

Javaユーザにとって上記のリストはなかなか辛い物があります。

ListBufferはパッケージ名から分かる通りミュータブルなので内部の構造を変えることができます。つまりJavaで言うArrayListと同じようなことができます。

package com.tsukaby.scala.sample

import scala.collection.mutable.ListBuffer

object Main {
  def main(args: Array[String]) {
    val buf: ListBuffer[Int] = ListBuffer(1, 2, 3)

    buf += 4

    for (value <- buf.toList) {
      println(f"value=$value%d")
    }
  }
}

+=で要素を追加しており、toListでリストに変換しています。

java.util.Listとjava.util.ArrayList

Scalaでは普通にJavaのライブラリなどが使えます。Java用のライブラリを利用する場合、そのライブラリがjava.util.Listを返却してくる場合があるかと思います。そういうケースも考慮してScalaでJavaのListも使えるようになっておいた方が良いと思います。

package com.tsukaby.scala.sample

import scala.collection.JavaConversions._

object Main {
  def main(args: Array[String]) {
    val javaList: java.util.List[Int] = new java.util.ArrayList[Int]()

    javaList.add(1)
    javaList.add(2)
    javaList.add(3)
    javaList.add(4)

    for (value <- javaList) {
      println(f"value=$value%d")
    }
  }
}

大体いつものJavaと同じ感じです。ArrayListはnewする必要があります。

また、JavaConversionsという暗黙の型変換を行ってくれるクラスをimportしてください。これが無いとfor式の部分で以下のようにコンパイルエラーが出てしまいます。

Error:(14, 19) value foreach is not a member of java.util.List[Int]
    for (value <- javaList) {
                  ^

上記のfor式はあくまでScala用なのでJavaのListを受け付けてくれません。JavaConversionsをimportすると暗黙的にScalaのListに変換してくれるのでコンパイルが通る、という訳です。

java.util.Listからscala.Listへの変換

使用するライブラリなどによってscala.Listを受け付けているのか、java.util.Listを受け付けているのか変わってくると思います。上記2つは同じListとは言え、別の型ですので、ちゃんと意識しないとコンパイルエラーになります。

例えば先ほどの例とほぼ同じですが、以下はコンパイルエラーになります。

package com.tsukaby.scala.sample

object Main {
  def main(args: Array[String]) {
    val javaList: java.util.List[Int] = new java.util.ArrayList[Int]()

    printListContents(javaList)

  }

  def printListContents(list: scala.List[Int]) {
    for (value <- list) {
      println(f"value=$value%d")
    }
  }
}

(Intellij上だとType mismatch, expected: List[Int], actual: List[Int]と出て一瞬「!?」となります。)

ではどうするのかというと以下のようにします。

package com.tsukaby.scala.sample

import scala.collection.JavaConverters._

object Main {
  def main(args: Array[String]) {
    val javaList: java.util.List[Int] = new java.util.ArrayList[Int]()

    printListContents(javaList.asScala.toList)

  }

  def printListContents(list: scala.List[Int]) {
    for (value <- list) {
      println(f"value=$value%d")
    }
  }
}

JavaConverters._をimportすることでasScalaが使えるようになります。これで変換が可能です。asScalaではBuffer型になるため、ここではtoListしてscala.Listにしています。

scala.Listからjava.util.Listへの変換

これも同じです。JavaConverters様々ですね。

asJavaで変換できています。

package com.tsukaby.scala.sample

import scala.collection.JavaConverters._

object Main {
  def main(args: Array[String]) {
    val scalaList: scala.List[Int] = scala.List(1, 2, 3, 4)

    printListContents(scalaList.asJava)

  }

  def printListContents(list: java.util.List[Int]) {
    for(i <- 0 until list.size()){
      val value = list.get(i)
      println(f"value=$value%d")
    }
  }
}

scala.Listから配列への変換

まず以下のような配列を標準出力に吐き出すJavaコード(ライブラリ)があったとします。

package com.tsukaby.scala.sample;

public enum HogeUtil {
  ;

  public static void printArray(int[] array) {
    for (int i : array) {
      System.out.println(String.format("value=%d", i));
    }
  }
}

scala.Listは型が違いのでそのままではprintArrayに与えてやることはできません。

これは簡単でtoArrayするだけです。

package com.tsukaby.scala.sample

object Main {
  def main(args: Array[String]) {
    val scalaList: scala.List[Int] = List(1, 2, 3, 4)
    
    HogeUtil.printArray(scalaList.toArray)
  }
}

 java.util.Listから配列への変換

少しだけトリッキーですが、コード自体は全然大したこと無いです。

package com.tsukaby.scala.sample

import java.util
import collection.JavaConverters._

object Main {
  def main(args: Array[String]) {
    val javaList: java.util.List[Int] = new util.ArrayList()

    javaList.add(1)
    javaList.add(2)
    javaList.add(3)
    javaList.add(4)


    // NG Array[AnyRef]
    //HogeUtil.printArray(javaList.toArray)

    // Compile OK, but Runtime NG
    //HogeUtil.printArray(javaList.toArray(new Array[Int](0)))

    // OK
    HogeUtil.printArray(javaList.asScala.toArray)
  }

}

例によってJavaConvertersをimportした上でasScalaし、toArrayすればOKです。

javaList.toArrayのようにするとArray[AnyRef]が帰ってくるため、コンパイルが通りません。

javaList.toArray(new Array[Int](0))のようにするとコンパイルは通りますが、実行するとエラーになります。

Error:(20, 34) overloaded method value toArray with alternatives:
  [T](x$1: Array[T with Object])Array[T with Object] <and>
  ()Array[Object]
 cannot be applied to (Array[Int])
    HogeUtil.printArray(javaList.toArray(new Array[Int](0)))
                                 ^

詳しく調べてないので予想ですが、Javaで言うInteger(intのラッパークラス)の配列に変換されているからNGなのかな、と。

配列からscala.List, java.util.Listへの変換

package com.tsukaby.scala.sample

import collection.JavaConversions._
import collection.JavaConverters._

object Main {
  def main(args: Array[String]) {
    val arr: Array[Int] = Array(1, 2, 3, 4)

    val javaList = arr.toList.asJava
    for(value <- javaList){
      println(f"value=$value")
    }
    
    val scalaList = arr.toList
    for(value <- javaList){
      println(f"value=$value")
    }

  }

}

普通にtoListとasJavaを使うだけですね。簡単!

やり方さえ分かってしまえばどうということは無いですが、若干面倒ですね。暗黙変換はまあ楽と言えば楽ですが。できれば使用するライブラリはscalaのもので揃えたいところですね。

以上、JavaおよびScalaのList, Arrayの使い方でした!