관리 메뉴

HAMA 블로그

스칼라 강좌 (13) - 클래스와 객체 본문

Scala

스칼라 강좌 (13) - 클래스와 객체

[하마] 이승현 (wowlsh93@gmail.com) 2016. 7. 16. 10:30

클래스와 객체 

   

 

 클래스,필드,메소드
  
 객체 생성
  클래스는 객체의 청사진이다.
  class ChecksumAccumulator {
  } 

  이런 클래스 가 있을때  

  new ChecksumAccumulator  // 이렇게 해주면 객체가 만들어 진다.
  클래스 안에는 필드와 메소드를 넣을 수 있다. 이 둘을 합쳐 멤버라고 한다.

  

  필드는 var 이나 val 로 정의하며

  메소드는 def 로 정의 한다.

  class ChecksumAccumulator {
       var sum = 0 

  } 

 위의 클래스를 가지고 객체를 2개 만들면 

 val a = new ChecksumAccumulator 

 val b = new ChecksumAccumulator

 

 해당 객체안의 sum 필드는 다른 메모리를 참조 할 것이고 0 을 바라 볼 것이다.

 sum 이 var 이기 때문에, 나중에 다른 Int 값을 재할당 할 수 있다.

 a.sum = 3 // 요렇게 

 객체가 val 인데도 불구하고 내부의 값을 바꿀 수 있다는 점을 체크하라. 

 다만 처음 생성한 그 객체를 바라보고 있다는 확신은 가질 수 있다. 

 

 필드 접근성  

* 필드를 비공개로 만들어서 직접 접근하지 못하게 하라. private 을 사용한다. 

    - 아무것도 안붙히면 전체 공개. (자바와 다르다) 

 

 메소드 특성 

스칼라 메소드 파라미터에서 중요한 점은 이들이 val 이라는것이다.

  def add(b : Byte) : Unit = {

b = 1  // 파라미터가 val 이라 불가능 !

sum += b

   }

 * return 을 명시적으로 사용하지 말라. 

   스칼라는 메소드가 한 값을 계산하는 표현식인 것 처럼 생각하는걸 권장한다.

 * 메소드가 오직 하나의 표현식만 계산하는 경우 {} 중괄호를 없앨 수 있다.

   def add(b:Byte) : Unit = sum += b

 * 메소드의 결과 타입이 Unit 인 경우는 부수 효과를 위해 사용한다는 뜻이다. 

 * Unit 생략하고 = 를 없애서 {} 로 메소드를 나타낼 수 있는데, 이건 프로시저 처럼 보이게 하는데 
   프로시저란 오직 부수 효과를 얻기 위해서만 사용하는 메소드를 의미한다. 

   즉 def add(b: Byte) { sum += b}  처럼 사용한다는 뜻이데, 여기서 함정이 있는게 

   def add2 () { " test string " }  이건 Unit 를 리턴하며 
   def add3 () = { " test string " }  이건 String 을 리턴한다는 것이다. 

   즉 프로시저 처럼 작성하면 리턴이 무조건 Unit 이 된다는점~ 

 

 세미콜론 추론 

  스칼라에서는 보통 문장 끝의 세미콜론(;) 을 생략 할 수 있다.

  다만 한 줄에 여러 문장이라면 사용해야한다. 

  가끔 헷갈리는것은

  x

  + y 

 스칼라는 위 문장을 x 와 +y로 파싱한다. 

 (x

 +y)  이렇게 하거나 

+ 를 줄의 끝에 넣으면 우리가 의도하는대로 된다. 

x + 

y  

 

싱글톤 객체

스칼라와 자바의 가장 큰 차이점 중 하나는 스칼라는 정적 멤버가 없다는 것이다.

대신 전용으로 사용할 수 있는 싱글톤 객체를 제공한다. 

class 대신 object 라는 키워드를 사용한다.

import scala.collection.mutable.Map 

object ChecksumAccumulator {

	private val cache = Map[String, Int) ()
	def calculate(s: String) : Int = 

		if (cache.contains(s))
			cache(s)
		else {

			val acc = new ChecksumAccumulator

		for (c <- s)
			acc.add(c.toByte)
		val cs = acc.checksum()
		cache += (s -> cs)    // s 키, cs 값 으로 연관관계를 만듬. 
		cs
}

}

이 싱글톤 객체는 이름이 저 위에 있는 class ChecksumAccumulator 와 같다.

이렇게 싱글톤 객체는 동일하게 만들 수 있는데 이걸 동반 객체라고 하며 

같은 파일안에 있어야한다. 

이런 클래스와 동반객체는 상대방의 비공개 멤버에 접근 할 수 있게 된다.

즉 자바의 정적멤버가 외부에 따로 만들어져 있다고 보면 된다.

ChecksumAccumulator.calculate("hello") 이렇게 호출 할 수 있다. 

* 팁:  코드에서 맵 경우 WeakHashMap 같은 걸 사용하면 메모리가 부족할때 가비지콜렉터가 캐시의 원소를 수집.

- 싱글턴 객체는 파라미터를 받을 수 없고 클래스는 받을 수 있다.

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

  독립객체로는 유틸리티 메소드를 모아두는곳 이나 진입점으로 사용할 수 있다. 

 

스칼라 애플리케이션 

import ChecksumAccumulator.calculate

object Sumer {

	def main(args: Array[String]){

		for (arg <- args)

			println(arg : ": " + calculate(arg() )  // 메소드만 사용함!

	}

}

 * 스칼라는 항상 java.lang 과 scala 패키지의 멤버를 암시적으로 임포트한다.

또한 Predef 이라는 싱글톤 객체도 임포트하는데 여기엔 유용한 메소드가 많이 있다.

println 이라든지 assert 라든지..

 

 * 스칼라의 import 는 자바의 static import 로 생각 할 수 있다. 스칼라에서 다른 점은 싱글톤 뿐 아니라 어느 객체에서라도 멤버를 임포트 할 수 있다는 것이다.

* 자바는 공개  클래스 이름과 파일 이름이 같아야 하지만 스칼라에서는 상관없다. 

* scala.Application 이라는 트레이트를 통해 더 간단히 main 을 작성 할 수 있다.

import ChecksumAccumulator.calculate

object Sumer extends Application  {

	for (arg <- List("fall","winter"))

		println(arg : ": " + calculate(arg() )  // 메소드만 사용함!

}

 

클래스 예제

 

1) 간단한 클래스  

 

# 한줄 만으로 class 완성! 
class Person(var name: String, var age: Int) // 기본 생성자가 class 이름과 나란히~ 

# class 사용 
val al = new Person("Al", 42) 
al.name // "Al" 
al.age // 42

 

 

2) 자바와 다른 특이한 모습

 

// class 정의 
class Person(val firstName: String, val lastName: String) {
 println("the constructor begins") // 희안하죠? 객체로 만들어지는 동시에 호출됩니다.
 val fullName = firstName + " " + lastName 
 val HOME = System.getProperty("user.home"); 
 def foo { println("foo") } 
 def printFullName { println(fullName) } 

 printFullName // 희안하죠? 객체로 만들어지는 동시에 호출됩니다. 
 println("still in the constructor") // 희안하죠? 객체로 만들어지는 동시에 호출됩니다. } 

// 다음과 같이 나옵니다. 클래스 내부의 것들이 생성됨과 동시에 실행되네요. 
scala> val p = new Person("Alvin", "Alexander") 
the constructor begins 
Alvin Alexander 
still in the constructor 
p: Person = Person@68f507d2

 

3) 보조 생성자 

 this 를 사용하여  보조적 생성자를 만들수 있네요. (클래스 이름가지고 만들지 않음) 

 

class Pizza { // 기본 생성자에는 매개변수를 받는게 없군요. 
  var crustSize = 12 var crustType = "Thin" 
  def this(crustSize: Int) { // 매개변수 하나 받는 생성자~ 
     this() 
      this.crustSize = crustSize 
  } 
  def this(crustSize: Int, crustType: String) { // 매개변수 2개 받는 생성자 
       this(crustSize) 
       this.crustType = crustType 
  } 
  override def toString = { "A %s inch pizza with %s crust.".format(crustSize, crustType) } }

 

  println(new Pizza)
  println(new Pizza(14))
  println(new Pizza(16, "Thick"))

 

4) 생성자 매개변수에게 디폴트 값을~

 

class Socket (var timeout: Int = 10000) // 디폴트 값을 사용함 
val s = new Socket s.timeout // Int = 10000
val s = new Socket(5000) s.timeout // Int = 5000

 

5) 추상 클래스를 사용할 경우는 언제? 

 

추상클래스는 생성자 매개변수를 가질수 있지만, trait 는 그럴 수 없다. 
 * trait 는 자바의 인터페이스 비슷한 것 으로 추후에 설명 예정.

 

// 컴파일 안됨 trait Animal(name: String) 
// 추상 클래스는 가능 ! abstract class Animal(name: String)

 

abstract class BaseController(db: Database) {
  def save { db.save }
  def update { db.update }
  def delete { db.delete }
  def connect                             // abstract since it has no implementation
  def getStatus: String                   // an abstract method that returns a String
  def setServerName(serverName: String)   // an abstract method that takes a parameter
}

 

6) 스칼라 case 클래스 

 case 클래스는  유용한 행사코드를 자동으로  생성할 수 있다.

case class Person(var name: String, var age: Int) // class 앞에 case 를 붙여주는것으로

 

case class Person(var name: String, var age: Int) // class 앞에 case 를 붙여주는것으로

case class 를 사용하면 다양한 혜택과 자동으로 다음을 만들어 준다:

  • toString, equals, 와 hashCode  메소드를 만들어 준다.
  • 생성자 파라미터들에게 게터와 세터 속성이 부여된다.
  • 더이상 인스턴스로 만드는데 new 를 사용할 필요가 없다.
  • match/case 문에서 편리하게 이용할 수 있다.

 

Comments