함수형 객체는 변경 불가능한 객체를 의미합니다.

그래서 변경 불가능한 객체의 장/단점에 대해 살펴보면  일단 객체 생명주기 동안 상태가 변하지 않기 때문에 객체의 내부 값이 어떤지 바로 알 수 있습니다.

변경 가능한 객체는 내부 상태가 변하기 때문에 객체를 사용한 곳을 추적해야만 하는 번거로움이 있습니다.

전달이 자유롭습니다. 메소드의 인자로 전달할 때 자바의 경우 메서드 내부에서 객체 상태 변경을 막기 위해 복사를 합니다.

깊은 복사를 할 경우 cloneable 을 상속해서 clone 메서드를 구현해야 합니다. 귀찮습니다. 하지만 변경 불가능한 객체는 애초에 내부 상태 변경이 불가능하기 때문에 자유롭게 전달할 수 있습니다.

당연히 내부 상태 변경이 불가능하기 때문에 여러 스레드에서 접근해도 괜찮습니다.

HashSet에 변경 가능한 객체를 키로 사용했을 경우 나중에 변수 값을 바꿀 때 HashSet에서 해당 객체를 찾을 수 없을 때가 있는 데 이를 방지해 줍니다.

단점은 단순한 상태 변경만 할 때에도 객체를 그대로 복사하기 때문에 복사하는 객체가 복잡하다면 성능이 떨어질 수 있다는 단점이 존재합니다.

 

책에서는 분수를 표현하는 클래스를 구현하는 예제를 통해서 함수형 객체를 설명합니다.

분수 클래스 명세는 위와 같습니다.

 

변경 불가능한 객체는 객체 생성 후에 값을 변경할 수 없기 때문에 인스턴스에 필요한 정보를 생성할 때 제공해야 합니다.

Rational 객체는 분자와 분모 값이 필요하기 때문에 인스턴스를 생성할 때 분자, 분모의 값을 인스턴스에 전달해 줘야 합니다.

그래서 아래와 같이 클래스를 정의할 수 있습니다. 클래스 이름 주고 괄호 안에 nd 식별자가 있습니다. nd는 클래스 파라미터라고 하고

괄호 안에 있는 것을 주 생성자라고 합니다. 그래서 객체를 생성할 때 아래와 같이 하면 n10이 들어가고 d2 들어갑니다.

이렇게 자바와 달리 별도 생성자 메서드 없이 생성할 수 있습니다.

객체를 생성할 시 별도의 로직을 넣고 싶다면 아래와 같이 클래스 내부에 넣으면 됩니다.

그러면 알아서 컴파일러가 클래스 내부에 있으면서 필드나, 메서드가 아닌 것들은 주 생성자 내부로 넣습니다.

그래서 아래와 같이 출력이됩니다.

 

인스턴스를 생성했을 때 빨간 상자와 같이 인터프리터가 메시지를 출력했습니다. 이 출력은 객체의 toString 메서드를 호출한 겁니다.

기본적으로 toString은 클래스 이름 골뱅이, 16진수 숫자를 출력합니다.

나중에 디버그나 로그 메시지, 테스트 할 시 프로그래머에게 객체에 대한 명확한 정보를 제공해 주기 위해 toString을 오버라이딩에 재정의할 수 있습니다.

분수와 분자를 출력해주는 toString으로 바꾸는 예제가 가운데와 같습니다.

기존 객체에 toString 메서드를 메서드 앞에 override 키워드를 통해 오버라이딩해서 구현할 수 있습니다.

 

선결 조건이란 메소드나 생성자가 전달받은 값에 대한 제약, 호출하는 이가 지켜야할 요구 조건을 의미합니다.

저희 분수 클래스는 분모가 절대 0일 수 없습니다. 그래서 객체를 생성할 때 분모에 0이 들어오지 않도록 해 분모가 0이 되지 않도록  차단하는 선결조건을 만들 수 있습니다.

선결조건은 주생성자 안에 require 키워드를 이용해 정의할 수 있습니다.

아래와 같이 만약에 분모가 0Rational 객체를 생성하려고 한다면 아래와 같이 예외가 발생해서 객체 생성을 막습니다.

 

