Edited at

java経験者のScala入門メモ [ケースクラス、オブジェクト]

More than 3 years have passed since last update.


はじめに

今回はさらっと終わらせてしまうつもりでしたが、ケースクラスが予想以上に重そうなので

ケースクラスとオブジェクトに関することだけまとめます


ケースクラス

ケースクラス、以前のクラスとは違うタイプのクラスがまだあるのかと思いますが

やはり、クラスといえばただクラスであるようにjava経験者には馴染みにくいものかもしれません


ケースクラスとは

ざっくり言ってしまえば、以下の様に書ける

classの前にcaseをつけたクラスのことです


case class TestCaseClass(num:Int)

これだけではなにかわからないのでケースクラスの特徴についてまとめます

ケースクラスの特徴は大きく5つあります

・インスタンス生成newを省略できる

・基本コンストラクタ引数(前回の記事、クラス、コンストラクタを参照)にすべてvalがついた状態になる

・パターンマッチに使える(なんじゃそりゃ)

・コンパニオンオブジェクト(後述)が生成される

・便利なメソッドが生成される(これメイン)


生成される便利なメソッド達


apply

applyメソッドはインスタンスを生成します

さっき言ってたcaseクラスの特徴と矛盾するじゃねーかというツッコミをもらいそうですが

caseクラスは完全にnewを省略してインスタンスを生成しているのではなく

インスタンスを生成する為にapplyメソッドを呼び出し、インスタンスを生成しているらしいです

例えばこう使う


case class Person(name:String,age:Int)

val person = Person.apply("me",20)
//コンストラクタ引数として渡したいものをapplyに渡す


copy

copyメソッドは現在生成してある既存のインスタンスをベースに

新しいインスタンスを生成できるメソッドです

例えばこう使う


case class Person(name:String,age:Int,from:String)

//applyでインスタンス生成
val p1 = Person.apply("me",20,"first instance")
//copyでp1ベースの新しいインスタンスを生成
val p2 = p1.copy(from = "second instance")
//fromだけ変えて後は同じ


equalsと==、canEqual

equalsメソッドは基本コンストラクタ引数の値で参照が等しいか判断するメソッドです

a.equals(b)と書くことで使用できますが(case class aとcase class bを仮定した場合)

==演算子でも同じ判定が行えるので a == bとも書けます

実際につかってみるとこんな感じ


case class Person(name:String,age:Int)

val p1 = Person.apply("me",20)
val p2 = p1.copy()
val p3 = p1.copy(name = "you")

p1 == p2 //return -> true
p1 == p3 //return -> false


ここまで踏まえてcanEqual

canEqualはインスタンスが同じクラスから生成されたものであるか判定するメソッドです

上のインスタンスが存在すると仮定すると


p1.canEqual(p2)
p1.canEquals(p3)//どれもreturn -> true

case class PersonFake()

val p4 = PersonFake.apply()
p4.canEquals(p1)//return -> false


ここから意味不明なcurried、tupled

ここまで来るとさすがに意味不明感がすごく

この分野の説明だけでは説明しきれないのがこの3つ

厄介すぎる…

tupledはtupleに関することだとはなんとなくわかると思います

pythonユーザーであれば尚更しっくりくるかもしれません

pythonユーザーにわかるように言うなら

コンストラクタ引数をtupleで取るように変換するメソッドのことです

pythonユーザーはこれで理解できるかもしれませんが

javaユーザには正直つらいものが見えてくる


まずtupledから

まずtupledから説明しちゃいましょう、まだ軽いので

多分Scala入門でもあとから出てくるとは思いますが

タプル(tuple)とは型が違う値でも関係なしに格納出来る配列、コレクション、リストのようなものです

それをコンストラクタ引数にしてしまうのでこうなります


case class Person(name:String,age:Int)

val change = Person.tupled//これで引数をタプルにしてしまう

val tuple = ("me",20)//これがタプル()で囲む

//引数がタプルになったクラスに↑のタプルを渡す
change(tuple)

こういうものなのです(書いてる本人もどこで使うかあまりわかってない)


つぎにcurried

これがjavaユーザーにとってケースクラスを理解する上での山場になるでしょう

まずcurriedメソッドはapplyメソッドをカリー化するメソッドです

この時点で書いてる本人も理解してません

なんでもケースクラス以外でもカリー化できるらしく

それがとても混乱しますね

この記事を書いている間に色々調べた結果、なんとなくわかったのでざっくりいうと

引数が複数あるものを引数がひとつのものに変えるという動作をカリー化というらしいです

ちなみにScalaユーザーズグループのチャットで聞いたとき

「case classを定義するとobject クラス名というコンパニオンオブジェクトが自動生成されそれがFunctionNなのでcurriedメソッドが使える、逆にそれを自分で定義してしまうとFunctionNにならずcurriedメソッドが無いからカリー化できない」ということもあるらしいです


case class Person(name:String,age:Int,from:String)

val curried = Person.curried//カリー化したものをcurriedに代入
val function = curried("me")(20)//先にnameとageを決めてしまう

function("curried complete")//これでクラスのコンストラクタ引数を一つにできた

ということらしいです


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

さっきcurriedのところでも少し触れた様にケースクラスを定義すると

コンパニオンオブジェクトというものが同時に定義されます

詳しく話していくとまた複雑になってしまうのでざっくり言うと

コンパニオンオブジェクトとは「同じソース、パッケージ、クラス内で定義された同名オブジェクトのことで、お互いにプライベートなメンバにアクセスできるやつ」と覚えておいてください


その他メソッド

ここでは紹介しきれなかったメソッドの中に

hashCode(基本コンストラクタで受け取った値からハッシュ値作成)とかtoString(クラス名と基本コンストラクタで受け取った値を返す)などなどあります

詳しくは自分がこの記事を書くのに参考にしているこのサイトを参照してもらえると詳しくわかると思います


オブジェクト

ケースクラスの次は

コンパニオンオブジェクトとかでも出てくるオブジェクトというものを説明します


オブジェクトの特徴

シングルトンなオブジェクトをscalaでは生成できて

静的メンバを記述していく(javaでいうstatic)


オブジェクトの定義

オブジェクトはクラスと似ていて

object object名{}

の様に定義します


object Test{
def method(num:Int) = n+1
val a = 1
val b = 2
}

オブジェクトのメンバにアクセスしたいときは

//object Test{...}を仮定
Test.method(1)//return -> 2

Test.a // get -> 1
Test.b // get -> 2


オブジェクトは値

オブジェクトは値として扱える(型はobject名.type)ので変数や関数に渡したり、戻り値にすることが出来る


object Test{
def method = 1
}

val test: Test.type = Test
test.method

これ以外にも


def function(test:Test.type) = test.method
def function2 = Test

みたいな定義方法もできます


オブジェクトと継承

オブジェクトにクラスを継承させることはできても

オブジェクトをオブジェクトやクラスに継承させることはできません

java風に言うならスーパークラスにはなれない


内部オブジェクト

オブジェクトの中にオブジェクトを作る場合


object A{
object B{
val b = 1
}
}

//bにアクセスするなら
A.B.b

クラスの中にオブジェクトを定義するとき


class A{
object B{
val b = 1
}
}

val a1 = new A
val a2 = new A

a1.B.b = 2
a2.B.b//get -> 1


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

コンパニオンオブジェクトとは「同じソース、パッケージ、クラス内で定義された同名オブジェクトのことで、お互いにプライベートなメンバにアクセスできるやつ」