스칼라 클래스를 공부하고 있다.

자바 클래스와 비슷한 Member 클래스를 스칼라로 만들었다. 


class Member(_id: Int, _name: String) {
private var id = _id
private var name = _name

def get(_id: Int): String = {
name
}

def set(_id: Int, _name: String) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

클래스 필드의 modifier는 private로 선언해야 외부에서 사용할 수 없다.(modifier를 쓰지 않으면 public이다, 참고로 protected는 자바처럼 그대로 존재한다.)

상위 클래스에서 이미 선언된 toString()을 정의할 때는 override를 써야 컴파일 에러가 발생하지 않는다.




사용하는 방법은 다음과 같다. 인스턴스를 생성하고 Member를 얻는다.


val member : Member = new Member(1, "samuel")
println(member.get(1))
println(member)

결과는 다음과 같다. 


samuel

(1, samuel)



public 필드를 가진 Dummy 클래스를 사용해보자.

class Dummy() {
var x = ""
}


Dummy 클래스를 다음처럼 사용할 수 있다.

val dummy = new Dummy()
dummy.x = "1"
println(dummy.x)





싱글톤 객체(Singleton object)를 생성해본다. object Member를 하나 추가해본다.


class Member(_id: Int, _name: String) {
private var id = _id
private var name = _name

def get(_id: Int): String = {
name
}

def set(_id: Int, _name: String) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

object Member {
def print(_member : Member) : Unit = {
println("id : " + _member.id + ", name : " + _member.name)
}
}



사용할 때는 static method처럼 사용할 수 있다. 

object Member 를 class Member의 동반 객체(companion object)라 부른다. 반대로 class Member를 object Member의 동반 클래스(companion class)라 부른다.

val member : Member = new Member(1, "samuel")
Member.print(member)



결과는 다음과 같다.


id : 1, name : samuel



위의 코드는 일반 생성 코드와 싱글톤 객체의 메소드가 있으니 좀 보기 이상할 수 있다.

아예 싱글톤 객체로만 사용해본다.

object Member에 apply 메소드를 추가하여 new 없이 생성자 역할을 하도록 한다. 



class Member(_id: Int, _name: String) {
private var id = _id
private var name = _name

def get(_id: Int): String = {
name
}

def set(_id: Int, _name: String) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

sealed trait Print {
def print(_member : Member) : Unit
}

object Member extends Print {
def print(_member : Member) : Unit = {
println("id : " + _member.id + ", name : " + _member.name)
}

def apply(_id: Int, _name: String) : Member = {
new Member(_id, _name)
}
}



new 생성자 없이 Member 객체를 생성했다. 

Member.print(Member(1, "samuel"))
Member.print(Member(2, "jackson"))

결과는 다음과 같다.


id : 1, name : samuel

id : 2, name : jackson





자세한 내용은 아래 링크를 참조한다.

http://knight76.tistory.com/entry/scala-%EC%8B%B1%EA%B8%80%ED%86%A4-%EA%B0%9D%EC%B2%B4-%EB%8F%85%EB%A6%BD-%EA%B0%9D%EC%B2%B4-%EB%8F%99%EB%B0%98-%ED%81%B4%EB%9E%98%EC%8A%A4




이번에는 unapply 메소드를 소개한다. unapply 메소드는 apply 메소드의 반대되는 개념이다. 

스칼라에서는 apply 메소드를 injection이라 부르고, unapply를 extraction이라 부른다. unapply를 통해 객체를 얻었다면, extractor object라 부른다.



다음 예제를 살펴본다.  unapply를 추가했고, 리턴 값을 튜플로 했다. 


 

class Member(_id: Int, _name: String) {
private var id = _id
private var name = _name

def get(_id: Int): String = {
name
}

def set(_id: Int, _name: String) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

sealed trait Print {
def print(_member : Member) : Unit
}

object Member extends Print {
def print(_member : Member) : Unit = {
println("id : " + _member.id + ", name : " + _member.name)
}

def apply(_id: Int, _name: String) : Member = {
new Member(_id, _name)
}

def unapply(member: Member) : Option[(Int, String)] = {
if (member.id > 0 && !member.name.isEmpty)
Some(member.id, member.name)
else
None
}
}

object Main extends App {
println(Member.unapply(new Member(1, "samuel")))
}


결과는 다음과 같다.


Some((1,samuel))









에는 case class를 생성한다. case class는 생성자 매개변수를 노출하고 패턴 매칭을 가능케 한다. equals와 toString 메소드를 지원한다.


예시로 Member 추상 클래스와 하위 case class를 생성한다.

abstract class Member

case class CompanyMember(name: String) extends Member {}
case class SchoolMember(name: String) extends Member {}
case class CommunityMember(name: String) extends Member {}


case class를 테스트해본다.

val member = CompanyMember("samuel")
println(member) // toString
println(member == CompanyMember("samuel")) //equals

결과는 다음과 같다.


CompanyMember(samuel)

true




이제 패턴 매칭을 해본다. printMember 메소드 하나를 만들고, case-match 문을 간단히 추가하다.


def printMember(member: Member) {
member match {
case CompanyMember(n) => println("Company Member")
case SchoolMember(n) => println("School Member")
case CommunityMember(n) => println("Community Member")
case _ => println("unknown")
}
}

실행 코드는 다음과 같다.


val list = List(CompanyMember("samuel"), CommunityMember("matt"), 
                SchoolMember("jeff"))
list.foreach { printMember }

결과는 다음과 같다.


Company Member

Community Member

School Member



case class가 abstract class Member를 상속받았지만, 이를 trait로 바꿔도 동작한다.


sealed trait Member

case class CompanyMember(name: String) extends Member {}
case class SchoolMember(name: String) extends Member {}
case class CommunityMember(name: String) extends Member {}








지금까지, 실행 코드는 지금까지 object Main에 def main(args: Array[String]): Unit을 사용했다.


object Main {
def printMember(member: Member) {
member match {
case CompanyMember(n) => println("Company Member")
case SchoolMember(n) => println("School Member")
case CommunityMember(n) => println("Community Member")
case _ => println("unknown")
}
}

def main(args: Array[String]): Unit = {
val list = List(CompanyMember("samuel"), CommunityMember("matt"), SchoolMember("jeff"))
list.foreach { printMember }
}
}


object Main이 App 트레이트를 상속하면, 조금 단순해 질 수 있다.



object Main extends App {
def printMember(member: Member) {
member match {
case CompanyMember(n) => println("Company Member")
case SchoolMember(n) => println("School Member")
case CommunityMember(n) => println("Community Member")
case _ => println("unknown")
}
}

val list = List(CompanyMember("samuel"), CommunityMember("matt"), SchoolMember("jeff"))
list.foreach { printMember }
}


내부 구현은 아래 링크를 참조한다.

https://charsyam.wordpress.com/2015/02/27/%EC%9E%85-%EA%B0%9C%EB%B0%9C-scala-%EC%9D%98-app-trait%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94%EA%B0%80/



나는 여기서 디컴파일된 부분을 살펴본다.

object Main extends App {
println("App")
}


Main$.class 파일에 해당 내용이 담겨져 있다. 


public final class Main$

