클래스, 객체, 인터페이스
클래스 계층 정의
코틀린에서의 인터페이스 안에는 추상 메소드 뿐만 아니라 구현이 있는 메소드도 구현할 수 있다. 하지만 인터페이스에는 필드가 들어갈 수는 없다 자바에서 인터페이스, 추상메소드를 설정해주는 과정이 각각 implements, extends 키워드를 통해서 진행했었는데, 코틀린에서는 단순하게 :(콜론)만 붙여서 클래스의 확장과 인터페이스의 구현을 넣을 수 있다 여기서 추상메소드를 받아서 override 을 하는 과정에서 사실 자바에서는 무조건 사용할필요는 없었지만 코틀린에서는 무조건 @Override 요 애노테이션을 붙혀야만 한다. 상위 클래스에 있는 메소드와 시그니처가 같은 메소드를 우연히 하위 클래스에서 선언하는 경우에는 컴파일이 안되기 때문에 override나 메소드 이름으로 잘 구분해줘야 한다 인터페이스도 default 구현을 하는 것이 가능은 하다 변수에서 간단하게 디폴트를 넣어주는 것처럼 = 을 통해서 기능을 작성해주면 된다 상속한 인터페이스를 호출할때도 기존에 자바에서는 리턴타입.super.사용할함수() 이렇게 사용했었는데 코틀린에서는 super<리턴타입>.사용함수() 이렇게 사용한다 코틀린은 기본적으로 자바 6와 호환되도록 설계되었다 그래서 인터페이스에 디폴트 메소드를 설정하는 자바 7인가 8에 들어간 기능이라서 따로 지원하지 않는다 따라서 코틀린은 디폴트 메소드가 인터페이스를 일반 인터페이스와 디폴트 메소드 구현이 정적 메소드로 들어있는 클래스를 조합해 구현한다
자바에서 final로 상속을 금지하지 않는 모든 클래스를 다른 클래스가 상속할 수 있다 취약한 기반 클래스라는 문제는 하위 클래스가 기반 클래스에 대해 종속적으로 가진 기능에 대해서 만약 기반 클래스가 변경됨으로써 깨지는 문제를 의미한다 그래서 어떤 클래스가 자신을 상속하는 방법에 대해 제한적인 규칙을 주지 않는 한, 그 클래스의 클라이언트는 생성자의 의도에 맞게 override 해서 사용하지 않을 가능성이 있다 그래서 effective java 에서는 특별하게 하위 클래스에서 재정의를 통해서 기능을 활용할 가능성이 있는 클래스가 아니라면 final 을 통해서 제한하라 라고 하고 있다 이러한 철학을 코틀린에도 적용되어서 코틀린에서의 클래스와 메소드는 기본적으로 final 이다 어떠한 클래스의 상속을 허용하기 위해서는 open 이라는 키워드를 통해서 재정의를 가능하게 해줘야한다 그리고 추가로 재정의를 허용하고 싶은 메소드나 필드에도 open을 붙혀줘야 한다 참고로 클래스에 open 을 붙히고 내부의 함수에서 재정의를 제한하고 싶다면 final 키워드를 붙혀줘야 하는 점을 기억하자
이렇게 사용한다
코틀린에서 제공해주는 상속 제어 변경자들은 아래와 같다
final : 요 키워드가 있으면 재정의할 수 없으며, final 키워드는 클래스 멤버의 기본 변경자이다
open : 요 키워드가 있으면 재정의할 수 있으며, 이게 있어야 재정의가 가능하다
abstract : 이게 있으면 무조건 재정의해줘야 한다
override : 상위 클래스나 상위 인스턴스의 멤버를 재정의한다는 의미
다음은 접근 제어자이다 일단 자바와 비슷하게 public, protected, private 이렇게 2가지가 있다 자바에서는 기본적으로 private이지만 코틀린에서는 기본적으로 public 이다 추가로 코틀린에서는 그 3개가 아니라 internal 이라는 접근 제어자도 존재한다 internal 이라는 접근 제어자는 모듈 내부에서만 볼 수 있다는 의미이다 이 제어자가 있다는 의미는 모듈의 구현에 대해서 진정한 캡슐화를 제공하겠다는 의미이다 리스트로 해서 보자
public : 적지 않는 디폴트로는 public 이고 클래스 멤버에서 작성하면 모든 곳에서 사용할 수 있고, 최상위에 선언하면 모든 곳에서 사용할 수 있다
internal : 클래스 멤버로는 같은 모듈 안에서만 사용힐 수 있다. 그리고 최상위에 선언하면 같은 모듈 안에서만 사용할 수 있다
protected : 클래스 멤버로는 하위 클래스 안에서만 사용할 수 있다. 그리고 최상위에서는 선언할 수 없다
private : 같은 클래스 안에서만 사용할 수 있다. 그리고 최상위에 선언하면 같은 파일 안에서만 사용할 수 있다.
참고로 protected 멤버는 오직 어떤 클래스나 그 클래스를 상속한 클래스 안에서만 보인다 클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근할 수 없다 또한 코틀린에서는 외부 클래스가 내부 클래스나 중첩된 클래스의 private 멤버에 접근할 수 없다는 점이다
코틀린에서 클래스 내부에서 다른 클래스를 선언하는 중첩 클래스가 존재한다. 근데 특징이 있다면 중첩 클래스는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근할 수 없다는 것이 특징이다 코틀린 중첩 클래스는 아무런 변경자가 안붙으면 자바의 static 중첩 클래스와 같다 -> 그래서 만약에 내부 클래스로 변경해서 바깥쪽 클래스에 대한 참조를 포함하고 싶다면 inner 키워드를 통해서 명시해줘야 한다 예시로 클래스 B가 있고 그 안에 A가 있을 때 A에 대해서
중첩 클래스(바깥쪽 클래스에 대한 참조를 저장하지 않음) : 자바에서는 static class A 이렇게 적고 코틀린에서는 class A
내부 클래스(바깥쪽 클래스에 대한 참조를 저장함) : 자바에서는 class A 이렇게 적고 코틀린에서는 inner class A
참고로 inner 안에서 상위 클래스의 참조에 접근하려면 this@Outer 이러한 키워드를 사용해서 접근해야 한다
클래스 계층을 정의 시 계층 확장을 제한할 수 있다 코틀린 컴파일러에서는 when에서 항상 default 으로 else 분기를 넣어줘야만 한다 그래서 대부분 default 으로는 예외를 던져서 처리하곤한다 근데 사실 좀 귀찮기도하고 default 분기가 있으면 클래스 계층에 새로운 하위 클래스를 추가하더라도 컴파일러가 모든 경우를 처리하는지 판단하기 쉽지 않다 그래서 이 방법을 해결하기 위해서는 sealed 이라는 클래스를 사용할 수 있다 sealed 이라는 키워드를 붙히면, 그 상위 클래를 상속한 하위 클래스 정의를 제한하는 것이 가능하다 즉, sealed 클래스의 하위 클래스를 정의할 때는 반드시 상위 클래스 안에 중첩을 시켜주어야 한다 when 에서 sealed 클래스의 모든 하위 클래스를 처리한다면, default 분기가 필요없다
뻔하지 않은 생성자와 프로퍼티를 갖는 클래스 선언
코틀린에서는 생성자를 하나 이상으로 선언하는 것이 가능하다 코틀린은 주 생성자와 부 생성자를 구분하며, 초기화 블록이라는 것을 통해서 초기화 로직을 구현하는 것이 가능하다 보통 클래스의 선언은 {} 가 필수이지만 코틀린에서는 따로 없이도 선언이 가능하다
이렇게만으로 선언하는 것이 가능한데, 이렇게 클래스 이름 뒤에 그냥 괄호로 파라미터를 넣어준 것을 보고 주 생성자라고 부른다 주 생성자의 용도는 생성자 파라미터를 지정하고, 그 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의하는 이렇게 2가지 목적으로 쓰인다
이렇게 보면 constructor 와 init 이렇게 2가지 키워드를 볼 수 있다 init 은 초기화 블록을 의미한다 -> 초기화 블록은 객체가 생성될때, 즉 인스턴스화 될 때 초기화코드가 실행된다 그리고 constructor 는 주생성자나 부생성자를 정의할 때 사용된다 주 생성자는 제한적이기 때문에 별도의 코드를 포함할 수 없기 때문에 초기화 블록이 필요하다. 생성자를 사용할 때 초기값을 주기 위해서 init 메소드에 넣어주겠다 이건데, 사실 굳이 init에 넣지 않고 생성자를 선언하는 시점에서 넣어주는 것이 가능하다
요렇게 사용할 수도 있다 모든 생성자 파라미터에 디폴트 값을 지정하면 컴파일러가 우선은 빈 생성자를 만들고 거기에다가 디폴트값을 사용해서 초기화를 진행한다 만약에 따로 생성자를 지정하지 않는다면 빈 생성자를 만들어준다 추가로 만약에 해당 생성자의 접근을 제한하고 싶다면 constructor 키워드 앞에서 private 같은 것을 통해서 제한해주는 것도 가능하다 이렇게가 간단하게 생성자를 만드는 방법이였고 이제는 부 생성자를 확인해보자
부 생성자는 상위 클래스를 다른 방식으로 초기화하는 방식이다 클래스에 주 생성자가 없다면 모든 부 생성자는 반드시 상위 클래스를 초기화해주거나 다른 생성자에게 해당 작업을 위임해줘야 한다 부 생성자가 필요한 주된 이유는 자바의 상호 운용성이다. 원하는 파라미터만을 가지고 생성자를 만드는 것이라고 볼 수 있다 이외에도 인터페이스에서도 추상 프로퍼티 선언을 넣는 것이 가능하다. 인터페이스에 있는 프로퍼티는 뒷받침하는 필드 같은 정보가 들어있지 않다 -> 그래서 필요하면 인터페이스를 구현한 하위 클래스에서 상태저장을 위한 프로퍼티등을 만들어야 한다 인터페이스에서도 프로퍼티를 선언하면 세터와 세터가 있는 프로퍼티를 선언하는 것이 가능하다
컴파일러가 생성한 메소드 - data 클래스
자바에서 클래스를 만드는 과정에서 항상까지는 아니여도 편의성을 위해서 클래스에서 만들어주는 세트메뉴같은 친구들이 있다 equals, hashCode, toString 이런 메소드들이다 물론 요놈들을 구현하는데 있어서 항상 ide에서 도와주기 때문에 정말 단순하게 만드는 것이 가능하긴 하다 그래도 결국은 귀찮게 해당 클래스에 자동이라고는 해도 구현을 해야하지만 코틀린에서는 이런애들을 따로 구현하지 않아도 코틀린 컴파일러가 만들어줄 수 있다 그 방법은 class 앞에 data이라는 키워드가 들어가면 된다! -> 그렇다고 항상 data를 붙힐 것은 아니고 애초에 클래스를 생성할 때 옵션으로 만들 수 있다 그냥 data키워드로 들어가 있는 놈들 가지고 만들어주면 내부에 인스턴스 간 비교를 위한 equals, HashMap과 같은 해쉬 기반 컨테이너에서 키로 사용할 수 있는 HashCode, 클래스의 각 필드를 선언 순서대로 표시하는 문자열 표현을 만들어주는 toString 이렇게 3가지를 자동으로 모든 프로퍼티를 고려해서 만들어준다 이외에도 copy()라는 메소드도 제공해준다 data 키워드를 가지고 있는 클래스는 일단 모든 프로퍼티를 읽기 전용으로 만들어서 데이터 클래스를 불변으로 만들라고 권장해주고 있다 그 의미는 var로 선언해줄 수도 있지만 val으로 선언해두는 것을 추권장한다는 것이다 만약에 data로 만든 객체를 HashMap와 같은 컨테이너에 담는다고 생각했을때 객체가 불변이여야 컨테이너도 망치지 않을 것이기 때문이다 그리고 불변객체여야 해당 프로그램에 대해서 추론하기도 더욱 쉽게 추론할 수 있다 그리고 멀티 스레드인 경우에도 해당 객체에 대해서 동기화 작업에 신경을 덜쓰게 된다 이렇게 불변 객체에 대한 장점이 많기 때문에 이러한 객체를 더 자주 사용하라고 제공해주는 메소드가 바로 copy()이다 copy이라는 메소드는 해당 객체를 복사하면서 일부 프로퍼티를 바꿀 수 있게 해준다 객체를 메모리상에서 직접 바꾸는 대신 복사본을 만드는 편이 더 낫다고 한다 복사본은 원본과 다른 생명주기를 가지며, 복사를 하면서 일부 프로퍼티 값을 바꾸거나 복사본을 제거해도 원본과는 전혀 상관이 없기 때문이다
그리고 위임이라는 개념도 존재한다 객체지향 시스템에서 취약점은 보통 구현 상속에 의해 발생한다고 한다 -> 하위 클래스가 상위 클래스의 메소드 중 일부를 오버라이드하면 하위 클래스는 상위 클래스의 세부 구현 사항에 의존하게 된다 결국 상위 클래스가 바뀌면 하위 클래스가 다 바뀌어야하는 그러한 문제가 있다 모든 클래스를 기본적으로 final으로 취급하면 상속을 염두에 두고 open 변경자로 열어둔 클래스만 확장할 수 있다 열린 상위 클래스의 소스코드를 변경할때는 open 변경자를 보고 해당 클래스를 다른 클래스가 상속하리라 예상할 수 있기 때문에 변경 시 하위 클래스를 깨지 않기 위해 좀 더 조심할 수 있다 데코레이터 패턴의 핵심은 상속을 허용하지 않는 클래스 대신 사용할 수 있는 새로운 클래스를 만들되, 기존 클래스와 같은 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스를 데코레이터 내부에 필드로 유지하는 것이다 이때 새로 정의해야하는 기능은 데코레이터의 메소드에 정의하고, 그대로 필요한 부분은 기존 클래스에게 요청을 전달한다 여기서 위임이라는 키워드가 나오늗네 기존 클래스의 메소드에게 요청을 하는 것과 같은 방식이다 -> 위임이란, 언어가 제공하는 일급 시민 기능을 지원한다는 점이다 일급 시민이란? 변수에 담을 수 있고 함수의 인자로 전달할 수 있고 함수의 반환으로 전달할 수 있는 3가지의 조건을 충족하는 그러한 객체이다 인터페이스를 구현할 때 by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시하는 것이 가능하다
object 키워드
object 키워드를 사용하는 경우는 2가지가 있다 -> object declaration 을 통해서 싱글턴을 정의하는 방법 중 하나이고, companion object을 통해서 인스턴스 메소드는 아니지만 어떤 클래스와 관련있는 메소드와 팩토리 메소드를 담을 때 사용한다 또한 companion object에 접근할 때는 동반 객체가 포함된 클래스의 이름을 사용할 수 있다 마지막으로 anonymous inner class 대신 객체식을 사용할 수 있다 코틀린은 객체 선언 기능을 통해서 싱글톤을 지원해준다 여기서 객체 선언은 클래스 선언과 그 클래스에 속한 단일 인스턴스의 선언을 합친 선언이다 객체의 선언은 object 키워드로 시작한다 -> 객체 선언은 클래스를 정의하고 그 클래스의 인스턴스를 만들어서 변수에 저장하는 모든 작업을 한번에 진행한다 클래스와 마찬가지로 객체 선언 안에도 프로퍼티나 메소드 초기화블록등을 넣을 수 있지만 생성자는 객체 선언에 사용할 수 없다
코틀린에는 static 멤버가 없다 즉 static 키워드를 제공하지 않는다는 점이다 그 대신 코틀린에서는 패키지 수준의 최상위 함수와 객체 선언을 활용한다 그리고 대부분의 경우에는 최상위 함수를 활용하는 편을 권장한다 companion object 항목 내부에 구현한 프로퍼티나 메소드는 자바의 정적메소드나 정적필드와 동일하게 사용하는 것이 가능하다 companion object가 private 생성자를 호출하기 쉽다 -> companion object는 자신을 둘러싼 클래스의 모든 private 멤버에 접근이 가능하기 때문에 바깥쪽 클래스의 private 생성자도 호출하기 쉽고 이는 결국 팩토리 패턴을 구현하기 가장 적합한 위치라는 것이다 companion object는 클래스 안에 정의된 입란 객체이다 -> 그래서 이름을 붙히거나 상속하거나 확장함수를 넣거나 이러한 작업들이 가능하다 클래스의 companion object는 일반 일반객체와 비슷한 방식으로, 클래스에 정의된 인스턴스를 가리키는 정적 필드로 컴파일된다 만약에 따로 companion object에 이름을 안붙혔다면 자바쪽에서 companion이라는 이름으로 그 참조에 접근하는 것이 가능하다 Person.Companion.~~ 이렇게 참조해서 가져간다
companion object 키워드를 제외하고도 방법이 있다 -> 무명 클래스를 object 키워드를 통해서 무명 내부 클래스로 내부에서 구현하는 것도 가능하다
Last updated
Was this helpful?