Class의 생성자와 초기화 블록은 다음과 같이 한번에 초기화 할 수 있으며, 또한 분리할 수도 있다.
fun main(args: Array<String>) {
val person = Person("Alice", 23)
println("이름 = ${person.name}")
println("나이 = ${person.age}")
}
// 초기화를 한군데서 할 경우.
class Person constructor(name:String, age:Int)
{
val name:String
val age:Int
init
{
this.name = name
this.age = age
}
}
// 초기화를 분리할 경우.
class Person constructor(name:String, age:Int)
{
val name:String
val age:Int
init
{
this.name = name
}
init
{
this.age = age
}
}
또한 위와 같이 class를 초기화 및 선언 할 수 있으나, 다음과 같이 한번에 선언하는 것도 가능하다.
class Person(val name:String, val speed:Int=0)
Class는 다음과 같이 여러가지 보조 생성자를 선언할 수 있다.
class [클래스 이름] constructor(매개변수)
{
// 보조 생성자 1
constructor(매개변수):this(인수)
{
...
}
// 보조 생성자 2
constructor(매개변수):this(인수)
{
...
}
...
}
위의 구조를 기반으로 생성된 예제는 다음과 같다.
fun main(args: Array<String>) {
val person4 = Person4Class()
println("Name = ${person4.name}")
println("Age = ${person4.age}")
println("Zone = ${person4.zone}")
val person4_1 = Person4Class("csjung", 30)
println("Name = ${person4_1.name}")
println("Age = ${person4_1.age}")
println("Zone = ${person4_1.zone}")
val person4_2 = Person4Class("csjung", 30, "Seoul")
println("Name = ${person4_2.name}")
println("Age = ${person4_2.age}")
println("Zone = ${person4_2.zone}")
}
class Person4Class {
var name:String
var age:Int
var zone:String
init {
this.name = "Empty"
this.age = 0
this.zone = "Empty"
println("Init Block 실행 중 :: ${this.name}, ${this.age}")
}
constructor() {
println("보조 생성자 실행 중 :: ${this.name}, ${this.age}")
}
init {
println("Init Block 실행 중 2 :: ${this.name}, ${this.age}")
}
constructor(name:String, age:Int) : this() {
println("보조 생성자 실행 중 2 :: ${this.name}, ${this.age}")
this.name = name
this.age = age
}
constructor(name:String, age:Int, zone:String) : this(name, age) {
println("보조 생성자 실행 중 3 :: ${this.name}, ${this.age}")
this.zone = zone
}
init {
println("Init Block 3 실행 중 :: ${this.name}, ${this.age}, ${this.zone}")
}
}
위의 예제에 대한 결과는 다음과 같다.
------------------------------------
Init Block 실행 중 :: Empty, 0
Init Block 실행 중 2 :: Empty, 0
Init Block 3 실행 중 :: Empty, 0, Empty
보조 생성자 실행 중 :: Empty, 0
Name = Empty
Age = 0
Zone = Empty
------------------------------------
Init Block 실행 중 :: Empty, 0
Init Block 실행 중 2 :: Empty, 0
Init Block 3 실행 중 :: Empty, 0, Empty
보조 생성자 실행 중 :: Empty, 0
보조 생성자 실행 중 2 :: Empty, 0
Name = csjung
Age = 30
Zone = Empty
------------------------------------
Init Block 실행 중 :: Empty, 0
Init Block 실행 중 2 :: Empty, 0
Init Block 3 실행 중 :: Empty, 0, Empty
보조 생성자 실행 중 :: Empty, 0
보조 생성자 실행 중 2 :: Empty, 0
보조 생성자 실행 중 3 :: csjung, 30
Name = csjung
Age = 30
Zone = Seoul
위의 예제를 보면, "보조 생성자 실행 중 "이라는 문구 표시가 첫번째는 하나만, 두번째는 2 가지, 세번째는 3가지가 출력된다.
이는 생성자 뒤에 있는 this( ... ) 라는 함수에 따라 어떤 생성자가 호출되는 지를 보여주는 것이다.
예를 들면, 세번째는 this(name, age)는 "constructor(name:String, age:Int) : this()" 생성자를 호출하며, 이는 또한 다시 "constructor()" 생성자를 호출한다.
Class에서의 Property에 대한 Getter/Setter 선언 방법은 다음과 같다.
class Person
{
var age:Int = 0
get()
{
return field
}
set(value)
{
field = if(value >= 0) value else 0
}
}
fun main(args: Array<String>) {
val person = Person()
person.age = -30
println(person.age) // Result : 0
}
위와 같이 Getter와 Setter를 정의하게 되면, main함수에서 Property에 값을 설정 및 가져올 때, 해당 정의 내용을 호출하게 된다.
또한 Class를 이용하여, 연산자 오버로딩을 할 수 있다.
Kotlin에서는 다음과 같이 함수 이름을 사용하고 있다.
단항 연산자
+a : unaryPlus
-a : unaryMinus
!a : not
이항 연산자
+ : plus
- : minus
* : times
/ : div
% : rem
+= : plusAssign
-= : minusAssign
*= : timesAssign
/= : divAssign
%= : remAssign
비교 연산자
>, <, >=, <= : compareTo
==, != : equals
|
이를 이용한 예제 코드는 다음과 같다.
class Coordinate(var x:Int = 0, var y:Int = 0)
{
operator fun plus(other:Coordinate):Coordinate
{
return Coordinate(x + other.x, y + other.y)
}
operator fun minus(other:Coordinate):Coordinate
{
return Coordinate(x - other.x, y - other.y)
}
operator fun times(other:Coordinate):Coordinate
{
return Coordinate(x * other.x, y * other.y)
}
operator fun div(other:Coordinate):Coordinate
{
return Coordinate(x / other.x, y / other.y)
}
fun print()
{
println("x:$x, y:$y")
}
}
fun main(args: Array<String>) {
val coorA = Coordinate(10, 20)
val coorB = Coordinate(20, 20)
var coorPlus = coorA + coorB
var coorMinus = coorA - coorB
var coorTiems = coorA * coorB
var coorDiv = coorA / coorB
coorPlus.print() // Result : x:30, y:40
coorMinus.print() // Result : x:-10, y:0
coorTiems.print() // Result : x:200, y:400
coorDiv.print() // Result : x:0, y:1
}
Class 의 Property를 Index 로 접근할 수 있다.
이를 Indexed Access Operator라고 한다.
Property의 get/set을 다음의 예제와 같이 구현 하면 된다.
class Person(var name:String, var birthday:String)
{
operator fun get(position:Int):String
{
return when(position) {
0 -> name
1 -> birthday
else -> "unknown"
}
}
operator fun set(position:Int, value:String)
{
when(position) {
0 -> name = value
1 -> birthday = value
}
}
}
fun main(args: Array<String>) {
val person = Person("Kotlin", "2020-08-20")
println(person[0]) // Result : Kotlin
println(person[1]) // Result : 2020-08-20
println(person[-1]) // unknown
person[0] = "Java"
println(person.name) // Java
}
위에서 보시는 것 과 같이 "position" 0을 name으로 1을 birthday로 정의 하면, person[0] 은 name으로 처리되고, person[1]은 birthday로 처리하게 된다.
invoke 연산자는 함수 이름 없이 호출할 수 있다.
다음의 예제 코드를 참고하길 바란다.
class MyFunction(val id:Int, val name:String)
{
operator fun invoke(value:Int)
{
println(value)
println("id:$id\nname:$name")
}
}
위와 같이 정의 하게 된다면, 다음과 같이 함수 이름없이 사용이 가능하다.
val myFun = MyFunction(1234567, "Invoke Operator Test Code")
myFun(108)
중위 표기법에 대한 사용 법
구성 : 피연산자 + 연산자 + 피연산자
구성 방법 : 멤버 함수의 매개 변수가 하나뿐이면 함수 호출을 중위 표기법으로 표기할 수 있다.
정의 방법 : 중위 표기법으로 정의할 시, 멤버 함수 선언문 앞(fun)에 infix 를 붙여야 한다.
예제는 다음과 같다.
class InfixTestClass(var x:Int = 0, var y:Int = 0) {
infix fun from (base:InfixTestClass) : InfixTestClass
{
return InfixTestClass(x - base.x, y - base.y)
}
}
fun main(args:Array<String>)
{
val infixTestCls = InfixTestClass(3, 6) from InfixTestClass(1, 1)
println("Infix test =============================== ")
println(infixTestCls.x)
println(infixTestCls.y)
println("========================================== ")
}
Class 상속에 대한 사용 법
상속하기 위한 부모 Class에 대해서는 open이라는 키워드로 정의 하여야 한다.
그리고 상속 되는 Class에서는 ":" 이후에 부모 클래스를 정의 하면 된다.
이에 대한 사용 법은 다음과 같다.
open class InheritancePerson(val name:String, val age:Int)
class InheritanceStudent(name:String, age:Int, val id:Int) : InheritancePerson(name, age)
Class에 대한 상속은 위와 같이 한다면, 함수 오버라이딩은 다음과 같이 부모 클래스의 함수에 open 키워드로 정의 한 후, 자식 클래스에 존재하는 동일 명칭의 함수에 override 키워드를 사용하면 된다.
사용 법은 다음과 같다.
open class AAA
{
open fun func() = println("AAA")
}
class BBB : AAA()
{
override fun func()
{
super.func()
println("BBB")
}
}
fun main(args: Array<String>)
{
AAA().func()
BBB().func()
}
위의 코드에 대한 결과물을 보면 다음과 같은 결과물이 돌출된다.
Result
-----------------
AAA
AAA
BBB
만약 멤버 함수의 재 오버라이딩을 막고 싶다면, 다음과 같이 final을 붙여주면 된다.
open class AAA
{
open fun func() = println("AAA")
}
open class BBB : AAA()
{
final override fun func()
{
super.func()
println("BBB")
}
}
open class CCC : BBB()
{
override fun func() <-- 에러 발생.
{
}
}
위에서 CCC는 빌드 에러가 발생하게 된다.
프로퍼티 또한 멤버함수와 같이 오버라이딩을 할 수 있다.
사용법은 위와 같으며, 이에 대한 사용 방법은 다음의 예제 코드를 참고하여, 이해하길 바란다.
open class AAA
{
open var number = 10
get()
{
println("AAA number Getter 호출됨")
return field
}
set(value)
{
println("AAA number Setter 호출됨")
field = value
}
}
class BBB : AAA()
{
override var number: Int
get()
{
println("BBB number Getter 호출됨")
return super.number
}
set(value)
{
println("BBB number Setter 호출됨")
super.number = value
}
}
fun main(args: Array<String>)
{
val test = BBB()
test.number = 5
test.number
}
이에 대한 결과물은 다음과 같다.
Result
-----------------
BBB number Setter 호출됨
AAA number Setter 호출됨
BBB number Getter 호출됨
AAA number Getter 호출됨
접근 지정자에 대해서 알아보겠다.
보통 C/C++/Java등에서는 Class 별로 접근 권한을 지정할 수 있다.
대표적으로 public / protected / private 라는 키워드를 사용한다.
그러나 Kotlin에서는 internal 이라는 새로운 키워드가 존재한다.
이는 해당 Project 내에서는 모두 접근이 가능하도록 한다는 권한을 뜻하는 것이며, 또한 IntelliJ Project 에서만 사용 가능한 것으로 알고 있다.
추상 클래스 및 Interface를 생성할 경우, java와 동일하게 abstract 키워드를 사용하여, 정의 하면 된다.
사용 방법에 예는 다음과 같다.
// ---------------------- 추상 클래스
abstract class Person
{
abstract fun getSalary(): Int
}
class Student(private val tuition: Int) : Person()
{
override fun getSalary() = -tuition
}
class Professor(private val classCount: Int) : Person()
{
override fun getSalary() = classCount * 200
}
// --------------------- Interface
interface Printable
{
fun print(): Unit
}
class AAA : Printable
{
override fun print()
{
println("Hello")
}
}
Kotlin에서는 내부 클래스를 선언할 때에는 inner 키워드를 사용하여, 정의 한다.
그리고 내부 클래스에서 외부 클래스의 변수 및 함수를 접근하고자 할 경우, "this@{Out Class 이름}을 표기하여야 한다.
사용 하는 방법에 대한 예는 다음과 같다.
class Outer(private val value: Int)
{
fun print()
{
println(this.value)
}
inner class Inner(private val innerValue: Int)
{
fun print()
{
this@Outer.print()
println(this.innerValue + this@Outer.value)
}
}
}
fun main(args: Array<String>)
{
val instance: Outer = Outer(610)
val innerInstance: Outer.Inner = instance.Inner(40)
innerInstance.print()
}
Kotlin에서는 Data에 특화된 class가 존재한다. 이를 데이터 클래스라고 한다.
해당 class를 선언할 시, data 라는 키워드를 정의 하여야 한다.
또한 data class를 선언할 시, 다음과 같이 함수들이 오버라이딩 된다.
1. equals, hashCode, toString 멤버 함수가 자동으로 오버라이딩 된다.
2. equals 멤버 함수는 각 프로퍼티의 값이 모두 같으면 true로 처리되며, 만약 하나라도 다를 시에는 false로 표기하도록 오버라이딩 된다.
3. toString 멤버 함수는 class명(프로퍼티명=값, ...) 형태로 문자열로 표기 되도록 오버라이딩 된다.
또한 데이터 클래스에 한하여, 여러개의 변수로 쪼개는 것이 가능하다.
이에 대한 예제는 다음과 같다.
data class Employee(val name: String, val age: Int, val salary: Int)
fun main(args: Array<String>)
{
val first = Employee("John", 30, 3000)
val second = Employee("Page", 24, 5300)
val third = first.copy()
val first_2 = Employee("John", 30, 3000)
println(first.toString())
println(third.toString())
println(first == second)
println(first == third)
println(first == first_2)
// ---------- Data class 쪼개는 방법
val (name, _, salary) = Employee("John", 30, 3300)
println(name)
println(salary)
}
(위의 예제에서 보시면, _ 이 있는데, 이를 선언하게 되면, 해당 변수의 값 을 무시할 수 있다.
참고 : 초보자를 위한 Kotlin 200제 라는 책을 기반으로 공부 내용을 정리하였습니다.