클래스 파라미터 n d는 자신의 클래스 내부에서만 접근 가능하다. 외부에서 접근이 불가능합니다.

 

lessThan 메서드는 호출한 분수 객체가 인자로 받은 분수 객체 보다 작을시 True리턴합니다.

이 때 호출한 분수 객체의 numerdonom에 접근하기 위해 this를 쓴 것을 볼 수 있습니다. 물론 여기서는 this를 생략해도 괜찮습니다.

아래 maxlessThan을 이용해서 더 큰 분수 객체를 리턴합니다. 여기서는 this를 무조건 서야합니다. 만약 인자로 들어온 객체보다 크다면 자기 자신을 리턴해야 하는데 이를 할 방법이 this 말곤 없기 때문입니다.

 

보조 생성자에 대해 알아보겠습니다. 보조생성자는 주생성자가 아닌 모든 생성자를 말하고코드와 같이 def this(…)으로 시작합니다.

위 코드는 def this로 시작했으므로 보조 생성자가 됩니다. 이 생성자의 역할은 분모가 1인 경우 분자만을 이용해 간결하게 표현하기 위해서

해동 보조 생성자는 분자만 입력으로 받고 내부적으로 주생성자를 호출하되 분모가 1이 되도록 두번째 인자로 1을 전달합니다.

그래서 오른쪽과 같이 3만 인자에 넣으면 3/1 객체가 생성됩니다.

생성자를 사용할 때 주의할 점은 모든 보조생성자는 다른 생성자를 호출하는 코드로 시작해야 합니다. 즉 첫 구문이 this가 됩니다.

 

이번에는 분자에 6, 분모에 4를 넣었을 대 6/4 가 아니라 약분된 상태인 3/2로 깔끔하게 내부 객체 상태를 관리하고 싶어졌습니다.

그래서 주 생성자 내부에서 분자와 분모의 최대공약수를 구해 각 분자와 분모를 최대 공약수로 나눠서 기약분수로 만들어 관리하고자 합니다.

그래서 맨 아래 gcd라는 최대공약수를 구하는 메서드를 정의합니다.(유클리드 알고리즘) gcd 메서드는 최대공약수를 구하지 분수 자체에 대한 메서드가 아니라서 외부에 공개할 필요가 없습니다.

그래서 private라는 키워드를 붙여서 외부에 공개되지 않도록 합니다.

그래서 주생성자가 호출되면 먼저 클래스 파라미터인 nd의 최대공약수를 구합니다. 마찬가지로 최대공약수를 공개할 필요가 없어 private 키워드를 붙였습니다.
약분 하기 위해서 nd를 최대공약수로 나눠 줍니다.

그러면 오른쪽 과 같이 2/3가 나옵니다.

 

스칼라는 라이브러리를 언어가 기본 지원하는 것처럼 정의하는 것을 추구합니다.

그래서 add 메서드를 +로 바꿔서 기본 연산자 같이 바꿀 수 있습니다.

또한 앞장에서 보았듯이 스칼라는 연산자의 가장 앞 글자로 연산자 우선순위를 결정합니다.

+ 보다 * 글자가 우선순위가 높으므로 오른족 코드와 같이 *먼저 계산 되는 것을 볼 수 있습니다 .

 

 

싱글톤 객체 같은 경우 컴파일러가 $클래스이름으로 클래스를 생성한다
사용해도 되긴 하지만 컴파일러가 만들어낸 식별자와 충돌할 가능성이 생긴다.

여기서 상수는 val로 된 모든 것을 의미하는 것이 아닙니다. Val 또한 array 타입일 경우 내부 원소 값이 변경 가능하기 때문이다.

여기서 말하는 val은 항상 동일한것 3.14 pi 같은 걸 의미합니다.

 

스칼라는 라이브러리를 언어가 기본 지원하는 것처럼 정의하는 것을 추구합니다.

메소드 호출 시점에 컴파일러는 오버로드한 메소드 중 인자의 타입이 일치하는 메소드를 선택한다.

 

 

+ Recent posts