あるプログラマの日記

プログラマのメモ、出来事、考えたこと、勉強とかの雑記

java.nio.ByteBuffer

ByteBuffer は便利

DataInputStream の readFully みたいに指定サイズを
Byte バッファに読み込んで ByteBuffer にして返すメソッド。
この時のデータはリトルエンディアン

import java.nio.{ByteBuffer,ByteOrder}
..snip..
  def read[S <: { def read(b: Array[Byte], n: Int, len: Int): Int }](in: S)
    (size: Int): ByteBuffer = {
    val b = new Array[Byte](size)
    val bbuf = ByteBuffer.wrap(b)
    bbuf.order(ByteOrder.LITTLE_ENDIAN)
    var num = 0
    while (num < size) {
      val cnt = in.read(b, num, size - num)
      if (cnt < 0) throw new EOFException()
      num += cnt
    }
    bbuf
  }
..snip..

読みだしたデータを ByteBuffer にして扱うと
Int, Short, Double, 文字列 等の各種の型でデータを取り出して処理する際に便利。

..snip..
  val bbuf = read[InputStream](in)(32)

  println(" Int data : %d".format(bbuf.getInt(0)))
  val sdat: Int = bbuf.getShort(4)
  println(" Short data: %d".format(sdat))

  println(" Double data : %.10f".format(bbuf.getDouble(16)))
..snip..

ファイル保存した文字列を表示するときのエンコーディング指定

いまさらだがファイル保存した文字列のエンコーディング指定でハマった。
Shift_JIS でバイナリファイルに保存した文字列を読みだして
ローカルのエンコーディング(この時は EUC-JP)で表示する時のやり方

  def getStr(bb: ByteBuffer, pos: Int, size: Int): Pair[String,String] = {
    val enc = System.getProperty("file.encoding")
    val buf = new Array[Byte](size)
    for (i <- 0 until size) buf(i) = bb.get(pos + i)
    val hex = for (i <- 0 until size) yield { "%02X".format(buf(i)) }
    (hex.mkString, new String(new String(buf, "SJIS").getBytes(enc), enc))
  }

読みだしたデータを ByteBuffer で渡して表示エンコーディング文字列と
その文字列の16進数ダンプを返すメソッド。
データの ByteBuffer とその読み出し位置(pos)、読み出しサイズ(size)を
引数に指定して、読みだした文字列(表示エンコーディング)とその文字列
の16進数ダンプの文字列の Pair を返す。

