Strong 감자의 공부

Ch_ 06 내장자료형 알아보기 본문

CS/문법_Kotlin

Ch_ 06 내장자료형 알아보기

ugyeong 2023. 8. 19. 18:50

목차 

1. 내장 자료형 알아보기

  • 1.2 문자와 문자열 자료형
  • 1.3 Any, Unit, Nothing 클래스
  • 1.4 배열

2. 자료형 처리 알아보기

  • 2.1 널러블 여부
  • 2.2 타입 변환
  • 2.3 구조 분해

1. 내장 자료형 알아보기 

🔶1.2 문자와 문자열 자료형

문자열은 기본으로 변경할 수 없는 객체이다. 제공되는 메서드는 기존 문자열을 변경해서 새로운 문자열을 만들어준다. 문자열을 변경할려면 StringBuilder클래스를 사용해야한다. 

 

⚫문자열의 원소는 문자 클래스의 객체이다.

fun main(){
    println("강" in "강아지") // 문자열 내에 특정 문자가 포함되어 있는지 in 연산자로 확인
    val sb = StringBuilder("강아지")
    println("강아지".javaClass.kotlin)
    println(sb.javaClass.kotlin)
    println('강' in sb)
    println("강아지"== sb.toString()) // 문자열과 연산하기위해선 StringBuilder는 다른 클래스라 문자열로 변환필요
}
// 결과값 
true
class kotlin.String
class java.lang.StringBuilder
true
true

 

⚫ 문자열 메서드로 내부 조회

fun main(){
    val myString = "문자열 인덱스 조회"
    val item = myString[2]
    println(myString.first()) // 문자열의 첫번째 문자
    println(myString.last()) // 문자열의 끝문자
    println(myString[0]) // 인덱스 0번
    println(myString[myString.length-1]) // 인덱스 마지막 문자
    println(myString.lastIndex) // 마지막 인덱스 값 반환
    println(myString.get(2)) // 인덱스 2번 문자
    println(myString.getOrElse(myString.length,{'2'})) // 인덱스 범위를 벗어날 경우 2를 반환 
    println(myString.getOrNull(myString.length)) // 인덱스 범위를 벗어날 경우 null 반환
}
// 결과값
문
회
문
회
9
열
2
null

 

⚫ 빈 문자열 처리

fun main(){
    val s1 = "\t"
    if(s1.isEmpty()) println("문자열 내에 문자가 없음") // \t 이스케이프 문자를 문자로 봄
    else println("이스케이프가 문자열에 있음")

    if(s1.isBlank()) println("빈 문자열") // \t 이스케이프 문자는 문자로 안봄
    else println("이스케이프가 문자열에 있음")

    val s2 = " 1234\t"
    println("문자 개수는 ${s2.length}") // 공백, 이스케이프문자도 문자로 취급 

    val s3 = s2.trimEnd()
    println("문자 개수는 ${s3.length}")

    val s4=s2.trimStart()
    println("문자 개수는 ${s4.length}")

    val s5=s2.trim()
    println("문자 개수는 ${s5.length}")
}
// 결과값
이스케이프가 문자열에 있음
빈 문자열
문자 개수는 6
문자 개수는 5
문자 개수는 5
문자 개수는 4

 

⚫ 문자열 비교와 대소문자 등의 메서드

fun main() {
    val s1 = "Eagle"
    val s2 = "eagle"

    if (s1 == s2) println("대소문자 구분x") // == 대소문자 구분함
    else println("대소문자 구분함")

    val res1 = s1.compareTo(s2) // 대소문자 비교하지 않는게 디폴트로 false, 즉 대소문자 구분함
    println(res1)
    if (res1 == 0) println("대소문자 구분x")
    else println("대소문자 구분함")

    val res2 = s1.compareTo(s2, true) // 대소문자 비교함 
    println(res2)
    if (res2 == 0) println("대소문자 구분x")
    else println("대소문자 구분함")

    val s3 = "gamja"
    println(s3.replaceFirstChar({ it.uppercase() })) // 첫번째 문자를 대문자로 변경
    println("DolJavBan".replaceFirstChar({ it.lowercase() })) // 문자열의 첫번째 문자인 대문자를 소문자로 변경

    println(s3.uppercase()) // 다 대문자로 변경
    println(s3.lowercase()) // 다 소문자로 변경

    val s4 = "튜플" to "만들기"
    println(s4)
}
// 결과값
대소문자 구분함
-32
대소문자 구분함
0
대소문자 구분x
Gamja
dolJavBan
GAMJA
gamja
(튜플, 만들기)

 

