class ChecksumAccumulator {
// 여기에서 클래스를 정의
// 멤버(member) : 클래스 내에 정의된 필드와 메소드
// 메소드는 def 로 정의하며 실행할 코드를 담는다.
var sum = 0
}
// 클래스를 인스턴스화 할 때, 스칼라 런타임은 해당 객체의 상태 (각 변수들의 내용)을 담아둘 메모리를 확보
val acc = new ChecksumAccumulator
val csa = new ChecksumAccumulator
acc.sum = 3
메모리에서 각 객체의 모양은 다음과 비슷할 것이다.
ChecksumAccumulator 내에서 정의한 sum 이 var(mutable)이기 때문에, 나중에 다른 Int 값을 sum에 재할당 할 수 있다.
ex ) acc.sum = 3
acc가 val(immutable)임에도 불구하고, acc 가 참조하는 객체의 내부를 변경할 수 있다.
acc와 csa가 val(immutable)이기 때문에 다른 객체를 할당하지 못한다.
객체의 상태를 해당 인스턴스가 살아있는 동안 항상 바르게 유지하는 것
$ cassTest.scala
package com.ahnlab.scala
class classTest{
private var sum = 0 // 비공개 필드(private) 으로, 외부에서 직접 접근할 수 없다. 클래스 내부의 함수에서만 접근이 가능하다.
def add(b: Byte): Unit = { // 클래스 내부의 함수에서 비공개 필드 상태를 변경
sum += b
}
def checksum(): Int = { // 비공개 필드를 return
return (sum & 0xFF)
}
}
-----------------------------------
$ test.scala
package com.ahnlab.scala
object test extends App{
println("Hello, World!")
val acc = new classTest
val asc = new classTest
acc.add(10) // 클래스 함수를 통해 sum 필드 변경
println(acc.checksum()) // return 을 통해 sum을 확인할 수 있다.
}
스칼라는 멤버를 공개적(public)으로 선언하기 위해서는 접근 수식자를 정의하지 않는다. (Java의 public) 기본 접근 수준은 전체 공개이다.
class classTest{
private var sum = 0
def add(b: Byte): Unit = { # 파라미터 b 는 val로 기본으로 정의된다.
b = 1 # 파라미터를 재정의하게 되면, 컴파일 에러가 발생.
sum += b
}
스칼라는 문장 끝에 세미콜론(;)을 생략할 수 있다.
한 줄에 여러 문장을 넣을려면 세미콜론으로 구분은 해줘야한다.
val s = "hello"
val a = "world"; println(a)
-------
x
+y
# 스칼라는 위 프로그래밍을 x와 +y로 파싱한다. 의도가 x + y라면, 아래와 같이 괄호로 감싸서 표현할 수 있다.
(x
+y)
# 아래와 같이 +를 줄의 끝에 넣어서 표현할 수 있다. (스칼라는 줄 끝에 연산자 배치하는 게 일반적인 코딩 스타일 이다.)
x +
y +
z
다음 세가지의 경우가 아니라면, 세미콜론과 똑같이 취급한다.
어떤 줄의 끝이 어떤 명령을 끝낼 수 있는 단어로 끝나지 않는다. 즉 마침표(.)나 중위 연산자 등이 줄 의 맨 끝에 있다.
다음 줄의 맨 앞이 문장을 시작할 수 없는 단어로 시작한다.
줄이 (...) 같은 괄호 사이에 있거나, [...] 같은 각괄호 사이에서 끝난다. 어차피 이런 경우 내부에 여러 문장이 들어갈 수 없다.
스칼라 클래스에는 java와 다르게 static 멤버가 없다. (정적 변수, 정적 메소드)
대신 싱글톤 객체(Singleton object)를 제공한다.
싱글톤 객체는 object 라는 키워드로 정의한다.
싱글톤 객체의 이름이 클래스와 같을 때, 그 객체는 클래스의 동반 객체이며, 클래스는 동반 클래스 이다.
다만 클래스와 동반 객체는 반드시 같은 소스 파일 내에 정의되어야 한다.
클래스와 동반 객체는 상대방의 비공개 멤버에 접근 가등
// 클래스 ChecksumAccumulator 정의
class ChecksumAccumulator{
private var sum = 0
def add(b: Byte) = sum += b
def checksum(): Int = return (sum & 0xFF)
}
// 싱글톤 객체 ChecksumAccumulator 정의
object ChecksumAccumulator extends App{
private val cache = mutable.Map.empty[String, Int]
def calculate(s: String) : Int =
if (cache.contains(s))
cache(s)
else {
val acc = new ChecksumAccumulator // 여기서의 ChecksumAccumulator 는 class이다. new는 클래스를 인스턴스화 할 때만 사용한다.
for (c <- s)
acc.add(c.toByte)
val cs = acc.checksum()
cache += (s -> cs) // 한번 계산한 checksum을 캐싱하기 위한 변경 가능한 맵
cs
}
}
위와 같이 사용한 cache는 메모리를 희생시켜 계산 시간을 버는 것으로써 성능의 문제 가능성이 있다.(예제를 위한 예시일 뿐)
→ 일반적으로 메모리 문제를 해결할 수 있을 경우 캐시 사용을 권장하며, scala.collection, jcl, WeakHashMap 과 같은 weak map을 사용하는게 좋다.
싱글톤 객체의 메소드도 자바의 정적 메소드와 비슷한 방식으로 호출할 수 있다.
싱글톤 객체 이름 뒤에 점을 찍고 메소드 이름을 사용하여 호출 할 수 있다.
ChecksumAccumulator.calculate("test test")
싱글톤 객체는 1급 계층으로써 타입을 정의하지 않는다.(스칼라 수준의 추상화에서)
→ 객체 정의만 있다면, 해당 객체 타입의 객체를 만들 수 없다.(동반 클래스를 정의해야 한다.)
싱글톤 객체는 슈퍼클래스 확장(extend)하거나 트레이트를 믹스인(mix in)할 수 있다
클래스와 싱글톤 객체의 차이
→ 싱글톤 객체는 파라미터를 받을 수 없고 클래스는 받을 수 있다.
컴파일러는 싱글톤 객체를 합성한 클래스의 인스턴스로 구현하고, 이를 정적 변수가 참조
→ ChecksumAccumulator 싱글톤 객체를 위해 만들어낸 클래스의 이름은 ChecksumAccumulator$
동반 클래스가 없는 싱글톤 객체를 독립객체(독립 싱글톤객체) 라고 한다.
→ 필요한 도구메소드를 한곳에 모아둘 때 사용
→ 스칼라 애플리케이션의 진입점을 만들때 사용
스칼라 프로그램을 실행하려면 Array[String]을 유일한 인자로 받고 Unit 을 반환하는 main이라는 메소드가 들어 있는 독립 싱글톤 객체 이름을 알아야 한다.
// Summer 애플리케이션 정의
import ChecksumAccumulator.calculate // ChecksumAccumulator 객체의 calculate 메소드를 임포트한다.
object Summer {
// 애플리케이션의 시작점
def main(args: Array[String]) = {
for (arg A- asgs)
println(arg + ": " + calculate(arg))
}
}
참고
스칼라는 항상 java.lang 과 scala 패키지의 멤버를 암시적으로 임포트한다. 또한 scala 패키지에 있는 Predef 라는 싱글톤 객체의 멤버도 항상 임포트한다.
(Predef.println, Predef.console, Predef.assert)
※ Scala는 자바처럼 공개클래스와 파일의 이름을 똑같이 않만들 수 있다. 하지만, 자바처럼, 클래스 이름과 파일 이름을 똑같이 만드는 것을 권고한다. (프로그래머의 작업 편의를 위해)
$ scalac ChecksumAccumulator.scala Summer.scala
※ 하지만, 컴파일 시간이 느리다. 매번 컴파일러를 시작할 때 마다 소스 코드를 처리하기도 전에 jar 파일의 내용을 검사, 다른 초기화 작업을 수행하느라...
그래서 fcs(Fast Scala Compiler) 라고 하는 스칼라 컴파일러 데몬을 사용할 수 있다. scalac나 fsc 나 둘 다 Java Class 파일이 생긴다.
Scala인터프리터는 Scala코드를 Java 바이트 코드로 컴파일한 다음, 그 바이트코드를 클래스 로더에 읽어서 실행하는 매커니즘을 사용
$ fsc ChecksumAccumulator.scala Summer.scala
이제 main 메소드가 있는 Summer 애플리케이션을 실행할 수 있다.
$ scala Summer of love
스칼라는 scala.App 트레이트를 제공한다.
import ChecksumAccumulator.calculate
object FallWinterSpringSummer extends App {
for (season <- List("fall", "winter", "spring")) pr ntln(season + " + calculate(season))
}