본문 바로가기
공부/kotlin

코틀린의 클래스

by 샤샤샤샤 2023. 3. 21.
//ex21
fun main() {
    //2중 반복문
    // *****
    // *****
    // *****
    // *****
    // *****
    for( i in 0 until 5) {
        for( j in 0..4 ){
            print("*")
        }
        println()
    }
    //연습문제
    // readLine()함수로 n을 입력해서 3이 나오면,
    //     *
    //    ***
    //   *****
    // n을 입력해서 4가 나오면
    //     *
    //    ***
    //   *****
    //  *******
    print("n을 입력하세요: ")
    // readLine() 함수로 입력받은 값을 정수형으로 변환합니다.
    val n = readLine()?.toInt() ?: 0

    for (i in 1..n) {
        // 공백 출력
        for (j in 1..n-i) {
            print(" ")
        }

        // 별표 출력
        for (j in 1..2*i-1) {
            print("*")
        }

        println() // 다음 줄로 넘어갑니다.
    }
}

예외처리

사용자 정의 예외

class <사용자예외 클래스 이름>(message: String) : Exception(message)
.
.
.
 throw <사용자예외 클래스 이름>("문자열")
 }

사용자가 정해둔 예외가 발생할시, 미리 지정해둔 문자열이 출력된다.

 

코틀린의 클래스

코틀린의 함수는 함수지향언어의 특징을 가지고 있지만, 동시에 객체지향언어의 특징도 가지고 있기에 클래스 역시 그렇다. 객체지향 언어의 특징은 1. 프로퍼티와 메소드를 가진다, 2. 인스턴스와 객체를 가진다, 3. 생성자 함수를 만든다, 4. 상속을 받는다, 5. 다형성을 지닌다, 6. 캡슐화가 가능하다, 7. get/set함수를 통해 프로퍼티에 접근할수 잇다, 8. 추상화 클래스, 또는 인터페이스가 존재한다는 것이다.

이중 코틀린만의 독특한 특징을 몇개 짚어보겠다.

 

코틀린의 생성자

생성자는 constructor 라는 예약어를 통해 설정해줄수 있으며, init이라는 예약어를 통해 생성자 함수가 실행할 코드를 지정해줄수 있다.

class Person constructor ( _name: String,  _age: Int) {
   val name : String
   val age : Int
   
    init {
        name = _name
        age = _age
    }
 }

그러나 이는 너무 길기에, 보통 프로퍼티(맴버 변수)를 외부로부터 받아오는 클래스의 생성자함수의 경우 프로퍼티 선언 자체를 생략하며, init블록과 constructor 역시 생략 가능하다.

아래는 최대로 생략된 함수다.

class Person (val name: String, val age: Int) {
}

주생성자와 부생성자

코틀린에는 주생성자와 부생성자가 존재한다. 주생성자는 오직 하나만 존재할수 있으며 자바의 생성자 함수와 같은 형식으로 작성한다.

class Person (val name: String, val age: Int) {
    init{
        println("$name, $age")
    }
}
// 클래스 명(인자){ 실행문 }

 

주 생성자는 init으로 실행시킬 코드가 없다면 init블록 역시 생략 가능하다.

반면 부 생성자는 init 대신 constructor로 실행할 코드를 적어줄수 있다.

class Person2(val name: String, val age: Int) {
    init {
        println("주 생성자 호출됨: name=$name, age=$age")
    }
    //constructor : 부생성자 예약어, 기본값 설정 역할
    constructor(name: String) : this(name, 30) {
        println("부 생성자 호출됨: name=$name, age=$age")
    }
    constructor(age: Int) : this("이름없음", age) {
        println("부 생성자 호출됨: name=$name, age=$age")
    }
}

constructor를 사용하면 생성자 매개변수에 기본값을 설정해줄수 있다. 이렇게 하면 주생성자의 매개변수를 모두 받지 못했을때, 기본값으로 지정된 값이 저절로 프로퍼티에 들어가게된다. 이는 아래와 같이 표현할수도 있다.

class D(val name: String) {

    var age: Int = 20

//    컴파일 에러!
//    constructor(name: String, age: Int) {
//        this.age = age
//    }

    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}

보다시피 주생성자함수를 통하지 않고 부생성자함수로 프로퍼티를 재정의하려고 하니 오류가 발생한다. 

