1. 객체(Object)
객체란, 상자, 사람, 가방 등 우리가 인식할 수 있는 물체 또는 물건을 뜻한다.
객체들은 각자의 고유한 속성과 동작을 갖고 있다.
SW 관점에서 객체란, 서로 연관 있는 변수(속성)들을 묶어놓은 데이터 덩어리를 뜻한다.
코틀린에서 object는 {} 중괄호로 시작과 끝을 나타낸다.
이 사이에 object에 포함시킬 변수들을 선언한다.
object에 포함된 변수들을 선언과 동시에 초기화 하고 있으며, name, age처럼 object에 포함된 변수들은 프로퍼티(property)라고 부른다.
fun main(args: Array<String>): Unit {
// person 변수에 object(객체)를 저장
val person = object {
/* object에 포함된 변수들은 선언과 동시에 초기화
object에 포함된 변수들은 프로퍼티(property) == 속성 이라고 부른다.
프로퍼티는 반드시 선언과 동시에 초기화를 해야함 */
val name: String = "홍길동"
val age: Int = 36
}
println(person.name)
println(person.age)
}
코틀린의 프로퍼티는 언뜻 보면 자바의 필드와 동일하지만, 사실 프로퍼티는 필드와 같지 않다.
프로퍼티는 필드와 Getter, Setter가 합쳐진 개념이다.
2. 클래스(Class)
클래스 선언 하는 방법은 다음과 같다.
class 클래스 이름 {
프로퍼티
}
class Person {
var name: String = ""
var age: Int = 0
}
fun main(args: Array<String>): Unit {
val person: Person
person = Person()
person.name = "홍길동"
person.age = 36
val person2 = Person()
person2.name = "이동욱"
person2.age = 40
val person3 = Person()
person3.name = "Ellie"
person3.age = 29
println(person.name)
println(person.age)
println(person2.name)
println(person2.age)
println(person3.name)
println(person3.age)
}
- object 키워드로 객체를 일일이 생성했을 때는 객체의 타입에 이름이 없었다.
심지어 객체를 여러 개 생성하면 객체의 형태가 완전히 같아도 서로 다른 타입으로 인식 된다.
클래스로 생성된 객체는 공장에서 찍혀 나오는 공산품과 같기 때문에 모두 동일한 타입을 갖는다.
- 객체를 생성할 때 자바에서 사용하는 new 키워드는 코틀린에서 사용하지 않는다.
new 없이 생성자만 호출하면 된다.
- 코틀린의 기본 접근지정자는 public
자바는 default인데 코틀린이 이렇게 정의한 이유는 자바의 수많은 클래스와 메서드들이 public으로 선언되어 있다는 점 때문이다. 즉, 간결한 코드를 중시하기 위해서!
- 참조 변수를 초기화하지 않은 채 프로퍼티에 접근하면 오류 발생
val person: Person
person.age = 23
(Variable 'person' must be initialized)
반드시 참조 변수에 Person()을 대입해 주어야 한다.
3. ===, !== 연산자
=== 연산자 : 두 참조 변수가 '같은 객체'를 가리키고 있으면 true, 아니면 false
!== 연산자 : 두 참조 변수가 '서로 다른 객체'를 가리키고 있으면 true, 아니면 false
코틀린의 === 연산자는 자바의 == 연산자와 같다.
코틀린에서의 == 연산자는 자바의 equals 메서드를 호출한 것과 같은 효과
fun main(args: Array<String>): Unit {
var a = "one"
var b = "one"
println(a === b)
b = "on"
b += "e"
println(a !== b)
b = a
println(a === b)
}
4. 멤버 함수(Member Function)
클래스에 내장된 함수를 멤버함수라고 한다.
특정 클래스와 강한 연관이 있는 함수는 아예 클래스 안으로 내장시킬 수 있다.
class Building {
var name = "" // 건물명
var date = "" // 건축일자
var area = 0 // 면적(m²)
/* 아래와 같이 클래스에 내장된 함수를 멤버 함수라고 부른다.
각 프로퍼티 이름 앞에 this. 붙어있다.
this는 멤버 함수가 호출될 때, 어떤 객체로부터 호출된 것인지를 나타내는 키워드이다.
즉, building.print()와 같이 멤버 함수를 호출하면 this 키워드는 building 참조 변수가 가리키는 객체로 치환된다.
this.는 생략 가능하므로, println("이름: " + name)과 같이 프로퍼티 이름만 써도 상관 없다. */
fun print() {
println("이름: " + this.name)
println("건축일자: " + this.date)
println("면적: ${this.area}m²")
}
}
// printBuilding 함수는 Building의 인스턴스만을 위한 함수
// 이렇게 특정 클래스와 강한 연관이 있는 함수는 아예 클래스 안으로 내장시킬 수 있다(print)
fun printBuilding(buildng: Building) {
println("이름: " + buildng.name)
println("건축일자: " + buildng.date)
println("면적: " + buildng.area)
}
프로퍼티와 멤버 함수의 매개변수 이름이 중복될 때는
this를 사용하여 구분할 수 있다.
class AAA {
var num = 15
fun memberFunc(num: Int) {
println(num) // 매개변수
println(this.num) // 프로퍼티
}
}
fun main(args: Array<String>): Unit {
val a = AAA()
a.memberFunc(53)
}
5. 힙 영역의 존재 이유
객체는 왜 스택이 아닌 힙 영역에 생성되는지 살펴보자
class Product {
var name = ""
var price = 0
}
fun main(args: Array<String>): Unit {
val product: Product
product = createProduct()
// val product: Product = createProduct() // 한 줄로 간결하게 코딩 가능하다
printProductInfo(product)
processProduct(product)
printProductInfo(product)
}
fun createProduct(): Product {
val apple = Product()
apple.name = "Apple"
apple.price = 1000
return apple
}
fun processProduct(product: Product) {
product.price += 500
}
fun printProductInfo(product: Product) {
println("이름: ${product.name}")
println("가격: ${product.price}")
}
먼저, 변수의 선언이 이루어지므로 이때까지의 메모리 상황은 아래와 같다.

