본문 바로가기

개발 노트/Kotlin

[kotlin] 문법 3주차 정리 - 기초

# 메서드 설계 

- 클래스의 함수멤버함수메서드

 

- Kotlin의 메소드 구조

fun 메소드이름(변수명:자료형, 변수명:자료형 ....) : 반환자료형 {
		소스코드 로직
}

 

 

- 예제

  • 두 개의 숫자를 더하는 메소드를 만들고, 전달하는 숫자에 따라 덧셈결과를 알려주기
fun main() {
    var num1 = readLine()!!.toInt()
    var num2 = readLine()!!.toInt()

    // sum이라는 이름의 메소드를 호출!
    sum(num1, num2)
}

fun sum(num1:Int, num2:Int) {
    var result = num1 + num2
    println("num1과 num2의 덧셈결과는 ${result}입니다.")
}


//결과
10
5
num1과 num2의 덧셈결과는 15입니다.

 

 

  • 오늘의 정보를 알려주는 메소드를 만들기
fun main() {
    // displayInfo라는 이름의 메소드를 호출!
    displayInfo()
}

fun displayInfo() : Unit {
    println("오늘의 날씨는 화창합니다")
    println("오늘은 검정색을 조심하세요")
}

//결과
오늘의 날씨는 화창합니다
오늘은 검정색을 조심하세요

 

-> 반환할 자료형이 없을때는 Unit으로 명시하거나 생략할 수 있다 

 

 

  • 수학점수를 입력받아 등급을 출력해주는 프로그램 만들기
fun main() {
    var myMathScore = readLine()!!.toInt()
    
    var myRank = checkRank(myMathScore)
    println("나의 등급은 : ${myRank}")
}

fun checkRank(score:Int) : String {
		return when(score) {
			in 90..100 ->	return "A"
			in 80..89 -> return "B"
			in 70..79 -> return "C"
			else -> return "D"
		}
}

//결과
88
나의 등급은 : B

 

 

 

# 클래스 설계 

#  객체지향 프로그래밍 이란??

  • Object Oriented Programming (OOP)
  • 코틀린은 모든것이 클래스형태이므로 객체화할 수 있다
  • 프로그램에서 필요한 데이터를 추상화시켜 상태와 행위를 가진 객체를 만들수있다 
  • 객체들간의 적절한 결합을 통해 유지보수를 쉽게 할수있다
  • 5대 키워드 (클래스, 추상화, 캡슐화, 상속, 다형성)

 

# 클래스 란??

  • 프로그램의 각 요소별 설계도
  • 코틀린에서는 class 키워드를 활용해서 클래스를 만들수있다
  • 클래스에는 정보(프로퍼티)행위(메소드)를 작성한다
  • 보통 하나의 파일한개의 클래스를 의미하지만, 하나의 파일안여러개의 클래스가 존재할 수도 있다

# 클래스 구조

class 클래스이름 {
	  정보1
		정보2

		행위1
		행위2
}

 

 

# 다양한 클래스들 

- 데이터 클래스(data class)

  -> 프로퍼티만 가지고있는 클래스 / 정보만 가지고 있는 클래스 

  -> 개발하다보면 아주 많이 사용

data class 클래스이름 {
	정보1
	정보2
}

 

- 실드 클래스 (sealed class) [나중에 다시 학습]

  -> 클래스 상속과 관련된 개념 / 상속받을 수 있는 자식클래스들을 미리 정의

  -> 무분별한 상속을 방지

sealed class 부모클래스 {
	class 자식클래스1 : 부모클래스생성자
	class 자식클래스2 : 부모클래스생성자
}

 

- 오브젝트 클래스 (object class)  [나중에 다시 학습]

  -> Java의 static 대신 사용하는 키워드

  -> 프로그램을 실행하는 동시에 인스턴스화한다

 

- 열거 클래스 (enum class)  [나중에 다시 학습]

  -> 여러 곳에 동일한 상수를 정의하거나, 상수 외부에 관련된 변수나 함수를 정의하게 되면 코드가 증가할수록 관리가 어려워운데, enum class를 이용해서 상수값에 대한 관리 지점을 줄일 수 있다

 

 

 

# 예시코드

-> 클래스 설계만 해봄

-> 프로그램에서 사용하는 특정 캐릭터들의 설계도를 작성하는 코드

fun main() {
    
}

class Character {
    var name:String = ""
    var hairColor:String = ""
    var height:Double = 0.0

    fun fireBall() {
        println("파이어볼!")
    }
    fun compositing(device1:String, device2:String): String {
        var device3 = device1 + device2
        println("새로운 무기인 ${device3}입니다")
        return device3
    }
}

 