..snip..
  val (hex, str) = getStr(bbuf, 32, 16)

  println(" str: %s".format(str))
  println(" hex: %s".format(hex)
..snip..

[Scala] java のアレは scala でどう書くの

基本的なことでも慣れないうちは、どう書けばよいかと、ググる日々..
scala でプログラミングし出して慣れない scalajava のアレはどう書くのか?
と簡単な処理でも勝手が違うので少し戸惑った事。

byte バッファ

java だと

byte[] buffer = new byte[size];

scala で 配列は Array[T] になるので byte 配列は

val buffer = new Array[Byte](size)

DataInputStream の readFully メソッドに java と同じ byteバッファとして指定可能。

import java.io.{File,FileInputStream,DataInputStream}
..snip..
val file = new File(filename)
val in = new DataInputStream(new FileInputStream(file))
val size = file.length.toInt
val buffer = new Array[Byte](size)
using[DataInputStream, Unit) { in => in.readFully(buffer) }
..snip..

break

scala 2.8 から使用可能。ライブラリで対応している。

breakable { .. } で囲ってそのブロック内のループ処理から break
してループを抜ける。

import scala.util.control.Breaks.{break, breakable}
..snip..
        breakable {
            while (true) {
                val ok = .. // 何かの処理
                if (ok) break
            }
        }
..snip..

break メソッドで、例外を投げて、breakable メソッドで
例外をキャッチして処理の流れを制御している。
正常処理の流れを例外で制御しているのが、普段、java のコードレビューで
やってはダメと指摘している手前、ちょっと.. 微妙かも

scala/util/control/Breaks.scala の中身(抜粋)

package scala.util.control

..snip.. // 省略

class Breaks {	
  private val breakException = new BreakControl
	
  /** A block from which one can exit with a `break''. */
  def breakable(op: => Unit) {
    try {
      op
    } catch {
      case ex: BreakControl => 
        if (ex ne breakException) throw ex
    }
  }
	 
..snip.. // 省略
	
  /* Break from dynamically closest enclosing breakable block
   * @note this might be different than the statically closest enclosing
   * block!
   */
  def break() { throw breakException }
}

object Breaks extends Breaks
	
private class BreakControl extends ControlThrowable
	

構造的部分型(Structural Subtyping)

型の階層構造とは関係なく特定のメソッドを定義して、このメソッドを持つ型として定義できる。
Rubypython のダックタイピングと同じようなもの

戻り値の型が Unit で引数無しの close メソッドを持つ型を構造的部分型として指定

  def using[S <: { def close(): Unit }, U](s: S)(f: S => U): U = {
    try f(s) finally s.close
  }

scala では type の抽象型定義 で C の typedef と似たような抽象型を定義することができる
type Hoge = Foobar

抽象型に構造的部分型を定義することも可能

  type Closeable2 = { def close(): Unit }

  def using[S <: Closeable2, U](s: S)(f: S => U): U = {
    try f(s) finally s.close
  }

長さによって、短い場合は直接指定して長くなる場合は抽象型に定義するとよさそう。
簡単なインターフェースは構造的部分型を使う。

上限境界、下限境界

上限境界、下限境界を指定して型パラメータまたは抽象型の型に制限をつけることができる。

上限境界(upper bounds)

[T :< Parent]

  • T は、型 Parent のサブ型(子の型)を参照
  • 型パラメータまたは抽象型の型のスーパー型(親の型: Parent)から許可
  • サブ型への境界は Nothing まで。つまり型階層の一番下
trait Hoge {
  val item: String
  val t: String
  def getId: String = item + t
}

class Foo(val item: String) extends Hoge {
  val t = "foo"
}

class Bar(val item: String) extends Hoge {
  val t = "bar"
}

object Foo {
  def checkType[T <: Hoge](a: T, b: T) = a.t == b.t
  def checkItem[T <: Hoge](a: T, b: T) = a.item == b.item
  def check[T <: Hoge](a: T, b: T) = a.getId == b.getId

  def main(argv: Array[String]) {
    val foo1 = new Foo("2012")
    val foo2 = new Foo("2011")
    val bar = new Bar("2012")
    println(checkType(foo1, foo2))
    println(checkType(foo1, bar))
    println(checkItem(foo1, foo2))
    println(checkItem(foo1, bar))
    println(check(foo1, foo2))
    println(check(foo1, bar))
  }
}


結果

$ scala Foo
true
false
false
true
false
false

下限境界(lower bounds)

[T >: Sub]

  • T は、型 Sub のスーパー型(親の型)を参照
  • 型パラメータまたは抽象型の型はサブ型(子の型: Sub)までを許可
  • スーパー型への境界は Any まで。つまり型階層の一番上
class Foobar[+T](elem: T, elems: Foobar[T]) {
  def add[P >: T](item: P): Foobar[P] = new Foobar(item, this);
  def getList[P >: T](tail: List[P]): List[P] = {
    elems match {
    case null => elem :: tail
    case _ => elems.getList(elem :: tail)
    }
  }
}

object Foobar {
  def main(argv: Array[String]) {
    val foobar = new Foobar[AnyVal](0, null)
    val foobar2 = foobar.add(1).add(2).add(3).add(4);
    foobar2.getList(Nil).foreach(println)
  }
}

結果

$ scala Foobar
0
1
2
3
4

型パラメータの変位指定(variance)

型パラメータで型の横に共変(+), 反変(-)の変位アノテーションを付けて変位指定ができる。
変位指定アノテーションを付けない場合は非変(nonvariant)になる。
どのパラメータ型を渡せるかの3種類の規則を指定。

共変(covariant)

  • 型コンストラクタに共変の変位指定アノテーション[+T]を指定すると T とそのサブ型も扱えるようにできる。
scala> class Foo[+T](val a: T)
defined class Foo

scala> def bar(b: Foo[AnyVal]) = println(b.a)
bar: (b: Foo[AnyVal])Unit

scala> bar(new Foo[AnyVal](1))
1

scala> bar(new Foo[Int](1))
1

scala> bar(new Foo[Any](1))
<console>:10: error: type mismatch;
 found   : Foo[Any]
 required: Foo[AnyVal]
              bar(new Foo[Any](1))
                  ^
  • T型とTのサブ型は指定できるが、T のスーパー型は指定できない。
  • ScalaのListの定義は共変。List[Int] は Lint[Any] の引数を受け取るメソッドに指定できる。
  • イミュータブルなコレクションの型パラメータは共変が便利。
sealed abstract class List[+A] extends LinearSeq[A] 
	                                  with Product 
	                                  with GenericTraversableTemplate[A, List]
	                                  with LinearSeqOptimized[A, List[A]] {
..snip..

非変(nonvariant)

  • 型コンストラクタに変位指定アノテーションを何も指定しない場合は T 以外の型を指定できない。
  • T 型のみ指定できる。T のサブ型も T のスーパー型も指定できない。
scala> class Foo2[T](val a: T)
defined class Foo2

scala> def bar2(b: Foo2[AnyVal]) = println(b.a)
bar: (b: Foo2[AnyVal])Unit

scala> bar2(new Foo2[AnyVal](1))
1

scala> bar2(new Foo2[Int](1))
<console>:10: error: type mismatch;
 found   : Foo2[Int]
 required: Foo2[AnyVal]
Note: Int <: AnyVal, but class Foo2 is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
              bar(new Foo2[Int](1))
                  ^
scala> bar2(new Foo2[Any](1))
<console>:10: error: type mismatch;
 found   : Foo2[Any]
 required: Foo2[AnyVal]
Note: Any >: AnyVal, but class Foo2 is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
              bar2(new Foo2[Any](1))
                   ^

Scala の ArrayBuffer の定義は非変。
ミュータブルコレクションの型パラメータを非変にしておくと同じ型しかパラメータ型として指定できない制約をかけることができる。

class ArrayBuffer[A](override protected val initialSize: Int) 
	  extends Buffer[A] 
..snip..

javaでは配列が共変でコレクションは非変。

反変(contravariant)

  • 型コンストラクタに反変の変位指定アノテーション[-T]を指定するとTのスーパー型を扱えるようにできる。
  • T型とTのスーパー型は指定できるが、Tのサブ型は指定できない。
scala> class Foo3[-T]
defined class Foo3

scala> class T1
defined class T1

scala> class T2 extends T1
defined class T2

scala> class T3 extends T2
defined class T3

scala>  def bar3(a: Foo3[T2]) = println(a)
bar3: (a: Foo3[T2])Unit

scala> bar3(new Foo3[T1])
$line1.$read$$iw$$iw$Foo3@1ad9b0f

scala> bar3(new Foo3[T2])
$line1.$read$$iw$$iw$Foo3@2445d7

scala> bar3(new Foo3[T3])
<console>:13: error: type mismatch;
 found   : Foo3[T3]
 required: Foo3[T2]
              bar3(new Foo3[T3])
                   ^

関数(変換処理)の型の入力は反変、出力は共変 trait Funtcion1[-T1, +R] extends AnyRef

暗黙の型変換 (implicit conversion)

型変換用の関数の定義に implicit つけると型変換が必要な場面で自動的に型変換関数が実行される。

例えば forへ指定する Int to Int で暗黙の型変換が使用されている。
1 to 3 は RichInt の to メソッドから Range(1,2,3) が返される。
1 の Int は、暗黙の型変換で Int から RichInt に変換されて to メソッドが呼び出される。

scala> val foo = 1 to 3
foo: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3)

to メソッド はscala.runtime.RichInt で定義されている。

final class RichInt(val self: Int) extends ScalaNumberProxy[Int] with RangedProxy[Int] {
..snip..
  def to(end: Int): Range.Inclusive = Range.inclusive(self, end)
..snip..
}

Int から RichInt への暗黙の型変換関数 intWrapper は、scala.Predef へ extends している scala.LowPriorityImplicits で定義されている。
Int に定義がないメソッドが呼ばれた場合は RichInt で定義しているメソッドを探して見つかった時に RichInt へ変換して該当するメソッドが呼ばれる。

object Predef extends LowPriorityImplicits {
..snip..
}

LowPriorityImplicits には Int 以外にも Byte や Short 等から Rich.. 型への暗黙の型変換関数が用意されている。

class LowPriorityImplicits {
..snip..
  implicit def byteWrapper(x: Byte)       = new runtime.RichByte(x)
  implicit def shortWrapper(x: Short)     = new runtime.RichShort(x)
  implicit def intWrapper(x: Int)         = new runtime.RichInt(x)
  implicit def charWrapper(c: Char)       = new runtime.RichChar(c)
  implicit def longWrapper(x: Long)       = new runtime.RichLong(x)
  implicit def floatWrapper(x: Float)     = new runtime.RichFloat(x)
  implicit def doubleWrapper(x: Double)   = new runtime.RichDouble(x)  
  implicit def booleanWrapper(x: Boolean) = new runtime.RichBoolean(x)
..snip..
}

同じ種類の型変換の暗黙の関数定義があると暗黙の型変換を自動的に行う際にエラーになる。

scala> implicit def intWrap(a: Int) = new runtime.RichInt(a)
intWrap: (a: Int)scala.runtime.RichInt

scala> 1 to 5
<console>:9: error: type mismatch;
 found   : Int(1)
 required: ?{val to(x$1: ?>: Int(5) <: Any): ?}
Note that implicit conversions are not applicable because they are ambiguous:
 both method intWrapper in class LowPriorityImplicits of type (x: Int)scala.runt
ime.RichInt
 and method intWrap in object $iw of type (a: Int)scala.runtime.RichInt
 are possible conversion functions from Int(1) to ?{val to(x$1: ?>: Int(5) <: An
y): ?}
              1 to 5
              ^

まとめ

  • 暗黙の型変換用の関数は「implicit」をつけて定義する。
  • 使われる変換関数は自動的に最適な型が選択され変換される。
  • 同じ種類の型変換関数が複数ある場合はエラーになる。
  • 型変換対照を探すのは同じスコープ内に限定される。
  • 暗黙の型変換を使うとそのクラスに無いメソッドを呼び出せるようになって便利な反面、乱用するとわけがわからなくなりそう。
    • 暗黙の型変換関数を定義するクラス、オブジェクトを限定してあらかじめ決めておく *1
    • スーパークラスからサブクラスへの暗黙の型変換関数は不要

*1:どこでどう型変換されているのかがわからなくなるため