createProduct 함수 안으로 실행 흐름이 이동하고,
Product의 인스턴스를 생성하고 apple 참조 변수에 그 위치를 저장한다.

apple 참조 변수가 가리키는 객체의 프로퍼티에 값을 채워넣는다.
apple 참조 변수가 가지고 있는 참조값을 반환한다.

apple 참조 변수는 createProduct 함수가 끝났으므로 스택에서 지워진다.
하지만, apple 참조 변수가 가리키고 있던 객체는 힙에서 지워지지 않는다.
이후 product에 apple이 가지고 있던 참조값이 저장되어 다음과 같은 상태가 된다.

createProduct 함수 안에서 생성한 객체를 main 함수에서도 접근할 수 있다.
printProductInfo 함수로 product의 참조값을 전달하고 있다.
전달받은 참조값을 매개변수 product에 저장하며, 이제 두 개의 참조 변수가 하나의 객체를 가리키게된다.
전달받은 참조값을 매개변수 product에 저장하고 product가 가리키는 객체의 변수들을 출력한다.

printProductInfo 함수의 product 매개변수가 사라져 메모리 공간은 다시 기존으로 돌아간다.

processProduct 함수를 호출하며 product의 참조값을 전달하고 있다.
전달받은 참조값을 매개변수 product에 저장. 이제 두 개의 참조 변수가 하나의 객체를 가리키게 된다.

product 매개변수가 가리키고 있는 객체의 price 프로퍼티를 수정하고 있다.
main 함수의 product 참조 변수도 product 매개변수와 동일한 객체를 가리키고 있기 때문에 함께 영향을 받는다.

문자열간 + 연산 시 주의점
문자열간 + 연산 시에도 String 변수는 문자열의 참조값을 저장하기 위한 공간만 갖고 있고 실제 문자열은 힙 영역에 생성이 된다.
아래 코드를 살펴보자
fun main(args: Array<String>): Unit {
var first = "Hello "
var second = "World"
first += second
}
first 변수에 first와 second를 합쳐서 저장하는 것처럼 보이지만 실제 힙영역에는 문자열에 새로운 문자열이 덧붙여지는 게 아니라 기존의 문자열은 그대로 남고 합쳐진 문자열이 생성된다.

이러한 문제를 해결하기 위해 가비지 컬렉션 기능을 이용하여 미아가 된 객체를 삭제하고 있다.
'TIL > Kotlin' 카테고리의 다른 글
[TIL/Kotlin] 코틀린 중급문법_예외 처리, 예외 던지기 (0) | 2023.05.14 |
---|---|
[TIL/Kotiln] 코틀린 중급문법_생성자와 초기화(init)블록, 보조생성자, Getter/Setter, 연산자 오버로딩, 번호붙은접근연산자, 호출연산자, in연산자, 멤버함수의 중위표기법, 상속, 업캐스팅, 오버라.. (0) | 2023.05.06 |
[TIL/Kotlin] 코틀린 기초문법_함수, Unit 타입, 디폴트 인수와 가변 인수 (0) | 2023.05.05 |
[TIL/Kotlin] 코틀린 기초문법_흐름 제어(조건문과 반복문, Label) (0) | 2023.05.05 |
[TIL/Kotlin] 코틀린 기초 문법_표현식, 변수선언, 비트연산자, 정수/실수/문자/문자열, 표현식, 타입별명 (0) | 2023.05.05 |