1. 추상 타입
추상 타입을 이용하면 선언 시점에서 어떤 타입인지 알려지지 않은 타입을 참조할 수 있다.
추상타입을 사용하지 않는 아래 예제를 보면
<java />
class Food
abstract class Animal {
def eat(food: Food)
}
class Grass extends Food
class Cow extends Animal {
// override def eat(food: Grass) {} // 파라미터 타입이 다르기 때문에 오버라이드 할 수 없다.
override def eat(food: Food) {}
}
- 동물(
Animal
)은 음식(Food
)을 먹는다. - 소(
Cow
)는 풀(Grass
)을 먹는다. (소는 동물을 상속받고, 풀은 음식을 상속받는다)
하지만, 파라미터가 다르기 때문에 Animal
을 상속 받는 Cow
는 def eat(food: Grass)
로 오버라이드(Override
) 할 수 없다. (단지, 메소드 명이 같고 파라미터가 다른 오버로드딩(Overloading
) 관계일 뿐)
추상 타입을 이용하면 위 코드를 모델링 할 수 있다.
<java />
class Food
abstract class Animal {
type SuitableFood <: Food // <: Food는 Food 를 상속받는 서브 클래스들을 의미함
def eat(food: SuitableFood) // Food 를 상속받는 서브 클래스들을 파라미터로 가짐
}
class Grass extends Food
class Cow extends Animal {
override type SuitableFood = Grass // Food 의 서브클래스라는 것을 알려줌
override def eat(food: Grass) {}
}
type
키워드와<: Food
를 이용해서,Food
를 상속받는 서브클래스 타입이라는 것을 명시해준다.Grass
는Food
의 서브클래스이다.Cow
에서type
을 이용해서Grass
가Food
의 서브클래스라는 것을 명시한다.eat
메소드를 오버라이드(override
) 할 수 있다.
만약, Food
를 상속받는 Grass
와 Fish
가 있을 때, Cow
에게 Fish
를 먹이면 컴파일 오류가 발생한다. 반대로 Grass
를 먹일때는 오류가 발생하지 않는다. (의도한대로 동작을 한다)
<java />
val cow = new Cow()
cow.eat(new Grass)
class Fish extends Food
cow.eat(new Fish) // 컴파일 에러

Grass 와 Fish 모두 Food를 상속 받는 서브클래스이지만, Grass와 Fish는 아무런 관계가 없기 때문에 Cow 클래스의 eat 메소드에 파라미터 타입인 Grass가 아닌, Fish 타입을 넣었기 때문에 오류가 발생한다.
1.1. 경로 의존 타입
곰(bear
) 클래스를 생성해보자.
<java />
class Grass extends Food
class Fish extends Food
class Cow extends Animal {
override type SuitableFood = Grass
override def eat(food: Grass) {}
}
// Bear 클래스 생성
class Bear extends Animal {
override type SuitableFood = Fish
override def eat(food: Fish) {}
}
Cow
와 Bear
는 동일한 SuitableFood
타입을 갖는다.
그렇다면, Bear
에게 Cow
의 SuitableFood
를 먹이면 정상 동작을 할까? 답은 No
<java />
val cow = new Cow
cow.eat(new Grass)
val bear = new Bear
bear.eat(new cow.SuitableFood) // 컴파일 오류 발생
bear.eat(new bear.SuitableFood)

타입의 이름이 SuitableFood로 같지만, 경로 의존 타입(path-dependent type)이 다르다. (쉽게 말해서, 타입이 다르다는 뜻)
cow.SuitableFood
!= bear.SuitableFood
- 소의 SuitableFood 타입은
cow.SuitableFood
이다. - 곰의 SuitableFood 타입은
bear.SuitableFood
이다.
1.1.1. 자바와의 차이점
<java />
class Outer {
class Inner
}
자바에서는 위 내부 클래스(Inner)를 표현 할 때, Outer.Inner
라고 표현한다.
스칼라에서는 내부 클래스를 표현할 때, Outer#Inner
로 표현하다.
자바와 마찬가지로 스칼라에서도 내부 클래스 인스턴스에는 그 인스턴스를 둘러싸고 있는 외부 인스턴스(Outer
)를 가리키는 참조값을 가지고 있다.
따라서, 외부 인스턴스 참조값이 있어야 내부 클래스에 접근 할 수 있다. (반대로 말하면, 외부 인스턴스를 지정하지 않으면, 내부 클래스에 접근 할 수 없다)
<java />
val o1 = new Outer
val o1_i = new o1.Inner
val o1_i2 = new Outer#Inner // 오류. 외부 인스턴스 없이 Inner 클래스에 접근할 수 없다.

1.2. 세분화한 타입
풀을 먹는 동물들이 포함된 목초지(Pasture) 클래스를 만들어 보자.
<java />
class AnimalThatEatsGrass extends Animal {
override type SuitableFood = Grass
override def eat(food: Grass) = {}
}
class Pasture { // 목초지
var animals: List[AnimalThatEatsGrass] = Nil
}
위에서 Cow
라는 풀을 먹는 동물을 만들었다.
하지만, 소 뿐만 아니라 다른 동물을 포함하기 위해, 풀을 먹는 동물(AnimalThatEatsGrass
)을 만들어서 또 다시 선언하는 것은 장황하다.
세분화한 타입을 사용하면, 위 문제를 해결할 수 있다.
<java />
class Pasture2 {
var animals: List[Animal{type SuitableFood = Grass}] = Nil
}
따로 클래스를 만들 필요없이 중괄호로 멤버 목록을 덧붙이면 된다.
'Language > Scala' 카테고리의 다른 글
Scala의 열거형(Enumeration type) (0) | 2020.02.23 |
---|---|
Scala 추상 val 초기화 (0) | 2020.02.23 |
Scala 추상 멤버 (타입멤버, 메소드, val, var) (0) | 2020.02.22 |