코틀린에서 클래스 내에 선언한 변수를 '프로퍼티'라고 부른다. 자바에서는 이런 변수를 '필드'라고 부른다. 자바의 필드는 단순한 변수 선언 부분만을 가지므로 접근하기 위한 메서드를 일일이 만들어야한다. 하지만 코틀린에서는 변수 선언 부분과 기본적인 접근 메서드를 모두 가지고 있기 때문에 프로퍼티라고 새롭게 부르는 것이다.

예를 들어, 어떤 사람의 이름과 나이에 대한 정보를 나타내기 위해 Person 클래스에 변수에 해당하는 name, age라는 필드를 가지고 있다고 가정하자. name, age를 공개하고 싶지 않다면 가시성 지시자로 private를 지정한다. 그러면 age 필드는 해당 필드가 속한 Person이 아닌 곳에서는 접근이 불가하다. 그러면 이름과 나이를 읽거나 설정하려면 어떻게 해야 할까?

바로 게터(Getter)와 세터(Setter)를 만들면 된다. age의 경우 getAge( )와 setAge( )를 만들어야 필드에 접근할 수 있는 것이다.


자바의 게터는 반환값의 자료형이 참조할 멤버 필드의 자료형과 일치해야 한다. 자바는 접근 메서드를 직접 만들어야해서 코드가 아주 읽기 어려운 단점이 있다.

class Person(var name: String, var age: Int)

코틀린에서는 이렇게 축약이 된다.

==

이 두 코드는 같은 코드다.

객체 user를 생성하고 점으로 프로퍼티에 접근한다. user.name은 프로퍼티에 직접 접근한 것처럼 보이나 코틀린 내부적으로 접근 메서드가 내장되어 있다. 여기서 val로 설정되어 있던 id에 값을 할당하려하면 오류가 발생한다. 즉, user.id = 2 와 같은 코드는 실행 불가하다.

코틀린의 프로퍼티에서 게터와 세터는 자동으로 생성된다.
.
.
.

기본 게터와 세터 직접 지정하기

프로퍼티를 var로 선언하는 경우에는 게터와 세터 둘 다로 선언할 수 있지만, val로 선언하는 경우는 게터만 가능하다. 즉, 값을 바꿀 수 없다는 뜻이다.

게터와 세터를 지정했다.

value : 세터의 매개변수로 외부로부터 값을 가져옴
field : 프로퍼티를 참조하는 변수

user1.age = 35 는 정숫값이 value에 할당된다. value라는 이름은 정해진 것은 아니므로 변경할 수 있따. 하지만 field는 이름이 정해져 있어 변경할 수 없다.

보조 필드의 역할

field는 프로퍼티를 참조하는 변수로 보조 필드라고도 한다. get( ) = field는 결국 각 프로퍼티의 값을 읽는 특별한 식별자다. 만일 게터와 세터 안에서 filed 대신 get( ) = age와 같이 사용하면 프로퍼티의 get( )이 다시 호출되는 것과 같으므로 무한 재귀 호출에 빠질 수 있다.

그래서 임시적인 보조 필드를 사용해 프로퍼티 변수에 접근하는 것이다.
.
.
.

커스텀 게터와 세터

coco라는 이름을 대문자로 바꾸어 새로운 세터가 실행된다. 만일 보안 때문에 외부에서 name에 접근하지 못하게 하려면 private를 넣어주면 된다. 즉, 6째 줄에 private set 이라 적어주면 된다.

보조 프로퍼티


이름이 null이 되는 경우를 처리하기 위해 임시적으로 tempName이라는 프로퍼티를 사용한다. 이와 같은 것을 보조 프로퍼티라고 한다.

프로퍼티의 오버라이딩

프로퍼티는 기본적으로 오버라이딩할 수 없는 final 형태로 선언된다. 만일 프로퍼티를 오버라이딩 가능하게 하려면 open 키워드를 사용해야 한다.

open 키워드를 통해 정의하면 오버라이딩이 가능하고 아니면 불가하다. 오버라이딩 시 상위 클래스에 프로퍼티를 val로 정의한 경우 하위 클래스에서 var로 변경할 수 있으나 반대는 안된다. (val -> var [O], var -> val [X])

세터를 이용한 나이 속이기



나이에 따라 판별하는 세터를 만들어 사용했다.
.
.
.

지연 초기화

프로퍼티를 선언하면 기본적으로 모두 초기화해야 한다. 하지만 객체의 정보가 나중에 나타나는 경우는 초기화하기 힘들다. 그 때 지연 초기화를 사용한다.

lateinit

의존성이 있는 초기화나 유닛 테스트를 위한 코드를 설정하면 매번 초기화하기 불편하다. 즉, 의존성이 있는 경우에는 지연 초기화를 해야한다.
또 해당 자료형의 프로퍼티를 즉시 사용하지 않는데도 미리 생성해서 초기화한다면 메모리가 낭비될 수 있다. 모듈 별로 소스 코드를 테스트하는 유닛 테스르를 할 때는 임싲거으로 객체를 생성시켜야 하는 경우가 많다.