-> main함수에 아무것도 없기때문에 출력값은 없음 (클래스 설계만 함)

 

 

 

# 생성자의 활용 

- 클래스(설계도)를 실체화 할 때 최초로 실행할 로직을 작성

- 기본 생성자와 명시적 생성자가 존재

- 기본 생성자는 이전까지 클래스를 만들던 행위와 차이가 없음)

- 명시적 생성자init생성자(주생성자) Constructor생성자(부생성자) 가 있음 

 

  • Init 생성자 (주 생성자) 의 사용 예시
fun main() {

}

// 클래스 선언부에 생성자를 명시함
class Character(_name:String, _hairColor:String, _height:Double) {
    var name:String = ""
    var hairColor:String = ""
    var height:Double = 0.0
    

// 매개변수를 직접 넘기지않음
    init {
        println("매개변수없는 생성자 실행 완료!")
    }



    fun fireBall() {
        println("파이어볼!")
    }
    
    
    fun compositing(device1:String, device2:String): String {
        var device3 = device1 + device2
        println("새로운 무기인 ${device3}입니다")
        return device3
    }
}

 

-> 클래스의 생성자가 호출될때, init 안의 코드가 실행된다

-> Constructor 생성자와 비교해볼때, init은 매개변수를 직접 넘기지 않는다는 차이가 있다

-> 무조건 name, hairColor, height만 넘기는 생성자만 만들수 있음 ( 한 가지의 형태로 클래스를 실체화할때 유용)

 

 

  • Constructor 생성자 (부 생성자)의 사용 예시
fun main() {

  }

  class Character {
      var name:String = ""
      var hairColor:String = ""
      var height:Double = 0.0



      // 명시적 생성자 (Constructor)
      // _name, _hairColor, _height와 같이 생성자에 변수를 넘기는 경우에 사용함
      constructor(_name:String, _hairColor:String, _height:Double) {
          println("${_name}을 생성자로 넘겼어요")
          println("${_hairColor}를 생성자로 넘겼어요")
          println("${_height}를 생성자로 넘겼어요")
      }



      fun fireBall() {
          println("파이어볼!")
      }
      
      
      fun compositing(device1:String, device2:String): String {
          var device3 = device1 + device2
          println("새로운 무기인 ${device3}입니다")
          return device3
      }
  }

 

-> init 생성자와 비교했을때, Constructor 생성자는 매개변수를 직접 넘겨서 사용한다

-> Constructor를 여러개 만들어서 어떤생성자는 name만 받고, 어떤 생성자는 name,hairColor,height를 받도록 설정해서 때에 따라 원하는 생성자를 선택할 수 있음 ( 여러 형태로 클래스를 실체화할때 유용)

 

 

  •   Constructor로 여러개의 생성자를 만드는 예시 코드 ( 두 개의 생성자를 선택해서 실체화 할 수 있다)
fun main() {

}

class Character {
    var name:String = ""
    var hairColor:String = ""
    var height:Double = 0.0
    var age:Int = 0
    var gender:String = ""

    // 명시적 생성자 (Constructor)
    // _name, _hairColor, _height와 같이 생성자에 변수를 넘기는 경우에 사용함
    constructor(_name:String, _hairColor:String, _height:Double) {
        println("${_name}을 생성자로 넘겼어요")
        println("${_hairColor}를 생성자로 넘겼어요")
        println("${_height}를 생성자로 넘겼어요")
    }
    // _name, _hairColor, _height, _age, _gender와 같이 생성자에 변수를 넘기는 경우에 사용함
    constructor(_name:String, _hairColor:String, _height:Double, _age:Int, _gender:String) {
        println("${_name}을 생성자로 넘겼어요")
        println("${_hairColor}를 생성자로 넘겼어요")
        println("${_height}를 생성자로 넘겼어요")
        println("${_age}를 생성자로 넘겼어요")
        println("${_gender}를 생성자로 넘겼어요")
    }

    fun fireBall() {
        println("파이어볼!")
    }
    fun compositing(device1:String, device2:String): String {
        var device3 = device1 + device2
        println("새로운 무기인 ${device3}입니다")
        return device3
    }
}

 

 

 

# 객체의 활용 

- 객체란??

- 모든 인스턴스를 포함하는 개념

- 클래스 타입으로 선언된것들 

 

- 인스턴스란??

- 클래스형태로 설계된 객체를 실체화하면 인스턴스가 생김

- 인스턴스는 메모리 공간을 차지 

 

- 클래스를 실체화해보자

