이 포스트는 Programming in Scala 4/e
를 읽고 작성한 내용입니다.
스칼라의 모든 클래스는 공통의 슈퍼 클래스 Any를 상속한다. 즉, 모든 클래스가 Any의 서브클래스이므로 Any가 정의해둔 메서드는 모두 '보편적인' 메서드이다.
어느 객체에서든 Any의 메서드를 호출할 수 있는 반면, 스칼라 클래스 계층의 맨 밑바닥의 Null과 Nothing 같은 클래스는 모든 클래스의 서브 클래스의 역할을 한다.
이러한 스칼라의 계층 구조를 자세히 공부할 예정이다.
스칼라의 계층 구조는 개략적으로 위의 이미지로 정리할 수 있다.
루트 클래스에 있는 Any
에는 2개의 서브 클래스가 있다. 이는 각각 AnyVal
과 AnyRef
이다. 이때, AnyVal
은 위의 이미지에 나와있듯, Double, Float, Long, Int, Short, Byte를 하위 클래스로 가지고 있다. 즉, Java의 원시타입
을 서브 클래스로 가지고 있는 것이다. 이렇게 AnyVal
의 모든 인스턴스는 리터럴로 선언할 수 있다. 이들은 new
를 이용해 인스턴스화할 수 없다.
루트 클래스 Any의 또다른 서브 클래스는 AnyRef
가 존재한다. 이 클래스는 스칼라의 모든 참조 클래스
의 기반 클래스이다. 즉, AnyRef는 java.lang.Object에 별칭을 붙인것에 지나지 않는다. 따라서 자바로 작성한 클래스나 스칼라로 작성한 클래스는 모두 AnyRef를 상속받는다.
자바 플랫폼의 스칼라 프로그램에서는 AnyRef나 Object를 서로 바꿔서 사용할 수 있지만, 어디서나 AnyRef를 사용하는 것이 권장된다.
우리는 위의 이미지에서 Any
클래스가 가장 최상위 클래스인 것을 알 수 있는데, 그렇다면 모든 클래스에서 사용할 수 있는 클래스 Any의 메서드들에는 무엇이 있을까?
final def == (that: Any): Boolean
final def != (that: Any): Boolean
def equals(that: Any): Boolean
def ##:Int
: 이 메서드 역시 해시값을 얻을 수 있는 메서드이다.def hashCode:Int
def toString: String
## vs hashCode
교재에는 ##와 hasCode 모두 해시값을 리턴하는 메서드라고 나와있다. 그래서 둘의 차이를 알아보았다.##
null 안정성을 가지고 있다. 따라서, null.##을 실행하면 0을 리턴하는 반면, null.hashCode를 실행하면 null pointer exception을 리턴한다.
Any의 메서드에는 final
로 선언되어 변경할 수 없는 ==
와 !=
메서드가 존재하며, 오버라이드할 수 있는 equals
, ##
, hashCode
, toString
이 있다.
val x:String = "abcd".substring(2)
val y:String = "abcd".substring(2)
x == y // 출력값은?
만약 위의 코드가 Java 코드였으면 x == y
는 false가 나와야 한다. 두 변수 내에 저장된 값들은 같지만, 참조하는 값은 다르기 때문에 equals
를 사용하지 않으므로 false가 나와야 한다.
하지만, Scala에서 위의 코드는 true
를 반환한다. 하지만, 가끔 참조 동일성
이 필요한 경우가 있다. 이 경우 hash cons
를 사용한다. eq
를 사용하여 hash cons를 비교할 수 있다.
실제 Scala 터미널에서 실행한 코드이다. ==
를 통해 동등성을 확인한다. 하지만 eq
메서드를 사용하면 두 변수의 참조값이 같은지를 확인하므로 false를 리턴한다. 또한, ne
라는 메서드 역시 존재하는데, 이 메서드는 not eq
로 생각하면 편하다. 즉, 같은 값을 참조하고 있지 않다
에 대한 것을 묻는 메서드이다.
Scala 계층 구조를 그린 맨 위의 그림에서 최 하위에 scala.Nothing과 Scala.Null이 존재하는 것을 확인할 수 있다. 이들은 스칼라 객체지향 타입 시스템의 일부 "특이한 경우를 처리하기 위한 특별 타입이다.
Null 클래스는 null 참조
의 타입이다. 이 클래스는 모든 참조 타입의 서브 클래스이다. Null은 값 타입과는 호환성이 없다. 즉, AnyRef
의 모든 서브타입의 최하위 클래스이며, AnyVal
과는 전혀 상관없다.
위와 같이 AnyVal
의 하위 타입인 Int형 변수에 Null을 넣으면 오류가 나는 것을 알 수 있다.
Nothing
타입은 스칼라 클래스 계층의 맨 밑바닥에 존재한다. 이 타입은 다른 모든 타입의 서브타입이다. 하지만, 이 타입의 값은 존재하지 않는다. 값이 없는 타입은 어떤 의미로 이해해야 할까?
Nothing의 쓸모 중 하나는 비정상적인 종료
를 표시하는 것이다.
def error(message:String): Nothing =
throw new RuntimeException(message)
위와 같이 비정상적인 종료를 나타내기 위하여 사용한다.
스칼라가 제공하는 값 클래스를 보조하는 자신만의 값 클래스를 정의
할 수 있다. 내장된 값 클래스와 마찬가지로, 클래스의 인스턴스도 보통은 래퍼 클래스를 사용하지 않고 자바 바이트코드로 컴파일
될 것이다.
제네릭 코드처럼 래퍼가 필요한 문맥에서는 자동으로 박싱과 언박싱이 이루어진다.
class Dollars(val amount:Int) extends AnyVal {
override def toString() = "$ "+amount
}
위는 사용자 정의 값 클래스이다. 값 클래스를 정의하기 위해서는 AnyVal
클래스를 상속하면 된다. 그리고, 유일한 파라미터
앞에 val
을 넣으면 된다.
스칼라의 클래스 계층을 가장 잘 활용하고 싶다면, 문제 영역에 잘 들어맞는 새로운 클래스를 정의하면 된다. 심지어 동일한 클래스를 다른 목적에 재활용할 수 있는 경우에도 가능하면 새로운 클래스를 정의하는 편이 좋다. 그렇게 만든 클래스 내에 메서드나 필드가 없어서 소위 아주 작은 타입이라고 불릴 만한 것이라 할지라도, 클래스를 하나 더 정의하면 컴파일러가 더 잘 돕게 만들 수 있다.
##
and hashCode
?
많은 것을 배웠습니다, 감사합니다.