만약 주생성자가 존재한다면, 부생성자는 무조건 주생성자에게 직, 간접적으로 생성을 위임해야 하기 때문이다. 아래의 코드는 this를 통해 주생성자를 호출해 생성을 위임했다.

 

클래스의 상속

코틀린에서 모든 메서드와 클래스는 기본적으로 자바의 final 속성을 부여받았다. 따라서 상속하기 위해선 open이라는 변경자를 붙여줘야 하며, 설사 클래스에 open을 붙였다 하더라도 메서드의 오버라이드를 허용하고 싶다면 메서드 앞에서 open을 적어줘야 한다.

open class Animal(val name: String, val age: Int) {
   open fun speak() {
            println("$name is $age years old.")
       }
}

class Cat(name: String, age: Int) : Animal(name, age) {
   override fun speak() {
        println("$name meows softly.")
    }
}

fun main(){
    val cat = Cat("나비", 6)
}

// 결과: 나비 meows softly.

상속은 다른 예약어 없이 :(콜론) 을 통해 가능하다.

상속받은 클래스의 주생성자와 부생성자 역시 사용 가능하다.

 

this와 super

자바와 마찬가지로 this와 super을 사용 가능하다.

this는 자기 자신을 가리키며, super은 상속받은 부모 클래스를 가르킨다.

 

오버라이드(override) : 재정의

상위 클래스의 함수를 하위클래스에서 재정의하는 행위. 이를 위해선 open 변경자를 붙여줘야만 가능하다.

 

접근 제한자(가시성 변경자)

자바와 똑같이 4가지 종류의 접근제한자를 가지고 있으며, 따로 설정되지 않으면 기본적으로 public의 속성을 가지게 된다.

public : 모든 클래스에서 접근 가능

protected : 하위 클래스에서 접근 가능

internal : 같은 모듈(폴더)안에서 접근 가능

private : 같은 클래스에서만 접근 가능

 

추상화 클래스와 인터페이스

추상화 클래스

추상화 클래스는 프로퍼티 또는 함수를 선언만 해두고, 실행 코드나 직접적인 값은 상속받은 클래스가 재정의하도록 한다.

생성자를 사용 가능하며, 프로퍼티는 미리 초기화해둘수도 있다. 다만 추상화 클래스를 다중으로 상속받는 것은 불가능하다.

 

fun main() {
    val circle = Circle(5.0)
    circle.display()

    val rectangle = Rectangle(10.0, 5.0)
    rectangle.display()
}

abstract class Shape {
    abstract val name: String
    abstract fun area(): Double //넓이(면적)
    abstract fun perimeter(): Double  //둘레(선의 길이)

    fun display() {
        println("Shape: $name")
        println("Area: ${area()}")
        println("Perimeter: ${perimeter()}")
    }
}

class Circle(val radius: Double) : Shape() {
    override val name: String = "Circle"

    override fun area(): Double {
        return Math.PI * radius * radius
    }

    override fun perimeter(): Double {
        return 2 * Math.PI * radius
    }
}

class Rectangle(val width: Double, val height: Double) : Shape() {
    override val name: String = "Rectangle"

    override fun area(): Double {
        return width * height
    }

    override fun perimeter(): Double {
        return 2 * (width + height)
    }
}

 

인터페이스

추상화와 비슷하나, 프로퍼티 초기화가 불가능하다. 또한 생성자를 가질수도 있고, 다중상속도 가능하다. 주로 행동을 정의한다.

fun main() {
    val car = Car2("Tesla")
    car.drive()
    car.displayMaxSpeed()

    val bicycle = Bicycle2("Giant")
    bicycle.drive()
    bicycle.displayMaxSpeed()
}

interface Drivable {
    val maxSpeed: Double //추상화 프러퍼티, open 예약어 생략

    fun drive() //추상화 메소드

    fun displayMaxSpeed() { //open 예약어 생략
        println("Max speed: $maxSpeed")
    }
}
// : 콜론 : 인터페이스 상속시 사용함
class Car2(val model: String) : Drivable {
    override val maxSpeed: Double = 200.0

    override fun drive() {
        println("The $model car is driving.")
    }
}

class Bicycle2(val brand: String) : Drivable {
    override val maxSpeed: Double = 30.0

    override fun drive() {
        println("The $brand bicycle is being ridden.")
    }
}
  인터페이스 추상화 클래스