⚫ 문자 필터링 처리

문자열도 여러 문자들을 가지므로 filter 등 내부 순환을 제공한다.

메서드  filter는 내부순환을 돌면서 람다표현식이 실행한 결과가 참인 경우만 추출하고 for를 대체할 수 있다.

 

아래 확장함수는 문자 중 모음만 추출한다. 

fun Char.isEnglishVowel(): Boolean =
    this.lowercase() == 'a'.toString()
            || this.lowercase() == 'e'.toString()
            || this.lowercase() == 'i'.toString()
            || this.lowercase() == 'o'.toString()
            || this.lowercase() == 'u'.toString()

fun main() {
    val s = "Abcd EfgHijk"
    val res1 = s.filter { e -> e.isEnglishVowel() } // 문자열 필터해서 맞는 문자만 추출
    println("result1 = ${res1}") //result = AEi

    var res2 = ""
    for (i in s) { // i의 자료형은 Char
        if (i.isEnglishVowel()) {
            res2 += i.toString()
        }
    }
    println("result2 = ${res2}") // result2 = AEi

}

 

⚫ 리스트 등의 문자열 처리

fun main() {
    val words = listOf("gamja", "doljaban", "mom", "boy", "atom", "abo")

    // filter는 반환값이 List<string>
    val res1 = words.filter { e -> e.startsWith("bo") } // 리스트에서 bo로 시작하는 원소들을 리스트에서 가져온다.
    println("리스트에서 b로 시작하는 문자= $res1")

    val res2 = words.filter { e -> e.endsWith("a") } // 리스트에서 a로 끝나는 원소들을 리스트에서 가져온다.
    println("리스트에서 a로 끝나는 문자= $res2")

    val ml = mutableListOf("abc", "fgg", "vvv", "rtaby", "affb", "yab", "abf")

    // find는 반환값이 string
    val res3 = ml.find { it.startsWith("ab") } // 리스트의 앞에서부터 순환하는데 문자열 앞이 ab로 시작하면 반환
    val res4 = ml.findLast { it.startsWith("ab") } // 리스트의 뒤에서부터 순환하는데 문자열 앞이 ab로 시작하면 반환
    println("res3 : ${res3}")
    println("res4 : ${res4}")


    val res5 = ml.find { it.endsWith("ab") } // 리스트의 앞에서부터 순환하는데 문자열 뒤가 ab로 시작하면 반환
    val res6 = ml.findLast { it.endsWith("ab") } // 리스트의 뒤에서부터 순환하는데 문자열 뒤가 ab로 시작하면 반환
    println("res5 : ${res5}")
    println("res6 : ${res6}")

}
// 결과값 
리스트에서 b로 시작하는 문자= [boy]
리스트에서 a로 끝나는 문자= [gamja]
res3 : abc
res4 : abf
res5 : yab
res6 : yab

 

⚫ 문자열 분해와 결합 등 메서드 

fun main() {
    val s1 = "Today is a sunny day."
    val w1 = s1.replace("sunny", "rainy") // 1인수를 2번째 인수로 변경
    println(w1)

    val s2 = "Today is a sunny day."
    println(s2.contains("Today")) // 인수 포함 여부

    val word = "독수리, 매, 올빼미, 까치"
    val w2 = word.split(",")
    println(w2) // 문자열을 , 로 구분 후 list로 반환

    val joins = w2.joinToString("/") // 문자열 리스트들을 /로 문자열로
    println(joins)

    // 문자열 자르기
    val w3 = "감자는 공부중"
    println(w3.substring(2, 3)) // 문자열 인덱스 2<= <3 자름 
    println(w3.slice(1..2)) // 문자열 인덱스 1~2포함 자름
}
// 결과값
Today is a rainy day.
true
[독수리,  매,  올빼미,  까치]
독수리/ 매/ 올빼미/ 까치
는
자는

 

⚫변경가능한 문자열 StringBuilder 