- 정보와 행위를 작성한 클래스를 실체화해서 프로그램에 로딩

- 정보가 행위가 그대로 로딩되는것이 아니라 위치정보를 메모리에 로딩

- 프로그램은 객체의 위치정보를 변수에 저장해두고, 필요할 때 참조

 

 

- 예시코드 ( Character 클래스를 객체화해서 여러가지 캐릭터를 만드는 코드)

fun main() {

		// 불마법사로 객체화
    var magicianOne = Character("불마법사", "red", 180.2)
		println("${magicianOne.name}의 머리색상은 ${magicianOne.hairColor}입니다")
		magicianOne.fireBall()

		// 냉마법사로 객체화
    var magicianTwo = Character("냉마법사", "blue", 162.2, 25, "여")
		println("${magicianTwo.name}의 머리색상은 ${magicianTwo.hairColor}이고 나이는 ${magicianTwo.age}입니다.")
		magicianTwo.fireBall()
        
}



class Character {
    var name:String = ""
    var hairColor:String = ""
    var height:Double = 0.0
    var age:Int = 0
    var gender:String = ""


    // 명시적 생성자 (Constructor)
    // _name, _hairColor, _height와 같이 생성자에 변수를 넘기는 경우에 사용함
    constructor(_name:String, _hairColor:String, _height:Double) {
        println("${_name}을 생성자로 넘겼어요")
        println("${_hairColor}를 생성자로 넘겼어요")
        println("${_height}를 생성자로 넘겼어요")
				name = _name
				hairColor = _hairColor
				height = _height
    }
    
    
    // _name, _hairColor, _height, _age, _gender와 같이 생성자에 변수를 넘기는 경우에 사용함
    constructor(_name:String, _hairColor:String, _height:Double, _age:Int, _gender:String) {
        println("${_name}을 생성자로 넘겼어요")
        println("${_hairColor}를 생성자로 넘겼어요")
        println("${_height}를 생성자로 넘겼어요")
        println("${_age}를 생성자로 넘겼어요")
        println("${_gender}를 생성자로 넘겼어요")

				name = _name
				hairColor = _hairColor
				height = _height
				age = _age
				gender = _gender
    }


    fun fireBall() {
        println("파이어볼!")
    }
    
    
    fun compositing(device1:String, device2:String): String {
        var device3 = device1 + device2
        println("새로운 무기인 ${device3}입니다")
        return device3
    }
}



//결과
불마법사을 생성자로 넘겼어요
red를 생성자로 넘겼어요
180.2를 생성자로 넘겼어요
불마법사의 머리색상은 red입니다
파이어볼!
냉마법사을 생성자로 넘겼어요
blue를 생성자로 넘겼어요
162.2를 생성자로 넘겼어요
25를 생성자로 넘겼어요
여를 생성자로 넘겼어요
냉마법사의 머리색상은 blue이고 나이는 25입니다.
파이어볼!

 

 

 

# 상속

- 클래스를 상속

- 클래스(설계도) 간의 관계를 더욱 끈끈하게 만들 수 있음

- 공통적인 요소들이 있다면 부모/자식 클래스를 구분해서 상속관계를 만들 수 있다

- 코틀린은 다른 언어들과 달리 생략된 final 키워드로 기본적으로 상속을 막아두었다 -> 무분별한 상속으로 예상치 못한 흐름을 방지하기 위해

- 부모클래스 open 키워드를 활용해서 상속 관계를 만들 수 있어요

 

 

- 상속을 어디에 사용할까??

- 클래스의 내용을 변경해야하는경우, 부모 클래스만 변경하는것으로 번거로움을 줄일 수 있음

 

 

- 상속 예시코드

-> 닭, 참새, 비둘기는 새

-> 닭, 참새, 비둘기라는 새라는 부모에서 출발했다는 관계를 만들어줄 수 있음 

-> 자식클래스에서는 fly()라는 함수를 써주지않았지만, 부모클래스에서 상속받았기때문에 fly()라는 함수 사용할 수 있음 

fun main() {
    var bird = Bird()
    var chicken = Chicken()
    var sparrow = Sparrow()
    var pigeon = Pigeon()

    bird.fly()
    chicken.fly()
    sparrow.fly()
    pigeon.fly()
}

open class Bird {
    fun fly() {
        println("새는 날아요~")
    }
}

class Chicken : Bird() {

}

class Sparrow : Bird() {

}

class Pigeon : Bird() {

}


//결과
새는 날아요~
새는 날아요~
새는 날아요~
새는 날아요~

 

 

- 부모클래스에서 생성자를 활용하는 경우에는, 자식에서 객체 생성시에 전달해줘야한다

