S-JIS[2010-10-29/2017-01-18] 変更履歴

Scalaの演算子

Scalaの演算子のメモ。


Javaの演算子との対応

Javaの演算子に対してScalaでどのような演算子(メソッド)を使うかの比較。

Java(Javaの演算子の優先度順) Scala 説明
演算子 概要 演算子
メソッド
( ) 優先する式 (1 + 2) ( )
{ }
(1 + 2)
{1 + 2}
 
new
.
[]
新規生成 new 型[n] new new Array[型](n) 配列
new クラス() new new クラス() クラス
メンバー obj.value . obj.value  
obj.method() . obj.method()  
配列 array[0] () array(0) 配列
++ -- 後置inc/dec n++
n--
    インクリメント・デクリメントはScalaには無い。
→xuweiさんのscalaでは、なぜインクリメントやデクリメントができないのか?
++ --
+ -
~
!
( )
前置inc/dec ++n
--n
   
符号 +n
-n
+ - +n
-n
正符号・負符号
ビット否定 ~n ~ ~n ビット反転
論理否定 !b ! !b trueをfalseに、falseをtrueにする
キャスト (型)obj asInstanceOf obj.asInstanceOf[型] asInstanceOf
* / % 乗算・除算・剰余 m * n
m / n
m % n
* / % m * n
m / n
m % n
 
+ - 加算・減算 m + n
m - n
string + n
+ - m + n
m - n
string + n
 
<< >> >>> ビットシフト n << r
n >> r
n >>> r
<< >> >>> n << r
n >> r
n >>> r
 
< >
<= >=
instanceof
大小比較 m < n
m > n
m <= n
m >= n
< >
<= >=
m < n
m > n
m <= n
m >= n
 
型比較 obj instanceof クラス isInstanceOf obj.isInstanceOf[クラス] isInstanceOf
== != 等価
不等価
m == n
m != n
== !=
eq ne
m == n
m != n
obj1 eq obj2
obj1 ne obj2
JavaとScalaでは比較内容優先順位が異なる。
& ビットAND m & n & m & n  
^ ビットXOR m ^ n ^ m ^ n  
| ビットOR m | n | m | n  
&& 条件AND b1 && b2 && b1 && b2  
|| 条件OR b1 || b2 || b1 || b2  
? : 条件 b ? m : n if if (b) m else n Scalaでは三項演算子(条件演算子)は無いが、if式で代用。
=
*= /= %=
+= -=
<<= >>= >>>=
&= ^= |=
代入 m = n
m *= n
=
*= /= %=
+= -=
<<= >>= >>>=
&= ^= |=
m = n
m *= n
Scalaでは代入式の戻り値はUnit

Scalaの演算子(メソッド)・式

Scalaでは、数値(IntやLong、Double等)もクラスであり、それらに対する演算(演算子)はそのクラスのメソッドとして定義されている。
例えば「+」は「+」という名前のメソッドであり、「1+2」は「1.+(2)」と書くことも出来る。
(逆に、もしIntにplusというメソッドが定義されていたら、「1.plus(2)」や「1 plus 2」と書くことが出来る。ピリオドを使わない書き方を中置記法と言う)

