scala는 함수형 프로그래밍언어 이기도 하면서 java, python과 같이 객체지향 프로그래밍언어 이기도 하다. scala에서 객체를 정의하는 방법에 대해 알아보자.
하나의 인스턴스 밖에 가질 수 없는 클래스, singleton 객체이다. 객체를 직접 생성하는 대신 아래와 같이 이름으로 해당 객체에 직접 접근한다.
object Validator {
def validate(stamp: Stirng): Unit = {...}
}
Validator.validate(...)
해당 객체는 reference 될 때 lazy하게 instantiate 된다. scala에는 static keyword가 존재 하지 않고, static하길 원하는 멤버(i.e. main
)는 singletone 객체인 object에 선언한다.
java의 class와 동일하다. 단 constructor가 따로 존재 하지 않으며, method 밖의 모든 라인이 객체 instantiate시 사용된다. 아래와 같이 정의할 수 있다.
class Connector(user: String, password: String) {
val connection = ...
def connect(): Unit = {...}
}
아래와 같이 객체를 생성할 수 있다.
val dbConnector = new Connector(conf.user, conf.password)
일반적인 class와 비슷하지만 조금 다른 점이 있다. 아래와 같이 정의 후 사용할 수 있다.
case class Connector(user: String, password: String)
val dbConnector = Connector(conf.user, conf.password)
new
keyword가 사용이 안된다. 자동으로 생성되는 메소드(i.e. apply
, toString
, ...)가 클래스에 포함되어 객체 생성에 사용되기 때문이다. case class는 immutable한 데이터를 표현할 때 유용하며, 패턴 매칭에도 도움이 된다. 이 부분은 추후 정리하겠다.
반면 case class는 계층적인 클래스 구조에서는 유용하지 않을 수 있다. 자동 생성되는 메소드들은 서브 클래스에 새로 추가되는 필드들을 고려하지 않고, 따라서 case class를 상속받는 class에서 자동 생성된 메소드들을 사용시 유효하지 않은 결과를 가져올 수 있다.
trait은 java의 interface와 유사하며, class간에 interface와 필드를 공유하는 데 사용된다. trait은 instantiate 될 수 없다. 다음과 같이 정의할 수 있다.
trait Iterator[A] {
def hasNext: Boolean
def next(): A
}
제너릭 타입을 사용하여 trait을 정의하였다. 따라서 재사용성이 좋다. 정의한 trait을 상속받는 class를 아래와 같이 선언하였다.
class IntIterator(to: Int) extends Iterator[Int] {
private var current = 0
override def hasNext: Boolean = current < to
override def next(): Int = {
if (hasNext) {
val t = current
current += 1
t
} else 0
}
}