클래스를 선언할 때 프로퍼티 선언은 null을 허용하지 않는다. 하지만 lateinit 키워드를 사용하면 바로 할당하지 않아도 컴파일러가 허용한다.

lateinit의 제한

  • var로 선언된 프로퍼티만 가능하다.
  • 프로퍼티에 대한 게터와 세터를 사용할 수 없다.


lateinit으로 선언했기 때문에 16째 줄에서 초기화하지 않은 채 선언할 수 있다. isInitialized는 프로퍼티가 초기화되었는지 검사하는 코틀린 표준 함수의 API다. true는 프로퍼티가 할당되었다는 뜻이며 false는 할당되지 않았다는 뜻이다. 느낌표는 Boolean 값의 반대를 의미하므로 첫 번째 조건식이 false일 경우를 판단한다.

lazy

lateinit은 val은 허용하지 않고 var로만 선언해야 했다. 하지만 var로 선언하면 언제든 값이 변경될 수 있다. val로 선언한 객체나 프로퍼티를 나중에 초기화하려면 lazy를 사용하면 된다.

lazy의 특징

  • 호출 시점에 lazy 정의에 의해 블록 부분의 초기화를 진행한다.
  • 불변의 변수 선언인 val에서만 사용 가능하다. (읽기 전용)
  • val이므로 값을 변경할 수 없다.

1번에 의해 test 객체를 생성 -> 초기화 블록인 2번 내용 실행 -> test 객체의 3번 flow 메서드 실행 -> 4번 실행 -> 5번에서 사용된 $subject에 의해 subject 프로퍼티 최초로 접근 -> 6번 실행, 람다식 맨 마지막 문장 반환 -> 7번 내용으로 초기화 -> 8번에서는 이미 초기화 된 내용 재사용

subject one -> 최초 초기화 시점
subject two -> 이미 초기화된 값 사용

1번의 by lazy를 사용해 person 객체를 지연 초기화하고 있고, 3번의 lazy만을 사용해 위임 변수를 받아서 지연 초기화에 사용하고 있다.

이것이 초기화되는 시점은 객체의 프로퍼티나 메서드가 접근되는 시점에서 초기화된다. 즉 객체에 lazy가 선언된 시점에서 객체가 생성되는 것이 아니라, 코드의 접근 시점인 4번과 5번에서 초기화되는 것이다.

by lazy와 lazy 할당의 차이점은 by lazy는 객체의 위임을 나타내며 lazy는 변수에 위임된 Lazy 객체 자체를 나타낸다. 즉 이 변수의 value 한 단계를 더 거쳐 객체의 멤버인 value.name과 같은 형태로 접근해야 한다.

by를 이용한 위임

특정 클래스를 확장하거나 이용할 수 있도록 by를 통한 위임이 가능하다. by를 사용하면 하나의 클래스가 다른 클래스에 위임하도록 선언하여 위임된 클래스가 가지는 멤버를 참조 없이 호출할 수 있다.
프로퍼티 위임은 프로퍼티의 게터와 세터를 특정 객체에게 위임하고 그 객체가 값을 읽거나 쓸 때 수행하도록 하는 것을 말한다.

위임을 사용하는 이유는 뭘까? 기본적으로 코틀린이 가지는 표준 라이브러리는 open으로 정의되지 않은 클래스를 사용한다. 즉, final 형태이다. 따라서 상속이나 직접 클래스의 기능 확장이 어렵다. 오히려 이렇게 어렵게 만듦으로써 표준 라이브러리의 무분별한 상속에 따른 복잡한 문제를 방지할 수 있다.


위임을 사용하여 특정 참조없이 go 메서드를 호출하여 객체 지향의 다형성이 실현되었다.

observable( ) 함수와 vetoable( ) 함수의 위임

observable( ) 함수는 프로퍼티를 감시하고 있다가 특정 코드의 로직에서 변경이 일어날 때 호출되어 처리된다. 특정 변경 이벤트에 따라 호출되므로 콜백이라고도 불린다.
betoable( ) 함수는 observable( ) 함수와 비슷하지만 반환값에 따라 프로퍼티 변경을 허용하거나 취소할 수 있다는 점이 다르다.

두 함수 모두 초깃값을 위한 initialValue가 있으며 프로퍼티 값이 변경될 때 호출하는 콜백인 onChange( )가 있다.

observable( ) 함수


User 클래스의 name 프로퍼티를 1번과 같이 observable( ) 함수로 위임한다. 초깃값을 NONAME이다. 4번과 5번 같이 값의 변경이 일어나면 3번을 실행한다. 값의 변경이 일어날 때 observable 함수의 코드가 수행된다.

vetoable( ) 함수