fun main() {
    var str = StringBuilder()
    println("1. 문자열 : $str")
    println(" 길이= ${str.length} 용량=${str.capacity()}") // 현재 사용가능한 용량 확인

    // append는 마지막인덱스에 추가
    str.append(5) // 마지막 인덱스에 5추가
    str.append("강아지")
    str.append('가')
    str.append(true)
    println("2. 문자열 : $str")
    println(" 길이= ${str.length} 용량=${str.capacity()}") // 현재 사용가능한 용량 확인

    // insert는 특정 인덱스에 추가
    str.insert(1, "000")
    println("3. 문자열 : $str")
    println(" 길이= ${str.length} 용량=${str.capacity()}") // 현재 사용가능한 용량 확인

    // 삭제
    str.deleteCharAt(0) // 0번째 인덱스갑 삭제
    println("4. 문자열 : $str")
    str.delete(0, 2) // 인덱스 0~1번째 원소 삭제
    println("5. 문자열 : $str")
    str.clear()
    println("6. 문자열 : $str")

    str.append("안녕")
    println("7. 문자열 : $str")
    str.setCharAt(0, '하') // 인덱스 0번 문자 변경
    println("8. 문자열 : $str")

    str.append("안녕하세요 감자구리구리구리구리")
    println("9. 문자열 : $str")
    println(" 길이= ${str.length} 용량=${str.capacity()}") // 현재 사용가능한 용량 확인, 늘어남 

}
// 결과값
1. 문자열 :
길이= 0 용량=16
2. 문자열 : 5강아지가true
길이= 9 용량=16
3. 문자열 : 5000강아지가true
길이= 12 용량=16
4. 문자열 : 000강아지가true
5. 문자열 : 0강아지가true
6. 문자열 :
7. 문자열 : 안녕
8. 문자열 : 하녕
9. 문자열 : 하녕안녕하세요 감자구리구리구리구리
길이= 18 용량=34

🔶1.3 Any, Unit, Nothing 클래스

⚫ Any 클래스

어떤 클래스를 정의하든 Any 클래스는 자동으로 상속한다. 그래서 이 클래스에 확장함수를 지정하면 공통 메서드로 사용이 가능하다.

Any 클래스에서 제공하는 toString, hashCode 메서드는 하위 클래스에서 재정의해 사용할 수 있다. 보통 최상위 클래스에 정의된 메서드는 공통 메서드이다. 

 

Unit 클래스

보통 반환값이 없다면 Unit 객체를 반환한다. println()함수도 출력 이후에 Unit클래스의 객체를 반환한다.  그래서 반환자료형에 Nothing을 쓰면 에러가 난다. Unit은 아무것도 하지 않지만, 항상 값을 반환한다.

 

⚫ Nothing 클래스

함수를 반환 때 아무것도 없다는 것을 표시하는 클래스이다. 보통 예외 등을 처리할 때 이 클래스의 객체가 발생한 것으로 여긴다. 이 클래스 또한 Any 클래스를 상속해서 구현한다.

 

🔶1.4 배열

