👩🏻💻 상속받은 부모 클래스의 프로퍼티와 메서드 중에자식 클래스에서는 다른 용도로 사용해야 하는 경우가 있음 👩🏻💻 동일한 이름의 메서드나 프로퍼티를 사용할 필요가 있을 경우에override 키워드를 사용해서 재정의할 수 있음 👩🏻💻 오버라이드 할 때는 프로퍼티나 메서드도 클래스 처럼 앞에open을 붙여서 상속할 준비가 되어 있어야 함
메서드 오버라이드
📍 상속할 메서드 앞에 open 키워드를 붙이면 오버라이드 할 수 있지만, open 키워드가 없는 메서드는 오버라이드 할 수 없음
open class BaseClass {
open fun opened() {
}
fun notOpened() {
}
}
class ChildClass : BaseClass() {
override fun opened() {
super.opened()
}
}
프로퍼티 오버라이드
📍 메서드 오버라이드처럼 프로퍼티 역시 open으로 열려 있어야만 오버라이드를 할 수 있음
open class BaseClass2 {
open var opened: String = "I am"
}
class ChildClass2: BaseClass2() {
override var opened: String = "You are"
}
2. 추상 클래스
🚀 추상 클래스는 그 자체로는 객체를 만들 수 없는 클래스로, 일반적으로 추상 메소드가 포함된 클래스를 말함 ➡️ 추상 메소드란 아직 구현되지 않고 추상적으로만 존재하는 메소드 🚀 추상 클래스와 추상 메소드 앞에는 abstract 키워드를 붙임 🚀 상속받는 자식 클래스에 특정 메소드 구현을 강제하고 싶을 때 사용 🚀 추상 클래스 자체로는 직접 객체를 만들 수 없고 자식 클래스에서 추상 메소드를 구현한 다음, 자식 클래스의 객체를 생성하면 됨.
abstract class Game {
fun startGame() {
println("게임을 시작했습니다.")
}
// 추상 메소드
abstract fun printName()
}
class OverWatch: Game() {
override fun printName() { // 추상 메소드 구현
println("오버워치입니다.")
}
}
fun main() {
val overWatch = OverWatch()
overWatch.startGame() // 게임을 시작했습니다.
overWatch.printName() // 오버워치입니다.
}
간단하게 보면 클래스는 변수와 함수의 모음. 그룹화할 수 있는 변수와 함수를 한 군데에 모아 놓고 사용하기 쉽게 이름을 붙여놓은 것을 클래스 라고 이해하면 된다.
1) 코틀린에서 사용되는 클래스의 기본 구조
class 클래스 이름 {
var 변수
fun 함수() {
코드
}
}
2) 클래스 코드 작성하기
👻 클래스를 만들기 위해서는 먼저 클래스의 이름을 정하고, 이름 앞에 class 키워드를 붙여서 만들 수 있음 👻 클래스 이름 다음에는 클래스의 범위를 지정하는 중괄호({}) 가 있어야 함 ➡️ 이 중괄호를 스코프 Scope라고 하는데, 클래스에서 사용했기 때문에 클래스 스코프라고 함
class 클래스 이름 { // 클래스 스코프 class scope }
✓ '몇몇 예외'는 있지만 대부분의 코드는 클래스 스코프 안에 작성.
👻 작성된 클래스를 사용하기 위해서는 생성자라고 불리는 함수가 호출되어야 함
👻 코틀린은 프라이머리 primary와 세컨더리 Secondary 2개의 생성자를 제공
2. 생성자
1) 프라이머리 생성자
class Person 프라이머리 생성자() { }
함수에 있는 코드를 실행하려면 함수를 호출해야 함. 클래스도 마찬가지로 클래스를 사용한다는 것은 곧 클래스라는 이름으로 묶여 있는 코드를 실행하는 것이기 때문에 함수 형태로 제공되는 생성자를 호출해야지만 클래스가 실행
⚡️ 프라이머리 Primary 생성자는 마치 클래스의 헤더처럼 사용할 수 있으며, constructor 키워드를 사용해서 정의하는데 조건에 따라 생략할 수 있음 ⚡️ 프라이머리 생성자도 결국 함수이기 때문에 파라미터를 사용할 수 있음
class Person constructor(value: String) { // 코드 }
📍 생성자에 접근 제한자나 다른 옵션이 없다면 constructor 키워드를 생략할 수 있음
class Person(value: String) { // 코드 }
📍 클래스의 생성자가 호출되면 init 블록의 코드가 실행되고, init 블록에서는 생성자를 통해 넘어온 파라미터에 접근할 수 있음
fun main() {
class Person1 constructor (str: String) {
init {
println("생성자로부터 전달받은 값은 ${str}입니다.")
}
}
var person1 = Person1("1") // 생성자로부터 전달받은 값은 1입니다.
}
📍 init 초기화 작업이 필요하지 않으면 init 블록을 작성하지 않아도 됨 ➡️ 대신 파라미터로 전달된 값을 사용하기 위해서는 파라미터 앞에 변수 키워드인 val을 붙여주면 클래스 스코프 전체에서 해당 파라미터를 사용할 수 있음
fun main() {
class Person2(val str: String) {
fun process() {
println("생성자로 생성된 변수의 값은 ${str}입니다.")
}
}
var person2 = Person2("2")
person2.process() // 생성자로 생성된 변수의 값은 2입니다.
}
✓ 생성자 파라미터 앞에 var도 사용할 수 있으나, 읽기 전용인 val을 사용하는 것을 권장
2) 세컨더리 생성자
세컨더리 Secondary 생성자는 constructor 키워드를 마치 함수처럼 클래스 스코프 안에 직접 작성할 수 있음 그리고 다음과 같이 init 블럭을 작성하지 않고 constructor 다음에 괄호를 붙여서 코드를 작성할 수 있음
class Person3 {
constructor(str: String) {
println("생성자로부터 전달받은 값은 ${str}입니다.")
}
}
📍 세컨더리 생성자는 파라미터의 개수, 또는 파라미터의 타입이 다르면 (오버로딩) 여러 개를 중복해서 만들 수 있음
fun main() {
class Sample {
constructor(str: String) {
println("생성자로부터 전달받은 값은 ${str}입니다.")
}
constructor(value: Int) {
println("생성자로부터 전달받은 값은 ${value}입니다.")
}
constructor(value1: Int, value2: String) {
println("생성자로부터 전달받은 값은 ${value1}, ${value2}입니다.")
}
}
val sample = Sample(1,"2") // 생성자로부터 전달받은 값은 1, 2입니다.
}
3) Default 생성자
생성자를 작성하지 않을 경우 파라미터가 없는 프라이머리 생성자가 하나 있는 것과 동일
fun main() {
class Student { // 생성자를 작성하지 않아도 기본 생성자가 동작.
init {
// 기본 생성자가 없더라도 초기화가 필요하면 여기에 코드를 작성.
}
}
}
3. 클래스의 사용
클래스명()
👻 클래스의 이름에 괄호를 붙여서 클래스의 생성자를 호출. constructor 키워드를 호출하지 않음 ⚡️ 아무런 파라미터 없이 클래스명에 괄호에 붙여주면 생성자가 호출되면서 init 블록 안의 코드가 자동으로 실행 ⚡️ 세컨더리 생성자의 경우 init 블록이 먼저 실행되고 constructor 블록 안의 코드가 실행
👻 코틀린에서는 자바보다 클래스를 좀 더 편하게 정의할 수 있음 ⚡️ 자바의 보일러 플레이트 코드가 삭제되고 필요한 코드들만 작성하면 되도록 바뀜 * 보일러 플레이트 코드 : 상용구 코드. 변형이 거의 또는 전혀 없이 여러 위치에서 반복되는 코드 문구.
fun main() {
class Car(val color: String)
/*
객체를 생성. 자바에서는 new 키워드를 사용했지만, 코틀린에서는 필요가 없음.
*/
var car = Car("blue") // 객체 생성
}
👩🏻💻 맵은 키 Key와 값 Value의 쌍으로 입력되는 컬렉션 👩🏻💻 맵의 키는 리스트의 인덱스와 비슷한데 맵에는 키를 직접 입력하고 중복되지 않도록 해야 함
⚡️ 제네릭으로 키와 값의 데이터 타입을 지정해서 맵을 생성 ⚡️ 맵의 요소는 Pair(A, B)를 사용하는 데 이는 A to B로도 표현할 수 있음
// 읽기 전용 맵
val immutableMap = mapOf("name" to "tom", "age" to 28, "age" to 20, "height" to 170)
println(immutableMap) // {name=tom, age=20, height=170} 중복이 있을 경우 마지막 키값이 저장됨
자바에서 리스트 List, 셋 Set, 맵 Map 등 여러 자료 구조를 사용하는데, 코틀린에서도 이러한 컬렉션을 모두 사용할 수 있을 뿐만 아니라 몇 가지 편리한 함수를 추가로 제공. 또한 코틀린은 컬렉션을 읽기 전용 immutable 컬렉션과 읽기-쓰기 mutable 컬렉션으로 크게 두 가지로 나눔
1) 리스트 List
👻 리스트 List는 저장되는 데이터에 인덱스를 부여한 컬렉션이며 중복된 값을 입력할 수 있음 👻 코틀린에서 동적으로 리스트를 사용하기 위해서는 리스트 자료형 앞에 뮤터블 Mutable 이라는 접두어 prefix가 붙음 💫 접두어가 있는 리스트도 있지만 잘 사용하지 않기 때문에 항상 mutableList, mutableMap, mutableSet을 사용
뮤터블과 이뮤터블
프로그래밍 언어에서 뮤터블은 변할 수 있는 데이터 타입을 가르키는 용어. 변수로는 var이 뮤터블 그리고 반대 개념인 이뮤터블 Immutable이 있는데 이것은 val과 같이 변할 수 없는 데이터 타입을 가르키는 용어.
⚡️ 코틀린은 컬렉션 데이터 타입을 설계할 때 모두 이뮤터블로 설계 ⚡️ 기본 컬렉션인 리스트, 맵, 셋은 모두 한 번 입력된 값을 바꿀 수 없음 ⚡️ 컬렉션의 원래 용도인 동적 배열로 사용하기 위해서는 뮤터블로 만들어진 데이터 타입을 사용해야 함
읽기 전용 리스트는 listOf() 함수를 사용 읽기 쓰기 모두 가능한 리스트는 mutableListOf() 함수를 사용
// 뮤터블
val mutableList = mutableListOf("MON", "TUE", "WED")
// 3개의 값을 가진 크기가 3인 동적 배열 리스트가 생성
2) 리스트에 값 추가하기 : add
👻 mutableList 변수에 add 메서드를 사용해서 값을 추가 👻 값이 추가되면서 동적으로 리스트의 공간이 자동으로 증가
mutableList.add("THU")
// 입력될 위치인 인덱스를 따로 지정해주지 않아도 입력되는 순서대로 인덱스가 지정.
println(mutableList)
// [MON, TUE, WED, THU]
3) 리스트에 입력된 값 사용하기 : get
👻 입력할 때와는 다르게 사용할 때는 인덱스를 지정해서 몇 번째 값을 꺼낼 것인지 명시
var variable = mutableList.get(1) // 두 번째 값을 변수에 저장
variable = mutableList[1]
println(variable) // TUE
4) 리스트 값 수정하기 : set
👻 특정 인덱스 값을 수정
mutableList.set(1, "수정할 값") // 두 번째 값을 수정
println(mutableList) // [MON, 수정할 값, WED, THU]
mutableList[1] = "수정할 값" // 두 번째 값을 수정.
5) 리스트에 입력된 값 제거하기 : removeAt
👻 리스트에 입력된 값의 인덱스를 지정해서 삭제
println(mutableList.get(1)) // 수정할 값
mutableList.removeAt(1) // 두 번째 값을 삭제
// 두 번째 값을 삭제하면 세 번째 값부터 인덱스가 하나씩 감소하면서 빈자리의 인덱스로 이동
println(mutableList.get(1)) // WED
6) 빈 리스트 사용하기
👻 아무것도 없는 빈 리스트를 생성하면 앞으로 데이터 타입을 알 수 없기 때문에 값의 타입을 추론할 수 없음 👻 빈 컬렉션의 경우 앞에서처럼 '데이터타입of'만으로는 생성되지 않고 데이터 타입을 직접적으로 알려주는 방법을 사용해야 함
var 변수명 = mutableListOf<컬렉션에 입력될 값의 타입>() var stringList = mutableLisfOf<String>()
// 생성
val stringList = mutableListOf<String>() // 문자열로 된 빈 컬렉션을 생성
stringList.add("월")
stringList.add("화")
println(stringList[1]) // 화
7) 컬렉션 개수 가져오기 : size
👻 size 프로퍼티를 사용하면 컬렉션의 개수를 가져올 수 있음
println("stringList에는 ${stringList.size}개의 값이 있습니다.")
// stringList에는 2개의 값이 있습니다.
예제
문자열을 저장할 수 있는 List 객체를 생성하고 여기에 "a", "b", "c", "d", "e"를 저장한 후 이것을 출력하는 프로그램을 생성 출력 예: [a, b, c, d, e]
fun main() {
val PI = getPi()
println("지름이 10인 원의 둘레는 ${PI}입니다.") // 지름이 10인 원의 둘레는 3.14입니다.
}
2. 함수 파라미터의 정의
🐰 함수에 입력되는 파라미터는 마치 변수를 정의하듯이 '이름 : 타입'의 형태로 정의 ➡️ 여러 개의 파라미터가 정의될 경우는 콤마로 구분 🐰 코틀린에서 함수 파라미터를 통해 입력되는 모든 값은 변하지 않는 immutable 🐰 코틀린에서의 함수 파라미터는 모두 읽기 전용 키워드 val이 생략된 형태
fun 함수이름((val 생략) name1: String, name2: Int, name3: Double) {실행코드}
fun newFun(name: String, age: Int = 29, weight: Double = 65.5) {
// name = "tom"; // Val cannot be reassigned
println("name의 값은 ${name}입니다.")
println("age의 값은 ${age}입니다.")
println("weight의 값은 ${weight}입니다.")
}
// 정의된 newFun() 함수를 호출할 때 기본값이 없는 첫 번째 파라미터에만 값을 입력하면
// 두 번째와 세 번째 파라미터에는 설정한 기본값이 자동으로 입력.
newFun("Hello")
/*
name의 값은 Hello입니다.
age의 값은 29입니다.
weight의 값은 65.5입니다.
*/
2) 파라미터 이름으로 값을 입력하기
🥕 함수에 정의된 파라미터가 많을 경우 입력하는 값의 의미가 명확하지 않을 때가 있음 🥕 이럴 경우 순서와 상관 없이 정의된 파라미터 이름을 지정해서 직접 값을 입력할 수 있음
fun main() {
newFun("Michael", weight=67.5)
/*
name의 값은 Michael입니다.
age의 값은 29입니다.
weight의 값은 67.5입니다.
*/
}
응용 예제
📍login() 메서드와 logout() 메서드를 선언 login() 메서드를 호출할 때는 매개값으로 id와 passwd를 제공하고, logout() 메서드는 id만 매개값으로 제공
그외의 값일 경우에는 false를 리턴 2) logout() 메서드는 "admin 아이디가 로그아웃 되었습니다"가 출력
fun main() {
print("아이디를 입력해주세요. >> ")
val id = readln()
print("비밀번호를 입력해주세요. >> ")
val password = readln()
val result = login(id = id, password = password)
if (result) {
println("로그인 되었습니다.")
logout(id)
} else {
println("id 또는 password가 올바르지 않습니다.")
}
}
fun login(id:String, password: String): Boolean {
return id == "admin" && password == "1234"
}
fun logout(id: String) {
println("$id 아이디가 로그아웃 되었습니다.")
}
📍 배열을 전달 받아 짝수만 출력하는 메서드 작업
fun main() {
val numbers = IntArray(10)
for (i in numbers.indices) {
numbers[i] = i + 1
}
print("main() : ")
for (i in numbers.indices) {
print(numbers[i])
if (i < numbers.size - 1) {
print(", ")
}
} // main() : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
println()
printOdd(numbers) // printOdd() : 2, 4, 6, 8, 10
}
fun printOdd(numbers: IntArray) {
print("printOdd() : ")
for (i in numbers.indices) {
if (numbers[i] % 2 == 0) {
print(numbers[i])
if ( i < numbers.size - 1) {
print(", ")
}
}
}
val odds = numbers.filter {result -> result % 2 == 0}
println(odds)
}
📍 키보드로 부터 학생 수와 각 학생들의 점수를 입력받아서, 최고 점수 및 평균 점수를 구하는 프로그램
fun main() {
var run = true // 반복문의 조건으로 사용 -> 값이 false가 된다면 반복문이 종료
var scoreList: IntArray = IntArray(0)
while (run) {
println("-----------------------------------------------------")
println("1.학생수 | 2.점수입력 | 3.점수리스트 | 4.분석 | 5.종료")
println("-----------------------------------------------------")
print("선택> ")
val selectNo: Int = readln().trim().toInt()
when (selectNo) {
1 -> scoreList = inputStudentNum() // 학생수를 입력 받아서 배열 생성
2 -> inputScores(scoreList) // 생성된 배열의 갯수 만큼 점수 입력
3 -> printScores(scoreList) // 입력받은 배열의 값을 출력
4 -> printAnalysis(scoreList) // 최고 점수, 평균 점수 출력
5 -> run = setRun() // run 값 변경
}
}
println("프로그램 종료")
}
fun inputStudentNum():IntArray {
print("학생수> ")
var studentNum = readln().toInt()
return IntArray(studentNum)
}
fun inputScores(scores:IntArray) {
for (i in scores.indices) {
print("scores[$i] > ")
scores[i] = readln().toInt()
}
}
fun printScores(scores:IntArray) {
for (i in scores.indices) {
println("scores[$i] : " + scores[i])
}
}
fun printAnalysis(scores: IntArray) {
var max = scores[0]
var sum = scores[0]
var avg: Double
for (i in 1 until scores.size) {
if (scores[i] > max) {
max = scores[i]
}
sum += scores[i]
}
avg = (sum / scores.size).toDouble()
println("최고 점수: " + max)
println("평균 점수: " + avg)
}
fun setRun(): Boolean {
return false
}
🐝 여러 개의 값을 담을 수 있는 대표적인 자료형인 배열은 값을 담기 전에 먼저 배열 공간의 개수를 할당하거나 초기화 시에 데이터를 저장해 두면 데이터의 개수만큼 배열을 크기가 결정 🐝 먼저 개수를 정해 놓고 사용해야 하며 중간에 개수를 추가하거나 제거할 수 없음
💡 배열은 다른 데이터 타입과 마찬가지로 변수로 저장해서 사용할 수 있으며 다음과 같은 형태로 선언 var 변수 = Array(개수)
fun main() {
// 배열 객체는 Int, Long, Char 등과 같은 기본 타입 뒤에 Array를 붙여서 만듦.
var students: IntArray = IntArray(10) // 변수 student에 Int(정수형) 공간을 10개 할당하라는 의미
var longArray = LongArray(10)
var charArray = CharArray(10)
var floatArray = FloatArray(10)
var doubleArray = DoubleArray(10)
println(students[0]) // 0 -> 따로 초기화 하지 않으면 0으로 초기화.
}
1) 문자열 배열에 빈 공간 할당하기
🍯 String은 기본 타입이 아니기 때문에 StringArray는 없지만 다음과 같이 사용할 수 있음
var stringArray = Array(10, {item -> "" })
🍯 괄호 안의 첫 번째 숫자인 10만 변경해서 사용하면 그 숫자만큼 빈 문자열로 된 배열 공간을 할당 ➡️ 자바에서는 문자열 배열을 생성하면서 초기화 하지 않으면 null이 초기값이 됨 ➡️ 하지만 코틀린은 기본적으로 Null 사용을 허용하지 않기 때문에빈 문자열을 이용해서 따로 초기화해야 함
1. 배열을 선언한 변수명 옆에 대괄호([])를 사용하고, 대괄호 안에 값을 저장할 위치의 인덱스 번호를 작성 그리고 등호(=)를 사용해서 값을 입력 배열명[인덱스] = 값
2. set 메서드를 사용할 수 있음. 배열이 제공하는 set()메서드에 인덱스와 값을 파라미터로 넘겨주면 됨 배열명.set(인덱스, 값)
fun main() {
students[0] = 90
students.set(1, 91)
}
4) 배열에 있는 값 꺼내기
🍯 값을 입력할 때와 같은 방식으로 인덱스로 값을 가져올 수 있음
💡 대괄호 안에 인덱스를 입력해서 가져올 수 있으며 값을 꺼내는 배열의 메서드는 get() 배열명[인덱스] 배열명.get(인덱스)
fun main() {
// 배열 students의 일곱번째 값을 seventhValue 변수에 저장
var seventhValue = students[6]
// 배열 students의 열번째 값을 tenthValue 변수에 저장
var tenthValue = students.get(9)
}
응용 예제
fun main() {
/* for문을 이용해서 arr 배열의 합을 구하세요 */
val arr = intArrayOf(10, 20, 30, 40, 50)
var sum = 0
// 작성 위치
// 1. until 사용
for (i in 0 until arr.size) {
sum += arr[i]
}
println("arr 배열의 합은 $sum")
// 2. for in 사용
sum = 0
for (item in arr) {
sum += item
}
println("arr 배열의 합은 $sum")
// 3. indices 사용
println(arr.indices) // 0..4
sum = 0
for (i in arr.indices) {
sum += arr[i]
}
println("sum = $sum")
}