기존 값보다 새 값이 커야만 프로퍼티의 교체 작업이 진행된다. 조건에 맞지 않으면 거부권을 행사한다.
이 함수는 컬렉션과 같이 큰 데이터를 다룰 때 유용하다.
.
.
.

정적 변수와 컴패니언 객체

동적인 초기화 없이 사용할 수 있는 변수가 정적 변수나 컴패니언 객체다. 이것은 동적인 메모리에 할당 해제되는 것이 아닌 프로그램을 실행할 때 고정적으로 가지는 메모리로 객체 생성 없이 사용할 수 있다.

컴패니언 객체

static 키워드가 없는 대신 컴패니언 객체를 제공한다.

language와 work는 컴패니언 객체이므로 객체 생성 없이 접근할 수 있다. 컴패니언 객체는 실제 객체의 싱글톤(Singleton)으로 정의된다.

싱글톤이란 전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며, 생성된 객체를 어디에서든지 참조할 수 있도록 하는 디자인 패턴의 하나이다. 이를 사용하는 이유는 객체가 서로 동일한 정보를 가질 때 하나의 메모리만 유지해 자원의 낭비를 줄일 수 있기 때문이다.

코틀린에서 자바의 static 사용


자바 클래스를 먼저 만들고 같은 패키지에서 코틀린 파일을 만든다. 자바의 static 필드나 메서드를 코틀린에서도 객체 생성 없이 손쉽게 접근할 수 있다.

만약 자바 코드에서 코틀린의 컴패니언 객체에 접근하려면 어떻게 할까?

자바에서 코틀린 컴패니언 객체 사용하기

애노테이션 표기법을 사용해야 한다.

const는 컴파일 시간의 상수다. 컴파일 시간의 상수란 val과 다르게 컴파일 시간에 이미 값이 할당되는 것으로 자바에서 접근하기 위해 필요하다. val은 실행 시간에 할당한다. const는 Int, Double형과 같이 기본형으로 사용할 자료형과 String형에만 적용할 수 있다.

@JvmStatic 애노테이션은 자바 소스에서 코드를 해석할 때 Companion을 생략할 수 있게 한다.



KJob 클래스를 사용하는 JOB를 정의한다. 이것은 @JvmField 애노테이션으로 정의되었기 때문에 KCustomer.JOB.getTitle( )과 같은 방법으로 접근하거나 KJob에 대한 객체를 만들고 접근할 수 있다.

컴패니언 객체는 외부 클래스에서 private 프로퍼티에도 접근할 수 있기 때문에 유틸리티 클래스 등을 만드는 데 사용할 수 있다.

최상위 함수

우리가 지금까지 클래스 없이 만든 함수는 객체 생성 없이도 main함수 어디에서든 실행할 수 있었다. 이를 최상위 함수 혹은 패키지 레벨 함수라고 한다.

이 코드는 클래스나 객체가 없으나 최상위 함수인 packageLevelFunc( ) 함수가 main블록에서 잘 실행된다.

이를 역컴파일하면 최상위 함수는 JVM에서 실행되기 위해 static으로 선언되어 있음을 알 수 있다.

코틀린 클래스에 멤버 메서드처럼 접근할 수 있다.

만일 접근할 클래스 이름을 바꾸고 싶다면 @file:JvmNAme("ClassName")을 코드 상단에 입력하면 된다.


.
.
.

Object와 싱글톤

만약 새로 하위 클래스를 선언하지 않고 조금 변경한 객체를 생성하고 싶다면 어떻게 해야 할까?

코틀린은 object 표현식이나 object 선언으로 처리할 수 있다.

object로 선언된 OCustomer는 멤버 프로퍼티와 메서드를 객체 생성 없이 이름의 점 표기법으로 바로 사용할 수 있다. 이것 역시 단일 인스턴스를 생성해 처리하기 때문에 싱글톤 패턴에 이용된다.

object 선언 방식을 사용하면 접근 시점에 객체가 생성된다. 생성자 호출을 하지 않으므로 object 선언에는 주 생성자와 부 생성자를 사용할 수 없다. 하지만 초기화 블록인 init이 들어갈 수 있는데 최초 접근에서 실행된다.


INSTANCE를 사용해 object 선언으로 생성된 인스턴스에 접근했다.

object 표현식

object 표현식은 object 선언과 달리 이름이 없으며 싱글톤이 아니다. 따라서 object 표현식이 사용될 때마다 새로운 인스턴스가 생성된다. 결과적으로 이름이 없는 익명 내부 클래스로 불리는 형태를 object 표현식으로 만들 수 있다.

1번에 익명 객체가 object 표현식으로 만들어졌다. 여기서 익명 객체는 Superman 클래스를 상속해 fly 메서드를 오버라이딩하고 있다. 결국 하위 클래스를 만들지 않고도 Superman 클래스의 fly 메서드를 오버라이딩해 변경했다.

0개의 댓글