384
2
부
기본기 다지기
8.6
클래스의 필드
주 생성자의 인자에
val
이나
var
키워드를 붙이면 인스턴스의 필드가 된다는 사실을 알려주면
서 이번 장을 시작했다. 케이스 클래스의 경우
val
이 있다고 가정한다. 이런 관례는 소스 코드
에서 불필요한 부분을 줄여준다. 하지만 바이트 코드로 번역한 결과는 어떨까?
실제로 스칼라는 자바가 명시적으로 하는 것을 암시적으로 할 뿐이다. 내부에는 컴파일러가 생
성한 비공개 필드가 있고, 그 필드에 대한 ‘게터’와 ‘세터’ 접근자 메서드가 만들어진다. 다음과
같은 단순한 스칼라 코드를 생각해보자.
class Name(var value: String)
개념적으로 이는 다음 코드와 같다.
class Name
(
s
:
String
)
{
private var
_
value
:
String
=
s
//
➊
def value
:
String
=
_
value
//
➋
def value
_
=
(
newValue
:
String
):
Unit
=
_
value
=
newValue
//
➌
}
➊ 밖에서 볼 수 없는 필드를 만든다. 이 경우에는 변경 가능하다.
➋ ‘게터’ 또는 읽기 메서드다.
➌ ‘세터’ 또는 쓰기 메서드다.
value
_
=
라는 메서드 이름의 관례를 알아두자. 컴파일러는 사용자가 이와 같은 메서드 이름을
사용할 때
_
를 생략하도록 허용한다. 따라서 객체의 필드에 값을 대입하는 중위 연산을 제공하
는 것과 같은 효과를 얻는다.
scala
>
val name
=
new Name
("
Buck
")
name
:
Name
=
Name
@
2aed6fc8
385
8
장
스칼라 객체지향 프로그래밍
scala
>
name
.
value
res0
:
String
=
Buck
scala
>
name
.
value
_
=
("
Bubba
")
name
.
value
:
String
=
Bubba
scala
>
name
.
value
res1
:
String
=
Bubba
scala
>
name
.
value
=
"
Hank
"
name
.
value
:
String
=
Hank
scala
>
name
.
value
res2
:
String
=
Hank
val
키워드를 사용해서 변경 불가능한 필드를 선언하는 경우에는 이런 쓰기 메서드가 만들어지
지 않는다.
읽기와 쓰기 메서드 안에 원하는 논리를 구현하고 싶다면 직접 이런 관례를 따를 수 있다.
케이스 클래스가 아닌 클래스의 생성자에는 필드가 아닌 인자를 선언할 수 있다.
val
과
var
를
모두 생략하면 된다. 예를 들어 인스턴스를 만들 때는 필요하지만 만든 다음에는 필요가 없는 값
을 그런 인자로 선언할 수 있다.
그런 값이라도 여전히 클래스의 본문 안에서는 영역 안에 있음을 기억하라. 예전에 본 암시적
변환 클래스 예제에서, 암시적 클래스는 보통 인스턴스를 만들기 위해 인자를 참조했지만, 대부
분의 경우 그 인자를 인스턴스의 필드로 선언하지는 않았다. 예를 들어
5
.
2
.
7
절 ‘유령 타입’에 있
는
Pipeline
예제를 기억해보자.
object Pipeline
{
implicit class toPiped
[
V
](
value
:
V
)
{
def
|
>
[
R
]
(
f
:
V
=
>
R
)
=
f
(
value
)
}
}
toPiped
의
|
>
메서드 안에서 참조하는
value
는 필드가 아니다. 생성자 인자가
val
이나
var
로
선언되어 있지 않아서 필드가 아닌 경우에도 클래스 본문에서는 여전히 그 인자를 볼 수 있다.
386
2
부
기본기 다지기
따라서 그 타입의 멤버 (예를 들면 메서드)는 해당 값을 사용할 수 있다. 자바나 다른 대부분의
OO
언어에서 정의한 생성자와 비교해보라. 그런 언어에서는 생성자도 메서드기 때문에, 생성
자에 인자로 전달된 값은 생성자 메서드 밖에서는 볼 수 없다. 따라서 다른 메서드에서 보기 위
해서는 그 인자를 공개 또는 비공개 필드로 ‘저장’해야 한다.
이런 인자를 그냥 필드로 만들지 않는 이유는 뭘까? 해당 타입을 사용하는 클라이언트는 필드를
볼 수 있다 (물론
13
장에서 설명하겠지만, 필드가
private
거나
protected
가 아닌 경우 ). 이런
인자가 정말로 사용자에게 노출시켜야 할 논리적 상태의 일부가 아니라면 필드로 만들어서는 안
된다. 대신 이들은 클래스 본문에서 전용으로 사용할 수 있다.
8.6.1
단일 접근 원칙
왜 스칼라는 필드
value
에 대한 읽기와 쓰기 메서드의 이름이
getValue
와
setValue
여야 한다
는 자바빈즈
JavaBeans
명세(
http
://
bit
.
ly
/
1wNbg1E
)를 따르지 않는지 궁금할 것이다. 그 대신
스칼라는 다음과 같은 단일 접근 원칙
Uniform
Access
Principle
을 따른다.
Name
예제에서 본 것처럼, 클라이언트는 접근자 메서드를 통하지 않고도 ‘단순’ 값 필드를 읽을
수 있지만, 실제로는 메서드를 호출하고 있다. 반면 클래스 본문에서 기본 가시성을 사용해서 공
개 필드를 선언한다면 단순 필드에 그냥 접근할 수 있다.
class Name2
(
s
:
String
)
{
var value
:
String
=
s
}
이제
value
는 공개 필드며, 접근자 메서드는 없어진다.
이를 사용해보자.
scala
>
val name2
=
new Name2
("
Buck
")
name2
:
Name2
=
Name2
@
303becf6
scala
>
name2
.
value
res0
:
String
=
Buck
387
8
장
스칼라 객체지향 프로그래밍
scala
>
name2
.
value
_
=
("
Bubba
")
name2
.
value
:
String
=
Bubba
scala
>
name2
.
value
res1
:
String
=
Bubba
사용자의 ‘경험’은 동일하다. 사용자 코드는 내부 구현과 무관하다. 따라서 필요에 따라 내부 구
현을 단순 필드에서 접근자 메서드로 바꿀 수 있다. 예를 들어 필드값을 쓸 때 값을 검증하게 만
들거나, 효율성을 위해 값을 읽을 때 필요할 때만 결과를 만들어낼 수 있다. 반대로, 접근자 메서
드를 공개 필드로 바꿔서 메서드 호출에 따른 부가 비용을 없앨 수 있다 (어쨌든
JVM
이 아마도
그런 부가 비용을 없애줄 것이다 ).
따라서 단일 접근 원칙은 클라이언트 코드가 클래스의 내부 구현에 대해 아는 내용을 최소화한
다는 중요한 이점이 있다. 단일 접근 원칙을 따르면 클라이언트의 코드를 바꾸지 않으면서 클래
스의 내부 구현을 바꿀 수 있다. 다만 재컴파일은 필요하다.
스칼라는 접근 보호나 종종 있을 수 있는 값을 읽고 쓰는 것 외에 다른 기능을 구현해야 하는
경우의 장점을 희생시키지 않고 이 원칙을 구현한다.
NOTE
_
스칼라는 자바 형식의 게터나 세터 메서드를 사용하지 않는다. 대신 단일 접근 원칙을 지원한다. 그
원칙 아래에서는 ‘단순’ 필드에 대한 읽기와 쓰기 구문이 필드를 접근자 메서드를 통해 간접적으로 읽거나 쓰
는 구문과 같아 보인다.
하지만 때로 자바 라이브러리와의 상호 운용을 위해 자바빈즈 형식의 접근자 메서드가 필요할
수 있다.
scala
.
reflect
.
BeanProperty
(
http
://
bit
.
ly
/
1toozqm
)나
BooleanBeanProperty
(
http
://
bit
.
ly
/
1u1960A
) 애노테이션을 사용해서 클래스를 애노테이션할 수 있다.
22
.
3
절
‘자바빈즈 프로퍼티’를 보라.
8.6.2
단항 메서드
컴파일러가
foo
라는 필드에 대해
foo
_
=
이라는 대입 메서드를 정의할 수 있도록 허용한다는 것
을 배웠다. 그리고 이를 ‘
myinstance
.
foo
=
값
’이라는 구문을 사용해서 편하게 사용할 수 있게
Get 프로그래밍 스칼라: 실용적인 스칼라 활용법을 익히는 가장 확실한 실전 바이블 (2.11.x 버전 기반) now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.