4. 클래스와 객체

dev.jhjhj·2021년 8월 17일
0

Scala

목록 보기
3/10

4.1 클래스, 필드, 메소드

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) 기본 접근 수준은 전체 공개이다.

스칼라 메소드의 파라미터 타입 (val)

class classTest{
  private var sum = 0          
  def add(b: Byte): Unit = {    # 파라미터 b 는 val로 기본으로 정의된다.
    b = 1  # 파라미터를 재정의하게 되면, 컴파일 에러가 발생.
    sum += b
  }

스칼라 권장 프로그래밍 스타일

  • 스칼라는 명시적으로 return을 사용하지 않아도, 메소드의 가장 마지막 계산 값을 return 한다.
    -> 메소드를 가장 작은 단위의 기능으로 설계하고, 각 기능들을 작게 나누어 프로그래밍 하는 것을 권장

4.2 세미콜론 추론

스칼라는 문장 끝에 세미콜론(;)을 생략할 수 있다.
한 줄에 여러 문장을 넣을려면 세미콜론으로 구분은 해줘야한다.

val s = "hello"
val a = "world"; println(a)

-------
x
+y
# 스칼라는 위 프로그래밍을 x와 +y로 파싱한다. 의도가 x + y라면, 아래와 같이 괄호로 감싸서 표현할 수 있다.
(x
+y)

# 아래와 같이 +를 줄의 끝에 넣어서 표현할 수 있다. (스칼라는 줄 끝에 연산자 배치하는 게 일반적인 코딩 스타일 이다.)
x +
y +
z

세미콜론 추론 규칙

다음 세가지의 경우가 아니라면, 세미콜론과 똑같이 취급한다.

  1. 어떤 줄의 끝이 어떤 명령을 끝낼 수 있는 단어로 끝나지 않는다. 즉 마침표(.)나 중위 연산자 등이 줄 의 맨 끝에 있다.

  2. 다음 줄의 맨 앞이 문장을 시작할 수 없는 단어로 시작한다.

  3. 줄이 (...) 같은 괄호 사이에 있거나, [...] 같은 각괄호 사이에서 끝난다. 어차피 이런 경우 내부에 여러 문장이 들어갈 수 없다.

4.3 싱글톤 객체

스칼라 클래스에는 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
    }
}
  1. 위와 같이 사용한 cache는 메모리를 희생시켜 계산 시간을 버는 것으로써 성능의 문제 가능성이 있다.(예제를 위한 예시일 뿐)
    → 일반적으로 메모리 문제를 해결할 수 있을 경우 캐시 사용을 권장하며, scala.collection, jcl, WeakHashMap 과 같은 weak map을 사용하는게 좋다.

  2. 싱글톤 객체의 메소드도 자바의 정적 메소드와 비슷한 방식으로 호출할 수 있다.

  3. 싱글톤 객체 이름 뒤에 점을 찍고 메소드 이름을 사용하여 호출 할 수 있다.
    ChecksumAccumulator.calculate("test test")

  4. 싱글톤 객체는 1급 계층으로써 타입을 정의하지 않는다.(스칼라 수준의 추상화에서)
    → 객체 정의만 있다면, 해당 객체 타입의 객체를 만들 수 없다.(동반 클래스를 정의해야 한다.)

  5. 싱글톤 객체는 슈퍼클래스 확장(extend)하거나 트레이트를 믹스인(mix in)할 수 있다

  6. 클래스와 싱글톤 객체의 차이
    → 싱글톤 객체는 파라미터를 받을 수 없고 클래스는 받을 수 있다.

  7. 컴파일러는 싱글톤 객체를 합성한 클래스의 인스턴스로 구현하고, 이를 정적 변수가 참조
    → ChecksumAccumulator 싱글톤 객체를 위해 만들어낸 클래스의 이름은 ChecksumAccumulator$

  8. 동반 클래스가 없는 싱글톤 객체를 독립객체(독립 싱글톤객체) 라고 한다.

→ 필요한 도구메소드를 한곳에 모아둘 때 사용
→ 스칼라 애플리케이션의 진입점을 만들때 사용

4.4 스칼라 애플리케이션

스칼라 프로그램을 실행하려면 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)

  • Summer 애플리케이션을 실행하려면 Summer.scala 파일로 저장
  • ChecksumAccumulator를 호출하므로, ChecksumAccumulator와 동반객체인 ChecksumAccumulator 를 함꼐 ChecksumAccumulator.scala 라는 파일에 저장

※ Scala는 자바처럼 공개클래스와 파일의 이름을 똑같이 않만들 수 있다. 하지만, 자바처럼, 클래스 이름과 파일 이름을 똑같이 만드는 것을 권고한다. (프로그래머의 작업 편의를 위해)

  • Summer.scala나 ChecksumAccumulator.scala는 스크립트가 아니기 때문에 해당 파일을 실행할 수 없다. 따라서 기본 스칼라 컴파일러인 scalac를 사용한다.
$ 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

4.5 App 트레이트

스칼라는 scala.App 트레이트를 제공한다.

  • 트레이트를 사용하기 위해서는 정의할 싱글톤 뒤에 extends App 이라고 선언한다.
  • main 메소드를 선언하는 대신, main 메소드에 넣고 싶은 코드를 extends App와 같이 선언된 싱글톤 객체에 정의하면 된다.
import ChecksumAccumulator.calculate

object FallWinterSpringSummer extends App {
	for (season <- List("fall", "winter", "spring")) pr ntln(season + " + calculate(season))
}
profile
어제보다 더 나은

0개의 댓글