jam 블로그

러닝스칼라 8장 - 클래스 본문

IT Book Study/러닝 스칼라

러닝스칼라 8장 - 클래스

kid1412 2020. 9. 28. 23:38
728x90

클래스란?

데이터 구조와 함수의 조합으로, 객체지향 언어의 핵심 구성 요소

특징

  • 상속(inheritance) : 다른 클래스로 확장할 수 있어서 서브 클래스와 슈퍼 클래스의 계층 구조 생성 가능
  • 다형성(polymorphism) : 서브 클래스가 부모 클래스를 대신하여 작업하는 것이 가능
  • 캡슐화(encapsulation) : 클래스의 외관을 관리하는 프라이버시 제어를 제공
class User
val u = new User
val u1 = new User
val isAnyRef = u.isInstanceOf[AnyRef]

/*
    defined class User
    u: User = User@4c478184
    u1: User = User@2e6ef924
    isAnyRef: Boolean = true
*/

AnyRef는 classes의 부모 타입입니다.

클래스 기본

**// 1번 예제**
class User {
  val name: String = "jmkim"
  def greet: String = s"Hello from $name"
  override def toString = s"User($name)"
}

val u = new User
println(u.greet)
/*
    defined class User
    u: User = User(jmkim)
    Hello from jmkim
*/

toString을 override 함으로써 기존에 User@4c478184 처럼 보여지는 것을
u: User = User(jmkim)처럼 변경 됩니다.

**// 2번 예제**
class User(val name: String) {
  def greet: String = s"Hello from $name"
  override def toString = s"User($name)"
}

val u = new User("jmkim")
println(u.greet)
/*
    defined class User
    u: User = User(jmkim)
    Hello from jmkim
*/

1번 예제와 다르게 name을 매개 변수로 받으면서 바로 클래스 필드로 사용하는 방법입니다.

다음은 리스트를 사용하여 다양한 것을 해봅니다.

val users = List(new User("jam"), new User("karl"), new User("jason"), new User("sophie"), new User("kai"), new User("lucas"), new User("jeff"))
val usersSize = users.size
val userNameSize = users map (_.name.length)
val sorted = users sortBy (_.name)
val jason = users find (_.name contains "jason")
val greet = jason map (_.greet) getOrElse "Hi"

val test = users find (_.name contains "test")
val greet1 = test map (_.greet) getOrElse "Hi"

/*
    users: List[User] = List(User(jam), User(karl), User(jason), User(sophie), User(kai), User(lucas), User(jeff))
    usersSize: Int = 7
    userNameSize: List[Int] = List(3, 4, 5, 6, 3, 5, 4)
    sorted: List[User] = List(User(jam), User(jason), User(jeff), User(kai), User(karl), User(lucas), User(sophie))
    jason: Option[User] = Some(User(jason))
    greet: String = Hello from jason
    test: Option[User] = None
    greet1: String = Hi
*/

클래스 상속, 다형성

상속 : 서브 클래스가 부모 클래스로부터 필드와 메소드를 extends 키워드로 확장된 것이며, override로
상속받은 메소드를 재정의 할 수 있습니다.

class A {
  def hi = "Hello from A"
  override def toString = getClass.getName
}

class B extends A
class C extends B {
  override def hi = "hi C -> " + super.hi
}

val hiA = new A().hi
val hiB = new B().hi
val hiC = new C().hi
/*
    defined class A
    defined class B
    defined class C
    hiA: String = Hello from A
    hiB: String = Hello from A
    hiC: String = hi C -> Hello from A
*/

다형성 : 서브 클래스는 자신의 부모 클래스를 확장하므로 부모의 필드와 메소드를 100% 지원하지만 그 역은 성립하지 않을 수 도 있습니다.

class A {
  def hi = "Hello from A"
  override def toString = getClass.getName
}

class B extends A
class C extends B {
  override def hi = "hi C -> " + super.hi
}

val a: A = new A
val a: A = new B
val b: B = new B
val b: B = new A

/*
    defined class A
    defined class B
    defined class C
    a: A = A
    a: A = B
    b: B = B
    Error:(14, 12) type mismatch;
        found   : A
        required: B
        val b: B = new A
*/

