Scalaの演算子について解説

Scala 3.3.4
最終更新:2023年12月7日

[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください

この記事では、Scalaの演算子について説明します。

演算子とはなにか

「演算子」は、各種の演算を表す記号・シンボルです。
演算を行う対象を「被演算子」と言います。
また、「演算子」と「被演算子」を記述する位置によって以下のように区別します。

  • 中置記法
    演算子を2つの被演算子の間に記述する
  • 前置記法
    演算子を被演算子の前に記述する
  • 後置記法
    被演算子の後ろに演算子を記述する

一般的によく使われる演算子には、以下のようなものがあります。
Scalaでもこれらの演算子を使用できます。

  • 算術演算子
  • 比較演算子
  • 論理演算子
  • ビット演算子

Scalaで使える基本型

Scalaの演算子を説明する前にScalaで使える基本型を紹介します。
Scalaで使える基本型と、それらの型のインスタンスがもてる値の範囲は以下のとおりです。

範囲
Byte-128 から 127
Short-32768 から 32767
Int-2147483648 から 2147483647
Long-9223372036854775808 から 9223372036854775807
Float32ビットのIEEE 754単精度浮動小数点
Double64ビットのIEEE 754倍精度浮動小数点
Char16ビット符号なしUnicode文字
StringCharのシーケンス
Booleantrue または false

Scalaの基本型で使える演算子

先ほど説明したScalaの基本型で使える演算子について説明します。

算術演算子

ここでは、以下の算術演算子について説明します。

  • 四則演算
    • 加算(+)
    • 減算(-)
    • 乗算(*)
    • 除算(/)」
  • 剰余(%)
  • 正(+)、負(-)

Scalaでは、StringBoolean以外の基本型で上記の演算子が利用できます。

以下、Replでの実行例です。

  • 加算(+)
scala> val x = 1 + 1 x: Int = 2
  • 減算(-)
scala> val x = 2L - 1L x: Long = 1
  • 乗算(*)
scala> val x = 2.0 * 9.0 x: Double = 18.0
  • 除算(/)
scala> val x = 11.1f / 3f x: Float = 3.7
  • 剰余(%)
scala> val x = 10 % 3 x: Int = 1
  • 負(-)
scala> val x = -2 x: Int = -2

また、-は以下のように数値の符号を反転することができます。

scala> val x = 2 x: Int = 2 scala> val y = -x y: Int = -2
  • 正(+)
scala> val x = +4 x: Int = 4

比較演算子

次に以下の比較演算子について説明します。

  • より大きい(>)
  • より小さい(<)
  • 以上(>=)
  • 以下(<=)
  • 等しい(==)
  • 等しくない(!=)

Scalaの基本型はすべて、上記の比較演算子が利用できます。
これらの演算子は、中置記法で記述し被演算子の比較結果をBooleanで返します。

以下、Replでの実行結果です。

  • より大きい(>)
scala> val x = 1 > 2 x: Boolean = false
  • より小さい(<)
scala> val x = 1 < 2 x: Boolean = true
  • 以上(>=)
scala> val x = 1.0 >= 1.0 x: Boolean = true
  • 以下(<=)
scala> val x = 1.0 <= 1.0 x: Boolean = true
  • 等しい(==)
scala> val x = "hogehoge" == "poupou" x: Boolean = false
  • 等しくない(!=)
scala> val x = "hogehoge" != "poupou" x: Boolean = true

論理演算子

次に以下の論理演算子について説明します。

  • AND(&&)
  • OR(||)

論理演算子は、Booleanの被演算子に対して中置記法で記述し、Booleanを返します。
&&は、被演算子が両方ともtrueの場合にtrueを返します。
||は、被演算子のどちらか一方が、trueの場合にtrueを返します。

以下、Replでの実行結果です。

  • AND(&&)
scala> val x = true && true x: Boolean = true scala> val x = true && false x: Boolean = false scala> val x = false && true x: Boolean = false
  • OR(||)
scala> val x = true || false x: Boolean = true scala> val x = false || true x: Boolean = true scala> val x = false || false x: Boolean = false

ビット演算子

整数値型に対しては、次のビット単位の演算ができます。

  • ビット単位AND(&)
    ビット単位AND演算は、ビット単位のAND演算を行います。
    以下の例では、1(0001)と3(0011)のビット単位のAND演算を行い、1(0001)を返します。

以下、Replでの実行結果です。

scala> val x = 1 & 3 x: Int = 1
  • ビット単位OR(|)
    ビット単位AND演算は、ビット単位のOR演算を行います。
    以下の例では、1(0001)と3(0011)のビット単位のOR演算を行い、3(0011)を返します。

以下、Replでの実行結果です。

scala> val x = 1 | 3 x: Int = 3
  • ビット単位XOR(^)
    ビット単位AND演算は、ビット単位のOR演算を行います。
    以下の例では、1(0001)と3(0011)のビット単位のXOR演算を行い、2(0010)を返します。

以下、Replでの実行結果です。

scala> val x = 1 ^ 3 x: Int = 2
  • ビット反転(~)
    ~は、各ビットを反転した結果を返します。
    以下の例の被演算子の2はIntのため、2進数で表現すると以下のようになります。
    2(00000000000000000000000000000010)の各ビットを反転して、-3(11111111111111111111111111111101)を返します。

以下、Replでの実行結果です。

scala> val x = ~2 x: Int = -3
  • 左シフト(<<)
    <<は指定したビット数、左にシフトし空になったビットには0を設定します。
    以下の例の被演算子の1はIntのため、2進数で表現すると以下のようになります。
    1(00000000000000000000000000000001)を2ビット左にシフトして、4(00000000000000000000000000000100)を返します。

以下、Replでの実行結果です。

scala> val x = 1 << 2 x: Int = 4
  • 右シフト(>>)
    >>は指定したビット数、右にシフトし空になったビットには最上位のビットを設定します。
    以下の例の被演算子の1はIntのため、2進数で表現すると以下のようになります。
    4(00000000000000000000000000000100)は、2ビット左にシフトして、1(00000000000000000000000000000001)を返します。
    -4(11111111111111111111111111111100)は、2ビット左にシフトして、-1(11111111111111111111111111111111)を返します。

以下、Replでの実行結果です。

scala> val x = 4 >> 2 x: Int = 1 scala> val x = -4 >> 2 x: Int = -1
  • 符号なし右シフト(>>>)
    >>>は指定したビット数、右にシフトし空になったビットには0を設定します。
    以下の例の被演算子の1はIntのため、2進数で表現すると以下のようになります。
    -4(11111111111111111111111111111100)は、2ビット右にシフトして、1073741823(00111111111111111111111111111111)を返します。

以下、Replでの実行結果です。

scala> val x = -4 >>> 2 x: Int = 1073741823

オブジェクトの等価性について

==!=は、基本型以外のオブジェクトでも利用できます。
例えば、以下のようにListに対しても==を使用できます。
「list1」、「list2」は別々のオブジェクトですが、要素が同じなので==の結果はtrueです。

scala> val list1 = List(1, 2, 3) list1: List[Int] = List(1, 2, 3) scala> val list2 = List(1, 2, 3) list2: List[Int] = List(1, 2, 3) scala> val x = list1 == list2 x: Boolean = true

==、!=は、すべてのオブジェクトで利用できる

==!=は、すべてのオブジェクトで利用できます。
では、どのように2つのオブジェクトが同じかどうかを判定しているのかを説明します。

以下のクラスを例に==!=の動作を見てみます。
PointクラスはInt型のメンバー変数x、yを持っています。
x、yの両方の値が同じ場合は、オブジェクトは同じものとします。

class Point(val x: Int, val y: Int): @annotation.nowarn override def equals(x: Any): Boolean = println("[called equals]") x match case o: Point => this.x == o.x && this.y == o.y case _ => false

オブジェクトが同じかどうかを==!=で判定するには、equalsメソッドを実装します。
equalsメソッドでは引数で渡されたオブジェクトと自身の値が同じかどうかを判定して、同じ場合はtrue、違う場合はfalseを返します。

次に、以下の関数を使用して==!=がどのように動作するかを見てみます。

def checkForEquality(p1: Point, p2: Point): Unit = if(p1 == p2) println("p1 is equal to p2.") else println("p1 is not equal to p2.")

では、先ほどの関数を呼び出してみます。
左辺がnull出ない場合はequalsメソッドが呼び出され、左辺がnullの場合はequalsメッソッドが呼び出されていません。

  • 値が同じ場合
val p1 = new Point(1, 2) val p2 = new Point(1, 2) checkForEquality(p1, p2)
[called equals] p1 is equal to p2.
  • 値が違う場合
val p1_1 = new Point(1, 2) val p2_2 = new Point(2, 4) checkForEquality(p1_1, p2_2)
p1 is not equal to p2.
  • 左辺がnullの場合
val p = new Point(1, 2) checkForEquality(null, p)
p1 is not equal to p2.

Java言語の==は、Scalaと結果が違う

Java言語でオブジェクトを==で比較した場合は、どのような動作になるでしょうか?

前述のScalaの例と同じPointクラスでJava言語の動作を確認してみます。

java
Point p1 = new Point(1, 2); Point p2 = new Point(1, 2);

Java言語の場合==では、参照先が同じ場合にtrueになりますが、Scalaのようにp1とp2が同じことを検出することはできません。

java
if(p1 == p2) { System.out.println("p1 is equal to p2."); } else { System.out.println("p1 is not equal to p2."); }
p1 is not equal to p2.

Java言語の場合は、以下のようにequalsメソッドを直接呼び出す必要があります。

java
if(p1.equals(p2)) { System.out.println("p1 is equal to p2."); } else { System.out.println("p1 is not equal to p2."); }
p1 is equal to p2.

ScalaでJava言語の==と同様の結果を得る

ScalaでJava言語の==と同様の結果を得ることはできるでしょうか?

Scalaでは、eqメソッドで参照先が同じことを確認できます。
以下は、eqメソッドを使用した例です。

def checkForEquality(p1: Point, p2: Point): Unit = if(p1.eq(p2)) println("p1 is equal to p2.") else println("p1 is not equal to p2.")

上記の関数を呼び出してみます。

val p1 = new Point(1, 2) val p2 = new Point(1, 2) checkForEquality(p1, p2) checkForEquality(p1, p1)

実行結果は以下のようになります。

p1 is not equal to p2. p1 is equal to p2.

また、neメソッドで参照先が違うことを確認することもできます。
以下は、neメソッドを使用した例です。

def checkForNotEquality(p1: Point, p2: Point): Unit = if(p1.ne(p2)) println("p1 is not equal to p2.") else println("p1 is equal to p2.")

上記の関数を呼び出してみます。

val p1 = new Point(1, 2) val p2 = new Point(1, 2) checkForNotEquality(p1, p2) checkForNotEquality(p1, p1)

実行結果は以下のようになります。

p1 is not equal to p2. p1 is equal to p2.

異なる基本型同士の演算

数値型の場合は、異なる型同士の演算ができます。
この時、キャストして型を合わせる必要はありません。 以下の例では、それぞれIntDoubleDoubleFloatの値を比較しています。

scala> 1 == 1.0 res3: Boolean = true scala> 2.0 > 1.9f res4: Boolean = true

Scalaの演算子はメソッドである

これまで説明してきたScalaの演算子は、すべてメソッドです。
そのため、加算演算子(+)も以下のように記述することができます。

scala> val x = 1.+(2) x: Int = 3

では、マイナス(-)のような前置記法の演算子の場合は、どうでしょうか?
前置記法の場合は、以下の命名規則に従ってメソッドが定義されています。

  • unary_ + 演算子

前置記法の演算子も以下のように通常のメソッド呼び出しで呼び出すこともできます。

scala> val x = 2 x: Int = 2 scala> val y = x.unary_- y: Int = -2

このようにScalaは演算子をメソッドで定義しています。
Java言語の場合は、演算子を定義することはできないため、BigIntegerBigDecimalでは、演算子を使用することができませんが、 ScalaのBigIntBigDecimalは基本型と同様に演算子を使用することができます。

以下、Replでの実行結果です。

scala> val x = BigInt(256) x: scala.math.BigInt = 256 scala> val y = x * x y: scala.math.BigInt = 65536

また、「異なる基本型同士の演算」で説明した内容が可能なのも、比較対象となる被演算子の型ごとにメソッドが定義されているからです。
例えば、Intには以下の==メソッドが定義されています。

  • def ==(x: Double): Boolean
  • def ==(x: Float): Boolean
  • def ==(x: Long): Boolean
  • def ==(x: Int): Boolean
  • def ==(x: Char): Boolean
  • def ==(x: Short): Boolean
  • def ==(x: Byte): Boolean

演算子には優先順位がある

乗算(*)が加算(+)よりも先に評価されるように演算子には優先順位があります。
例えば、1 + 2 * 3という式では、2 * 3が先に評価され結果は7になります。

また、括弧()を使えば評価の順序を変更することができます。
前述の式を以下のように括弧をつけて記述すると、1 + 2が先に評価され結果は9になります。
(1 + 2) * 3

Scalaは、演算式で使われるメソッドの先頭文字によって優先順位を決めています。
以下のリストは、先頭文字に与えられた優先順位を高いものから順に記述しています。

  1. 以下の特殊文字以外のすべての特殊文字
  2. * / %
  3. + -
  4. :
  5. = !
  6. < >
  7. &
  8. ^
  9. |
  10. すべての英字
  11. すべての代入演算子

優先順位には例外がある

先ほど優先順位は先頭文字によって決まると説明しましたが、代入演算子は優先順位が最低になります。
末尾が=で比較演算子でない演算子が代入演算子です。

以下の例では、*=は先頭を見ると+よりも優先順位は高いのですが、末尾が=の代入演算子なので、+よりも後に評価されます。

scala> var x = 2 x: Int = 2 scala> x *= 2 + 2 scala> println(x) 8

上記は以下と同じです。

scala> val x = (2 + 2) * 2 x: Int = 8

優先順位が同じ場合の評価順番

では、優先順位が同じ場合はどのような順番で評価されるのでしょうか?

優先順位が同じ場合は、末尾が:とそれ以外で違います。

末尾が:以外の場合は、左から順番に評価されます。

Replでの実行例を見てみます。

以下は、*/は優先順位が同じため、1.0 * 2.0が先に評価され、2.0 / 3.0を評価した値が式の値になります。

scala> val x = 1.0 * 2.0 / 3.0 x: Double = 0.6666666666666666

以下は、1.0 / 2.0が先に評価され、0.5 * 3.0を評価した値が式の値になります。

scala> val x = 1.0 / 2.0 * 3.0 x: Double = 1.5

末尾が:の場合は、右から順番に評価されます。

Listのメソッドの:::::を例に動作を説明します。

  • ::
    Listの先頭に値を追加する
  • :::
    Listを連結する

以下の例では、list1 ::: list2が先に評価され、99 :: List(1, 2, 3, 4, 5, 6)を評価した値が式の値になります。

scala> val list1 = List(1, 2, 3) list2: List[Int] = List(1, 2, 3) scala> val list2 = List(4, 5, 6) list2: List[Int] = List(4, 5, 6) scala> val result = 99 :: list1 ::: list2 result: List[Int] = List(99, 1, 2, 3, 4, 5, 6)

その他の演算子について

Scalaの基本型では、今まで説明した以外にも便利な演算子が利用できます。
以下は、Scalaの数値型で利用できる演算子の一例です。

  • max 被演算子のうち値が大きい方を返します。
scala> val x = 1 max 2 x: Int = 2
  • min 被演算子のうち値が小さい方を返します。
scala> val x = 1 min 2 x: Int = 1

これらの演算子はリッチラッパークラスに定義されており、暗黙の型変換によって実行されます。

以下、基本型に対するリッチラッパークラスの一覧です。

基本型リッチラッパークラス
Bytescala.runtime.RichByte
Shortscala.runtime.RichShort
Intscala.runtime.RichInt
Charscala.runtime.RichChar
Floatscala.runtime.RichFloat
Doublescala.runtime.RichDouble
Booleanscala.runtime.RichBoolean
Stringscala.collection.immutable.StringOps

サイト内検索