본문 바로가기

Language/Kotlin

[Kotlin] 7장. 다양한 클래스와 인터페이스 2 - 데이터 클래스와 연산자 오버로딩

728x90

DTO 

DTO는 Data Transfer Object로 데이터 전달을 위한 객체이다. JAVA에서는 POJO(Plain Old Java Object)라고 부르기도 하였다.

DTO는 다음과 같은 특성을 가지고 있다.

  • 구현 로직을 가지고 있지 않다.
  • 순수한 데이터 객체를 표현하기 때문에 보통 속성과 게터/세터를 가진다.
  • toString(), equals() 등과 같은 데이터를 표현하거나 비교하는 메서드를 가져야 한다.

하지만, Kotlin에서 DTO를 위해 데이터 클래스를 정의할 때, 게터/세터, toString(), equals()와 같은 메서드는 직접 만들 필요 없이 내부에서 자동적으로 생성된다.

자동으로 생성되는는 메서드는 다음과 같다.

  • 프로퍼티를 위한 게터/세터
  • 비교를 위한 equals()
  • 키 사용을 위한 hashCode()
  • 문자열로 변환해 순서대로 보여주는 toString()
  • 객체 복사를 위한 copy()
  • 프로퍼티에 상응하는 component1(), component2() 등

데이터 클래스

데이터 클래스는 오로지 데이터를 기술하는 용도로만 사용되며, 필요하다면 추가로 부 생성자나 init 블록을 넣어 데이터를 위한 간단한 로직을 포함할 수 있다.

데이터 클래스를 위해 data 키워드를 제공하며 다음과 같이 선언할 수 있다.

 

data class Sample(var name: String, var age: Int)

 

이때, 데이터 클래스는 다음 조건을 만족해야 한다.

  • 주 생성자는 최소한 하나의 매개변수를 가져야 한다
  • 주 생성자의 모든 매개변수는 val, var로 지정된 프로퍼티여야 한다
  • 데이터 클래스는 abstract(추상 클래스), open(상속), sealed, inner(내부 클래스) 키워드를 사용할 수 없다.

객체 디스트럭처링

디스트럭처링(Desstructuring)한다는 것은 객체가 가지고 있는 프로퍼티를 개별 변수로 분해하여 할당한다는 것을 말한다. 변수를 선언할 때 소괄호를 사용해서 분해하고자 하는 객체를 지정하여 사용한다.

 

val sam1 = Sample("Name1", "id1")
val sam2 = Sample("Name2", "id2")
val sam3 = Sample("Name3", "id3")

val custom = listOf(sam1, sam2, sam3)

for((name,id) in custom) {
	println("name = $name , id = $id")
}

 

for문을 보면 (name, id) 와 같이 프로퍼티를 개별 변수로 분해한 것을 확인할 수 있다.

 

내부 클래스 기법

내부 클래스 기법은 중첩(Nested) 클래스, 이너(Inner) 클래스 2가지 기법이 존재한다.

중첩 클래스

중첩 클래스는 기본적으로 정적(static) 클래스처럼 다뤄진다. 즉, 객체 생성 없이 접근이 가능하다.

 

 

외부 클래스인 Outer의 경우 main에서 객체 생성 없이 사용이 불가능 하지만, 중첩 클래스인 Nested의 경우 객체 생성 없이 사용이 가능한 것을 확인할 수 있다.

 

이너 클래스

이너 클래시는 바깥 클래스의 멤버들에 접근이 가능하며, 심지어 private 멤버도 접근이 가능하다.

 

 

중첩 클래스의 경우 외부 클래스의 멤버에 접근이 불가능 했지만, 이너 클래스에서는 외부 클래스인 InnerS의 멤버도 접근이 가능하다.

 

실드 클래스

sealed 키워드를 class와 함께 사용한다. 실드 클래스 그 자체는 추상 클래스와 같기 때문에 객체를 만들 수 없으며, 생성자도 기본적으로 private만 허용한다.

 

 

열거형 클래스