1, 다중구현 다중구현이 가능함 단일 상속만 가능
2. 생성자 생성자를 가질수 없다. 생성자 호출이 가능하다.
3. 프로퍼티(맴버 변수) 추상 프로퍼티를 가질수 있으나, 초기화는 불가능하다 추상 프로퍼티와 일반 프로퍼티 모두 가질수 있다.
4. 상태와 행동  메서드(행동)을 정의한다. 메서드(행동)와 프로퍼티(상태) 모두 정의한다.

 

다형성

코틀린 역시 다형성이 존재한다. 다형성이란 어떤 클래스가 상위 클래스의 타입 역시 자신의 타입으로 가질수 있는 성질을 말하는데, 이를 사용하면 서로 다른 클래스간 매개변수 전달이 용이해진다.

//다형성 : 상속받은 클래스 객체가 다양한 클래스타입을 가질 수 있는 것
//      : A와 B를 상속받은 클래스 C는 C타입, B타입, A타입을 가질 수 있다.
//      : 사용하는 이유 : 객체의 매개변수 전달이 용이하다.
fun main() {
    val circle = Circle2(2.0)
    val rectangle = Rectangle2(3.0, 4.0)

    printArea(circle)
    printArea(rectangle)
}

interface Shape2 {
    fun area(): Double
}

class Circle2(val radius: Double) : Shape2 {
    override fun area() = Math.PI * radius * radius
}

class Rectangle2(val width: Double, val height: Double) : Shape2 {
    override fun area() = width * height
}

fun printArea(shape: Shape2) {
    println("The area is ${shape.area()}")
}

 

 getter와 setter

기존 자바에서 getter와 setter함수는 private로 선언된 필드(맴버변수, 프로퍼티)를 불러오고 수정하는데 사용했으나, 코틀린의 get과 set은 값을 대입, 수정할때 추가적으로 실행할 코드를 작성할수 있다.

fun main() {
    val person = Person4()
    person.name = "John" //setter 자동호출됨
    println( person.name ) //getter 자동호출됨
}
class Person4{
    //get() : 값을 읽을 때 자동호출됨
    //set() : 값을 대입(변경)할때 자동호출됨
    val sex : String = "male"
    var age : Int = 10;
    var name: String = "Hong"
        get() {
            println("getter 호출됨")
            return field.toUpperCase()
        }
        set(value) {
            println("setter 호출됨")
            field = value.toLowerCase()
        }
}

 

val은 값의 변경이 불가능한 상수이기에 get만 사용 가능하다. 또한 get/set함수는 프로퍼티 아래에 선언된 프로퍼티에만 해당된다.

get/set함수(Accessor 메소드) 에서는 자신을 가르키는 field라는 예약어를 사용할수 있는데, Accessor 메소드가 작용하는 프로퍼티를 가르킨다.

자바의 코드로 보면 아래와 같다.

 

코틀린의 문자열

JS처럼 [인덱스] 접근이 가능하다.

자바와 달리 ==기호를 이용해 문자열 비교가 가능하다.

""" 문자열 """ 과 같은 식으로 줄바꿈 문자열을 표현할수 있다. 이때 줄바꿈이 되는 곳은 | 을 써넣어 표현할수 있다.

ex)

fun main() {
    val str2 = """Hello,
            |world!""".trimMargin()  // 삼중 쌍따옴표를 사용하여 멀티라인 문자열 생성
    println(str2)
}

trimMargin(): 문자열의 여백을 지운다.

 // 문자열 접근
    val str3 = "Hello, world!"
    val firstChar = str3[0]  // 문자열의 첫 번째 문자에 접근
    val lastChar = str3.last()  // 문자열의 마지막 문자에 접근
    val subStr = str3.substring(0, 5)  // 문자열의 일부를 추출
    val str4 = "hello, WORLD!"
    val isEqual = str3.equals(str4, ignoreCase = true) // 대소문자를 무시하고 
                                                       // 두 문자열이 동일한지 확인

substring(시작 인덱스, 끝 인덱스) : 문자열을 자른다.

 val index1 = str3.indexOf("world")  // 문자열에서 "world"가 처음 등장하는 위치를 찾음
 val index2 = str3.lastIndexOf("o")  // 문자열에서 마지막 "o"가 등장하는 위치를 찾음
 val isContains = str3.contains("wor")  // 문자열에 "wor"이 포함되어 있는지 확인

 

'공부 > kotlin' 카테고리의 다른 글

코틀린: 코틀린 함수  (1) 2023.03.20
코틀린: 코틀린의 기본 문법  (0) 2023.03.19