-
[스칼라] 스칼라 계층구조스칼라 2022. 2. 13. 15:22
programming in scala 4th edition 11장을 읽고 정리한 것입니다.
1. 스칼라의 클래스 계층구조
스칼라의 최상위 계층에는 Any가 정의되어있고 Scala any에 정의되어있는 메서드는 다음과 같다. 스칼라의 모든 클래스는 Any를 상속받기 때문에 스칼라 클래스는 기본적으로 동치비교, 해시값얻기, 문자열로 변환을 사용할 수 있다.
final def ==(that: Any): Boolean final def !=(that: Any): Boolean def equals(that: Any): Boolean def ##: Int def hasCode: Int def toString: String
루트 클래스 Any에는 AnyVal, AnyRef라는 서브클래스가 있다.
AnyVal은 모든 스칼라값클래스의 부모 클래스로 기본적으로 Byte, Short, Int, Long, Double, Float, Boolean, Char, Unit 이렇게 9가지의 클래스가 있다. Unit을 제외한 클래스들은 자바 원시타입에 대응되어 실행 시점에 자바 원시 타입 값으로 표현된다. 값 클래스는 추상클래스인 동시에 final 클래스여서 new 를 사용해 인스턴스화 할 수 없다. 값 클래스들은 서로 상속관계가 없다 (평면적이다). 각 값 클래스는 암시적변환이 적용되어 필요할때마다 자동으로 상위 타입으로 변환된다.
AnyRef는 스칼라의 모든 참조 클래스의 기반 클래스로 자바 플랫폼에서 java.lang.Object에 별명을 붙인 것이다.
2. 여러 기본클래스를 어떻게 구현했는가
Int의 경우 기본적으로 32비트로 정수를 표현한다는 점은 자바와 동일하다. 덧셈, 곱셈과 같은 연산은 자바의 기본 연산을 사용해서 구현하고 toString, Any타입 변수에 정수를 할당하는 작업들은 java.lang.Integer 클래스를 사용하는데, 스칼라의 경우에 이런 박싱, 언박싱을 관찰하기가 더 어렵다.
def isEqual(x: Int, y: Int) = { x == y } def isEqualAny(x: Any, y: Any) = { x == y } // Int 값 비교를 위한 스칼라 메소드
public class IntegerEqual { public boolean isEqual(Integer x, Integer y) { return x == y; } public boolean isEqualObject(Integer x, Integer y) { return x.equals(y); } public static void main(String[] args) { IntegerEqual integerEqual = new IntegerEqual(); System.out.println(integerEqual.isEqual(421, 421)); System.out.println(integerEqual.isEqualObject(1, 1)); } } // Integer 객체 비교를 위한 자바 코드
자바 8 이하에서는 int 를 Integer로 자동 변환하여 == 비교할경우 false 가 출력된다. 바로 위의 자바코드에서 isEqual(421, 421)의 결과는 false이다. ==가 참조의 동일성을 비교하기 때문이다.
스칼라에서는 ==로 비교할 경우 일반적인 (수를 비교하는) 비교를 하기 때문에 isEqual(x: Int, y: Int)를 이용한 비교가 true가 된다. 참조 비교를 위해서는 주로 eq를 활용한다.
val a = new String("abc") val b = new String("abc") print(a equals b) // true print(a eq b) // false
3. 바닥에 있는 타입
스칼라에는 Null, Nothing 타입이 있다. Null은 null 참조의 타입으로 모든 참조타입의 서브 클래스이다. Null은 값 타입과는 호환성이 없어서 아래 같은 활용은 할 수 없다.
val i: Int = null
Nothing타입은 에러 등 비정상적인 종료를 표현하기 위한 타입이다.
def returnError(): Nothing = { throw new Error }
Nothing 은 다른 모든 타입의 서브타입인데 이 점은 리턴 타입을 명시할때 편리하게 활용할 수 있다. 아래의 함수는 1보다 큰 입력을 받을 경우 에러를 발생시키지만 리턴타입에 Nothing 타입같은것을 명시할 필요는 없다.
def ifLagerThanOneReturnError(input: Int): Int = { if(input > 1) throw new Error input }
4. 자신만의 값 클래스 정의
AnyVal을 상속받고 특정 조건을 만족하도록 클래스를 작성하면 원하는 형태의 값 클래스를 정의할 수 있다. 아래는 값 클래스가 되기 위한 조건이다.
- 파라미터를 하나만 취한다
- def를 제외한 어떤 필드도 내부에 없어야 한다
- 값 클래스를 확장할 수 없고, 값 클래스는 equals나 hashCode를 재정의 할 수도 없다
아래는 값 클래스를 정의한 예이다. toString을 정의하여 출력시 더 편리하게 활용할 수 있다.
class Dollars(val amount: Int) extends AnyVal { override def toString() = "$" + amount } val money = new Dollars(100) money.amount print(money) // 출력: $100
한 가지 타입만 남용하는 것을 막기
코드상으로 아무 문제가 없다고 하더라도 기능에 맞게 클래스를 세분화하면 컴파일러의 도움을 받아 실수를 막을 수도 있다. 예를들어서 Html을 표현하는 다음과 같은 코드가 있다고 해보자.
def title(text: String, anchor: String, style: String): String = s"<a id='$anchor'><h1 class='$style'>$text</h1></a>"
위의 코드를 사용하는것은 아무 문제가 없어 보인다. 실제로도 돌아가는데 아무 지장이 없다. 위의 함수를 사용하다가 어느날 파라미터 순서를 뒤죽박죽으로 잘못 줬다고 해보자.
title("chap:vcls", "bold", "Value Classes")
코드가 그대로 수행되지만 원하는 결과는 아니다. text, anchor, style을 String 타입을 확장한 값 클래스로 정의하여 활용하면 이런 실수를 했을때 컴파일러가 에러를 발생시켜줄 수 있다.
def title(text: Text, anchor: Anchor, style: Style): Html = new Html( s"<a id='$anchor'><h1 class='$style'>$text</h1></a>" ) title( new Anchor("chap:vcls"), new Style("bold"), new Text("Value Classes") ) // title의 파라미터가 잘못 주어졌기 때문에 컴파일 에러가 발생함
'스칼라' 카테고리의 다른 글
[스칼라] 케이스 클래스와 패턴 매치 (0) 2022.02.21 [스칼라] 트레이트 (0) 2022.02.20 [스칼라] 상속과 구성 (0) 2022.02.03 [스칼라] 흐름 제어 추상화 (0) 2022.02.01 [스칼라] 함수와 클로저 (0) 2022.01.31