클래스
클래스란 값과 그 값을 사용하는 기능들을 묶어놓은 것으로 고유의 특징 값을 넣는 속성과 기능을 구현한 함수로 이루어졌다
코드
1
2
3
4
fun main(){
}
class MobilePhone(osName:String,brand:String,model:String)
코틀린에서 클래스 선언 법은 위 코드와 같이 class를 적은 뒤 클래스 명과 기본 생성자인 소괄호 안에 속성 값을 속성 값 명과 타입을 쉼표로 구분하며 선언할 수 있다
사실 여기서 constructor은 생략되었다
이러한 클래스는 인스턴스를 만드는 틀로 위에 선언한 클래스로 인스턴스를 만들어보자
- 참고로 객체란 변수, 자료 구조, 함수, 메서드, 식별자에 의해 참조된 메모리 상의 값 등을 의미하고 인스턴스는 서로 다른 속성의 객체를 지칭하는 용어이다
코드
1
2
3
4
fun main(){
val galaxy = MobilePhone("Android","Samsung","S23")
}
class MobilePhone(osName:String,brand:String,model:String)
이렇게 하면 galaxy라는 변수에 osName 속성에는 Android 라는 값을 brand 속성에는 Samsung 이라는 값을 model 속성에는 S23 이라는 값을 인스턴스를 만든 것이다
인스턴트
코드
1
2
3
4
5
fun main(){
val galaxy = MobilePhone("Android","Samsung","S23")
println("galaxy에 os는 ${galaxy.osName}")
}
class MobilePhone(val osName:String, val brand:String, val model:String)
출력
1
galaxy에 os는 Android
위 코드처럼 인스턴트를 담은 변수를 사용하는 법은 변수명뒤에 .을 찍고 속성명을 적으면 된다
또한
코드
1
2
3
4
5
fun main(){
val galaxy = MobilePhone(brand = "Samsung", model = "S23")
println("galaxy에 os는 ${galaxy.osName} , brand는 ${galaxy.brand} , model은 ${galaxy.model}")
}
class MobilePhone(val osName:String = "Android", val brand:String, val model:String)
출력
1
galaxy에 os는 Android , brand는 Samsung , model은 S23
위와 같이 특정 속성 값에 기본 값을 지정하거나 특정 속성 값만 담는 것도 가능하다
이니셜라이저
다음으로 이렇게 클래스를 선언하게 되면 init을 사용할 수 있는데 여기서 init은 이니셜라이저를 지칭하며 객체가 생성될 때 호출되는 것을 말한다
코드
1
2
3
4
5
6
7
8
9
10
fun main(){
val galaxy = MobilePhone("Android","Samsung","S23")
val iphone = MobilePhone("ios","Apple","14 Pro")
val mi = MobilePhone("Android","Xiaomi","12S Ultra")
}
class MobilePhone(val osName:String,val brand:String,val model:String){
init {
println("This phone OS is $osName \nThis phone Brand is $brand \nThis phone Model Name is $model \n")
}
}
출력
1
2
3
4
5
6
7
8
9
10
11
This phone OS is Android
This phone Brand is Samsung
This phone Model Name is S23
This phone OS is ios
This phone Brand is Apple
This phone Model Name is 14 Pro
This phone OS is Android
This phone Brand is Xiaomi
This phone Model Name is 12S Ultra
위 코드에선 init에 속성 값들을 출력하게 만들었으니 인스턴트들이 만들어질 때 속성 값들이 출력된 것이다
메소드
자주 쓰는 기능들은 클래스 안에 함수로 만들 수 있는데 이것을 멤버함수 또는 메소드라고 하며 기기에 사양을 위와 같이 생성 될 때가 아닌 내가 원하는 것만 출력시킬 때 편하게 메소드를 만들어보면
코드
1
2
3
4
5
6
7
8
9
10
11
12
fun main(){
val galaxy = MobilePhone("Android","Samsung","S23")
val iphone = MobilePhone("ios","Apple","14 Pro")
val mi = MobilePhone("Android","Xiaomi","12S Ultra")
iphone.spec()
}
class MobilePhone(val osName:String,val brand:String,val model:String){
fun spec (){
println("This phone OS is $osName \nThis phone Brand is $brand \nThis phone Model Name is $model \n")
}
}
출력
1
2
3
This phone OS is ios
This phone Brand is Apple
This phone Model Name is 14 Pro
멤버 변수
코드
1
2
3
4
5
6
7
8
9
10
11
fun main(){
val galaxy = MobilePhone("Android","Samsung","S23")
galaxy.price=1000
println("galaxy is ${galaxy.price}$")
}
class MobilePhone(val osName:String,val brand:String,val model:String){
var price : Int? = null
}
출력
1
galaxy is 1000$
클래스 내에서는 메소드 같이 함수 뿐만 아니라 변수도 선언할 수 있는데 이것을 멤버 변수라고 부른다
보조 생성자
보조 생성자란 클래스 내부에 정의하는 생성자로 constructor(인자){초기화}형태를 가진다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main(){
val galaxy = MobilePhone("Android","Samsung","S23",1000)
galaxy.spec()
}
class MobilePhone(val osName:String,val brand:String,val model:String){
var price : Int? = null
constructor(osName:String, brand:String, model:String, price : Int):
this(osName,brand, model){
this.price=price
}
fun spec (){
println("This phone OS is $osName \nThis phone Brand is $brand \nThis phone Model Name is $model \n" +
"This phone Model Prince is $price$\n")
}
}
출력
1
2
3
4
This phone OS is Android
This phone Brand is Samsung
This phone Model Name is S23
This phone Model Prince is 1000$
해당 코드에 대해 뜯어보면
1
constructor(osName:String, brand:String, model:String, price : Int)
라고 보조 생성자를 만들어줬고
1
2
:
this(osName,brand, model)
이 부분에서 생성자에 osName,brand, model 값을 받는다고 해준 것이고
1
2
3
{
this.price=price
}
이 부분은 객체 생성 시 보조 생성자에 전달된 price에 값이 클래스 내부에 생성한 멤버 변수인 price으로 지정한 것이다
초기화
코드
1
2
3
4
5
6
7
8
9
10
11
fun main(){
val galaxy = MobilePhone()
}
class MobilePhone(){
var brand : String
}
출력
1
Error
위 코드와 같이 클래스내에 변수를 선언하면 항상 초기화를 시켜주어야 한다
하지만 lateinit을 추가하여 나중에 초기화를 예정시키는 방법이 있다
코드
1
2
3
4
5
6
7
8
9
10
11
fun main(){
val galaxy = MobilePhone()
galaxy.brand
}
class MobilePhone(){
lateinit var brand : String
}
출력
1
Error
하지만 위와 같은 경우에도 아직 초기화를 시키지 않았는데 해당 값에 접근하려 한다면 오류가 나게 된다
그러니
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main(){
val galaxy = MobilePhone()
galaxy.brand
}
class MobilePhone(){
lateinit var brand : String
init {
brand = "Samsung"
}
}
출력
1
위와 같이 init으로 초기화를 시켜주게 되면 오류가 안나게 된다
private
private는 해당 클래스에서만 사용할 수 있게 하는 것이다 만약 그 밖에서 사용하려고 한다면 오류가 발생하게 된다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun main(){
val galaxy = MobilePhone()
galaxy.brand
}
class MobilePhone(){
private var brand : String
init {
brand = "Samsung"
}
}
출력
1
Error
게터와 세터
게터는 필드의 값을 반환하는 역할이고 세터는 필드를 초기화하는 역할이다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main(){
val galaxy = MobilePhone()
println(galaxy.brand)
}
class MobilePhone(){
var brand : String = "SAMSUNG"
get() {
return field.toLowerCase()
}
}
출력
1
samsung
해당 코드는 brand에 접근하려고 하면 소문자로 받는 코드이다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main(){
val galaxy = MobilePhone()
}
class MobilePhone(){
var price : Int = 100
get() = field
set(value) {
field = value
}
}
코드를 보면
1
2
3
4
get() = field
set(value) {
field = value
}
이 부분에 코드는 우리가
1
var price : Int = 100
이 변수를 만들 때 자동으로 생성되는 것으로 field(price)는 value 값과 같다라는 것이다
이 코드를 수정하면
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fun main(){
val galaxy = MobilePhone()
galaxy.price = -1
println(galaxy.price)
}
class MobilePhone(){
var price : Int = 100
get() = field
set(value) {
field = if(value > 0) value else throw IllegalArgumentException("price must be greater than zero")
}
출력
1
2
Error
price must be greater than zero
아렇게 만약 value가 0 이하일 시 price must be greater than zero 에러 메세지를 출력하게 코드를 만들 수 있다
데이터 클래스
데이터 클래스는 copy(),equals(),toString() 등 다양한 메소드를 자동으로 생성해주는 클래스로 기본 생성자에 1개 이상의 파라미터가 있어야 하고 기본 생성자의 파라미터가 val 또는 var 로 선언해야 한다는 등 조건이 있다
코드
1
2
3
4
5
6
7
8
9
data class User(val id: Long, var name: String)
fun main(){
val user1 = User(1,"Kotlin")
val user2 = User(2,"JAVA")
println(user1)
println(user2)
}
출력
1
2
User(id=1, name=Kotlin)
User(id=2, name=JAVA)
위 코드처럼 데이터 클래스는
1
data class User(val id: Long, var name: String)
이렇게 data를 class 앞에 적어주고 그 뒤 클래스명과 생성자에 파라미터를 하나 이상 있어야 되며 var나 val이여야 한다
또한 위에서 설명한 것처럼 유용한 메소드를 사용할 수 있는데
1
2
3
4
5
6
7
8
9
10
11
12
13
14
data class User(val id: Long, var name: String)
fun main(){
val user1 = User(1,"Kotlin")
val user2 = User(2,"JAVA")
val user3 = User(3,"Kotlin")
val copyUser1 = user3.copy(id = 1)
println(user3)
println(copyUser1)
println(user1.equals(copyUser1))
}
출력
1
2
3
User(id=3, name=Kotlin)
User(id=1, name=Kotlin)
true
위 코드처럼
1
val copyUser1 = user3.copy(id = 1)
copy하여 특정 값만 바꿀 수도 있고
1
println(user1.equals(copyUser1))
== 대신 equals()로 비교할 수도 있다
상속
코틀린에는 상속이라는 개념이 존재하여
크게 부모 클래스(상위 클래스)와 자식 클래스(하위 클래스)가 있고 자식 클래스는 부모 클래스를 선택하여 그 부모의 멤버를 상속받아 그대로 쓸 수 있게 된다
코드
1
2
3
4
5
6
7
open class Car (val name: String,val brand: String){
}
class ElectricCar(name: String, brand: String , batteryLife:Double) : Car (name, brand){
}
상속 방법은 위 코드처럼 처럼 부모 클래스가 될 클래스 앞에 open을 적어준 뒤 자식 클래스에는 : 뒤에 상속 받을 부모 클래스를 적어준다 여기서 명심할 점은
부모의 매개 변수를 자식에게도 추가해 줘야 한다 또한 부모의 매개 변수들 모두 추가했다면 자식에게 다른 배개 변수를 추가해도 된다
상속에 특징으로는
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
open class Car (val name: String,val brand: String){
var range:Double = 0.0
fun extendRange(amount: Double){
if (amount>0){
range+=amount
}
}
fun drive(distance: Double){
println("Drove for $distance KM")
}
}
class ElectricCar(name: String, brand: String , batteryLife:Double) : Car (name, brand){
}
fun main(){
var myCar = Car("A6","Audi")
var myEcar = ElectricCar("S-Model","Tesla",85.0)
myCar.drive(200.0)
myEcar.drive(399.0)
}
출력
1
2
Drove for 200.0 KM
Drove for 399.0 KM
이렇게 부모 클래스에 있는 프로퍼티와 메소드를 사용할 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
open class Car (val name: String,val brand: String){
var range:Double = 0.0
fun extendRange(amount: Double){
if (amount>0){
range+=amount
}
}
fun drive(distance: Double){
println("Drove for $distance KM")
}
}
class ElectricCar(name: String, brand: String , batteryLife:Double) : Car (name, brand){
}
fun main(){
var myCar = Car("A6","Audi")
var myEcar = ElectricCar("S-Model","Tesla",85.0)
myCar.drive(200.0)
myEcar.drive(399.0)
myCar.toString()
}
또한 어느 클래스든 Any 클래스를 상속 받기에 equals,hashCode,toString 같은 함수 또는 메소드를 사용할 수 있다
오버라이딩
부모클래스의 메소드를 자식클래스에서 재정의해서 사용할 수 있는데 이걸 오버라이딩이라고 한다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
open class Car (val name: String,val brand: String){
open var range:Double = 0.0
fun extendRange(amount: Double){
if (amount>0){
range+=amount
}
}
fun drive(distance: Double){
println("Drove for $distance KM")
}
}
class ElectricCar(name: String, brand: String , batteryLife:Double) : Car (name, brand){
override var range = batteryLife*6
}
fun main(){
var myCar = Car("A6","Audi")
var myEcar = ElectricCar("S-Model","Tesla",85.0)
myCar.range = 85.0
println("myCar range = ${myCar.range}\nmyEcar range = ${myEcar.range}")
}
출력
1
2
myCar range = 85.0
myEcar range = 510.0
이렇게 오버라이딩을 하려면 자식 클래스에 오버라이딩을 시킬 것에 override을 적으면 된다 여기서 주의 할 점은 오버라이딩 시킬 요소가 부모 클래스에서 open 되어 있어야 한다
인터페이스
인터페이스란 다른 클래스를 작성할 때 기본이 되는 틀을 제공하면서 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미한다 단 인터페이스에서는 프로퍼티의 상태 정보를 저장할 수 없어 프로퍼티의 초기화가 불가능하다
코드
1
2
3
4
interface Drivable{
val maxSpeed: Double
fun brake() : String
}
인터페이스는 위 코드처럼 이름 앞에 interface를 붙여 선언한다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Drivable{
val maxSpeed: Double
fun brake() : String
}
open class Car (val name: String, val brand: String, override val maxSpeed: Double):Drivable{
open var range:Double = 0.0
fun extendRange(amount: Double){
if (amount>0){
range+=amount
}
}
fun drive(distance: Double){
println("Drove for $distance KM")
}
override fun brake(): String {
return "brake!!"
}
}
클래스가 인터페이스의 기능을 확장한다면 위 코드처럼 : 뒤에 확장할 인터페이스 명을 붙인다 그리고 인터페이스에 있는 것들을 추가해줘야 한다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
interface Drivable{
val maxSpeed: Double
fun brake() : String
}
open class Car (val name: String, val brand: String, override val maxSpeed: Double):Drivable{
open var range:Double = 0.0
fun extendRange(amount: Double){
if (amount>0){
range+=amount
}
}
fun drive(distance: Double){
println("Drove for $distance KM")
}
override fun brake(): String {
return "brake!!"
}
}
class ElectricCar(name: String, brand: String, batteryLife:Double, maxSpeed: Double) :
Car (name, brand, maxSpeed){
}
fun main(){
var myCar = Car("A6","Audi",300.0)
var myEcar = ElectricCar("S-Model","Tesla",85.0,400.0)
print(myCar.brake())
}
출력
1
brake!!
그럼 위 코드처럼 사용할 수 있다
추상 클래스
추상 클래스는 인터페이스와 비슷한데 대략적인 설계의 명세와 공통의 기능을 구현한 클래스로 추상 클래스를 상속하는 하위 클래스는 추상 클래스의 내용을 더 구체화 해야 한다 다만 인터페이스와 달리 프로퍼티의 초기화가 가능하다
추상 클래스는
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
abstract class Car(val name : String, val color : String, val weight : Double) {
abstract var maxSpeed : Double
var year = "2021"
abstract fun start()
abstract fun stop()
fun displaySpecs() {
println("Name : $name, Color : $color, Weight : $weight, Year : $year, MaxSpeed : $maxSpeed")
}
}
위 코드처럼 abstract를 붙여 만들 수 있다 특징으로는
1
var year = "2021"
인터페이스랑 달리 위 코드 같이 초기값을 갖는 일반 프로퍼티가 가능하고
1
2
3
4
abstract var maxSpeed : Double
abstract fun start()
abstract fun stop()
이것들 처럼 추상 프로퍼티나 메소드로 만들 수도 있다 하지만 이것들을 사용하기 위해서는 해당 클래스가 추상 클래스이여야 하며 꼭 상속받는 클래스에서 재정의 해줘야 한다
또한 open을 써주지 않아도 상속이 가능하다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
abstract class Car(val name : String, val color : String, val weight : Double) {
abstract var maxSpeed : Double
var year = "2021"
abstract fun start()
abstract fun stop()
fun displaySpecs() {
println("Name : $name, Color : $color, Weight : $weight, Year : $year, MaxSpeed : $maxSpeed")
}
}
class ElectricCar(name: String, color: String, weight: Double, override var maxSpeed: Double): Car(name, color, weight) {
override fun start() {
println("ElectricCar start")
}
override fun stop() {
println("ElectricCar Stop")
}
}
fun main() {
val tesla =ElectricCar("Model S","Red",300.2,322.0)
tesla.displaySpecs()
}
출력
1
Name : Model S, Color : Red, Weight : 300.2, Year : 2021, MaxSpeed : 322.0
중첩 클래스
한 클래스안에 다른 클래스를 정의하면 중첩 클래스가 된다
추가로 중첩 클래스는 외부 클래스의 인스턴스에 종속적이지 않기 때문에 외부 클래스의 인스턴스 변수에 대해 접근할 수 없으며 인스턴스와 독립적으로 객체를 생성할 수 있습니다
코드
1
2
3
4
5
6
7
class Outer {
class Nested {
fun foo() = "Hello from Nested"
}
}
val nestedInstance = Outer.Nested().foo()
내부 클래스
1
2
3
4
5
6
7
8
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val innerInstance = Outer().Inner().foo()
내부 클래스는 중첩 클래스에 inner을 붙여 선언하며 인터페이스 안에 또는 내부 중첩 클래스가 아닌 곳에 선언될 수 없다는 특징을 가졌다 private이더라도 외부 클래스 멤버에 접근할 수 있고 내부 클래스는 외부 클래스 객체의 참조를 저장한다
확장 함수
확장 함수란 기존 클래스의 수정 없이 그 클래스에 새로운 함수를 추가하는 방법으로 이를 통해 기존 라이브러리의 클래스나 코틀린에서 기본적으로 제공하는 클래스에 사용자 정의 함수를 추가할 수 있다
코드
1
2
3
4
5
6
7
8
fun main() {
val str = "Hello World"
println(str.isNotEmptyString())
}
fun String.isNotEmptyString(): Boolean {
return this.isNotEmpty()
}
출력
1
true