fun main() {
    var bird = Bird("새")
    var chicken = Chicken("닭")
    var sparrow = Sparrow("참새")
    var pigeon = Pigeon("비둘기")

    bird.fly()
    chicken.fly()
    sparrow.fly()
    pigeon.fly()
}

open class Bird(name:String) {
    var name: String = ""

    init {
        // this는 현재 클래스의 상태변수를 의미합니다
        // var name: String = ""
        this.name = name
    }

    fun fly() {
        println("${name} 날아요~")
    }

}

class Chicken(name: String) : Bird(name) {

}

class Sparrow(name: String) : Bird(name) {

}

class Pigeon(name: String) : Bird(name) {

}

//결과
새 날아요~
닭 날아요~
참새 날아요~
비둘기 날아요~

 

 

 

# 오버라이딩 

- 오버라이딩이란??

- 상속받은 부모 클래스정보(프로퍼티)행위(메소드)재설계하는 것  [주로 행위를 재설개함]

- 필요한 기능이 있을때마다 별도의 이름으로 만들게된다면 일관성을 해치는데, 이런 문제를 해결

- 재사용성이 쉽고, 유지보수도 쉬움 

 

- 오버라이딩을 언제 사용할까??

- 공통적인 내용을 부모 클래스에서 관리하는건 좋지만, 자식 클래스의 개성을 살리고 싶을때 (자식클래스에서는 부모클래스와 살짝 다르게 사용하고싶을때)

 

 

- 오버라이딩 예시코드

 

<오버라이딩 단축키>    Control + O

 

-> 닭, 참새, 비둘기는 새라는 부모에서 출발했다는 관계를 만들었는데, fly메소드는 모두 동일한 코드를 가짐 (개성이 없음)

-> 오버라이딩을 사용해서 각 개체마다 개성을 더해서 날게하고 싶음 

fun main() {
    var bird = Bird("새")
    var chicken = Chicken("닭", 2)
    var sparrow = Sparrow("참새", "갈색")
    var pigeon = Pigeon("비둘기", "서울")

    bird.fly()
    chicken.fly()
    sparrow.fly()
    pigeon.fly()
}

open class Bird(name:String) {
    var name: String = ""

    init {
        // this는 현재 클래스의 상태변수를 의미합니다
        // var name: String = ""
        this.name = name
    }

    open fun fly() {
        println("${name}은 날아요~")
    }

}

class Chicken(name: String, age: Int) : Bird(name) {
    var age:Int = 0

    init {
        this.age = age
    }

    override fun fly() {
//        super객체는 부모의 객체를 의미하며 자동으로 생성됨
//        즉 부모객체의 fly메소드를 부르는 행위임
//        필요없으니 주석처리완료
//        super.fly()
        println("${age}살의 ${name}가 날아봅니다~ 꼬끼오!")
    }
}

class Sparrow(name: String, color: String) : Bird(name) {
    var color:String = ""

    init {
        this.color = color
    }

    override fun fly() {
//        super객체는 부모의 객체를 의미하며 자동으로 생성됨
//        즉 부모객체의 fly메소드를 부르는 행위임
//        필요없으니 주석처리완료
//        super.fly()
        println("${color}의 ${name}이 날아봅니다~ 짹짹!")
    }
}

class Pigeon(name: String, address: String) : Bird(name) {
    var address: String = ""

    init {
        this.address = address
    }

    override fun fly() {
//        super객체는 부모의 객체를 의미하며 자동으로 생성됨
//        즉 부모객체의 fly메소드를 부르는 행위임
//        필요없으니 주석처리완료
//        super.fly()
        println("${address} 살고있는 ${name}가 날아봅니다~ 구구!")
    }
}


//결과
새은 날아요~
2살의 닭가 날아봅니다~ 꼬끼오!
갈색의 참새이 날아봅니다~ 짹짹!
서울 살고있는 비둘기가 날아봅니다~ 구구!

 

-> 오버라이딩을 사용할때는, 부모객체의 fly메소드에 "open키워드"를 붙여줌!!

그래야지 해당함수를 자식객체가 재설계할수있다

 

 

 

 

# 오버로딩

- 오버로딩 이란??

- 오버라이딩이랑 비슷한 이름이지만 완전 다른것임 !

- 매개변수의 갯수자료형을 다르게해서 동일한 이름으로 메소드를 만들 수 있는 것!! 

(반환자료형(반환형)은 오버로딩에 영향을 주지 않음)

 

 

- 오버로딩을 어디에 사용할까??

- 두 개의 정수를 매개변수로 받아 더하는 메소드를 add라는 이름으로 만들었다