    implements App

{


    public long executionStart()

    {

        return executionStart;

    }


    public String[] scala$App$$_args()

    {

        return scala$App$$_args;

    }


    public void scala$App$$_args_$eq(String x$1[])

    {

        scala$App$$_args = x$1;

    }


    public ListBuffer scala$App$$initCode()

    {

        return scala$App$$initCode;

    }


    public void scala$App$_setter_$executionStart_$eq(long x$1)

    {

        executionStart = x$1;

    }


    public void scala$App$_setter_$scala$App$$initCode_$eq(ListBuffer x$1)

    {

        scala$App$$initCode = x$1;

    }


    public String[] args()

    {

        return scala.App.class.args(this);

    }


    /**

     * @deprecated Method delayedInit is deprecated

     */


    public void delayedInit(Function0 body)

    {

        scala.App.class.delayedInit(this, body);

    }


    public void main(String args[])

    {

        scala.App.class.main(this, args);

    }


    public final void delayedEndpoint$com$google$Main$1()

    {

        Predef$.MODULE$.println("App");

    }


    private Main$()

    {

        scala.App.class.$init$(this);

        delayedInit(new Main.delayedInit.body(this));

    }


    public static final Main$ MODULE$ = this;

    private final long executionStart;

    private String scala$App$$_args[];

    private final ListBuffer scala$App$$initCode;


    static

    {

        new Main$();

    }

}




이제, 간단한 보조 생성자를 살펴본다. def this를 사용할 수 있다.


class Test(n: Int, m: Int) {
def this(n: Int) = this(n, 0)
}

object Main extends App {
new Test(1, 0)
new Test(1)
}


전제 조건을 확인할 수 있는 require가 있다.



class Test(n: Int, m: Int) {
require(n > 0)
def this(n: Int) = this(n, 0)
}

object Main extends App {
new Test(1, 0)
new Test(1)
new Test(-1)
}


다음처럼 IllegalArgumentException이 발생한다. 


Caused by: java.lang.IllegalArgumentException: requirement failed

at scala.Predef$.require(Predef.scala:212)



참고로 동일한 기능으로 assert도 있다.


assert(n > 0)

다음처럼 AssertionError가 발생한다.


Exception in thread "main" java.lang.AssertionError: assertion failed



require는 생성자 매개변수의 검증을 위해, assert는 내부 로직의 검증을 위해 사용하는 것이 좋은 것 같다.






다음은 합성 타입(compound type)이다. 두 trait를 합쳐진 합성된(compound, 교집합) 채로 사용한다. 이를 통해 강력한 타입 시스템을 가지면서 유연성을 가질 수 있다. 


아래 예시는 joinAndFork 메소드에 적용할 수 있는 메소드는 Joinable과 Forkable을 상속한 클래스라고 명시적으로 규정할 수 있다. 


trait Joinable {
def join: String = { "join" }
}

trait Forkable {
def fork: String = { "fork" }
}

class JoFo extends Joinable with Forkable {
override def join: String = super.join
override def fork: String = super.fork
}

object Main extends App {

def joinAndFork(obj: Joinable with Forkable): Joinable = {
println(obj.join)
println(obj.fork)
obj
}
joinAndFork(new JoFo)
}

결과는 다음과 같다.


join

fork






apply 메소드와 unapply 메소드에서 사용했던 Member 클래스를 제네릭 클래스로 만들어본다.


class Member[T](_id: Int, _name: T) {
private var id = _id
private var name : T = _name

def get(_id: Int): T = {
name
}

def set(_id: Int, _name: T) = {
id = _id
name = _name
}

override def toString(): String = "(" + id + ", " + name + ")";
}

실행을 간단히 해본다.

val member = new Member(1, "aa")
println(member.get(1))

결과는 aa가 출력된다. 





다음은 묵시적 변환, 묵시적 클래스, 묵시적 매개변수 공부한 내용이다. 


http://knight76.tistory.com/entry/scala-class-%EC%98%88%EC%8B%9C-2-%EC%95%94%EC%8B%9C%EC%A0%81-%EB%B3%80%ED%99%98implicit-conversion


Posted by '김용환'
,