とほほのScala入門

トップ > とほほのScala入門

目次

Scalaとは

インストール

Scala のインストール方法を下記に示します。

# CentOS 8
# dnf -y install scala
# scala -version
Scala code runner version 2.10.6 -- Copyright 2002-2013, LAMP/EPFL

Hello world!

Hello world! を表示するプログラムは下記の様になります。拡張子は .scala です。

object hello {
  def main(args: Array[String]) {
    println("Hello world!")
  }
}

コンパイルには scalac コマンドを、実行には scala コマンドを使用します。

# scalac hello.scala
# scala hello
Hello world!

コメント

コメントには単行コメント // と複数行コメント /* ... */ を使用できます。

// 単行コメント

/*
 * 複数行コメント
 */

キーワード

Scala のキーワード(予約語)には下記があります。

abstract	case		catch		class
def		do		else		extends
false		final		finally		for
forSome		if		implicit	import
lazy		macro		match		new
null		object		override	package
private		protected	return		sealed
super		this		throw		trait
try		true		type		val
var		while		with		yield
_		:		=		=>
<-		<:		<%		>:
#		@

forSome は存在型を構成するものですが Scala 3.0 で廃止の予定です。

下記などの型があります。

Boolean		真偽値(true|false)
Char		文字('a')
String		文字列("ABC")
Byte		8ビット整数(127)
Short		16ビット整数(32767)
Int		32ビット整数(2147483647)
Long		64ビット整数(9223372036854775807L)
BigInt		任意精度整数
Float		32ビット浮動小数点数(1.2f)
Double		64ビット浮動小数点数(1.2e3)
BigDecimal	任意精度浮動小数点数
Symbol		シンボル
Unit		ユニット (())
Any		任意型(Any)
Option		オプション

真偽値(Boolean)

Boolean は真偽値を表す型です。true または false の値を持ちます。

val b1: Boolean = true
val b2: Boolean = false

文字(Char)

Char は1文字を表す型です。文字は '...' で囲みます。

val c1: Char = 'a'
val c2: Char = 'あ'

バックスラッシュ(\)は特別な意味を持ち、下記などのエスケープシーケンスを利用できます。

\\		// バックスラッシュ(\)
\'		// シングルクォーテーション(')
\"		// ダブルクォーテーション(")
\n		// 改行(LF)
\r		// 復帰(CR)
\t		// タブ(TAB)
\b		// バックスペース(BS)
\f		// フォームフィード(FF)
\301		// 文字コード(8進数)

Unicode を扱うことができます。ただし Char で扱えるのは U+0000~U+FFFF の範囲で、U+10000~U+10FFFF 領域の文字はサロゲートペアとして2つのCharを用いて表す必要があります。

\u3042		// 文字コード(\u0000~\uFFFFのUnicode)
\uD842\uDFB7	// 文字コード(サロゲートペア) ... 1つのCharでは扱えない

文字列(String)

String は文字列を表す型です。文字列は "..." で囲みます。エスケープシーケンスも利用できます。

val s1: String = "ABCDEFG"

s補完子(s"...") を用いて、文字列の中に変数や式を埋め込むことができます。

val name = "Yamada"
println(s"His name is $name. ${name} is ${12 + 14} years old.")

f補完子(f"...") を用いて、フォーマットを指定することができます。%05d は 0埋め5桁の10進数を意味します。

val num = 123
println(f"${num}%05d")		// => 00123

