* 제네릭과 폴리모픽 메소드
스칼라의 generic(제네릭) 클래스와 자바와 비슷하다.
class Stack[T] {
var elems: List[T] = Nil
def push(x: T) { elems = x :: elems }
def pop() { elems = elems.tail }
}
메소드에서 제네릭이 쓰이면 polymorphic methods라 한다.
def print[T](t: T): Unit = {
println(t)
}
* 타입 매개변수
스칼라에서 타입 바운드(type bounds)는 타입 매개변수와 타입 변수에 제약을 거는 행위이다. 이를 통해 타입에 안전하게(type safety) 코딩을 할 수 있도록 한다. 아래와 같은 3개의 타입 바운드가 존재한다.
- Upper Bound (자바에서는 extends)
- Lower Bound (자바에서는 super)
- Context Bound
이전에 View Bound( <%) : scala 2.10부터 deprecated되었고 Context Bound로 전환되었다.
먼저 Upper Bound (한국 말로 상위 타입 경계라 한다)
[T <: S ] 이렇게 표현할 수 있다. T는 타입 매개변수이고 S는 타입이다.
예시를 살펴보자.
class Member
class SchoolMember extends Member
class ClassMember extends SchoolMember
object Main extends App {
def print[T <: SchoolMember](t: T): Unit = {
println(t)
}
val member = new Member
val schoolMember = new SchoolMember
val classMember = new ClassMember
print(schoolMember)
print(classMember)
}
print메소드를 살펴보면, upper bound(<:)를 사용했다 .
print[T <: SchoolMember](t: T):
따라서 SchoolMember의 자식 클래스를 사용할 수 있도록 제한을 걸었다.
결과는 다음과 같다.
com.google.SchoolMember@3a03464
com.google.ClassMember@2d3fcdbd
upper bound(<:)의 제약을 넘어서면, 컴파일 타임 때는 발생하지 않지만,
타입 파라미터 바운드와 타입이 안 맞는다는 에러가 발생한다.
//print(member)
// Error :
// Error:(23, 3) inferred type arguments [com.google.Member]
// do not conform to method print's
// type parameter bounds [T <: com.google.SchoolMember]
// Error:(23, 9) type mismatch;
// found : Member
// required: T
다음은 Lower Bound이다 (한국 말로 하위 타입 경계라 한다)
자바의 super 개념과 동일하다.
간단한 예시를 살펴본다.
object Main extends App {
class LowerBounds[Parent] {
def print[T >: Parent](t: T) {
println(t)
}
}
class Parent
class Child extends Parent
val parent = new Parent
val child = new Child
val instance = new LowerBounds[Parent]
instance.print(parent)
instance.print(child)
}
Parent, Parent보다 큰 타입만 받도록 하는 예제이다. LowerBounds 클래스에 [T >: Parent] 라는 제약이 있는 print 메소드를 정의했다.
결과를 실행해본다.
Upper Bounds의 예시처럼 에러를 기대하겠지만, 실제로 테스트해보면 잘 출력된다. 단순한 Lower Bounds만으로는 스칼라가 에러를 출력하지 않는다. (에러가 안나는 원인에 대해서는 좀 더 공부해야 봐야겠다. )
com.google.Main$Parent@3a03464
com.google.Main$Child@2d3fcdbd
에러를 발생시키려면, implict으로 상속 구조를 표현해야 한다.
아래와 같이 LowerBounds 클래스를 재정의한다.
class LowerBounds[Parent] {
def print[Y](y: Y)(implicit ev: Parent <:< Y): Unit = {
println(y)
}
}
그러면, 아래 코드에서 에러가 발생한다.
instance.print(child)
에러 내용은 다음과 같다.
Error:(22, 17) Cannot prove that com.google.Main.Parent <:< com.google.Main.Child.
instance.print(child)
현재 Option의 많은 메소드가 바로 Lower bounds 의미를 가지고 있다. orNull 메소드를 보면, 이전 예시와 동일한 형태임을 알 수 있다.
@inline final def orNull[A1 >: A](implicit ev: Null <:< A1): A1
@inline final def getOrElse[B >: A](default: => B): B =
참고로, 좀 더 Lower Bounds를 살펴보려면, 프로그래밍 스칼라(오현석 역)에 Option을 가지고 설명이 아주 잘되어 있으니, 참고하기 바란다.
다음은 Context Bound이다.(한국 말로 문맥 바운드라 한다)
이를 타입 클래스 패턴이라고 하며, 아래 링크의 implicit class 부분을 참조한다.
http://knight76.tistory.com/entry/scala-class-%EC%98%88%EC%8B%9C-2-%EC%95%94%EC%8B%9C%EC%A0%81-%EB%B3%80%ED%99%98implicit-conversion
간단히 보려면, 아래 링크를 클릭한다.
https://github.com/FlyScala/ProgrammingScala/wiki/5.4-%ED%83%80%EC%9E%85-%ED%81%B4%EB%9E%98%EC%8A%A4-%ED%8C%A8%ED%84%B4
이제는 Type Variance(타입 변성)에 대한 설명이다.
간단히 설명하면 T가 S의 하위 타입이면, Stack[T]는 Stack[S]의 하위 타입이라는 말이다.
좋은 예시가 있어서 내용을 조금 이해하기 쉽게 만들었다.
참조 : https://coderwall.com/p/dlqvnq/simple-example-for-scala-covariance-contravariance-and-invariance
타입 매개변수마다 클래스를 생성한다.
+T는 Covariant(공변성),
-T는 Contravariant(역변성),
T는 Invariant(불변성)
class Animal {}
class Mammal extends Animal{}
class Dog extends Mammal{}
class Covariant[+T]
class Contravariant[-T]
class Invariant[T]
object Main extends App {
def method1(box:Covariant[Mammal]) = println(box)
//method1(new Box1[Animal]) //type mismatch compile error
method1(new Covariant[Mammal])
method1(new Covariant[Dog])
method1(new Covariant[Nothing])
def method2(box:Contravariant[Mammal]) = println(box)
method2(new Contravariant[AnyRef])
method2(new Contravariant[Animal])
method2(new Contravariant[Mammal])
// method2(new Contravariant[Dog]) //type mismatch compile error
// method2(new Contravariant[Nothing]) //type mismatch compile error
def method3(box:Invariant[Mammal]) = println(box)
//method3(new Invariant[Animal]) //type mismatch compile error
method3(new Invariant[Mammal])
//method3(new Invariant[Dog]) //type mismatch compile error
//method3(new Invariant[Nothing]) //type mismatch compile error
}
[method1 설명]
Mammal의 공변성은 Mammal과 Mammal의 하위 타입을 포함한다. Nothing도 된다.
[method2 설명]
Mammal의 역변성은 Mammal과 Mammal의 상위 타입을 포함한다. AnyRef도 된다.
[method2 설명]
Mammal의 불변성은 Mammal만 된다.