(メソッド呼び出しも含めた)各種の式の優先度は決まっている。
また、同優先度のメソッド同士では、メソッド名によって優先順位が変わる。
(四則演算には乗除算(*/)が加減算(+-)よりも優先度が高いといったルールがあるが、Scalaでは演算子はメソッドであり、メソッド名によって優先度が決まっている。
基本的にはメソッド名の先頭1文字によるが、末尾1文字が影響したりする。
識別子(メソッド名・演算子)に使える記号

Scalaの式(優先度順) 結合 備考
型推定        
リテラル   123
true
"abc"
  リテラル
null null null   Null型
識別子       変数名など
thisとsuper this
super
this
super
   
関数呼び出し        
メソッドを返す無名関数        
型引数 [ ]     ジェネリクス(パラメータ化された型)
タプル ( , )     Tuple
new new      
ブロック式 { }     ブロック式(複文)
前置メソッド + - ! ~ +n
-n
~n
!b
正符号・負符号・否定・ビット反転
「unary_」で定義する
引数のあるメソッド method() メソッド名の
先頭1文字
     
下記以外の記号   例えば「obj1 # obj2」とか作れる(?)
* / % m * n
m / n
m % n
乗算・剰余算
+ - m + n
m - n
加減算
: a :: list メソッド名の末尾文字がコロン「:」の場合は、右結合。
(その他は左結合)
コロンで終わるメソッド名(演算子)の場合、演算子の左右を入れ替えて記述する
= ! m == n
m != n
Javaではシフトや比較演算の方が等号・不等号より優先度が高いが、
Scalaでは逆。
しかし「1 << 2 == 4」は「1<<2」の方が先に計算される。不思議
< > m > n
m >= n
m < n
m <= n
n << r
n >> r
n >>> r
Javaではシフト演算の方が比較演算より優先度が高いが、
Scalaでは同順位。
左結合なので、左側にある方が先に計算される。
& m & n
b1 && b2
 
^ m ^ n  
| m | n
b1 || b2
 
記号以外の文字 obj.method(arg)
obj method arg
メソッド呼び出し
引数の無いメソッド method   obj.method 引数リストの無いメソッド呼び出し
代入演算子の演算部分       比較演算「>=」「<=」「==」「!=」以外で末尾が「=」だと代入演算子とみなされる。
末尾「_=」も代入。呼び出し時に「=」を付けるのも代入。
帰属型        
帰属注釈        
代入 = 変数 = 値   代入結果はUnit。つまり値を返さない。
if式 if else if (条件) 値 else 値    
while式 while while (条件) 文    
do式 do while do 文 while(条件)    
for式 for for (変数 <- 範囲) 文    
return式 return return 値    
throw式 throw throw 例外    
try式 try catch finally try { 文 }
catch { マッチ式 }
finally 文
   

参考: Scala開眼の3階:文と式


演算の注意点(JavaとScalaの演算の違い)

==とeq

Javaでは「==」「!=」の比較は、プリミティブ型の場合は値の比較だが、参照型の場合は参照(インスタンス)そのものが同じインスタンスを指しているかどうかを判定する。
参照型で値が等しいかどうかを判定したい場合はequals()メソッドを使う。

Scalaでは、「==」「!=」で値が等しいかどうかを比べる。(数値も文字列もその他のクラスも同様。実体としてはequals()メソッドが呼ばれる)
インスタンスが同一かどうかを調べる為には「eq」「ne」を使う。(Javaの「==」「!=」に相当)
(なお、==, !=はAnyのメソッド、eq, neはAnyRefのメソッドという位置づけ。[2017-01-18]

したがって、nullとの比較は「obj eq null」で行う。
が、「x == that」は具体的には「if(x eq null){ that eq null }else{ x.equals(that) }」と同じなので、「obj == null」で比較しても良い。[2017-01-18]
(参考: gakuzzzzさんのツイート


ただし、(なぜか)配列だけは異なる挙動を示す。

  数値の例 文字列の例 配列の例
== 1 == 1 true "abc" == "abc"
"abc" == new String("abc")
true
true
Array(1,2,3) == Array(1,2,3)
{val a = Array(1,2,3); a == a}
Array(1,2,3).sameElements(Array(1,2,3))
false
true
true
eq 1 eq 1 エラー "abc" eq "abc"
"abc" eq new String("abc")
true
false
Array(1,2,3) eq Array(1,2,3)
{val a = Array(1,2,3); a eq a}
false
true

==と<の優先度

Scalaの式の優先度の表によると、Scalaでは等値比較「==」の方が大小比較「<」やシフト演算「<<」より優先度が高い(Javaと逆)。

しかし以下の式を試してみると、正常に計算される。(「<<」の方が優先度が高い)

scala> 1 << 2 == 4
res185: Boolean = true

scala> (1 << 2) == 4
res186: Boolean = true

scala> 1 << (2 == 4)
<console>:6: error: overloaded method value << with alternatives:
  (Long)Int <and>
  (Int)Int
 cannot be applied to (Boolean)
       1 << (2 == 4)
       ^

シフト演算の結果はBooleanにはならない為、(優先度トップの)型推論でも働いて上手く調整してくれてるんだろうか?

あるいは、自分で演算子を作って同じ型を返すようにしたら、表どおりの優先度になるのかな?


isInstanceOfとasInstanceOf

Javaでは、キャストできるかどうか調べてOKの場合にキャストするのは、以下の様に書く。
(instanceofがtrueなら、必ずそのクラスにキャストできる)

	if (obj instanceof クラス) {
		final クラス v = (クラス)obj;
		〜
	}

Scalaでは、それぞれ判定用・変換用のメソッドがあるので、以下の様に書ける。

	if (obj.isInstanceOf[クラス]) {
		val v = obj.asInstanceOf[クラス]
		〜
	}

ただし、Scalaではmatch式を使う方がスマート。[2011-04-10]

  obj match {
    case v: クラス => 〜
    case _ =>
  }

ちなみに、isInstanceOf・asInstanceOfが長めの名前になっているのは、「あまり使うな」という意思表示らしい。

※isInstanceOf・asInstanceOfは、Anyクラスのメソッドという位置づけになっている


isInstanceOfとasInstanceOfを試しているときに、判定結果と変換可否が必ず対応するとは限らないようだ。と思ったが。
(isInstanceOf[Byte]はfalseを返すのに、asInstanceOf[Byte]で変換できる)

scala> var obj = 1
obj: Int = 1

scala> obj.isInstanceOf[Byte]
res183: Boolean = false

scala> obj.asInstanceOf[Byte]
res184: Byte = 1

考えてみると、Javaでもintはbyteではないがbyteへキャストできるから、それと同じか。[2011-04-10]


また、isInstanceOf[AnyVal]は使うことは出来ない。[2010-12-12]

scala> val n = 1
n: Int = 1

scala> n.isInstanceOf[AnyVal]
<console>:10: error: type AnyVal cannot be used in a type pattern or isInstanceOf test
       n.isInstanceOf[AnyVal]
                     ^

isInstanceOf[AnyRef]は(AnyRefでなくAnyValに属しているはずのクラスでも)常にtrueになる。
(数値はオートボクシングによりJavaのラッパークラスになり、それに「instanceof Object」するので(?)、当然常にtrueになる)

scala> n.isInstanceOf[AnyRef]
res13: Boolean = true

scala> ().isInstanceOf[AnyRef]
res14: Boolean = true

scala> false.isInstanceOf[AnyRef]
res15: Boolean = true

キャストは、asInstanceOfの他に「:型」という方法もある。[2011-06-21]
ただし、まるっきり同じというわけではない。

asInstanceOf : 備考
scala> 127.asInstanceOf[Byte]
res0: Byte = 127

scala> 128.asInstanceOf[Byte]
res1: Byte = -128
scala> 127:Byte
res3: Byte = 127

scala> 128:Byte
<console>:8: error: type mismatch;
 found   : Int(128)
 required: Byte
       128:Byte
       ^
:型」だと、範囲外の数値はエラーになる。
scala> 1.asInstanceOf[String]
java.lang.ClassCastException: java.lang.Integer
 cannot be cast to java.lang.String
scala> 1:String
<console>:8: error: type mismatch;
 found   : Int(1)
 required: String
       1:String
       ^
 
Javaで例えると、キャストして代入?
String s = (String)1;
Javaで例えると、キャストせずに代入?
String = 1;
キャストしない場合、コンパイル時に型(クラス)が不一致でエラーになる。
キャストする場合、コンパイルは通る(ように変換される)が実行時に失敗する。
ということだろうか?

代入の戻り値

Javaでは、代入式は代入された値を返す。したがって下記のような書き方で変数を初期化できる。
(「c = 1」は1を返すので、「b = c」はbに1を入れて1を返す。したがってaに1が入る)

	int a, b, c;
	a = b = c = 1;

しかしScalaでは代入式は戻り値を返さない(厳密にはUnit型なので、「()」を返す)ので、こういった書き方は出来ない。

ということは、Unit型の変数なら出来るかも…?

scala> var a: Unit = _
a: Unit = ()

scala> var b: Unit = _
b: Unit = ()

scala> var c: Unit = _
c: Unit = ()

scala> a = b = c = ()
a: Unit = ()

おー、出来た。けどUnit型の変数は「()」しか値を持てないから、意味無ぇ(爆)

これが出来ないと何が困るかというと、Javaでの以下のようなコーディングが出来なくなる。

	BufferedReader br = new BufferedReader(〜);
	String line = null;
	while ((line = br.readLine()) != null) {
		System.out.println(line);
	}

個人的にはこの書き方は好きじゃない。代入用のローカル変数をスコープの外で定義しないといけないから。
(C言語のFILEのオープン方法の頃からあまり好きじゃなかったなぁ)
なので元々使ってないから、自分は特に困らないけどね。

ただ、似たような書き方をする事は出来る。[2011-07-11]
テキストファイルの読み込み(while ({ line = br.readLine(); line ne null }))


Scalaの代入がUnitを返すのは、「副作用がある処理はUnitを返す」という論理からすると、素直な考え方らしい。[2012-03-07]


ちなみに、変数の定義時点であれば、複数の変数をまとめて初期化できる。[2011-01-15]

scala> val a, b, c = 1
a: Int = 1
b: Int = 1
c: Int = 1

Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま