Scala는 객체지향 프로그래밍을 지원하기 위해 클래스를 제공한다.
타 언어와 비교했을 때 더 간소한 문법을 가지고 있다.
기본적으로 Java와 같은 keyword를 사용하는 특징들이 있다.
Scala에서 클래스를 만드는 방법은 아주 간단하다. 아래 Book
클래스에는 책 제목 title
과 저자 author
가 멤버변수로 담겨져 있다.
class Book(var title: String, var author: String)
인스턴스는 클래스로부터 만들어진 각각의 객체를 뜻한다.
클래스가 설계도라고 하면 인스턴스는 그 설계도로 만들어진 각각의 물건이라고 생각하면 된다.
Scala 에서 인스턴스는 아래와 같이 만든다.
val myBook1 = new Book("My awesome book 1", "Me")
val myBook2 = new Book("My awesome book 2", "Me")
Scala 에서는 게터(getter)와 세터(setter) 가 자동으로 설정된다.
따라서 위의 클래스 정의 한 번으로 기본적인 기능들을 간단하게 사용할 수 있다.
예시
println(myBook1.title)
// 출력: "My awesome book 1"
myBook1.title = "My awesome book 1 updated!"
println(myBook1.title)
// 출력: "My awesome book 1 updated!"
위 기능을 제공하는 Java 클래스를 만드려고 했다면 Scala 보다 훨씬 긴 코드가 필요하다.
public class Book {
private String title;
private String author;
public Book(String title, String author) {
this.title = title;
this.author = author;
}
public String getTitle() {
return this.title
}
public String getAuthor() {
return this.author
}
public void setTitle(String title) {
this.title = title;
}
public void setAuthor(String author) {
this.author = author;
}
}
위 코드가 Scala 에서는 단 한 줄로 표현이 가능하다.
class Book(var title: String, var author: String)
이전에 클래스를 만들 때는 (변경 가능한 변수인) var
을 사용했는데, (변경 불가능한 변수인) val
을 사용하면 필드를 read-only 로 만들 수 있다.
class Book(val title: String, val author: String) // var 이 아닌 val!
val myBook1 = new Book("My awesome book 1", "me")
myBook1.title = "My awesome book 1 updated!" // Not allowed!
Scala 에서 객체지향 코드를 작성할 때는 변수를 변경할 수 있도록
var
을 사용하자.
함수형 프로그래밍을 할 때는 이와 같은 클래스보다 나중에 설명할case class
라는 것을 사용한다.
보조 생성자는 디폴트 생성자 외의 다른 방법으로 클래스 인스턴스 객체를 만드는 방법을 의미한다.
보조 생성자를 사용하면 클래스를 유연하게 만들 수 있다. this
메서드를 아래 규칙에 따라 정의함으로써 만들 수 있다.
this
보조생성자의 생성 변수 타입이 달라야 한다.이해가 어려우니 예시를 봐보자.
val DefaultAuthor = "me"
class Book(var title: String, var author: String) {
def this(title: String) = {
this(title, DefaultAuthor)
}
}
val myBook1 = new Book("My awesome book 1", "me")
val myBook2 = new Book("My awesome book 2") // Works!
println(myBook2.author)
// 출력: "me"
이처럼 보조 생성자를 사용하면 기본값을 사용해 객체를 만들 수 있다.
이전 보조 생성자가 헷갈린다면 걱정할 필요 없다.
Scala 에서 기본값 설정은 쉬워서 보조 생성자를 사용할 일은 많이 없다.
또한 명명변수를 사용해서 코드를 더 깔끔하고 읽기 편하게 작성할 수 있다.
class Book(
var title: String = "Default Title",
var author: String = "me"
) {
}
val myBook = new Book()
val myBook2 = new Book(title="My Title") // 생성자 변수 이름을 지정해 준다.
// val myBook2 = new Book("My Title") 보다 더 정확한 의미 파악이 가능해진다.
println(myBook.title)
// 출력: "Default Title"
println(myBook.author)
// 출력: "me"
println(myBook2.title)
// 출력: "My Title"
trait
(특성)는 Scala 의 아주 좋은 기능 중 하다나. Java 로 치면 Interface 와 비슷하지만 약간 다르다. trait
는 추상 클래스 (Abstract Class) 로도 사용할 수 있다.
자바의 Interface 처럼 어떻게 사용되는지 간단한 예시를 먼저 보자. 우선 trait 을 정의한다.
trait Car {
def engineStart(): Unit // Unit 은 Java 의 void 와 비슷하다고 생각하면 된다.
def engineStop(): Unit
}
자동차는 엔진 시동을 걸 수 있고 끌 수 있다. 세단, SUV 모두 자동차이기에 Car trait
을 extend
할 수 있다.
class Sedan extends Car {
def engineStart(): Unit = println("Engine Start")
def engineStop(): Unit = println("Engine Stop")
}
// 리턴타입 없이 아래처럼 작성해도 된다.
class SUV extends Car {
def engineStart() = println("Engine Start")
def engineStop() = println("Engine Stop")
}
Scala 에서는 with
키워드를 사용해 클래스에서 여러 특성을 사용할 수 있다. 예를 들어 버스와 택시같은 차들은 결제 기능도 있을 수 있다
trait PaymentModule {
def collectPayment(amount: Int): Boolean
}
class Bus extends Car with PaymentModule {
// Car
def engineStart(): Unit = println("Engine Start")
def engineStop(): Unit = println("Engine Stop")
// PaymentModule
def collectPayment(amount: Int): Boolean = {
// 결제를 위한 기능을 여기서 수행합니다.
return true
}
}
def main(args: Array[String]): Unit = {
val sedan: Car = new Sedan()
val suv: Car = new Suv()
val bus: Car = new Bus()
val cars = List(sedan, suv, bus)
cars.foreach(it => {
it.engineStart()
it.engineStop()
})
bus.asInstanceOf[PaymentModule].collectPayment(1600)
}
Scala 의 추상 클래스는 Java 의 추상 클래스와 비슷하다.
하지만 대부분의 경우 특성 (Trait)으로 충분해서 추상 클래스는 많이 사용할 일이 없다.
추상 클래스는 아래와 같은 경우에만 사용한다.
trait Car(name: String) // Not allowed! 컴파일 에러
abstract class Car(name: String) // OK
추상 클래스는 특성과 비슷한 문법을 가지고 있다. 이전에 봤던 자동차 특성을 추상 클래스로 만들어 보자.
abstract class Car (name: String) {
def engineStart(): Unit = println("Engine Start")
def engineStop(): Unit = println("Engine Stop")
def accelerate(): Unit // 추상 메소드
def brake(): Unit = println(s"$name braking!")
}
추상 메소드는 Car 추상 클래스를 상속받은 객체에서 (예를 들면 Bus 클래스와 같은) 직접 정의를 할 수 있다. 추상 메소드가 아닌 경우에도 override
키워드를 통해 구현을 재정의 할 수 있다.
class Bus(name: String) extends Car(name) {
override def engineStart() = println("Bus Engine Start")
def accelerate() = println("Bus accelerating!")
}
val myBus = new Bus("402")
myBus.engineStart
// 출력: "Bus Engine Start"
myBus.accelerate
// 출력: "Bus accelerating!"
myBus.brake
// 출력: "402 braking!"
new Bus("402")
실행 시 흐름new Bus("402")
class Bus(name: String)는 extends Car(name)를 통해 부모 생성자인 Car(name)에게 "402"를 전달한다.
따라서 Car 클래스의 생성자도 "402"를 매개변수 name으로 받아 초기화됨.
이 name은 Car 클래스의 메소드 brake() 안에서 사용됨:
def brake(): Unit = println(s"$name braking!")
abstract class Car(name: String)
name
은 생성자 인자일 뿐 클래스 내부 필드는 아님.하위 클래스에서 상위 클래스 생성자 호출
class Bus(name: String) extends Car(name)
에서 Car(name)
은 상위 생성자 호출."402"
가 상위 클래스 생성자로 그대로 전달됨.name
은 어디에 저장되어 있는가?
name: String
은 생성자 매개변수이므로, 클래스 안에서 사용 가능하지만 필드가 아님.val
로 바꿔야 함:abstract class Car(val name: String)