raw補完子(raw"...") を用いると、バックスラッシュ(\)やダブルクォート(")がメタ文字ではなく通常文字として扱われるようになります。raw"..." の代わりに """...""" と表現することもできます。正規表現を記述する際などによく使われます。

println("ABC\nDEF")		// ABC(改行)DEF
println(raw"ABC\nDEF")		// ABC\nDEF
println("""ABC\nDEF""")		// ABC\nDEF

raw文字列の中では改行コードを含めることができます。stripMargin メソッドを用いると行の先頭位置を揃えることができます。

println("""This is Japan.
That is America.""")

println("""This is Japan.
          |That is America.""".stripMargin)

整数(Byte, Short, Int, Long)

Byte は8ビット整数(-128~127)、Short は16ビット整数(-32768~32767)、Int は32ビット整数(-2147483648~2147483647)、Long は64ビット整数(-2^63~2^63-1) を表します。64ビット整数の末尾には L または l(小文字のL) をつけます。

val a1: Byte = 127
val a2: Short = 32767
val a3: Int = 2147483647
val a4: Long = 9223372036854775807L

任意精度整数(BigInt)

BigInt は任意の精度を持つ整数です。64ビットを超える整数を丸め誤差なしで扱うことができます。

val b1: BigInt = BigInt(9223372036854775807L)
val b2: BigInt = b1 + b1
println(b2)		// => 18446744073709551614

浮動小数点数(Float, Double, BigDecimal)

Float は32ビット浮動小数点数、Double は64ビット浮動小数点数、BigDecimal は任意精度の浮動小数点数を表します。Float整数には末尾に F または f をつけます(必須)、Double整数には末尾に D または d をつけます(省略可能)。

val a1: Float = 1.2f			// 1.2 のFloat型
val a3: Double = 1.2e3			// 1.2×103
val a3: BigDecimal = BigDecimal(1.2e99)	// 1.2×1099

シンボル(Symbol)

文字列は、同じ文字であってもそれぞれ値が等しい別のオブジェクトとして生成されますが、シンボル(Symbol)は、同じ文字のものは同じオブジェクトとして参照されます。'英数字 または Symbol(英数字文字列) で表します。String に比べて比較などのコストが低いため、マップのキーなどで利用されていたりもしましたが、Scala 3.0 で廃止の方向だそうです。

val s1: Symbol = 'ABC
val s2: Symbol = Symbol("ABC")

ユニット(Unit)

Unit は何もないことを示す型です。0個要素の タプル () が唯一の値となります。結果を返す必要のないメソッドの型を Unit にすることがよくあります。

def log(str: String): Unit = {
  ...
}

任意型(Any)

Any は全ての型の親クラスであり、任意の型を保持する型としても利用できます。

val v1: Any = 123
val v2: Any = "ABC"

任意値型(AnyVal)

AnyValAny のサブクラスであり、Boolean, Int, Float, Unit などの親クラスです。

var n1: AnyVal = 123
var n2: AnyVal = 12.3

任意参照型(AnyRef)

AnyRefAny のサブクラスであり、String などの親クラスです。AnyRef のサブクラスは値として null をもつことができます。

var s: AnyRef = "ABC"

オプション(Option)

Option は値が存在しないケースを含む際に使用される型です。Option[型名] は、値が存在する場合は Some()、値が存在しない場合は None となります。Nonenull は別物です。String 型変数は null になることができますが、Int 型変数は null になることができません。例えば、下記の例で map.get(n) は、Option[Any] 型のメソッドで、n が 1 であれば Some("ABC")、2 であれば Some(123)、3 であれば Some(null)、それ以外であれば None を返却します。

object main {
  def main(args: Array[String]) {
    val map = Map(1->"ABC", 2->123, 3->null)
    println(map.get(1))		// => Some("ABC")
    println(map.get(2))		// => Some(123)
    println(map.get(3))		// => Some(null)
    println(map.get(4))		// => None
  }
}

値が存在する場合、値は Some() でラッピングされています。ラッピングされた値から中身の値を取り出すには、.get, .getOrElse(), .orNull などのメソッドを利用します。

o = map.get(1)		// Some(123) or None
o.get			// 123 or エラー
o.getOrElse(-1)		// 123 or -1
o.orNull		// 123 or null

配列(Array)

Array は配列を扱います。

var arr: Array[String] = Array.empty	// 空配列を定義
arr = Array("A", "B", "C")		// ("A", "B", "C")の配列を定義
arr = arr :+ "D"			// 末尾に "D" を追加
arr = "@" +: arr			// 先頭に "@" を追加
println(arr.length)			// 要素数を取得
println(arr(0))				// 0番目の要素を参照
arr(0) = "$"				// 0番目の要素の値を "$" に変更
for (a <- arr) println(a)		// 各要素をループで処理
arr.foreach(a => print(a))		// 各要素をループで処理
arr.map(n => n + n)			// 各要素をループで処理した結果を返す
var arr2 = Array.ofDim[Int](2, 3)	// 多次元配列(2×3)の宣言
arr2(1)(2) = 10002			// 多次元配列への値設定

リスト(List)

List はリストを扱います。

var list = Nil				// 空リストはNil
list = List[Int](1, 2, 3)		// リストを定義
println(list(0))			// 0番目の要素を参照
// list(0) = 10				// イミュータブルなので変更はできない(エラー)
list = 0 :: list			// リストの先頭に要素を追加
list = list ++ List(4, 5)		// リストの末尾にリストを追加
list = list ::: List(6, 7)		// リストの末尾にリストを追加
println(list.length)			// 要素数を取得
for (n <- list) println(n)		// 各要素をループで処理
list.foreach(n => println(n))		// 各要素をループで処理
var list2 = list.map(n => n * 2)	// 各要素をループで処理した結果を返す

マップ(Map)

Map はマップを扱います。マップでは、キーと値のリストを管理することができます。

var map = Map(1->"Red", 2->"Green", 3->"Blue")	// マップを生成する
println(map(2))				// キーが2の値を取得する => "Green"
// println(map(4))			// 存在しないキーを指定すると実行時エラー
println(map.get(2))			// 値が存在すればSome(値)を、存在しなければNothingを返す

セット(Set)

Set は値の集合を扱います。集合では重複値は排除されます。

var set = Set("Red", "Green", "Blue")	// セットを生成する
set = set + "Yellow"			// セットに "Yellow" を加える(+=も利用可)
set = set + "Red"			// セットに "Red" を加えるが既に存在するので何も変わらない
set = set - "Red"			// セットから "Red" を削除する(-=も利用可)
import scala.collection.mutable.ListBuffer
    var lb = ListBuffer[Int](1, 2, 3)
    println(lb(0))
    lb(0) = 1
    lb += 4
    0 +=: lb
    lb.update(1, 999)
    println(lb)
    lb.remove(2)
    println(lb)
    for (n <- lb) print(n); println("")
    lb.foreach(n => println(n))
    var lb2 = lb.map(n => n * 2)
    println(lb2)

タプル(TupleN)

タプル は複数個の要素を持つ値です。Int 要素と String 要素を持つタプルの型は Tuple2[Int, String] と表されます。要素の個数に応じて Tuple1Tuple22 までを使用することができます。また、Tuple2[Int, String] は簡略的に (Int, String) と表すことができます。

class Foo {
  def getErrorInfo(): (Int, String) = (404, "Not Found")
}

object main {
  def main(args: Array[String]) {
    var foo = new Foo()
    var (err, msg) = foo.getErrorInfo()
    println(err + " : " + msg)
  }
}

型別名(type)

type を用いて、型に別名をつけることができます。

type Int16 = Short
type Int32 = Int
type Int64 = Long

クラス判定(isInstanceOf)

isInstanceOf[クラス名] は、値が指定したクラス、またはそのサブクラスであるか否かを判定します。

var n: Int = 123
n.isInstanceOf[Int]		// => true
n.isInstanceOf[Any]		// => true

キャスト(asInstanceOf)

asInstanceOf[クラス名] は、値を指定したクラスの値に変換(キャスト)します。

var n1: Short = 123
var n2: Int = n1.asInstanceOf[Int]

演算子

演算子には下記などがあります。

expr1 + expr2		// 加算
expr1 - expr2		// 減算
expr1 * expr2		// 乗算
expr1 / expr2		// 除算
expr1 % expr2		// 剰余

~expr			// ビット演算(NOT:補数)
expr1 & expr2		// ビット演算(AND)
expr1 | expr2		// ビット演算(OR)
expr1 ^ expr2		// ビット演算(XOR)
expr1 << expr2		// ビット演算(左シフト)
expr1 >> expr2		// ビット演算(右シフト)
expr1 >>> expr2		// ビット演算(符号なし右シフト)

expr1 == expr2		// expr1expr2 が等しければ
expr1 != expr2		// expr1expr2 が等しくなければ
expr1 > expr2		// expr1expr2 より大きければ
expr1 => expr2		// expr1expr2 以上であれば
expr1 < expr2		// expr1expr2 より小さければ
expr1 =< expr2		// expr1expr2 以下であれば
expr1 eq expr2		// expr1expr2 が同一インスタンスであれば
expr1 nq expr2		// expr1expr2 が同一インスタンスでなければ

!expr			// NOT(expr がfalseであれば)
expr1 && expr2		// AND(expr1 かつ expr2 が true であれば)
expr1 || expr2		// OR(expr1 または expr2 が true であれば)

var = expr		// 代入(代入式の戻り値は結果ではなくUnit)
var += expr		// var = var + expr と等価
var -= expr		// var = var - expr と等価
var *= expr		// var = var * expr と等価
var /= expr		// var = var / expr と等価
var %= expr		// var = var % expr と等価
var &= expr		// var = var & expr と等価
var |= expr		// var = var | expr と等価
var ^= expr		// var = var ^ expr と等価
var <<= expr		// var = var << expr と等価
var >>= expr		// var = var >> expr と等価
var >>>= expr		// var = var >>> expr と等価

Scala の演算子はすべてメソッドです。obj.method(arg)obj method arg と記述することができます。下記の例では、Year オブジェクトに add+ という名前のメソッドを定義して使用しています。演算子とメソッドは同じものだということが分かるかと思います。

class Year(year: Int) {
  def add(n: Int): Int = this.year + n
  def +(n: Int): Int = this.year + n
}
object main {
  def main(args: Array[String]) {
    var y = new Year(2000)
    println(y.add(21))  // => 2021
    println(y add 21)   // => 2021
    println(y.+(21))    // => 2021
    println(y + 21)     // => 2021
  }
}

値(val)と変数(var)

val は値(value)を定義します。値は固定値であり一度代入すると変更することができません。

val a = 123		// 値 a を定義し、初期値を 123 とする
val b: Int = 123	// 値 b を Int 型で定義し、初期値を 123 とする

var は変数(variable)を定義します。変数は変動値であり一度代入しても再代入することができます。

var c = 123		// 変数 c を定義し、初期値を 123 とする
var d: Int = 123	// 変数 d を Int 型で定義し、初期値を 123 とする

文と式

値を返さないものを (statement)、値を返すものを (expression) と呼びます。前述の valvar は文、後述の ifwhile などは式として定義されています。文や式の末尾にはセミコロン(;)を記述しますが、改行で区切られる場合は省略することも可能です。

var a = 1; var b = 2; var c = a + b
println(a); println(b); println(c)

ブロック式

{} の間に ; または改行で区切った複数の式を記述することができます。

{ expr1; expr2; ... }

if式

if 式は、expr1 が真であれば expr2 を、偽であれば expr3 を返します。

if (expr1) expr2
if (expr1) expr2 else expr3

使用例を下記に示します。

var x = 123
if (x == 123)
  println("OK")
else
  println("NG")

式として定義されているため、下記の様に値を返すことができます。

var a = 5
var x = if (a > 3) "BIG" else "SMALL"
println(x)			// => BIG

while式

while 式は、expr1 が真である間、expr2 を実行します。

while (expr1) expr2

使用例を下記に示します。

var n = 5
while (n < 0) {
  println(n)
  n = n - 1
}

do~while式

dowhile 式は、expr2 が真である間 expr1 を実行します。

do expr1 while (expr2)

使用例を下記に示します。

var n = 0
do {
  println(n)
  n += 1
} while (n < 5)

for式

for 式は、collection の一つ一つを var に代入しながら expr を実行します。

for (var <- collection) expr

使用例をいくつか紹介します。

for (i <- 1 to 10) println(i)			// 1~10 まで10回繰り返す
for (i <- 1 until 10) println(i)		// 1~9 まで9回繰り返す
for (i <- 0 to 10 by 2) println(i)		// 0, 2, 4, 6, 8, 10 まで繰り返す
for (i <- 10 to 1 by -1) println(i)		// 10~1 まで10回繰り返す
for (s <- Array("A", "B", "C")) println(s)	// A, B, C について繰り返す

下記の様に if条件を記述することができます。

for (i <- 1 to 10 if i % 2 == 0) println(i)
for (i <- 1 to 10 if i % 2 == 0; if i % 3 == 0) println(i)

for 式は通常 Unit () しか返却しませんが、yield を用いて新しいコレクションを返却することができます。

val list = Array("A", "B", "C")
val list2 = for (s <- list) yield s + s
for (s <- list2) println(s)		// => "AA", "BB", "CC"

下記の様にして二重ループをひとつの for式で記述することができます。

for (i <- 1 to 2; j <- 1 to 3) println(i * 100 + j)	// => 101, 102, 103, 201, 202, 203

マッチ式(match)

matchcase にマッチした値を返します。

object main {
  def main(args: Array[String]) {
    val msg = 2 match {
      case 1 => "One"
      case 2 => "Two"
      case 3 => "Three"
      case _ => "More"
    }
    println(msg)	// => Two
  }
}

| で条件を複数記述することができます。下記では 1 または 2 であれば "Small" を、さもなくば "Big" を返します。

val msg = 2 match {
  case 1 | 2 => "Small"
  case _ => "Big"
}

ガード条件 と呼ばれる if 条件を記述することもできます。

var msg = 2 match {
  case n if n < 5  => "Small"
  case n if n == 5 => "Equal"
  case n if n > 5  => "Big"
}

型によってマッチングさせることもできます。

def getValue(value: Any) = {
  value match {
    case n: Int    => "Int: " + n
    case s: String => "String: " + s
    case _         => "Unknown"
  }
}

例外処理(throw, try, catch, finally)

Java や C# 等と同様、例外処理を記述することができます。throw はエラー発生時に Exception から派生する例外オブジェクトを投げます。try は例外を受け取る範囲を指定します。catch には例外を受け取った時の処理を記述します。finally には例外の有無に関わらず常に実行する処理を記述します。

try {
  throw new IllegalArgumentException("arg1")
} catch {
  case e: IllegalArgumentException => println("IllegalArgumentException")
  case e: Exception => println("Unknown Exception")
} finally {
  println("Finally")
}

関数(=>)

=>関数(function)を定義します。下記では、x と y を引数として受け取り、その合計を返却する関数を定義しています。

val add = (x: Int, y: Int) => x + y

メソッド(def)

defメソッド(method)を定義します。下記では、x と y を引数として受け取り、その合計を返却するメソッドを定義しています。関数とメソッドは似ていますが、メソッドはオブジェクトと紐づいて this によりオブジェクトを参照できるなどの差異があります。

def add(x: Int, y: Int): Int = x + y

return はメソッドの戻り値を指定しますが、{ ... } の最後の式が自動的に戻り値となるため、あまり使用されません。下記の addA()addB() は結局同じ結果となります。

def addA(x: Int, y: Int): Int = {
  x + y
}
def addB(x: Int, y: Int): Int = {
  return x + y
}

Unit という型は何も返さないことを意味します。C言語系の void と同じようなものです。

def func(msg: Int): Unit = println(msg)

メソッドにはデフォルト値を指定することができます。名前を指定して引数を渡すこともできます。

object main {
  def func(name: String = "(unknown)", age: Int = -1): Unit = {
    println(name + "(" + age + ")")
  }

  def main(args: Array[String]) {
    func("Yamada", 26)	// Yamada(26)
    func("Yamada")	// Yamada(-1)
    func(age = 26)	// (unknown)(26)
  }
}

高階関数

Scala は 高階関数(higher-order function) をサポートしており、関数やメソッドを変数に代入したり、関数やメソッドの引数や戻り値に指定したりすることができます。下記の doFunc() は、第一引数で指定した回数分、第二引数で指定したメソッドを実行しています。

object main {
  def hello(): Unit = println("Hello")
  def doFunc(n: Int, fn: () => Unit): Unit = {
    for (i <- 1 to n) fn()
  }

  def main(args: Array[String]) {
    doFunc(5, hello)
  }
}

名前渡しパラメータ

引数と型の間に => をつけるとそのパラメータは使用されるまで評価されません。下記は n回ループするという処理を実装するものですが、stmt は n が 0 の時には一度も実行されずにすみます。何故「名前渡し」と呼ぶのかよく分かりませんが、値渡しではなく、参照としての名前のみを渡しておき、必要になった時に名前からその値を得ているのかと思います。

def loop(n: Int)(stmt: => Unit): Unit = {
  if (n > 0) {
    stmt
    loop(n - 1) { stmt }
  }
}
loop(5) {
  println("A")
}

クラス(class)

class はクラスを定義します。new はクラスのインスタンスを作成します。

class Person
var p = new Person

クラスのコンストラクタで属性を設定することもできます。属性値は下記の様にデフォルト値を設定することもできます。

class Person(name: String, age: Int = -1)
var p1 = new Person("Yamada", 26)
var p2 = new Person("Tanaka")

クラスはメソッドを持つことができます。

class Person(name: String, age: Int) {
  def getName(): String = this.name
  def getAge(): Int = this.age
}
var p = new Person("Yamada", 26)
println(p.getName())
println(p.getAge())

属性に val をつけると getter が、var をつけると getter/setter が自動生成され、.属性名 でアクセスできるようになります。

class Person(var name: String, var age: Int)
var p = new Person("Yamada", 26)
p.name = "Tanaka"
println(p.name)

getter/setter を下記の様に定義することもできます。

class Person {
  private var _name = "(unknown)"
  private var _age = -1
  def name = _name
  def name_= (newName: String): Unit = _name = newName
  def age = _age
  def age_= (newAge: Int): Unit = _age = newAge
}

object main {
  def main(args: Array[String]) {
    val p = new Person
    p.name = "Yamada"
    p.age = 26
    println(p.name)
    println(p.age)
  }
}

クラスの継承(extends)

extends を用いてクラスを継承することができます。下記の例では ClassA を継承した ClassB を定義しています。継承したクラスでは、親クラスのメソッドを利用することが可能です。

class ClassA {
  def foo(): Unit = println("ClassA")
}

class ClassB extends ClassA {
  def baa(): Unit = println("ClassB")
}

object main {
  def main(args: Array[String]) {
    val obj = new ClassB()
    obj.foo()
    obj.baa()
  }
}

override を用いて親クラスのメソッドを上書きすることができます。

class ClassB extends ClassA {
  override def foo(): Unit = println("ClassA!!!")
}

クラス継承の禁止(final, sealed)

final はクラスメソッドの継承やオーバーライドを禁止します。

final class ClassA {			// クラスの継承を禁止
  def hello() = println("Hello")
}
class ClassB {
  final def hello() = println("Hello")	// メソッドのオーバーライドを禁止
}

sealed は、クラスの継承を制限し、他のファイルに定義するサブクラスからの継承を禁止します。

sealed class ClassA {			// 他ファイル内のサブクラスからの継承を禁止する
  def hello() = println("Hello")
}

スーパークラス(super)

super は親クラスを示します。下記の例で、サブクラスである ClassBaction() を呼び出すと、まず、そのスーパークラスである ClassAaction() を呼び出したのち、ClassBaction() 処理を継続します。

class ClassB extends ClassA {
  override def action() {
    super.action()		// ClassAのaction()
    println("ClassB:action")
  }
}

多重継承を行っている場合、super[名前] で、対象とする親クラス(または トレイト)を指定することができます。[名前] を省略すると、extends ... with ... で指定した一番最後のクラス(またはトレイト)が対象となります。

class ClassC extends ClassA with TraitB {
  override def action() {
    super[TraitB].action()	// TraitBのaction()
    super[ClassA].action()	// ClassAのaction()
    super.action()		// TraitBのaction()
    println("ClassB:action")
  }
}

アクセス修飾子(protected, private)

通常のメソッドはパブリック(public)になっておりアクセス制限はありません。protected はアクセス制限を自分自身のクラス、および、サブクラスからの参照のみに限定します。private は自分自身のクラスからの参照のみに限定します。

class ClassA {
  def publicFunc() { println("publicFunc") }			// publicメソッド
  protected def protectedFunc() { println("protectedFunc") }	// protectedメソッド
  private def privateFunc() { println("privateFunc") }		// privateメソッド

  def funcA() {			// 自分自身のクラスのメソッド
    this.publicFunc()		//   publicメソッドにアクセスできる
    this.protectedFunc()	//   protectedメソッドにアクセスできる
    this.privateFunc()		//   privateメソッドにアクセスできる
  }
}

class ClassB extends ClassA {
  def funcB() {			// サブクラスのメソッド
    this.publicFunc()		//   publicメソッドにアクセスできる
    this.protectedFunc()	//   protectedメソッドにアクセスできる
    // this.private()		//   privateメソッドにはアクセスできない(エラー)
  }
}

class ClassC {
  def funcC() {
    var a = new ClassA()	// 他のクラスのメソッド
    a.publicFunc()		//   publicメソッドにアクセスできる
    // a.protectedFunc()	//   protectedメソッドにはアクセスできない(エラー)
    // a.privateFunc()		//   privateメソッドにはアクセスできない(エラー)
  }
}

protected[パッケージ名] は、指定したパッケージおよびそのサブパッケージからの参照に限定します。private[パッケージ名] は、指定したパッケージ自信からの参照に限定します。

package packageA {
  class ClassA {
    protected[packageA] def protectedXFunc() { println("protectedXFunc") }
    private[packageA] def privateXFunc() { println("privateXFunc") }
  }

  class ClassB {
    def funcB() {
      var d = new ClassA()		// 自分自身のパッケージ
      d.protectedXFunc()		//   protected[X]メソッドにはアクセスできる
      d.privateXFunc()			//   private[X]メソッドにもアクセスできる
    }
  }

  package packageC {
    class ClassC {
      def funcC() {
        var d = new ClassA()		// サブパッケージ
        d.protectedXFunc()		//   protected[X]メソッドにはアクセスできる
        // d.privateXFunc()		//   private[X]メソッドにはアクセスできない(エラー)
      }
    }
  }
}

抽象クラス(abstract)

abstract抽象クラス を定義します。抽象クラスでは継承されることを前提に、サブクラスで実装されるメソッドの型のみを定義します。

abstract class Foo {
  def hello(): Unit			// 型のみを定義
}
class Baa extends Foo {
  def hello(): Unit = println("Hello")	// 実体を定義
}
object main {
  def main(args: Array[String]) {
    var obj = new Baa()
    obj.hello()
  }
}

型パラメータ

Java でいうジェネリック型を、Scala では 型パラメータ と呼びます。型パラメータにより、フィールドやメソッドを様々な型に柔軟に対応させることができます。

class Data[A](var value: A) {
  def print(): Unit = println(value)
}

object main {
  def main(args: Array[String]) {
    var d1 = new Data[Int](123)
    var d2 = new Data[String]("ABC")
    d1.print()
    d2.print()
  }
}

ケースクラス(case)

Scala ではケースクラスという特別なクラスがあり、new 無しでもインスタンス化することができ、同じ属性値を持つケースクラスは等しいとみなされます。

case class Point(x: Int, y: Int)
val p1 = Point(100, 200)
val p2 = Point(100, 200)
println(if (p1 == p2) "Equal" else "Not equal")	// => Equal

オブジェクト(object)

object はオブジェクトを定義します。クラスの場合は class でクラスを定義して new でそのインスタンスを生成しますが、object は唯一のインスタンス(シングルトンオブジェクト)を直接生成することができます。

object Counter {
  private var counter = 0
  def countUp(): Unit = { counter += 1 }
  def getCount(): Int = counter
}
val cnt = Counter
cnt.countUp()
println(cnt.getCount())

コンパニオンオブジェクト

クラス名と同じ名前のオブジェクトはコンパニオンオブジェクトと呼ばれます。インスタンス化しなくても参照可能なスタティックフィールドやスタティックメソッドを定義することができます。

class Math
object Math {
  val Pi = 3.14f
}
println(Math.Pi)

ファクトリメソッド(apply())

apply() という名前のスタティックメソッドは、下記の様に省略形で呼び出すことができます。

val u1 = Person.apply("Yamada")	// 通常はこう呼び出すところを
val u2 = Person("Yamada)	// このように省略形で呼び出せる

下記の様にファクトリメソッド(インスタンス生成関数)としてよく使用されます。

class Person(name: String)
object Person {
  def apply(name: String): Person = new Person(name)
}
object main {
  def main(args: Array[String]) {
    var p = Person("Yamada")
  }
}

抽出子(unapply())

unapply() という名前のメソッドは抽出子とも呼ばれ、None を返す可能性のある case文において Some(n) から値を抽出する際によく使用されます。例えば、Person オブジェクトから主要属性である name を取り出す getName() メソッドが Option[String] 型で定義され、None を返す可能性がある場合、次のように記述します。

class Person(val name: String)
object Person {
  def apply(name: String): Person = new Person(name)
  def getName(p: Person): Option[String] = if (p.name != "Unknown") Some(p.name) else None
}
object main {
  def main(args: Array[String]) {
    var p = Person("Yamada")
    var name = Person.getName(p) match {	// p から主要属性 name を取り出す
      case Some(n) => n				// Some(値) であればその値
      case _ => "(Unknown)"			// さもなくば "(Unknown)" を返す
    }
    println(name)
  }
}

オブジェクトから、その主要属性を Option[型] で返却するメソッドを unapply() という名前で定義することによって、これを、下記の様に直観的に記述することが可能となります。Some() も抽出子をサポートしているため、上記の様に呼び出すことができています。

class Person(val name: String)
object Person {
  def apply(name: String): Person = new Person(name)
  def unapply(p: Person): Option[String] = if (p.name != "Unknown") Some(p.name) else None
}
object main {
  def main(args: Array[String]) {
    var p = Person("Yamada")
    var name = p match {
      case Person(n) => n
      case _ => "(Unknown)"
    }
    println(name)
  }
}

抽出子を用意しておくことで、複数の型に対する case 文を利用可能になることもメリットです。

var name = p match {
  case Person(n) => n
  case Dog(n) => n
  case Cat(n) => n
  case _ => "(Unknown)"
}

トレイト(trait)

trait はトレイトを定義します。英語の trait は「特徴、特質」を意味します。Java のインタフェースに似たもので、トレイトを extendswith で継承するクラスが実装すべきフィールドやメソッドの型を定義します。

trait Shape {
  def getArea(): Float	// 面積を求める getArea() を実装しなくてはならないことを宣言
}
class Rect(width: Float, height: Float) extends Shape {
  override def getArea(): Float = width * height
}
class Circle(r: Float) extends Shape {
  override def getArea(): Float = r * r * 3.14f
}
object main {
  def main(args: Array[String]) {
    val r1 = new Rect(100, 200)
    val c1 = new Circle(100)
    println(r1.getArea())
    println(c1.getArea())
  }
}

インタフェースと異なり、トレイトにはメソッドの実態を実装することもできます。

trait Shape {
  def shape(): Unit = println("Shape!")
}

ミックスイン(with)

クラスや抽象クラスはひとつしか継承することができませんが、トレイトは with によって多重継承・ミックスインさせることができます。

trait Foo { def foo(): Unit = println("Foo") }
trait Baa { def baa(): Unit = println("Baa") }
trait Baz { def baz(): Unit = println("Baz") }
class ClassA extends Foo with Baa with Baz

object main {
  def main(args: Array[String]) {
    val obj = new ClassA()
    obj.foo(); obj.baa(); obj.baz()
  }
}

暗黙の型変換(implicit)

implicit で暗黙の型変換メソッドを定義することができます。Scala は型の扱いが厳格であり、if 文は Boolean しか許しません。下記の例では Boolean を指定すべき箇所に Int の値を指定しているため、コンパイルエラーとなります。

object main {
  def main(args: Array[String]) {
    if (1) println("OK") else println("NG")	// コンパイルエラー
  }
}

下記の様にして、Int → Boolean への暗黙の型変換関数をスコープ内に定義しておいてやると、コンパイラが自動的にこの関数を組み込み、1 を true に変換してくれるようになります。

import scala.language.implicitConversions
object main {
  implicit def intToBoolean(n: Int): Boolean = n != 0
  def main(args: Array[String]) {
    if (1) println("OK") else println("NG")	// 暗黙の型変換により成功する
  }
}

下記の例でも、String 型である "Yamada" は hello() というメソッドを持っていませんが、String から MyString への暗黙の型変換メソッドをコンパイラが自動的に適用してくれることで、MyString のメソッドである hello() を直接呼び出すことが可能になっています。

import scala.language.implicitConversions
class MyString(val str: String) {
  def hello(): String = "Hello " + str
}
object main {
  implicit def stringToMyString(arg: String): MyString = new MyString(arg)
  def main(args: Array[String]) {
    println("Yamada".hello())
  }
}

Scala 2.10 からは class に直接 implicit を記述できるようになりました。

import scala.language.implicitConversions
object main {
  implicit class MyString(val str: String) {
    def hello(): String = "Hello " + str
  }
  def main(args: Array[String]) {
    println("Yamada".hello())
  }
}

暗黙のパラメータ(implicit)

implicit はまた暗黙のパラメータにも使用されます。例えばデータベースにアクセスする多くのメソッドがデータベースへのコネクション conn を引数に持つ場合、下記の様に conn を暗黙のパラメータに指定することで、それぞれのメソッドで conn を省略することが可能となります。まず、省略しない場合のプログラムは下記の様になります。

object main {
  def loadData(conn: Connection, dataId: Int): Data = { new Data }
  def saveData(conn: Connection, data: Data) = { println(data) }

  def main(args: Array[String]) {
    val conn = base.getConnection()
    var data = loadData(conn, 123)
    saveData(conn, data)
  }
}

これを、implicit で省略する形式に書き直すと下記の様になります。

object main {
  def loadData(dataId: Int)(implicit conn: Connection): Data = { new Data }
  def saveData(data: Data)(implicit conn: Connection) = { println(data) }

  def main(args: Array[String]) {
    implicit val conn = base.getConnection()	// connを暗黙パラメータに指定
    var data = loadData(123)			// connを省略できる
    saveData(data)				// connを省略できる
  }
}

遅延評価(lazy)

lazy は遅延評価を行います。遅延評価では、値が使用されるまで評価が行われません。下記の例では、lazy を指定しなければ、A, B, C, D の順番で出力されますが、lazy を指定すると a = funcA() は a が使用されるまで評価されず、B, A, C, D の順番で出力されます。

object main {
  def funcA(s: String) = { println("A"); "A" }
  def funcC(s: String) = { println("C"); "C" }
  def main(args: Array[String]) {
    lazy val a = funcA("")	// => "A"
    println("B")		// => "B"
    funcC(a)			// => "C"
    println("D")		// => "D"
  }
}

パッケージ(package)

package はパッケージを定義します。

package users
class User {
  def print() = println("User")
}

上記の内容を User.scala として作成し、下記の様にコンパイルすると、users ディレクトリとその配下に User.class ファイルが作成されます。

$ scalac User.scala
$ ls -l users
total 4
-rw-r--r--. 1 foo   foo  676 Jan 3 12:45 User.class

インポート(import)

import はパッケージをインポートします。

import users._			// users 配下のすべてをインポートする
import users.User		// users 配下の User をインポートする
import users.{User, User2}	// User と User2 をインポートする
import users.{User => U}	// User を別名 U としてインポートする

非変・共変・反変

通常の引数の型であれば、指定された型のサブクラスであればすべて許容されます。

def foo(v: AnyRef) = println(v)
foo("ABC": String)		// StringはAnyRefのサブクラスなので大丈夫

しかし、型パラメータの場合はサブクラスであっても許可されません。これを 非変 と呼びます。

class Foo[T](v: T)
class Baa(v: Foo[AnyRef])
val e = new Baa(new Foo[String]("ABC"))	// StringはAnyRefのサブクラスだけどコンパイルエラー

T+T とすることでサブクラスを指定することも許可できます。これを 共変 と呼びます。

class Foo[+T](v: T)
class Baa(v: Foo[AnyRef])
val e = new Baa(new Foo[String]("ABC"))	// サブクラスが許可されるようになる

T-T とすることでスーパークラスを指定することも許可できます。これを 反変 と呼びます。

class Foo[-T](v: T)
class Baa(v: Foo[AnyRef])
val e = new Baa(new Foo[Any]("ABC"))	// スーパークラスが許可されるようになる

上限型境界と下限型境界

型パラメータでは任意の型 T を指定することができますが、 T <: B上限型境界(Bおよびそのサブクラスに限定)を、T >: B下限型境界(Bおよびそのスーパークラスに限定)を指定することができます。上位型境界は単純に指定したクラスおよびサブクラスに限定したい時、下限型境界はリスト処理などで、ListNode[B] 型の .append() メソッドで B のスーパークラスを含めて処理したい場合などに利用されます。

class A				// クラスA
class B extends A		// クラスB(クラスAのサブクラス)
class C extends B		// クラスC(クラスBのサブクラス)

class D[T]			// 任意のクラスT
class E[T <: B]			// 任意のクラスT(ただしB以下のクラス) -- 上限型境界
class F[T >: B]			// 任意のクラスT(ただしB以上のクラス) -- 下限型境界

val d1 = new D[A]		// 任意のクラスなのでOK
val d2 = new D[B]		// 任意のクラスなのでOK
val d3 = new D[C]		// 任意のクラスなのでOK

// val e1 = new E[A]		// クラスB以下ではないのでエラー
val e2 = new E[B]		// クラスB以下なのでOK
val e3 = new E[C]		// クラスB以下なのでOK

val f1 = new F[A]		// クラスB以上なのでOK
val f2 = new F[B]		// クラスB以上なのでOK
// val f3 = new F[C]		// クラスB以上ではないのでエラー

アノテーション(@~)

コンパイラに対してアノテーション(付加情報)を指示することができます。@deprecated はそのメソッドが廃止予定であることを伝えます。

@deprecated("This method is deprecated", "2.10")
def hello() = println("Hello")

@tailrc はその関数が末尾再帰であることをコンパイラに伝えます。関数が末尾最適の条件を満たしていると再帰関数をスタック無しで呼び出せるように最適化されるため、メモリ枯渇やスタックオーバーフローの問題が低減されます。

import scala.annotation.tailrec
@tailrec			// fact1()は末尾最適ではないのでコンパイルエラー
def fact1(n: Int): Int = {
  if (n == 0) 1 else n * fact1(n - 1)
}

@tailrec			// fact2()は末尾最適なのでコンパイル成功
def fact2(n: Int, s: Int = 1): Int = {
  if (n == 0) s else fact2(n - 1, s * n)
}

@unchecked は、match において case が足りない旨の警告を抑止します。

def fn(x: Option[String]) = (x: @unchecked) match {
  case Some(y) => y
  // case _ => ""	// @unchecked無しの場合この行が無いと警告が出る
}

カリー化と部分適用

カリー化とは、複数の引数を持つ関数を、「元の関数の第1引数」を引数として「残りの引数を引数として結果を返す関数」を戻り値とする関数に変換することをいいます。Scala は関数をカリー化するメソッド curried をサポートしています。

var add = (x:Int, y:Int, z:Int) => x + y + z	// x, y, zの合計を求める関数
println(add(3, 4, 5))				// => 12
val curriedAdd = add.curried			// .curried でカリー化する
val curriedAdd2 = curriedAdd(3)			// 第1引数を受け取り、残りの引数を受け取る関数を返す
val curriedAdd3 = curriedAdd2(4)		// 次の第1引数を受け取り、残りの引数を受け取る関数を返す
println(curriedAdd3(5))				// 残りの引数を受け取り、結果を返す => 12
println(((curriedAdd(3))(4))(5))		// このようにも呼び出せる => 12
println(curriedAdd(3)(4)(5))			// このようにも呼び出せる => 12

部分適用 は、複数の引数を持つ関数に対して、一部の引数のみを渡して、残りの引数を受け取る関数を得ることを言います。

var add = (x:Int, y:Int, z:Int) => x + y + z	// x, y, zの合計を求める関数
println(add(3, 4, 5))				// => 12
val partialAdd = add(_:Int, _:Int, 5)		// 5だけ先に渡す
println(partialAdd(3, 4))			// 残りの引数を後から渡す => 12

XMLリテラル

Scala では XML をリテラルとして扱うことができます。

val xml =
  <root>
    <items>
      <item><name>foo</name><price>120</price></item>
      <item><name>baa</name><price>320</price></item>
      <item><name>baz</name><price>640</price></item>
    </items>
  </root>

{...} の間には式を入れることができます。

<item><name>foo</name><price>{fooPrice}</price></item>

\ はルート配下から子要素、孫要素を順次検索、\\ は子孫要素の中からダイレクトに検索を行います。

for (item <- xml \ "items" \ "item") println(item)
for (item <- xml \\ "item") println(item)

Copyright (C) 2021 杜甫々
初版:2021年1月3日 最終更新:2021年1月3日
http://www.tohoho-web.com/ex/scala.html