fun main(){
    val arInt = arrayOf(1,2,3,4)
    println(arInt)
    println(arInt.contentToString()) // 배열 자체 출력
    println(arInt.size) // count랑 같다.
    println(arInt.maxOrNull())

    val arString = arrayOf("감자", "돌자반")
    println(arString.maxOrNull())
}
// 결과값
[Ljava.lang.Integer;@3b9a45b3
[1, 2, 3, 4]
4
4
돌자반

 

⚫ 다양한 배열의 원소 출력법

 indices라는 IntRange 타입의 값을 반환하는데  최소인덱스..최대인덱스이다.

fun main() {
    val arInt = arrayOf(1, 2, 3, 4)

    val x = arInt.indices.iterator()
    while (x.hasNext()) {
        println("next이용 " + x.next())
        println(arInt.get(x.next()))
    }

    arInt.forEach { println("forEach" + it) }
    arInt.forEachIndexed { i, e ->
        println("$i = $e")
    }
}
// 결과값
next이용 0
2
next이용 2
4
forEach1
forEach2
forEach3
forEach4
0 = 1
1 = 2
2 = 3
3 = 4

2. 자료형 처리 알아보기 

🔶2.1 널러블 여부

⚫널러블 자료형 처리 규칙

- 널러블 자료형은 항상 널이 불가능한 자료형보다 상위 자료형이다. 그래서 널이 불가능한 자료형의 변수를 널러블 자료형의 변수에 할당할 수 있다. 반대로의 할당은 불가능하다.

- 널에 대한 체크는 컴파일 타임에 확정하지만, 실제 실행할 때는 처리하지 않는다. 그래서 널에 대한 처리는 전부 컴파일 처리할 때 예외를 발생시킨다.

- ?. : 널값이 들어오면 널로 처리하고 널값이 아니면 뒤에 오는 속성이나 메서드를 실행한다.

- ?: : 널값이면 다음에 들어오는 값으로 반환한다.

- !! : 널 값이 안들어온다고 확신할 경우만 사용한다. 널 값이 들어오면 예외를 발생시킨다.

 

⚫널 처리 메서드

fun main() {
    val nullableList: List<Int?> = listOf(1,2,null,4)
    println(nullableList.filterNotNull()) // 리스트내에서 널 제거

    val s1= " \t\n"
    println(s1.isNullOrEmpty()==false) // 비어있지 않은 걸로 봄
    println(s1.isNullOrBlank()==false) // 비어있는 걸로 봄
}
// 결과값
[1, 2, 4]
true
false

🔶2.2 타입변환

⚫ 타입 체크 및 변환 연산자

is / !is : 상속관계에 해당하는 타입일 때만 is , !is로 점검할 수 있다.

as/ as? : 타입을 변환할 때도 상속관계 내에서 가능하고 불가능할 경우는 as?로 처리하면 null이 반환된다.

 

스마트 캐스팅의 경우 lateinit 이나 get() = " " 와 같이 명확한 초깃값이 아닐경우 되지않는다. 반대로 by lazy{} 지연초기화나 get() = 190 와 같이 명확한 값이 들어오는 경우는 문제가 없다. 

 

⚫명시적인 자료형 변환 

fun main() {

    var x: String = "100"
    var y: String? = "3000"
    x = y as String // 널러블 문자열을 as를 사용해 일반 문자열로 변경해서 대입가능
    println(x) // 3000

    var e: String = "100"
    var f: String? = null
    val g = f as? String ?: 0 // 문자열에 null이 들어올 경우 as? 사용, ?:로 널대신 초기값반환
    println(g) // 0

}
// 결과값

 

🔶2.3 구조분해 알아보기 

⚫ 튜플 구조분해 

fun main() {
    val a = 100
    val b = 200
    val c = 300

    val st = Triple(a, b, c)
    val (a_, b_, c_) = st // 구조분해, 3개의 원소가 자동으로 변수에 할당
    println("$a_, $b_, $c_")

    // 투플내의 구조분해하는 속성확인, 퓨플 내 원소 조회
    val e = st.first
    val f = st.second
    val g = st.third
    println("$e, $f, $g")
	
    // 실제 구조분해를 해서 변수에 할당하면 componentN메서드가 실행됨.
    val e1 = st.component1()
    val e2 = st.component2()
    val e3 = st.component3()

    println("$e1, $e2, $e3")

}
// 결과값
100, 200, 300
100, 200, 300
100, 200, 300

 

⚫ 배열의 구조분해 

fun main() {
    val arr = arrayOf("a", "b", "c")

    println(arr.component1()) // a
    println(arr.component2()) // b
    println(arr.component3()) // c

    // 변수를 정의해 구조분해
    val (a1, a2, a3) = arr
    println("$a1, $a2, $a3") // a, b, c

    // 구조분해 시 미사용 변수를_로 지정
    val (_, _, cc) = arr
    println("$cc") // c

    // 변수 개수가 부족하면 그 위치까지만 구조분해됨. 
    val (_, bb) = arr
    println("$bb") // b 

}
// 결과값

 

⚫ 데이터 클래스의 구조분해 

import javax.xml.crypto.Data

data class DataClass(val name: String, val age: Int)

fun main() {
    val dc = DataClass("감자", 25)
    val (name, age) = dc // 변수할당 구조분해
    println("$name, $age")
    
    val n1 = dc.component1() // 메서드 사용 구조분해
    val n2 = dc.component2()
    println("name : $n1, age : $n2")

}
// 결과값
감자, 25
name : 감자, age : 25

 

⚫ 여러 인터페이스를 상속해 구현한 경우, 자료형으로 특정 인터페이스를 지정하면 클래스에 있는 모든 메서드를 호출하지 못하고 자료형에 지정된 인터페이스만 처리가 가능하다. 

책에선 자료형으로 Abstract이 가능하던데 인텔리제이에선 안된다.

interface c {
    fun add(x: Int, y: Int): Int
}

abstract class Ab {
    abstract fun addA(x: Int, y: Int): Int
}

class Add : c, Ab() {
    override fun add(x: Int, y: Int): Int = x + y
    override fun addA(x: Int, y: Int): Int = x + y
}


fun main() {
    val ai: c = Add()
    println(ai.add(100, 200))
    //println(ai.addA(100,200))은 인터페이스 범위가 아니라 호출 x
    
    val abb: Ab = Add()
    println(abb.addA(100, 200))
    //println(abb.add(100,200))은 추상클래스 범위가 아니라 호출 x
}
// 결과값 
300
300