여러 개의 상수를 선언하고 열거된 값을 조건에 따라 선택할 수 있는 특수한 클래스. 열거형 클래스는 실드 클래스와 거의 비슷하지만, 다양한 자료형을 다루지 못한다.

enum 키워드와  함께 선언할 수 있고 자료형이 동일한 상수를 나열할 수 있다.

 

enum class 클래스이름 [(생성자)] {
	상수1[(값)], 상수2[(값)], ...
    [; 프로퍼티 혹은 메서드]
}

 

대괄호로 처리가 되어있는 것은 생략이 가능한 것이다.

 

 

enum class 선언하는 방식을 3종류로 사용해 본 예제이다.

Sample2 의 SAM4를 보면 num 값은 직접 사용하는 값이 아닌, 상수에 사용될 값의 타입을 선언한 것임을 알 수 있다.

 

열거형 클래스의 각 상수는 객체로 취급되므로 몇 가지 기본적인 멤버를 제공한다.

  • name : 상수 이름 자체를 반환
  • toString() : 이름을 가져옴
  • ordinal : 순서 번호. (맨 앞에 선언된 것 부터 순서대로 0, 1, 2, ...)
  • values() : 모든 값을 가져옴

어노테이션 클래스

어노테이션(Annotation)은 코드에 부가 정보를 추가하는 역할. @기호와 함께 나타내는 표기법으로 주로 컴파일러나 프로그램 실행 시간에서 사전 처리를 위해 사용한다.

클래스 선언 시 annotation 키워드를 같이 선언하여 사용한다.

 

annotation class Fancy // 선언

...

@Fancy class SampleClass{ ... } // 사용


@Fancy class MyClass {
	@Fancy fun method(@Fancy pro: Int): Int {
		return (@Fancy 1)
	}
}

 

어노테이션이 들어갈 수 있는 위치는 위의 예시와 같다.

 

표준 어노테이션

  • @JvmName : filter()라는 이름을 자바에서 각각 filterStrings() 와 filterInts() 로 바꿔주는 것.
  • @Throw : 코틀린의 throw 구문이 자바에서도 포함되도록 한다.
  • @JvmOverloads : 코틀린에서 기본값을 적용한 인자에 함수를 모두 오버로딩 해준다.

표준 어노테이션은 자바와 원활하게 연동하는데 목적을 두고있다.

 

연산자 오버로딩

JAVA에서 사용하는 연산자와 대부분 같다. 그 중, Kotlin에서 처음 보는 것은 다음과 같다.

  • ?: (엘비스 연산자)  :  변수에 널 값이 들어갔을 때 널 값을 변환할 수 있는 함수의 결과를 만들어 준다.
  • as? : 자료형 변환에 대해 안전한게 변환할 수 있도록 도와준다. 캐스팅이 불가능하면 null 반환
  • ?. : 앞의 변수가 null이 아닐때만 오른쪽 함수가 수행되고, null이면 null이 반환된다.
  • .. : 범위를 표현할 때 사용
  • is : JAVA의 instanceof 연산자와 같은 역할. 자료형을 확인하여 일치하면 true를 리턴한다.

연산자의 경우 operator 연산자를 사용하여 오버로딩 하여 사용한다.

 

operator fun plus(p: Int): Int {
	return x+x+x
}

 

호출 연산자

호출 연산자(Invoke Operator)를 사용하기 위해서는 invoke라는 이름으로 함수를 만들어야 한다. invoke로 함수 이름을 선언하는 경우, 이름 없이 호출이 가능하게 된다.

 

class Manger {
	operator fun invoke(value: String) {
		println(value)
	}
}

fun main() {
	val manager = Manger()
	manager("Sample")
}

 

main에서 manager 객체를 사용하여 invoke 함수를 호출한 부분을 보면, 원래 manager.invoke("~") 방식으로 호출을 해야하지만, invoke는 이름 없이 호출할 수 있기 때문에 .invoke 부분을 생략하고 호출하였다.

728x90