목차
1. IntelliJ다운로드 및 설치
2. 프로젝트 생성
다운로드가 완료되면
3. 코틀린 기초
3.1. 출력문
3.2. 리터럴
3.2.1. 리터럴의 종류
리터럴 : 코드 작성시에 값을 작성하는 문법
- 정수(Int, Long) : 100, 100L, 1_000_000
- 정수 자료에 L이 붙으면 Long 형 자료를 의미한다.
- 큰 값의 정수를 구분하기 편하게 하기위해 천 단위 구분기호(,)와 같은 기능으로 '_'를 중간에 넣을 수 있다.
- 실수(Double, Float) : 11.11, 22.22F
- 'F'가 사용되면 Float형 자료이다.
- 문자(Char) : 'A', 'B'
- 문자열(String) : "문자열"
- 진위 논리 리터럴 : println(false)
//정수 리터럴
println(100) // Int
println(1000000000000000) // Long
println(1000000000000000L)
println(938_545_245_245);
//실수 리터럴
println(11.11)
println(22.22F)
// 문자 리터럴
println('A')
println('가')
// 문자열 리터럴
println("문자열") // String
//진위 논리 리터럴
println(true)
println(false)
// 참고로 '자료형은 모두 첫글자 대문자로 시작'한다.
// 복수행 String
println("동해물과 백두산이\n마르고 닳도록\n하느님이 보우하사\n우리나라 만세\n")
println("""동해물과 백두산이
|마르고 닳도록
|하느님이 보우하사
|우리나라 만세""".trimMargin())
// .trimMargin() : 호출객체가 되는 문자열에 각행의 앞뒤공백 및 행의 시작문자 ('|')를 제거해주는 함수.
// 큰 따옴표 세개를 이용하여 복수행의 문자열을 하나의 데이터로 다룰 수 있다.
3.2.2. 변수와 자료형
자료형의 이름 첫 글자는 대문자로 쓰여진다.
- 정수 : Long(8), Int(4), Short(2), Byte(1)
- 부호가 없는 정수형 : ULong(8), UInt(4), UShort(2), UByte(1)
- U가 붙은 자료형은 'Unsigned를 의미'하며, '음수가 없는 자료형'을 칭한다.
- Int가 -21억 ~ 21억의 표현범위라면, UInt는 0~42억까지의 표현범위를 갖는다
- 실수 : Double(8), Float(4)
- 논리 : Boolean(1)
- 문자 : Char(2)
- 문자열 : String
3.2.3. 변수의 선언
- var : 변수 선언 후 다시 값을 담을 수 있다.
- val : 변수 선언 후 다시 값을 담을 수 없다.
변수 선언 방식 -> var/val 변수명:타입 = 값
타입을 생략 시 저장하는 값에 따라 자료형이 자동으로 결정된다.
// Int a = 100; // - 자바의 선언 방식
val a:Int = 100; // - 코틀린의 선언 방식
val a1:Int = 100 // 변수명 뒤에 ':'를 쓰고 자료형을 이어서 씀으로써 변수를 선언한다
println("a1 = ${a1}") // ${}안에 변수명을 넣어 변수의 값을 출력문구와 함께 출력한다
println("a1 = $a1") // 리터럴 출력이 아닌 '변수 출력'인 경우 ${}에서 {}를 생략할 수 있다.
val a2:Int = 100 // 변수 선언시 자바스크립트처럼 자료형을 생략하면 저장하는 값에 따라 자동으로 자료형이 결정된다.
println("a2 = $a2")
var a3:Int = 100
val a4:Int = 100
// var변수는 값을 바꿀 수 있고, val은 초기화된 값을 변경할 수 없다.
a3 = 200
println("var a3 : $a3")
// val변수는 값을 다시 저장하지 못한다.
// a4 = 200 //에러발생
println("val a4 : $a4")
3.2.4. 변수의 null check
- 코틀린은 변수 선언 시 '그 변수가 null값을 가질 수 있는 변수인지, 아닌지 결정이 가능'하다.
- 일반적인 변수 선언은 null값을 허용하지 않는다.
- 이것은 프로그래밍시 null값이 저장되어 발생하는 오류를 방지하기 위함.
- 변수 선언 시, '자료형 뒤에 '?' 를 붙여 null을 허용'하게 할 수 있다.
var/val 변수명:자료형 = 값 // -> null 허용하지 않는 변수
var/val 변수명:자료형? = 값 // -> null 허용 변수
// var a5:Int=null //에러발생
var a5:Int=100
println("a5 : $a5")
var a6:Int? = null
println("a6 : $a6")
var a7:Int? = a5
println("a7 : $a7")
// var a8:Int =a6 //에러
// a8은 null을 허용하는 변수가 아니므로 아래 명령은 에러
// * 코틀린은 모든 기본 타임 변수 및 값을 객체로 변환하여 사용합니다. *
3.3. Function
fun 함수이름(매개변수) : 리턴타입 {
코드들..
}
3.3.1. 기본 함수(전달인수x, 반환x)
fun main(){
test1();
}
// 기본함수 (전달인수 없고 반환값이 없는 함수)
fun test1(){
println("test1함수 호출")
println("-------------")
}
3.3.2. 전달인수가 있는 함수
fun main(){
val a3:Int=200
test2(50, 12.23); // test2()함수 호출
// 매개변수가 있는 함수를 호출할 때, 전달인수를 넣는 동작이 하나하나 끝날 때 마다 '매개변수명 : '을 인텔리제이가 전달인수 앞에 붙여준다.
// 자동으로 입력되는 사항이니, 수동으로 입력ㄴㄴ
test2(a3, 12.23); // test2()함수 호출
// 변수가 전달인수로 전달될 때는 앞에 매개변수명이 붙지 않는다.
test2(a2=123.12, a1=300);
// 다른 프로그래밍 언어와 마찬가지로, 전달인수의 순서는 매개변수의 기술된 순서에 맞춰 작성해야한다.
// 다만 위와 같이 매개변수의 순서를 바꾸고자 한다면, '매개변수명='을 작성하여, 전달인수와 매개변수의 짝을 맞춰주면 가능하다.
}
fun test2(a1:Int, a2:Double ){
println("test2함수 호출")
println("a1:$a1")
println("a2:$a2")
println("-------------")
}
3.3.3. 매개변수에 기본값(defaultr값)이 지정된 함수
fun main(){
test3(100, 11.11);
test3(200);
// a2에 전달할 값이 생략되어 기본값 0.0이 a2변수에 대입된다.
test3(a2 = 30.123)
// a1에 전달할 값을 생략하려면 매개변수 이름을 써서 전달값만 전달한다.
test3()
// 모두 생략도 가능하다(default값이 있기 때문!)
}
// 매개변수에 기본값(디폴트)가 지정된 함수 : 해당 매개변수에 전달된 값이 없으면, 기본값이 변수에 대입된다.
fun test3(a1:Int = 0, a2:Double=0.0 ){
println("test3함수 호출")
println("a1:$a1")
println("a2:$a2")
println("-------------")
}
3.3.4. return(반환값)이 있는 함수
toInt() : 변수 또는 리터럴의 형변환(toInt -> Int로 변환)
fun main(){
val r1:Int = test4(100,200.12)
println("r1 : $r1")
val r2:Int = test4(1000, 2000.123)
println("r2 : $r2")
}
// 리턴(반환값)이 있는 함수
// fun 함수 이름(매개변수):리턴값의 자료형 { }
fun test4(a1:Int, a2:Double ) : Int {
println("test4 호출")
val result:Int = a1 + a2.toInt()
return result
} // 변수 또는 리터럴 .toInt() -> 변수 또는 자료형의 형변환(정수로 변환)
3.3.5. 반환값이 없는 함수 (Unit)
fun main(){
test5()
test6()
}
// Unit : void의 의미로 사용되며, 대부분은 생략된 채로 사용된다.
fun test5():Unit{
println("test5 호출")
println("--------------------")
}
fun test6(){
println("test6 호출")
println("----------------------------")
}
3.3.6. 오버로딩(Overloading)
fun main(){
test7()
test7(100)
}
// 함수의 오버로딩 - 매개변수의 형태와 갯수를 달리한 같은 이름의 함수들을 정의하여 사용한다.
fun test7(){
println("test7 호출 - 매개변수 x")
println("------------------------------")
}
fun test7(a1:Int=200){
println("test7 호출 - 매개변수 한 개(Int:$a1)")
println("----------------------------")
}
3.3.7. 함수 내부의 함수
fun main(){
fun test10(){
println("test10 호출")
}
test8()
// test9() // 에러 (불가능)
test10()
}
// 함수 내부의 함수
fun test8(){
println("test8 호출")
fun test9(){
println("test9 호출")
}
test9()
}
4. 예제
국어, 영어, 수학 점수를 입력 받아서 총점 평균 성적표 양식에 맞춰 출력하세요
package days01
import java.text.DecimalFormat
fun main(){
// 코틀린의 화면 입력
// 국어, 영어, 수학 점수를 입력 받아서 총점 평균 성적표 양식에 맞춰 출력하세요
print("국어점수 입력 : ")
val kor:Int = readLine()!!.toInt()
print("영어점수 입력 : ")
val eng:Int = readLine()!!.toInt()
print("수학점수 입력 : ")
val mat:Int = readLine()!!.toInt()
val tot:Int = sum(kor,eng,mat)
val avg:Double = average(tot)
prn(kor, eng, mat, tot, avg)
}
fun sum(kor:Int, eng:Int, mat:Int) : Int{
return kor + eng + mat
}
fun average(tot:Int) : Double {
return tot/3.0
}
fun prn(kor:Int, eng:Int, mat:Int, tot:Int, avg:Double):Unit{
val df1 = DecimalFormat("#,##0.0")
println("\t\t===성적표===")
println("-----------------------------------")
println("국어\t영어\t수학\t총점\t평균")
println("-----------------------------------")
println("$kor\t\t$eng\t\t$mat\t\t$tot\t\t${df1.format(avg)}\t")
println("-----------------------------------")
}
5. 연산자
5.1. 단항 연산자
- 항이 하나인 연산자
5.1.1. + : +양수 -> 양수 / +음수 -> 음수
val a1 : Int = 10; val a2:Int = -10;
// + : +양수 -> 양수, +음수 -> 음수
val r1 : Int = +a1; val r2:Int = +a2;
println("a1:$a1, r1:$r1");
println("a2:$a2, r2:$r2");
5.1.2. - : -양수 -> 음수 / -음수 -> 양수
val a1 : Int = 10; val a2:Int = -10;
// - : -양수 -> 음수, -음수 -> 양수
val r3:Int = -a1; val r4:Int = -a2;
println("a1:$a1, r3:$r3");
println("a2:$a2, r4:$r4");
5.1.3. ! : !true -> false / !false -> true
val a3:Boolean = true; val a4:Boolean = false;
// ! : !true -> false , !false ->true
val r5:Boolean = !a3; val r6:Boolean = !a4;
println("a3:$a3, r5:$r5"); println("a4:$a4, r6:$r6");
5.2. 이항 연산자
val r11:Int = 10 + 3
val r12:Int = 10 - 3
val r13:Int = 10 * 3
val r14:Int = 10 / 3
val r15:Int = 10 % 3
println("$r11, $r12, $r13, $r14, $r15")
5.3. 비교 연산자
val a14:Int = 10
val r17:Boolean = a14 == 10; val r18:Boolean = a14 != 10;
println("$r17, $r18")
val r19:Boolean = a14 == 20; val r20:Boolean = a14 != 20;
println("$r19, $r20")
println("-------------------------------------")
val a15:Int = 10
val r21:Boolean = a15 < 20; val r22:Boolean = a15>20
val r23:Boolean = a15 <= 10; val r24:Boolean = a15>=10
println("$r21, $r22, $r23, $r24")
5.4. IntRange
데이터의 범위를 나타내는 관리데이터
val r16:IntRange = 10..20 // 10부터 20까지의 영역데이터(관리데이터)
println("r16 : $r16") // 출력 : 10..20 내부숫자 11개를 모두 출력하려면 반복실행이 필요
val r25:IntRange = 20..30
var r26:IntRange = 50..60
println("r25 : $r25, r26 : $r26")
println("-------------------------------------")
print("r16 : ")
for(item in r16){
print("$item ")
}
println()
print("r25 : ")
for(item in r25){
print("$item ")
}
println()
print("r26 : ")
for(item in r26){
print("$item ")
}
6. if문
6.1. 기본 if문
val a1:Int = 10
// 기본 if문
if(a1==10) println("a1은 10이다")
if(a1!=10){
println("a1은 10이 아니다")
}
6.2. else문
val a1:Int = 10
// else문 : 조건식이 만족하지 않을 경우 수행
if(a1==10)println("a1은 10이다")
else{
println("a1은 10이 아니다")
}
if(a1==20)println("a1은 20이다")
else{
println("a1은 20이 아니다")
}
6.3. else-if문
val a1:Int = 10
// else if
if(a1==5){
println("a1은 5이다")
}else if(a1==10){
println("a1은 10이다")
}else if(a1==20){
println("a1은 20이다")
}else{
println("a1은 5, 10, 20이 아니다")
}
6.4. 코틀린에서의 if문의 변형 사용
// 코틀린에서만 사용할 수 있는 if문의 변형 사용
// 예제1
val a6:String = if(a5==10) "10이다" else "10이 아니다"
println("a6:$a6")
println("----------------------------------")
// 예제2
val a7:String = if(a5==10){
println("블록1 수행")
"10입니다" // 변수에 입력될 값은 각 경우에 해당하는 영역 맨 마지막에 작성된다.
}else{
println("블록2수행")
"10이 아니다"
}
println("a7:$a7")
7. when문
- 자바에는 switch문이 있다면, 코틀린에는 when문이 있다.
- 구조적으로는 비슷하지만 몇가지 차이점이 있으니 비교하며 학습해보자.
val a1 = 10
// switch-case문법과 비슷하다. 각 case를 '->'로 표시하고, break를 작성하지 않아도 된다.
when(a1){
1 -> println("a1은1이다")
2 -> {
println("수행되는 코드")
println("a1은 2이다")
}
3-> println("a1은 3이다")
else -> println("a1은 1,2,3이 아니다. a1:$a1")
}
val a2 = 3
when(a2){
1, 2 -> println("a2는 1 또는 2")
3, 4 -> println("a2는 3 또는 4")
5, 6 -> println("a2는 5 또는 6")
else -> println("a2는 1 ~ 6이 아닙니다")
} // (,)로 구분하여 두 가지의 경우를 하나의 case로 처리 가능
val a5 = 5
when(a5){
in 1..3 -> println("a5는 1~3 사이의 숫자")
in 4..6 -> println("a5는 4~6 사이의 숫자")
in 1..6 -> println("a5는 1~6 사이의 숫자")
else -> println("a5는 1~6사이의 숫자가 아닙니다")
} // in 키워드와 .. 연산자를 이용하여 범위를 골라내는 case를 작성할 수 있다.
val a6 = 20
val str:String = when(a6){
10 -> "a6는 10이다"
20 -> {
println("두 번째 경우의 수")
"a6는 20이다"
}
30 -> "a6는 30이다"
else -> "a6는 10~30이 아닙니다."
} // 각 케이스에서 선택된 값을 결과로 반환하여 변수의 값으로 활용할 수 있다.
println("str : $str");
fun main(){
// setValue()메서드 활용
val str1 = setValue(10)
val str2 = setValue(20)
val str3 = setValue(30)
val str4 = setValue(40)
println("str1:$str1")
println("str2:$str2")
println("str3:$str3")
println("str4:$str4")
}
// when이 내장되어있는 setValue()메서드 생성
fun setValue(a1:Int)= when(a1){
10->"a1는 10이다"
20->"a1는 20이다"
30->"a1는 30이다"
else->"a6는 10~30이 아닙니다."
}
8. 특별한 함수식
8.1. 자료형의 생략
코틀린에서 변수를 선언하면 보통 변수명 옆에 콜론( : )과 함께 자료형을 기술한다.
하지만, 변수를 선언할 때 초기화 되는 값의 자료형이 명확하다면 변수선언의 자료형은 생략이 가능하다.
함수의 리턴값을 담는 변수에도 마찬가지
fun main(){
// 함수의 리턴값의 자료형이 Int자료형으로 명확하기 때문에 따로 변수의 자료형을 쓰지 않는다.
val r3 = testFun1(100, 200)
println("r3(testFun1()의 결과): $r3") // r3(testFun1()의 결과): 300
val r4 = testFun2(100, 200)
println("r4(testFun2()의 결과): $r4") // r4(testFun2()의 결과): 300
val r5 = testFun3(100, 200)
println("r5(testFun3()의 결과): $r5") // r5(testFun3()의 결과): 300
}
// 1. 함수의 리턴값이 호출된 곳에서 리턴값을 받는 변수의 자료형을 결정한다.
fun testFun1(a1: Int, a2: Int): Int{
return a1 + a2
}
// 2. 함수의 리턴값이 간단한 수식으로 해결된다면 '='와 함께 연결하여 return명령을 대신한다.
// testFun1과 testFun2는 같은 동작을 수행.
fun testFun2(a1: Int, a2: Int): Int = a1 + a2
// 3. 리턴값의 자료형이 아래와 같이 정수와 정수의 연산이어서 그 결과도 정수라면,
// 그리고 그 정수가 리턴될 것이라면 함수의 리턴자료형도 생략이 가능하다.
fun testFun3(a1: Int, a2: Int) = a1 + a2
- testFun1()
- 함수의 리턴값이 호출된 곳에서 리턴값을 받는 변수의 자료형을 결정
- testFun2()
- '='로 return명령을 대신한다.
- testFun3()
- 함수의 리턴자료형도 생략이 가능
8.2. 람다식 선언
함수의 간략형 표현으로 함수의 몸체를 변수에 담아 사용하도록 변형한 형태.
val lambda1 : (Int, Int) -> Int = {a1:Int, a2:Int -> a1 + a2 } // 람다식의 풀버전
val r6 = lambda1(100, 200)
println("r6(lambda1()의 결과): $r6") // r6(lambda1()의 결과): 300
{ a1:Int, a2:Int -> a1 + a2 } 앞에 정의된 자료형에 맞춰 사용되는 실제 매개변수와 연산식 & 리턴값
- (Int, Int) : 매개변수의 갯수와 자료형
- Int = { , -> a1 + a2 } : 리턴값의 자료형과 리턴값
- lambda1 : 함수명
8.2.1. 함수의 원형(헤더)을 생략한 형태
val lambda2 = {a1:Int, a2:Int -> a1 + a2 } // 매개변수, 리턴값의 자료형이 명확하다면 ':'이후부터 '='이전 까지 생략이 가능ㅎ다.
// (Int, Int) -> Int : '함수의 원형(헤더)을 생략'한 형태
val r7 = lambda2(100, 200)
println("r7 : $r7") // r7 : 300
8.2.2. 함수 바디의 자료형을 생략한 형태
val lambda3 : (Int, Int) -> Int = {a1, a2 -> a1 + a2}
// 헤더를 생략하지 않고, 함수 바디 쪽 자료형을 생략한 형태
val r8 = lambda3(100, 200)
println("r8 : $r8") // r8 : 300
8.2.3. 함수 바디가 다중라인인 형태
// 함수의 몸체에 여러라인의 명령이 필요한 경우 원래 함수 형태 호출
val lambda4 = { a1:Int, a2:Int ->
var r1 = a1 + a2
var r2 = a1 + a2
r1 * r2 // 맨 마지막에 리턴값을 쓰면, 람다함수의 리턴값으로 인식된다.
}
val r9 = lambda4(100, 200)
println("r9 : $r9") // r9 : 90000
9. Anonymous Function ( 익명함수 )
두 개의 매개변수를 곱하여 리턴하는 익명함수를 생성한다.
// 익명함수 생성
val t1 = fun(x1:Int, x2:Int):Int{
return x1 * x2
}
9.1. '익명함수가 전달인수'인 형태
fun main(){
// 익명함수 생성
val t1 = fun( x1:Int, x2:Int ): Int{
return x1 * x2
}
// 익명함수를 전달인수로 전달
testFunc1( t1, 100, 200 ) // a3 : 20000
}
// 매개변수 중 하나는 함수를 저장할 수 있는 매개변수이다.
// 함수의 '매개변수에 람다함수 헤더부분을 전달된 함수에 맞게 매개변수명 함께 작성'한다
fun testFunc1( funcName:(Int, Int) -> Int, a1:Int, a2:Int) : Unit {
// var a3 = funcName(100,200)
var a3 = funcName(a1, a2)
println("a3 : $a3")
}
9.2. '전달인수에 익명함수를 기술'한 형태
fun main(){
// 전달인수로 직접 익명함수를 기술
testFunc1( fun( x1:Int, x2:Int ):Int{ return x1-x2 }, 300, 400 ) // a3 : -100
}
// 매개변수 중 하나는 함수를 저장할 수 있는 매개변수이다.
// 함수의 '매개변수에 람다함수 헤더부분을 전달된 함수에 맞게 매개변수명 함께 작성'한다
fun testFunc1( funcName:(Int, Int) -> Int, a1:Int, a2:Int) : Unit {
// var a3 = funcName(100,200)
var a3 = funcName(a1, a2)
println("a3 : $a3")
}
9.3. '전달인수로 람다함수'를 보내는 형태
fun main(){
// 전달인수로 람다함수를 보내는 경우 : testFunc1의 매개변수가 람다함수의 헤더이므로, 전달인수는 바디만 보내면 된다.
testFunc1( { x1:Int, x2:Int -> x1 + x2 }, 500, 600 ) // a3 : 1100
// 전달인수와 매개변수의 조합은 'funcName:(Int, Int) -> Int = {x1:Int, x2:Int -> x1+x2}'와 같다.
}
// 매개변수 중 하나는 함수를 저장할 수 있는 매개변수이다.
// 함수의 '매개변수에 람다함수 헤더부분을 전달된 함수에 맞게 매개변수명 함께 작성'한다
fun testFunc1( funcName:(Int, Int) -> Int, a1:Int, a2:Int) : Unit {
// var a3 = funcName(100,200)
var a3 = funcName(a1, a2)
println("a3 : $a3")
}
9.4. '리턴값이 함수인 함수'
- 함수의 '자료형은 리턴되는 람다함수의 헤더( (Int, Int) -> Int )를 작성'한다
- 실제 리턴되는 함수의 내용은 'return 키워드와 함께 익명함수 또는 람다함수를 작성'한다.
fun main(){
// '함수가' 리턴값으로 '리턴되어 변수에 저장'된다.
var t2 = testFunc2()
var r2 = t2(600, 700) // 정수형 매개변수 두 개, 정수 리턴으로 된 함수가 리턴되어 t2에 저장되고 실행되는 형태
println("testFunc2()가 실행되고, \n익명함수가 리턴되어 사용된 결과 : $r2")
}
// 리턴값이 함수인 함수의 정의
// 함수의 리턴자료형은 리턴되는 람다함수의 헤더( (Int, Int) -> Int )를 작성한다
// 실제 리턴되는 함수의 내용은 'return 키워드와 함께 익명함수 또는 람다함수를 작성'한다.
fun testFunc2():(Int, Int) -> Int{
// return fun(x1:Int, x2:Int):Int{return x1*x2} // 익명함수가 리턴값으로 사용
return { x1:Int, x2:Int -> x1 * x2 } // 람다함수가 리턴값으로 사용
}
10. Loop 반복문
10.1. for문
코틀린의 for문은 자바의 for(시작, 끝, 증감량){ }이 아닌, 범위데이터만 사용한다.
// 코틀린의 for문은 자바처럼 시작값 끝값 증감량 등을 사용하지 않고, 범위 데이터만 사용한다.
val a1 = 1..10
for(item in a1){
print("$item\t")
}
println("\n-----------------------")
// 범위 데이터로 사용되는 대상은 배열, 리스트, Map, ..을 이용한 범위데이터 등이 사용된ㄷ.
val a2 = 1..10 step 2 // 2씩 증가하는 범위 데이터
for(item in a2){
print("$item\t")
}
println("\n-----------------------")
// 10부터 1까지 줄어드는 범위데이터 사용
// val a3 = 10..1 <- 잘 못된 사용 방법
val a3 = 10 downTo 1
for(item in a3){
print("$item\t")
}
println("\n-----------------------")
val a4 = 10 downTo 1 step 2
for(item in a4){
print("$item\t")
}
10.2. while문 ( + do-while문 )
var a5 = 0
while(a5<10){
print("$a5\t")
a5++
}
println("\n-----------------------")
var a6 = 0
do{
print("$a6\t")
a6++
}while(a6<10)
println("\n-----------------------")
var a7 = 100
while(a7<10){
print("$a7\t")
a7++
}
println("\n-----------------------")
var a8 = 100
do{
print("$a8\t")
a8++
}
while(a8<10)
11. Array 배열
11.1. arrayOf( )
코틀린에서의 배열생성은 arrayOf( )메서드를 주로 사용한다.
// 코틀린의 배열생성은 arrayOf를 자주 이용합니다.
val array1 = arrayOf(10, 20, 30, 40, 50)
println("array1:$array1") //클래스 이름과 해시코드등이 출력된다.
// 배열의 요소들을 한 번에 출력하려면 contentToString 함수를 사용한다.
println("array1 : ${array1.contentToString()}")
11.2. 배열의 자료형
// 코틀린의 배열은 자료형의 제약이 없어서, 자바의 리스트 등 처럼 여러 자료형 데이터를 하나의 배열에 넣어서 사용할 수 있다.
// 다만, 그것이 권장할 만한 동작은 아니기 때문에 자료형을 제한하는 코드가 제공되어 사용된다.
//자료형 제약 없이 사용
val array2 = arrayOf(100, 11.11, "문자열", true)
println("array2 : ${array2.contentToString()}")
// 하나의 자료로 제한하여 사용
val array3 = intArrayOf(10, 20, 30, 40, 50) // 기본 자료형은 ArrayOf앞에 자료형을 붙여 쓰고 ( intArrayOf() )
println("array3 : ${array3.contentToString()}")
val array4 = doubleArrayOf(11.11, 22.22, 33.33, 44.44, 55.55) // doubleArrayOf()
println("array4 : ${array4.contentToString()}")
val array5 = arrayOf<String>("문자열1", "문자열2", "문자열3") // 객체형 자료는 제네릭을 사용한다. ( ArrayOf<> )
println("array5 : ${array5.contentToString()}")
// int array6 = new Int[5];
// String str = new String[5];
11.3. null 배열
코틀린은 null을 허용하지 않는 것이 default이므로 '?'로 허용해야 한다.
var array6 = Array(5,{0}) // 다섯개의 0이 채워진 배열 생성
var str = Array<String?>(5,{null}) // 다섯개의 null이 채워진 배열. 코틀린의 변수는 null을 허용하지 않는 것이 기본이므로 이와같이 '?'를 넣어 null을 허용한다.
println("array6 : ${array6.contentToString()}")
println("str : ${str.contentToString()}")
11.4. 배열 요소 꺼내기 ( for문 )
val array1 = arrayOf(10, 20, 30, 40, 50)
// 반복실행문으로 배열 요소 꺼내기
for(item in array1){
print("$item\t")
}
11.5. 배열객체의 메서드
println("첫 번째 값 : ${array1.first()}")
println("마지막 값 : ${array1.last()}")
println("30의 위치 : ${array1.indexOf(30)}")
println("평균 : ${array1.average()}")
println("합 : ${array1.sum()}")
println("개수 : ${array1.count()}")
println("개수 : ${array1.size}")
println("30을 포함하는가 : ${array1.contains(30)}")
println("1000을 포함하는가 : ${array1.contains(1000)}")
println("30을 포함하는가 : ${30 in array1}")
println("1000을 포함하는가 : ${1000 in array1}")
val array12 = arrayOf(5,1,3,7,10,8)
val array13 = array12.sortedArray()
println("요소를 오름차순으로 : ${array13.contentToString()}")
val array14 = array12.sortedArrayDescending()
println("요소를 내림차순으로: ${array14.contentToString()}")
12. 달력 예제
package days02
fun main(){
print("연도 입력 : ")
val year:Int = readLine()!!.toInt()
print("월 입력 : ")
val month:Int = readLine()!!.toInt()
print("일 입력 : ")
val day:Int = readLine()!!.toInt()
var days:Int = sumdays(year, month, day) // 전달인수에 year, day도 추가할 것
// sumdays 함수안에서 월에 해당하는 날짜 수 계산할 때 when을 이용하지 말고, 배열 생성 후 반복실행과 함께 계산할 것
val temp:Int = days%7
val weekday:String = selectWeekday2(temp)
println(weekday)
}
fun sumdays(y:Int, m:Int, d:Int):Int{
var days:Int = 0
val mdays = arrayOf(0,31,28,31,30,31,30,31,31,30,31,30,31) //배열 생성
if(y%4==0 && y%100!=0 || y%400==0) mdays[2] = 29 // 입력한 년도가 윤년이면 2월을 29일로 변경
days = 365*(y-1)
val ys:IntRange = 1..(y-1)
for(i in ys){
if(y%4==0 && y%100!=0 || y%400==0)days++
} // 입력한 년도의 전년도까지 있었던 윤년만큼 +1 반복
var i:Int = 0
while(i<m){
days+= mdays[i]
i++
} // 입력한 월의 전월까지 날짜를 합산
days += d // 입력한 일만큼 날짜 합산
return days
}
fun selectWeekday2(t:Int) = when(t){
1->"월요일"
2->"화요일"
3->"수요일"
4->"목요일"
5->"금요일"
6->"토요일"
7->"일요일"
else->"잘못된 연산"
}
13. Class ( 클래스 )
코틀린도 자바와 마찬가지로 '객체 지향 언어'이다.
13.1. 객체생성
fun main(){
// 생성된 클래스 형태로 객체를 생성한다.
// 자바문법 : TestClass1 obj1 = new TestClass1();
// 코틀린 : val 참조변수명 : 클래스명 = 클래스명()
val obj1:TestClass1 = TestClass1()
// 'new'를 쓰지 않는다고 새로운 공간이 만들어지지 않는 것이 아니라 '생략되어 사용될 뿐'이다.
println("obj1:$obj1")
val obj2:TestClass1 = TestClass1() // new를 쓰지 않아도 새 공간이 할당된다.
println("obj2:$obj2")
val obj3 = obj1
println("obj3:$obj3")
val obj4 = TestClass2()
println("obj4:$obj4")
}
// 클래스 선언
class TestClass1{ } // 비어있는 클래스
class TestClass2 // 비어있는 클래스를 생성 ( {}생략 )
// 비어있는 클래스를 생성한다면 {}를 생략할 수 있다.
13.2. 멤버변수 / 메서드에 접근
package days02
fun main(){
val obj5 = TestClass3()
// 멤버변수에 접근하는 순간 내부적으로 getter/setter가 실행되어 값을 이용하게 된다.
println("obj5.a1 : ${obj5.a1}") // 0
println("obj5.a2 : ${obj5.a2}") // 100
//obj.a1 = 100 // 에러 : 'setter가 없는 a1변수(val)는 값을 변경할 수 없다.'
obj5.a2 = 200
println("obj5.a2 : ${obj5.a2}") // 200
obj5.testMethod1() // testMethod1
obj5.testMethod2() // testMethod2
}
// 클래스 선언
class TestClass3{
// 멤버변수 : 멤버변수는 var 또는 val로 자유롭게 생성이 가능.
// 자료형은 초기값에 따라 결정되도록 생략 가능하다.
val a1:Int = 0 // getter만 있는 val변수는 밑줄이 없다.
var a2:Int = 100 // getter와 setter가 모두 생성되는 var 변수는 밑줄이 표시된다.
// 코틀린 클래스에서는 생성된 멤버변수는 기본적으로 private로 생성된다.
//멤버 메서드
fun testMethod1(){
println("testMethod1")
}
fun testMethod2(){
println("testMethod2")
}
}
13. 클래스의 초기화 : init, constructor ( 생성자 )
13.1. initializer ( init{} )
- 코틀린은 클래스에 init블럭을 만들어주면 객체 생성시 자동으로 처리되는 코드를 만들 수 있다.
- 멤버변수에 값을 초기화하는 기능도 가능하다.
- init블럭은 생성자와 비슷한 역할을 할 수 있는 역영이지만, 전달인수나 기타의 함수로서의 기능은 없는 단순블럭이다.
- init블럭은 멤버변수 초기화, 생성자는 그외 객체 생성시 해야할 일들이 실행된다.
fun main(){
val obj1 = TestClass11()
println("obj1:$obj1")
}
// 코틀린은 클래스에 init블럭을 만들어주면 객체 생성시 자동으로 처리되는 코드를 만들 수 있다.
// 멤버변수에 값을 초기화하는 기능도 가능하다.
// init블럭은 생성자와 비슷한 역할을 할 수 있는 역영이지만, 전달인수나 기타의 함수로서의 기능은 없는 단순블럭이다.
// init블럭은 멤버변수 초기화, 생성자는 그외 객체 생성시 해야할 일들이 실행된다.
class TestClass11{
init{
println("init블럭 처리 : 객체가 생성되면 자동으로 동작되는 부분")
}
}
13.2. constructor ( 생성자 )
- 생성자를 만들지 않으면 내부에 존재하는 디폴트 생성자가 사용되지만, 인위적으로 꺼내어 따로 기술할 수 있다.
- 코틀린 클래스의 생성자 이름은 클래스이름을 따르지 않고, 'constructor'로 사용된다.
- constructor'라는 이름의 생성자는 오버로딩이 가능하다.
13.2.1. 보조 생성자 ( 클래스 지역변수 내 선언 )
fun main(){
val obj2 = TestClass12() // 매개변수가 없는 생성자 호출
println("obj2:$obj2")
println("obj2.v1:${obj2.v1}")
println("obj2.v2:${obj2.v2}")
val obj3 = TestClass12(100,200) // 매개변수가 있는 생성자 호출
println("obj3:$obj3")
println("obj3.v1:${obj3.v1}")
println("obj3.v2:${obj3.v2}")
}
class TestClass12{
var v1:Int = 0
var v2:Int = 0
// 생성자(constructor)를 만든다.
// 생성자를 만들지 않으면 내부에 존재하는 디폴트 생성자가 사용되지만, 인위적으로 꺼내어 따로 기술할 수 있다.
// 코틀린 클래스의 생성자 이름은 클래스이름을 따르지 않고, 'constructor'로 사용된다.
constructor(){
println("매개변수가 없는 생성자 동작")
}
// 'constructor'라는 이름의 생성자는 오버로딩이 가능하다.
constructor(a1:Int, a2:Int){
println("매개변수가 두 개인 생성자 동작")
v1 = a1
v2 = a2
}
}
13.2.2. 주 생성자 ( 클래스 선언부에 선언 )
- 클래스명 옆에 기술하는 생성자
- '주(대표)생성자'라고 부른다.
- 그 외 클래스 내부에 만들어지는 다른 오버로딩된 생성자들은 '보조생성자'라고 부른다.
- 주생성자에서 매개변수들은 var, val 등을 붙여 선언한다.
- 이들은 생성자의 매개변수이기도 하지만, 클래스의 멤버변수가 되기도 한다.
- 클래스 TestClass13의 이름 옆의 생성자는 매개변수가 없는 디폴트생성자를 대체하는 생성자로서, 매개변수 없는 생성자는 현재 클래스에서 없다고 생각해야 한다.
- 디폴트 생성자를 추가해야한다면 보조생성자로 오버로딩 되도록 생성해야 한다.
fun main(){
val obj4 = TestClass13(100,200)
println("obj4.a1:${obj4.a1}") // obj4.a1:100
println("obj4.a2:${obj4.a2}") // obj4.a2:200
// val obj5 = TestClass13() // 에러 : 매개변수 두개 있는 생성자가 디폴트 생성자를 대체하고
// 별도로 디폴트 생성자를 오버로딩 하지 않았으므로 이것은 에러가 발생한다.
}
// 클래스명 옆에 기술하는 생성자
// 자바에는 없는 생성자이며 '주(대표)생성자'라고 부른다.
// 그 외 클래스 내부에 만들어지는 다른 오버로딩된 생성자들은 '보조생성자'라고 부른다.
// 주생성자에서 매개변수들은 var, val 등을 붙여 선언한다.
// 이들은 생성자의 매개변수이기도 하지만, 클래스의 멤버변수가 되기도 한다.
// 클래스 TestClass13의 이름 옆의 생성자는 매개변수가 없는 디폴트생성자를 대체하는 생성자로서, 매개변수 없는 생성자는
// 현재 클래스에서 없다고 생각해야 한다. 디폴트 생성자를 추가해야한다면 보조생성자로 오버로딩 되도록 생성해야 한다.
class TestClass13 constructor(var a1:Int, val a2:Int){
}
// class TestClass13 constructor(){}
// 클래스 생성시 매개변수가 없는 생성자는 이미 존재하기 떄문에 위와같은 표현은 따로 쓰지 않는다.
// class Test13{}
// 위 생성자의 모습은 이렇게도 바뀌어 사용할 수 있다. (클래스에 더 이상 쓸 내용이 없어서 중괄호 생략)
class TestClass14 (var a1:Int, val a2:Int)
13.2.3. 주 생성자 + 보조 생성자의 오버로딩
- ※ 클래스의 이름옆에 있는 대표생성자는 객체생성시에 반드시 한번은 호출되어야 하는 강제규칙이 있다.
- 이는 보조생성자가 있고 보조생성자가 호출되더라도 마찬가지이다.
- 만약 객체 생성시에 보조생성자가 호출되었다면, 해당 보조생성자에서 대표생성자를 재호출해야 에러가 없다.
- 보조생성자에서 디폴트 생성자를 재호출하는 방법은 constructor()옆에 :this(디폴트생성자에 맞는 전달인수)형태로 호출한다.
- 클래스 이름 옆에 대표 생성자는 "멤버변수를 포함하고 있기 때문에" 그를 호출하지 않으면 객체 생성시 멤버변수가 만들어지지 않는다는 말과 같다. 따라서 반드시 this 호출이 필요하다.
fun main(){
// val obj5:TestClass15 = TestClass15(10,20)
val obj6:TestClass15 = TestClass15() // 매개변수가 없는 보조생성자 호출
val obj7:TestClass15 = TestClass15(10) // 매개변수가 하나인 보조생성자 호출
}
// 클래스 이름 옆에 생성자를 만들고 나서 오버로딩된 다른 보조생성자를 사용하고 싶을 때
class TestClass15(var a1:Int, val a2:Int){
// ※ 클래스의 이름옆에 있는 대표생성자는 객체생성시에 반드시 한번은 호출되어야 하는 강제규칙이 있다.
// 이는 보조생성자가 있고 보조생성자가 호출되더라도 마찬가지이다.
// 만약 객체 생성시에 보조생성자가 호출되었다면, 해당 보조생성자에서 대표생성자를 재호출해야 에러가 없다.
// 보조생성자에서 디폴트 생성자를 재호출하는 방법은 constructor()옆에 :this(디폴트생성자에 맞는 전달인수)형태로 호출한다.
// 클래스 이름 옆에 대표 생성자는 "멤버변수를 포함하고 있기 때문에" 그를 호출하지 않으면 객체 생성시 멤버변수가 만들어지지 않는다는 말과 같다.
// 따라서 반드시 this 호출이 필요하다.
constructor() :this(10,20){
println("매개변수가 없는 보조생성자 호출")
}
constructor(a1:Int) :this(10,a1){
println("매개변수가 하나인 보조생성자 호출")
}
}
14. extends ( 상속 )
14.1. 부모 클래스를 상속하는 자식 클래스의 객체 생성
fun main(){
val s1 = SubClass1() //자식클래스 객체 생성
println("s1.subMember1:${s1.subMember1}") // s1.subMember1:200
s1.subMethod1() // SubClass1의 메서드 실행
println("s1.superMember1:${s1.superMember1}") // s1.superMember1:100
s1.superMethod1() // SuperClass1의 메서드 실행
}
// 상속이 될 클래스들이 final로 만들어진다. 이는 상속이 불가능하므로, 'open 키워드'를 앞에 붙인다
open class SuperClass1{
var superMember1 = 100
fun superMethod1(){
println("SuperClass1의 메서드 실행")
}
}
// 상속은 extends키워드 없이 부모클래스 이름을 ':', '()'와 함께 클래스 옆에 써준다.
class SubClass1 : SuperClass1(){
var subMember1 = 200
fun subMethod1(){
println("SubClass1의 메서드 실행")
}
}
14.2. 부모 클래스에 생성자가 있는 경우
fun main(){
val s2 = SubClass2()
s2.superMethod2()
println(" ------------------------- ")
val s3 = SubClass3()
s3.superMethod2()
println(" ------------------------- ")
val s4 = SubClass4(400)
s4.superMethod2()
}
// 만일 부모 클래스에 대표생성자가 있다면?
open class SuperClass2(val a1:Int){
fun superMethod2(){
println("superMethod2 실행 a1 = $a1")
}
}
// 상속을 위해 부모클래스 이름을 쓰고 괄호를 연결했다면, 괄호안에 부모클래스의 대표생성자 매개변수에 맞게 전달인수를 전달해야 한다.
// 그렇지 않으면 부모클래스의 멤버변수가 생성되지 않고, 상속도 안되는 에러가 발생한다.
class SubClass2 : SuperClass2(200){
}
// 자식 클래스의 생성자가 따로 기술되어야 한다면 아래와 같이 표현한다.
class SubClass3 : SuperClass2{
constructor() : super(300){
}
}
// 자식 클래스에도 대표생성자가 있다면
class SubClass4 (var a4:Int) : SuperClass2(a4){
}
15. Override
15.1. 일반적인 상속
fun main(){
// 상속이 구현된 자식 클래스를 이용하여 객체를 만들고 자식클래스가 새로 만든 변수를 출력하고 메서드를 실행해본다.
var obj1:SubClass1 = SubClass1()
println("obj1.subA1 : ${obj1.subA1}") // obj1.subA1 : 200
obj1.subMethod() // SubClass1의 subMethod1입니다
// 상속받은 부모 클래스의 변수를 출력하고, 부모클래스에 있는 메서드를 실행
println("obj1.superA1 : ${obj1.superA1}") // obj1.superA1 : 100
obj1.superMethod() // SuperClass1의 superMethod1입니다
}
// 일반적인 상속
open class SuperClass1{ // 상속을 하기위해 클래스를 open을 사용하여 생성
var superA1 = 100
fun superMethod(){
println("SuperClass1의 superMethod1입니다")
}
}
// SuperClass1 클래스를 상속받는 SubClass1을 생성
class SubClass1 : SuperClass1(){
var subA1 = 200
fun subMethod(){
println("SubClass1의 subMethod1입니다")
}
}
15.2. 오버라이딩을 위한 상속
fun main(){
val obj2:SubClass2 = SubClass2()
// 오버라이딩 된 메서드를 실행
obj2.superMethod2() // SubClass2의 오버라이딩 된 superMethod2
// 부모클래스의 레퍼런스 변수에 자식클래스의 인스턴스를 저장
val obj3:SuperClass2 = SubClass2()
// 부모클래스의 레퍼런스 변수로 실행한 메서드는 ... 자식 클래스에서 재정의된 메서드가 우선 실행된다.
obj3.superMethod2() // SubClass2의 오버라이딩 된 superMethod2
println(" ----------------------------------------- " )
// 전달인수로 부모 인스턴스 주소 전달
Test1(obj2); // SubClass2의 오버라이딩 된 superMethod2
val obj4:SuperClass2 = SuperClass2()
Test1(obj3) // SubClass2의 오버라이딩 된 superMethod2
Test1(obj4) // SuperClass2의 superMethod2입니다
}
// 메서드 오버라이딩을 위한 상속
open class SuperClass2{
// 오버라이드가 될 부모클래스의 메서드는 반드시 앞에 open을 붙여 쓴다.
open fun superMethod2(){
println("SuperClass2의 superMethod2입니다")
}
}
class SubClass2 : SuperClass2(){
override fun superMethod2(){
// super.superMethod2() // 부모 클래스의 오버라이딩 되기 전 메서드 호출 : super키워드 사용
println("SubClass2의 오버라이딩 된 superMethod2")
}
}
fun Test1(obj:SuperClass2){
obj.superMethod2()
}
16. Any
- 자바의 'Object 클래스와 같은 것'
- 모든 클래스의 부모 클래스 (최상위 클래스)
- 이미 만들어져 있거나, 앞으로 생성될 클래스에 따로 명시하지 않아도, 다른 클래스를 상속받지 않아도, 그 클래스는 Any클래스를 상속받고 있다.
- 다만 다른 클래스를 상속받고 있는 클래스는 그 부모 클래스가 Any클래스를 상속받고 있기 때문에 결과적으로 자기 자신도 Any클래스의 자식(손자) 클래스가 된다고 볼 수 있다.
fun main(){
val obj1 = TestClass11()
println("obj1 : $obj1") // obj1 : days03.TestClass11@330bedb4
// 참조변수를 println에 넣으면 Any클래스에서 상속받은 toString() 메서드가 실행되어
// 클래스 이름과 해시코드가 출력된다. obj1 : days03.TestClass11@해시코드
// Any클래스에서 상속받은 toString메서드를 오버라이드 클래스는 재정의된 메서드가 실행된다.
val obj2 = TestClass12()
println("obj2 : $obj2") // obj2 : TestClass12 객체입니다.
testFun(obj1) // a:days03.TestClass11@330bedb4
testFun(obj2) // a:TestClass12 객체입니다.
}
// toString이 재정의되지 않은 클래스
class TestClass11{ }
class TestClass12{
// Any클래스에서 상속받은 toString을 오버라이딩하여 사용할 수 있다.
override fun toString() : String{
return "TestClass12 객체입니다."
}
}
// 매개변수의 자료형이 Any라는 것은 모든 클래스의 객체가 전달인수로 전달될 수 있다는 뜻
fun testFun(a:Any){
println("a:$a")
}
17. this / super
17.1. this의 용도
- 멤버변수와 매개변수(또는 지역변수)의 이름이 같을 때, 그들을 구분하기 위해 사용한다.
- 대표생성자가 있는 경우 보조생성자에서 기본생성자의 매개변수 개수와 자료형에 맞춰서 반드시 호출해야하는데, 이때 this 키워드를 사용한다.
- 메서드 내부에 다른 메서드(이름하야 지역메서드:지역변수와 같은 의미)를 만들어 쓸 수 있는데 멤버메서드와 구분하기 위해 사용한다.
fun main(){
var obj1 = TestClass21()
obj1.testMethod1()
}
// ** this의 용도 **
// 멤버변수와 매개변수(또는 지역변수)의 이름이 같을 때 그들을 구분하기 위해 사용한다.
// 대표 생성자가 있는 경우 보조 생성자에서 기본 생성자의 매개변수 개수와 자료형에 맞춰서 반드시 호출해야하는데, 이때 this 키워드를 사용한다.
// 메서드 내부에 다른 메서드 (이름하야 지역메서드:지역변수와 같은 의미)를 만들어 쓸 수 있는데 멤버메서드와 구분하기 위해 사용한다.
class TestClass21 (var a2:Int){
var a1:Int = 200
constructor():this(300){
// 보조생성자는 반드시 대표생성자를 this키워드를 이용하여 호출해야한다. 대표생성자의 매개변수가 멤버변수로 생성되어야 하는데,
// 호출되지 않으면 생성되지 않기 떄문이다.
}
fun testMethod1(){
var a1 = 100
println("메서드의 지역변수 a1 : $a1")
println("TestClass21의 멤버변수 a1 : ${this.a1}")
// $this.a1이라고 쓰면, 클래스명@해시코드.a1으로 출력된다.
// 멤버변수의 값을 출력하고 싶다면, 위와같이 ${this.a1}라고 작성한다.
fun testMethod2(){
println("testMethod1() 내부의 testMethod2() 메서드")
}
testMethod2() // 지역메서드 우선 실행
this.testMethod2() // 멤버메서드 실행
}
fun testMethod2(){
println("멤버메서드 testMethod2()")
}
}
17.2. super의 용도
- 상속관계의 자식 생성자에서, 부모클래스의 생성자를 호출할 때, 자식 클래스의 이름 옆에 부모 클래스의 이름을 사용하지만, 보조 생성자에서는 super라는 키워드를 쓴다.
- 자식 클래스에서 오버라이드된 메서드 그리고 변수를 사용할 때, 인위적으로 부모의 메서드를 호추하거나 변수값을 취할 때 사용한다.
fun main(){
var obj2 = SubClass11()
obj2.superMethod1()
//SuperClass의 superMethod1()
//super.a1 : 100
//SubClass의 superMethod1
println(" ------------- ")
var obj3 = SubClass12(600)
obj2.superMethod1()
//SuperClass의 superMethod1()
//super.a1 : 100
//SubClass의 superMethod1
}
// ** super의 용도 **
// 상속관계의 자식 생성자에서, 부모클래스의 생성자를 호출할 때, 자식 클래스의 이름 옆에 부모 클래스의 이름을 사용하지만, 보조 생성자에서는 super라는 키워드를 쓴다.
// 자식 클래스에서 오버라이드된 메서드 그리고 변수를 사용할 때, 인위적으로 부모의 메서드를 호추하거나 변수값을 취할 때 사용한다.
open class SuperClass(var a2:Int){ //대표생성자
open var a1 = 100
constructor():this(200){ } // 보조생성자
open fun superMethod1(){
println("SuperClass의 superMethod1()")
}
}
// 대표생성자와 보조생성자가 모두 있는 부모클래스는 둘 중 어떤 형태로도 상속(부모클래스 생성자호출)이 가능하다.
class SubClass11 : SuperClass(/*200*/) {
override fun superMethod1() {
super.superMethod1() // 부모클래스의 메서드
println("super.a1 : ${super.a1}") // 부모클래스의 멤버변수
println("SubClass의 superMethod1")
}
}
class SubClass12 : SuperClass {
// constructor키워드로 생성된 생성자 이름 옆에 super키워드를 이용하여 부모클래스의 대표생성자를 직접 호출할 수 있다.
// 그 경우 Extends로 연결되는 부모클래스 이름 옆의 괄호는 지워진다.
// class SubClass12 : SuperClass() -> class SubClass12 : SuperClass
constructor(a1:Int) : super(a1){ }
}
18. Abstract 추상클래스
fun main(){
val obj1 = Sub1()
obj1.method1() // Super1의 method1이다.
obj1.method2() // 재정의된 Super1의 method2이다.
println(" ----------------------------- ")
val obj2 = Sub2()
testFun1(obj2)
// Super1의 method1이다.
// 재정의된 Super1의 method2이다.
testFun1(obj1)
// Super1의 method1이다.
// 재정의된 Super1의 method2이다.
}
// 부모클래스의 참조변수가 멤버변수로 있기때문에, 그 자식클래스의 객체를 전달인수로 전달할 수 있다.
fun testFun1(obj1:Super1){
obj1.method1()
obj1.method2()
}
// 추상클래스 : 상속을 위한 클래스, 멤버메서드의 override에 강제성을 부여하기 위해 사용한다.
open abstract class Super1{
// 재정의(override)의 강제성이 있는 메서드와 없는 메서드를 동시에 사용이 가능하다.
fun method1(){
println("Super1의 method1이다.")
}
open abstract fun method2() // 재정의(override) 강제성이 부여된 추상메서드
}
class Sub1:Super1(){
override fun method2() {
println("재정의된 Super1의 method2이다.")
}
}
class Sub2:Super1(){
override fun method2() {
println("재정의된 Super1의 method2이다.")
}
}
19. Interface
- 인터페이스 : 추상클래스가 할 수 없던, '다중상속'이 가능하다.
- 자바의 버전이 업그레이드 되면서,
'자바의 인터페이스에도 override의 강제성이 부여되지 않는 일반변수와 메서드 선언이 가능'해졌다.
fun main(){
var obj31 = TestClass31() // Inter1을 상속(구현)받은 자식클래스
obj31.inter1_Method1() // Inter1의 Method1이다.
obj31.inter1_Method2() // TestClass31의 Inter1에서 오버라이딩된 Method2이다.
println(" ------------------ ")
var obj32 = TestClass32() // Inter2를 상속(구현)받은 자식클래스
obj32.inter2_Method1() // Inter2의 Method1이다.
obj32.inter2_Method2() // TestClass32의 Inter2에서 오버라이딩된 Method2이다.
}
// 인터페이스 : 추상클래스가 할 수 없던, '다중상속'이 가능하다.
// 자바의 버전이 업그레이드 되면서, '인터페이스에도 override의 강제성이 부여되지 않는 일반변수와 메서드 선언이 가능'해졌다.
interface Inter1{
fun inter1_Method1(){
println("Inter1의 Method1이다.")
} // override의 강제성이 부여되지 않는 메서드
fun inter1_Method2() // override의 강제성이 부여된 메서드
}
interface Inter2 {
fun inter2_Method1() {
println("Inter2의 Method1이다.")
}
fun inter2_Method2()
}
// 인터페이스의 구현은 자식클래스명 옆에 ":"와 함께 인터페이스명만 기술한다.
class TestClass31:Inter1{
override fun inter1_Method2() {
println("TestClass31의 Inter1에서 오버라이딩된 Method2이다.")
}
}
class TestClass32 : Inter2{
override fun inter2_Method2() {
println("TestClass32의 Inter2에서 오버라이딩된 Method2이다.")
}
}
19.1. 다중 상속
fun main(){
// 다중상속
var obj33 = TestClass33() // Inter1과 Inter2를 상속(구현)받은 자식클래스
obj33.inter1_Method1() // Inter1의 Method1이다.
obj33.inter1_Method2() // TestClass33의 Inter1에서 오버라이딩된 Method2이다.
obj33.inter2_Method1() // Inter2의 Method1이다.
obj33.inter2_Method2() // TestClass33의 Inter2에서 오버라이딩된 Method2이다.
println(" ------------------ ")
// 부모중 해당 인터페이스가 구현되어있는 객체만 전달인수로 사용이 가능합니다.
// obj32는 test1에 전달이 불가능하다.
// testFun1(obj32)
// obj31는 전달이 불가능하다.
// testFun2(obj31)
// obj33(TestClass33)은 Inter1이 구현되어있어서 인수로 전달이 가능하다.
testFun1(obj33)
// Inter1의 Method1이다.
// TestClass33의 Inter1에서 오버라이딩된 Method2이다.
// obj33(TestClass33)은 Inter2가 구현되어있어서 인수로 전달이 가능하다.
testFun2(obj33)
// Inter2의 Method1이다.
// TestClass33의 Inter2에서 오버라이딩된 Method2이다.
}
interface Inter1{
fun inter1_Method1(){
println("Inter1의 Method1이다.")
} // override의 강제성이 부여되지 않는 메서드
fun inter1_Method2() // override의 강제성이 부여된 메서드
}
interface Inter2 {
fun inter2_Method1() {
println("Inter2의 Method1이다.")
}
fun inter2_Method2()
}
// 다중상속 클래스
// 두 개 이상의 인터페이스를 구현하려면 인터페이스명을 ','로 구분하여 기술한다.
class TestClass33 : Inter1, Inter2{
override fun inter1_Method2() {
println("TestClass33의 Inter1에서 오버라이딩된 Method2이다.")
}
override fun inter2_Method2() {
println("TestClass33의 Inter2에서 오버라이딩된 Method2이다.")
}
}
fun testFun1(obj:Inter1){
obj.inter1_Method1()
obj.inter1_Method2()
}
fun testFun2(obj:Inter2){
obj.inter2_Method1()
obj.inter2_Method2()
}
20. Companion
- 자바에서 'static'으로 사용되던 키워드가 'companion'이라는 키워드로 바뀌어 사용된다.
- 다만, 'static'은 각 변수나 메서드에 각각 붙여 사용하지만,
'companion'은 영역을 만들고 그 안에 정적멤버로 사용할 변수나 메서드를 다 때려 넣는다. - companion 영역에 선언된 멤버들은 객체 생성없이, 클래스 이름과 함께 바로 사용이 가능하다.
- 인스턴스 멤버는 companion(static)메서드에 접근할 수 없다.
- 정적 멤버 메서드에서는 정적멤버변수의 접근이 가능
fun main(){
var obj1 = TestClass()
println("obj1.a1 : ${obj1.a1}") // obj1.a1 : 100
obj1.testFun1()
// 인스턴스 메서드 testFun1 실행!
// a1:100
// a2:200
// companion메서드 testFun2 실행!
// a2:200
obj1.a1 = 200
println("obj1.a1 : ${obj1.a1}") // obj1.a1 : 200
println(" -------------------- ")
// companion 영역에 선언된 멤버들은 객체 생성없이, 클래스 이름과 함께 바로 사용이 가능하다.
println("TestClass.a2 : ${TestClass.a2}") // TestClass.a2 : 200
TestClass.testFun2()
// companion메서드 testFun2 실행!
// a2:200
}
class TestClass{
var a1 = 100 // 인스턴스 멤버 변수
companion object{
var a2 = 200
fun testFun2(){
println("companion메서드 testFun2 실행!")
//println("a1:$a1") // 에러발생 - 인스턴스 멤버는 companion(static)메서드에 접근할 수 없다.
println("a2:$a2") // 정적 멤버 메서드에서는 정적멤버변수의 접근이 가능
}
}
fun testFun1(){ // 인스턴스 멤버 메서드
println("인스턴스 메서드 testFun1 실행!")
println("a1:$a1")
println("a2:$a2") // 인스턴스 메서드는 companion변수(a2)를 언제든 사용이 가능하다.
testFun2()
}
}
21. 형변환 ( type casting )
21.1. 자료형변환
var a1:Int = 100
// var a2:Double = a1
var a2:Double = a1.toDouble()
var b1:Double = 123.12
// var b2:Int = b1
var b2:Int = b1.toInt()
var c1:String = "12345"
var c2:Int = c1.toInt()
var d1:String = "123.45"
var d2:Double = d1.toDouble()
// var d3:String = "안녕하세요"
// var number4:Int = d3.toInt() // 문자 -> 정수 : 형변환 불가능
21.2. 스마트 캐스트 ( 플로우 기반 형변환)
// ** 객체 타입 검사 **
// 위 두개의 경우는 형변환 전에 안전한 실행을 위해 형변환이 가능한지에 대한 점검이 필요하다.
// 자바에서는
// if(super1 instanceof subClass41){
// val sub1:subClass41 = (SubClass41)super1 //형변환 명령
// }
// 코틀린에서는
// if(super1 is SubClass41){
// super1 as SubClass41 //코틀린 강제 형변환 명령
// }
val super2:SuperClass4 = SubClass41()
val inter2:Inter4 = SubClass42()
// 부모레퍼런스가 저장하고 있는 자식인스턴스를 자식레퍼런스에 옮길텐데,
// 위에서 언급한 것처럼 코틀린은 옮기지 않고, 레퍼런스 변수 자체를 변경한다.
// 그 전에 변경이 가능한지 검사
if(super2 is SubClass41){
super2 as SubClass41 // if문의 중괄호 밖에서도 형변환 결과를 활용할 수 있다.
} else {
println("형변환이 불가능하다.")
} // 검사결과가 참이면 변환, 거짓이면 변환하지 않음
if(inter2 is SubClass42) {
// is에 의해 열린 if문 안에서는 as를 쓰지 않아도
// is연산자 값이 true라면 형변환이 가능한 상황이므로 스마트 캐스팅 실행
// 현재위치 중괄호{}안에서는 이미 inter2는 SubClass42의 레퍼런스로 변경된 상태이며, 그 상태로 사용이 가능하다.
// 다만 중괄호를 벗어나면 다시 Inter2(부모인터페이스)의 레퍼런스로 되돌아간다.
// 중괄호가 끝나도 계속 변경상태를 유지하려면 inter4 as SubClass42를 사용한다.
}
}
open class SuperClass4
interface Inter4
class SubClass41 : SuperClass4(){
fun subMethod1(){
println("SubClass41의 subMethod1입니다")
}
}
class SubClass42 : Inter4{
fun subMethod2(){
println("SubClass41의 subMethod2입니다")
}
}
22. List
- 배열과 동일하게 인덱스 번호를 통해 객체를 관리한다.
- 배열의 크기가 정해지면 변경할 수 없지만 List는 추가, 삭제 등이 가능하다.
<코틀린의 컬렉션>
List | 순서를 통해 관리한다. |
Map | 이름을 통해 관리한다. |
Set | 집합 관리 |
22.1. 리스트 생성
List 생성 메서드 | |
listOf( ) | 불변형 리스트 생성 |
mutableListOf( ) | 가변형 리스트 생성 |
emptyList( ) | 비어있는 불변형 리스트를 생성 |
lsitOfNotNull( ) | null을 제외한 나머지만으로 리스트를 생성한다. |
val list1 = listOf(10, 20, 30, 40, 50)
val list2 = listOf("문자열1", "문자열2", "문자열3")
println("list1:$list1") // list1:[10, 20, 30, 40, 50]
println("list2:$list2") // list2:[문자열1, 문자열2, 문자열3]
var list3 = mutableListOf<Int>() // 가변형이기 때문에 더러는 비어있는 상태로 시작되고 요소들을 추가하여 변경한다.
var list4 = mutableListOf("문자열1", "문자열2", "문자열3")
println("list3:$list3") // list3:[]
println("list4:$list4") // list4:[문자열1, 문자열2, 문자열3]
val list5 = emptyList<Int>()
println("list5:$list5") // list5:[]
// null을 제외한 나머지들로 리스트를 구성
val list6 = listOfNotNull(10, 20, null, 30, null, 40, 50)
println("list6:$list6") // list6:[10, 20, 30, 40, 50]
for (item in list1) {
print("$item ")
} // 10 20 30 40 50
println("\nlist1 size : ${list1.size}") // list1 size : 5
22.2. 리스트 요소에 접근
get( ) | 0부터 시작하는 인덱스 번호를 통해 객체를 추출할 수 있다. |
// 리스트 요소에 접근
// get : 0 부터 시작하는 인덱스 번호를 통해 객체를 추출할 수 있다.
// kotlin의 리스트는 배열과 동일하게 []를 사용할 수 있다.
println("list1 0 : ${list1.get(0)}") // list1 0 : 10
println("list1 1 : ${list1.get(1)}") // list1 1 : 20
println("list1 2 : ${list1[2]}") // list1 2 : 30
println("list1 3 : ${list1[3]}") // list1 3 : 40
22.3. 리스트 기타 메서드
indexOf( ) | 지정된 값의 인덱스 번호를 반환한다. 값이 없을 경우 -1을 반환 |
lastIndexOf( ) | 지정된 값이 마지막에 해당하는 값의 인덱스 번호를 반환한다. 값이 없을경우 -1을 반환 |
subList( ) | 지정된 범위의 값을 추출하여 새로운 리스트를 생성해 반환한다. |
// 기타 메서드
// indexOf : 지정된 값의 인덱스 번호를 반환한다. 값이 없을 경우 -1을 반환
// lastIndexOf : 지정된 값이 마지막에 해당하는 값의 인덱스 번호를 반환한다. 값이 없을경우 -1을 반환
// subList : 지정된 범위의 값을 추출하여 새로운 리스트를 생성해 반환한다.
val list7 = listOf(10, 20, 30, 10, 20, 30)
val index1 = list7.indexOf(20)
println("index1 : $index1") // index1 : 1
val index2 = list7.lastIndexOf(20)
println("index2 : $index2") // index2 : 4
val list8 = list1.subList(1, 3)
println("list8 : $list8") // list8 : [20, 30]
22.4. 가변형 리스트 메서드
add(객체) | 객체를 추가한다. |
add(인덱스, 객체) | 주어진 인덱스 위치에 객체를 삽입한다. |
remove(객체) | 주어진 객체를 제거한다 |
removeAt(인덱스) | 주어진 인덱스의 객체를 제거한다. |
set(인덱스, 객체) | 주어진 인덱스에 객체를 덮어씌운다. |
// 가변형 리스트의 메서드
list3.add(10)
list3.add(20)
list3.add(30) // 가변 리스트에 요소를 하나씩 추가한다.
list3.addAll(listOf(40, 50, 60)) // 가변 리스트에 요소를 한번에 여러개 추가한다.
println("list3 : $list3") // list3 : [10, 20, 30, 40, 50, 60]
list3.add(1, 100) // 특정인덱스에 데이터를 추가한다. 뒤에 요소를 한칸씩 밀린다.
println("list3 : $list3") // list3 : [10, 100, 20, 30, 40, 50, 60]
// 특정 인덱스에 데이터를 여러개 한번에 추가한다. 뒤에 요소를 한칸씩 밀린다.
list3.addAll(2, listOf(200,300,400))
println("list3 : $list3") // list3 : [10, 100, 200, 300, 400, 20, 30, 40, 50, 60]
// 리스트 요소중 해당값을 한개 또는 여러개 제거
list3.remove(100)
println("list3 : $list3") // list3 : [10, 200, 300, 400, 20, 30, 40, 50, 60]
list3.removeAll(listOf(200,300))
println("list3 : $list3") // list3 : [10, 400, 20, 30, 40, 50, 60]
list3.removeAt(1) //지정한 인덱스의 요소를 삭제
println("list3 : $list3") // list3 : [10, 20, 30, 40, 50, 60]
list3.set(1,200) // 지정한 인덱스에 지정한 값으로 수정한다.
println("list3 : $list3") // list3 : [10, 200, 30, 40, 50, 60]
list3[2] = 300 // 배열처럼 지정한 인덱스에 지정한 값으로 수정
println("list3 : $list3") // list3 : [10, 200, 300, 40, 50, 60]
22.5. 불변형 리스트를 가변형으로 변환
// 불변형 리스트(list1)를 가변형으로 변환
// list1.add(1000) // 불변형이기 떄문에 요소 추가는 에러
val list100 = list1.toMutableList() // 불변형 -> 가변형 : 불변형의 변환결과를 다른 참조변수에 전달
list100.add(1000)
println("list100 : $list100") // list100 : [10, 20, 30, 40, 50, 1000]
//val list200 = list100.to
//list200.add(300) // 에러