- 근데 두 개의 실수(소수)를 매개변수로 받아 더하는 메소드도 만들어야하는 상황

- 더하는거니까 add라는 이름이 적합한데 어떻게 해야할까?

- 이 상황에서는 더해야하는 자료형이 정수,실수로 다르니까 , 오버로딩으로 해결할 수 있음 

(addInt, addDouble 이렇게 메소드를 따로 만드는건 나중에 관리하기 힘들어짐 )

 

 

- 오버로딩 예시코드

-> 정수실수값 두개를 매개변수로 받아서 덧셈결과를 리턴해주는 add메소드 만듬

fun main() {
    var calc = Calculator()
    
    var intResult = calc.add(1,2)
    var doubleResult = calc.add(1.2, 2.2)
    
    println("정수 덧셈결과: ${intResult}")
    println("실수 덧셈결과: ${doubleResult}")
    
}

class Calculator {
    
    fun add(num1: Int, num2: Int): Int {
        return num1+num2
    }
    
    fun add(num1: Double, num2: Double): Double {
        return num1+num2
    }
}

//결과
정수 덧셈결과: 3
실수 덧셈결과: 3.4000000000000004

 

-> 같은이름의 add메소드여도, 정수로 들어가면 첫번째 add메소드, 실수로 들어가면 두번째 add메소드라고 컴퓨터가 자동으로 인식해서 출력해줌

 

 

 

 

# 인터페이스 

-인터페이스 란??

- 공통적으로 필요한 기능을 외부에서 추가해주는것!!

 

- 앞서 상속으로 닭, 참새, 비둘기와 부모 클래스인 Bird의 관계를 만들었는데, 새에는 많은 종류가 있고, 고유한 행동도 다르다. 따라서 기능을 더 추가해주고싶은데, 코틀린은 반드시 부모 클래스는 한개라서 모두 상속으로 처리할 수가 없다

 

- 이때 근본적인 공통점은 상속 받고, 추가적인 기능들은 인터페이스로 추가한다

- interface 키워드를 사용해서 인터페이스를 만든다

 

 

- 인터페이스 구조 (추상 메소드)

interface 인터페이스이름 {
	fun 메소드이름()
}

 

-> 메소드의 로직이 존재하지 않고 이름만 존재하는 것을 "추상메소드" 라고 함 

-> 어짜피 오버라이딩 해줄거기 때문에, 로직이 존재할 필요가 없음

(원래 인터페이스는 추상메소드만 허용하지만 최근에는 추상메소드가 아니여도 괜찮음. 그치만 인터페이스는 추상메소드를 작성하는 습관을 가지도록 하자!)

 

 

- 인터페이스를 어디에 사용할까??

- 상속으로 클래스들간의 관계는 성공적으로 구분한 상태인데,

닭, 참새, 비둘기까지는 문제없지만 오리가 추가된다면 고민이 생긴다

오리는 "헤엄치다" 라는 행위가 필요한데, 새는 헤엄치다라는 행위가 없기때문에 부모클래스에 추가하는것은 올바르지 않다. 이때 인터페이스를 사용해서 "헤엄치다" 라는 행위를 외부에서 추가해주는것이다 

 

 

- 인터페이스 예시코드

 

WaterBirdBehavior.kt

interface WaterBirdBehavior {
    fun swim()
 
}

-> 인터페이스를 추가해준다

 

Test.kt

fun main() {
    var bird = Bird("새")
    var chicken = Chicken("닭")
    var sparrow = Sparrow("참새")
    var pigeon = Pigeon("비둘기")
    var duck = Duck("오리")

    bird.fly()
    chicken.fly()
    sparrow.fly()
    pigeon.fly()
    duck.swim()
}

open class Bird(name:String) {
    var name: String = ""

    init {
        // this는 현재 클래스의 상태변수를 의미합니다
        // var name: String = ""
        this.name = name
    }

    open fun fly() {
        println("${name} 날아요~")
    }

}

class Chicken(name: String) : Bird(name) {

}

class Sparrow(name: String) : Bird(name) {

}

class Pigeon(name: String) : Bird(name) {

}

class Duck(name: String) : Bird(name), WaterBirdBehavior {
    override fun swim() {
        println("${name}가 수영해요~")
    }
}

//결과
새 날아요~
닭 날아요~
참새 날아요~
비둘기 날아요~
오리가 수영해요~

 

-> Duck클래스에 아까 만들어놓은 인터페이스인 WaterBirdBehavior를 추가해준다.

이렇게하면 이제 swim이라는 메서드를 오버라이딩 할수있게 된다