클래스 정의하기

입력 매개변수로 클래스 정의하기

class Car(val make: String, var reserved: Boolean) {
  def reserve(r: Boolean):Unit = {reserved = r}
}

val t = new Car("Tesla", false)
t.reserve(true)
println(s"My ${t.make} is now reserved? ${t.reserved}")
/*
    defined class Car
    t: Car = Car@2c4c333a
    My Tesla is now reserved? true
*/

val t2 = new Car(reserved = false, make = "Toyota")
println(t2)
/*
    t2: Car = Car@20a0dd40
    My Toyota is now reserved? false
*/

class Lotus(val color: String, reserved: Boolean) extends Car("Lotus", reserved)
val l = new Lotus("sliver", false)
println(s"Requested a ${l.color} ${l.make}")
/*
    defined class Lotus
    l: Lotus = Lotus@6067b0f0
    Requested a sliver Lotus
*/

입력 매개변수와 기본값으로 클래스 정의하기

class Car(val make: String, var reserved : Boolean = true, val year: Int = 2005) {
  override def toString = s"$year $make, reserved = $reserved"
}

val a = new Car("Acura")
val l = new Car("Lexus", year = 2010)
val p = new Car(reserved = false, make = "Porsche")
/*
    a: Car = 2005 Acura, reserved = true
    l: Car = 2010 Lexus, reserved = true
    p: Car = 2005 Porsche, reserved = false
*/

그 외의 클래스 유형

추상 클래스

추상 클래스는 다른 클래스들에 의해 확장되도록 설계되었으나 인스턴스를 생성하지 않는 클래스
서브클래스들이 필요로 하는 핵심적인 필드와 메소드를 실제 구현물을 제공하지 않으면서 정의하는 데에 사용

abstract class Car {
  val year: Int
  val automatic: Boolean = true
  def color: String
}

class Mini(val year: Int, val color: String) extends Car

val redMini: Car = new Mini(2005, "Red")
println(s"Got a ${redMini.color} Mini")
/*
    defined class Car
    defined class Mini
    redMini: Car = Mini@19fce221
    Got a Red Mini
*/

익명 클래스

익명클래스는 서브 클래스가 한번만 필요한 상황일 때 코드를 단순화 해주는 데에 도움을 줍니다.
추상 클래스를 따로 extends로 상속 받는 class를 만들지 않고 notification.register에 있는 것처럼
new Listener로 정의하여 익명 클래스를 만들어 넣습니다.

abstract class Listener {def trigger}
class Listening {
  var listener: Listener = null
  def register(l: Listener) {listener = l}
  def sendNotification() {listener.trigger}
}

val notification = new Listening()

notification.register(new Listener {
  def trigger {println(s"Trigger at ${new java.util.Date}")}
})

notification.sendNotification()
/*
    defined class Listener
    defined class Listening
    notification: Listening = Listening@1db58f45
    Trigger at Sat Jun 20 14:17:07 KST 2020
*/

그외의 필드와 메소드 유형

중복 정의된 메소드

동일한 이름과 반환값을 갖지만, 다른 입력 매개변수를 갖는 둘 또는 그 이상의 메소드를 가질 수 있습니다.

class Printer(msg: String) {
  def print(s:String):Unit = println(s"$msg: $s")
  def print(l:Seq[String]):Unit = print(l.mkString(", "))
}

new Printer("Today's Report").print("Foggy"::"Rainy":: "Hot" :: Nil)
/*
    defined class Printer
    Today's Report: Foggy, Rainy, Hot
*/

apply 메소드

apply 메소드는 기본 메소드 또는 인젝터 메소드로 불리며 메소드 이름 없이 호출 될 수 있습니다.

class Multiplier(factor: Int) {
  def apply(input: Int) = input * factor
}

val tripleMe = new Multiplier(3)
val tripled = tripleMe.apply(10)
val tripled2 = tripleMe(10)
/*
    defined class Multiplier
    tripleMe: Multiplier = Multiplier@43e42b0b
    tripled: Int = 30
    tripled2: Int = 30
*/

지연값

지연값은 자신이 처음 인스턴스화될 때에만 생성되며 val 키워드 앞에 lazy를 씁니다.
클래스 수명 내에 실행 시간과 성능에 민감한 연산이 한 번만 실행 될 수 있음을 보장하며,
보통 파일 기반의 속성, 데이터 베이스 커넥션 열기, 그 외 정말 필요한 경우 단 한번만 초기화 되어야 하는
불변의 데이터와 같은 정보를 저장하는데 사용합니다.

class RandomPoint {
  val x = {println("creating x"); util.Random.nextInt}
  lazy val y = {println("now y"); util.Random.nextInt}
}

val p = new RandomPoint()
println(s"Location is ${p.x}, ${p.y}")
println(s"Location is ${p.x}, ${p.y}")
/*
    defined class RandomPoint
    creating x
    p: RandomPoint = RandomPoint@6ecd22fd
    now y
    Location is 389314740, -1884004327
    Location is 389314740, -1884004327
*/

패키징

패키지는 스칼라 코드를 점으로 구분된 경로를 사용하여 디렉토리별로 정리할 수 있게 해줍니다.

mkdir -p src/com/oreilly
cat > src/com/oreilly/Config.scala
# class Config(val baseUrl:String = "http://localhost")
scalac src/com/oreilly/Config.scala
ls com/oreilly/Config.class
# com/oreilly/Config.class

패키징된 클래스에 접근하기

import java.util.Date
val d = new Date

import scala.collection.mutable._
// import scala.collection.mutable.{ArrayBuffer => ArrBuf, Queue}

val b = new ArrayBuffer[String]

b += "Hello"

val q = new Queue[Int]

q.enqueue(3,4,5)

val pop = q.dequeue
println(q)

/*
    import scala.collection.mutable._
    b: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer()
    res0: b.type = ArrayBuffer(Hello)
    q: scala.collection.mutable.Queue[Int] = Queue()
    res1: q.type = Queue(3, 4, 5)
    pop: Int = 3
    Queue(4, 5)
*/

패키징 구문

아래와 같이 package를 사용하여 만들 수 있습니다.

package com {
  package oreilly {
    class Config(val baseUrl: String = "http://localhost")
  }
}

val url = new com.oreilly.Config().baseUrl

프라이버시 제어

기본은 pubilc이며 상황에 따라서 protected, private를 사용하여 제어를 할 수 있습니다.

  • protected : 정의한 클래스와 서브 클래스의 코드에서만 접근이 가능합니다.
  • private : 정의한 클래스에서만 접근이 가능합니다.
class User(private var password: String) {
  def update(p: String): Unit = {
    println("Modifying the password!")
    password = p
  }
  def validate(p: String) = p == password
} 

val u = new User("1234")
val isValid = u.validate("4567")

u.update("4567")
val isValid = u.validate("4567")
/*
    defined class User

    u: User = User@2030ce87
    isValid: Boolean = false

    Modifying the password!
    isValid: Boolean = true
*/

프라이버시 접근 변경자

package com.oreilly {
  // []로 접근 블록을 지정해 줄 수 있습니다. 
  private[oreilly] class Config {
    val url = "http://localhsot"
  }

  class Authentication {
    private[this] val password = "jason"
    def validate = password.nonEmpty
  }

  class Test {
    println(s"url = ${new Config().url}")
  }
}

object Example extends App {
  val valid = new com.oreilly.Authentication().validate
  println(valid)

  new com.oreilly.Test
/*
    true
    url = http://localhsot
*/
}

종단 클래스와 봉인 클래스

종단 클래스(final class)는 더이상 상속을 받지 못하게 만들며, 변수에 final을 쓸 경우 override가 방지 됩니다.

봉인 클래스(sealed class)는 클래스의 서브클래스가 부모 클래스와 동일한 파일에 위치하도록 제한 합니다.
또한, 클래스의 계층 구조에 대해 안전하게 가정하는 코드를 작성 할 수 있습니다.

// 같은 파일 안에 class를 상속 할 경우
sealed class Fruit(color: String) {
  def printColor = println(color)
}

class Apple extends Fruit("Red") {
  def print = "Apple"
}

// 다른 파일에서 상속 할 경우
class Banana extends Fruit("Yellow") {

}
// illegal inheritance from sealed class Fruit
Comments