Strong 감자의 공부

Ch03_문장 제어 처리 알아보기 본문

문법_Kotlin

Ch03_문장 제어 처리 알아보기

ugyeong 2023. 7. 26. 13:30

-목차 -

1. 조건표현식 알아보기

-비교 연산자

-논리 연산자

-동등성

 

2. 조건문 알아보기

-if

-when

-예외

 

3. 순환표현알아보기 

-범위

-for, while, do while

-반복자+ 고차함수 조금


1. 조건 표현식 알아보기

 

1. 비교연산자

 

연신지 메서드전횐
> a.compareTo(b)>0
< a.compareTo(b)<0
a.compareTo(b)≥0
a.compareTo(b)<=0
== a?. equals(b)?: (b===null)
! = !(a?.equals(b) ?: (b===null)

b===null 중 === 은 참조값(=주소값 비교 )
a?.equals(b)에서 ?.은 a가 null일경우 . 만 있을 시 널예외가 발생함. 그래서 a가 널이라면 .?에의해 뒤의 메소드 연산을 하지 않는다.

fun main() {
	val a = 100
	val c = null
	val d = null
	println(c==d) // 결과값 true
	println(c===d) // 결과값 true
	println(c?.equals(d)) // 결과값 null
	println(c?.equals(d) ?: (c == null)) // 결과값 true 

}

+ 자바에서 참조 타입인 두 피연산자 사이에 ==를 사용할 경우

더보기

자바에서

참조 타입인 두 피연산자 사이에 ==를 사용할 경우 주소값으로 비교를 하게 된다. 두 피연산자의 주소값이 같은 곳을 가리키고 있다면 true를 반환하는 것이다. String의 경우 원시 타입이 아닌 참조 타입이기 때문에, 겉으로 보이는 문자가 똑같아 보여도 주소값이 다를경우 false가 출력된다.

String a = "hi" // 주소값 : 1번지

String b = "hi" // 주소값 : 2번지

System.out.println(a==b) // false

 

따라서 자바에서는 두 객체(참조 타입)의 동등성을 알기 위해서 equals를 호출해야 한다.

String a = "hi" // 주소값 : 1번지
String b = "hi" // 주소값 : 2번지

System.out.println(a.equals(b)) // true

 

 

코틀린에서도 == 연산자가 기본이다. 그러나 자바와는 동작 방식에 조금 차이가 있다. 원시 타입 두개를 비교할 때는 == 연산자가 동일하게 동작하지만, 참조 타입을 비교할 때 다르게 동작한다.

==는 내부적으로 equals를 호출한다. 따라서 참조 타입인 두 개의 String을 ==연산으로 비교하면 주소값이 아닌 값(동등성)비교를 한다.

val a: String = "hi"
val b: String = "hi"

println(a == b) // true

참조 타입의 주소 값을 비교(reference comparision)하고 싶다면?

코틀린은 자바에는 없는 ===연산자를 지원한다. 참조 비교를 위해서 === 연산자를 사용하면 된다. 즉, 자바의 주소 값 비교인 ==와 코틀린의 ===가 동일한 역할을 한다.

그러면 String 객체 a,b 에 대하여 a === b 는 false 가 되어야하지 않나요? 라고 생각할 수 있다. 하지만 Stirng 객체는 String pool 이라는 힙의 영역이 따로 존재하여 값을 String pool에 저장한다. 만약 이미 String pool 에 같은 값이 존재한다면 같은 주소를 참조하는 방식이다. 그래서 a === b 값이 true를 반환하게 된다. 즉, String class 에는 equals 를 객체의 equals 를 override(재정의) 한 것이다

 

출처-

https://wooooooak.github.io/kotlin/2019/02/24/kotiln_%EB%8F%99%EB%93%B1%EC%84%B1%EC%97%B0%EC%82%B0/

 

+스트링 수정과 참조

더보기

String 문자열은 참조 타입이므로, 문자열 뒤에 문자열을 추가하는 것 같은 수정 작업을 한다면,

기존에 참조하고 있던 String Pool 영역에 저장된 문자열에 추가하는 것이 아니라, 추가된 문자열을 새로 String Pool에 생성(저장)하여 참조를 이곳으로 바꾸는 것이다

https://ongveloper.tistory.com/160

+ 스트링 및 클래스 객체끼리 == ,=== 할 때

더보기
class UserA(var id: String)

class UserB(var id: String) {
    override fun equals(other: Any?): Boolean {
        if (this === other)
            return true
        other as UserB
        if (id != other.id)
            return false
        return true
    }
	
//레퍼런스를 정수로 바꿔줌 
    override fun hashCode(): Int {
        return id.hashCode()
    }
}

fun main() {
	val one = "ug"
	val two = "ug"

	println("one==two : " + (one == two))
	println("one===two : " + (one === two))

	var user1 = UserA("0")
	var user2 = UserA("0")

	println("user1 == user2 :" + (user1 == user2))
	println("user1 === user2 :" + (user1 === user2))

	var user3 = UserB("1")
	var user4 = UserB("1")

	println("user3 == user : " + (user3 == user4))
	println("user3 === user : " + (user3 === user4))


}

객체에서 equals 는 주소 비교 와 같다. 객체의 값을 비교하려면 equals 를 override 해야 한다.

equals를 override 하기 위해서는 hashCode 도 override 해야됨.

 

 

⚫ 실수도 숫자이므로 하나의 숫자인 객체가 유일하다 .

val d= 0.0
val e = -0.0

println(d==e) // 결과값 true
println(d===e) // 결과값 true

하지만 숫자나 문자열은 항상 값과 레퍼런스가 동일해야 하지만, 성능을 위해 필요할때마다 여러개를 만들어 처리 할 수 있다. 그래서 값이 같지만 레퍼런스는 가티 않을 수 있으므로 값만 비교하는 것을 권한다고 책에 써있다.

compare 메서드의 결과는 0, 음수, 양수이다.

후에 Comparator Comparable 메서드 구현할때 필요했다.

결과값 설명
0 두항이 같다
- 앞항이 작다.
+ 앞항이 크다.

 

2. 포함연산자처리

 

연산자 표현식 메서드
in a in b b.contains(a)
!in a ! in b !b.contains(a)

 

  • any, all, none처리
    • all : 모든것이 참인 경우
    • none : 모든것이 거짓인 경우
    • any : 하나라도 참이 경우
  • 여러 원소를 가진 배열이나 리스트 등에 특정 원소의 값이 있는지 확인해서 논리값으로 처리하는 메서드
fun main() {
	val height = 46
	println(" 1<=height<=46 ? : " + (height in 1..46)) //  true

	val list =listOf(1, 2, null)

	println(list.all({ it== null})) // false
	println(list.none({ it== null})) // false
	println(list.any({ it== null})) // true

}

 

3. 논리 연산자처리

 

연산자  의미 메서드
&& 둘 다 참이여야 참 그외 거짓 (a>b) and (a<c)
! !a : a가 참일때, !a는 거짓처럼 반대로 바꿔버림 !a

 

4. 함수도 일급객체(=영어: first-class object 란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체)

 

함수 참조 : 함수 정의 후 메모리에 로딩된 함수의 주소를 가져오는 리플렉션( :: ) 기능

함수도 객체라 레퍼런스 가지고

변수에 할당할 때 함수 참조를 사용해서 함수 레퍼런스를 가져옴.

해시코드로 변환하면 정수로 표시돼 쉽게 비교 가능

fun add(x: Int, y:Int) = x+y // 함수정의 시 동일한 참조 갖음. 

fun main() {
    val addVal  = ::add

    println((::add).hashCode() == addVal.hashCode()) // true
    println(addVal(10, 20)) // 30
}

2. 조건문 알아보기

1. if

 

a. 단순 조건처리

fun main() {
	val a = if (10 > 20) "성공" else "실패"
	print(a) // 실패
}

위의 예처럼 표현식 처리시 반드시 if else를 사용하고

표현식으로 처리할 때는 변수(위의 예에서 a )에 할당이나 반환이 필요

이를코드블록으로 처리 할 경우에도 반드시 마지막에 반환값표시(아래 예)

fun main() {
	val a = if (10 > 20) {
	println("참")
	true // 반환값 표시 
    } else {
	println("거짓") 
	false // 반환값표시
    }
    println("변수 $a")
}

// 결과값 
// 거짓
// 변수 false

 

b. 블록 구문 내의 지역변수

지역변수와 전역변수를 같은 이름으로 선언했다. 같은 이름이지만 관리하는 영역이 다르기 때문에 별도의 변수로 관리함.

블록 구문내에서 지정한 변수를 사용하면 전역변수에 접근할 수 없어서 지역변수에 할당된 값을 츌력한다.

fun main() {
    val x = 100
    var circle = 2

// 전역변수 참조
    while (circle-- > 0) { // ...1

        if (x == 100) { 
            val x = 200
	    println("참 $x")
        } else {
	    println("거짓 $x")
        }
        
	val x = 400
        println(x)
    }

    circle += 3 //... 1 에서 0이됐을때도 확인하기 때문에

// 지역변수 참조 
    while (circle-- > 0) {

        val x = 300

        if (x == 100) {
            val x = 200
	    println("참 $x")
        } else { 
	    println("거짓 $x")
        }
    }
}

출력값

참 200

400 참 200

400 거짓 300 거짓 300

→가장 가까운 변수의 값을 출력하고 {} 블록이 끝나면 지역변수들을 참조할 수 없는것 같다.

 

2.when 조건

  • 단일 특정 매칭 예) 1 -> println("1core")
  • 복합 특정 매칭 예) in 2..16 -> println("$cores Cores")
  • 패턴이 없는경우 예) else -> println("I want your machine")

⚫ 조건 → 처리된 결과 순

fun main() {
    val cores = Runtime.getRuntime().availableProcessors() // 노트북 코어 수 읽기 
    when (cores) {
        1 -> println("1core")
        in 2..16 -> println("$cores Cores")
        else -> println("I want your machine")
    }
}

// 결과
8 Cores

1, in 2..16 들(화살표 왼쪽 )을 패턴이라고 하는 것 같다.

 

⚫ when도 표현식으로 처리가능이라 결과값을 변수에 할당가능하다.

fun main() {
    val cores = Runtime.getRuntime().availableProcessors()

    val result: String = when (cores) {
        1 -> "1core"
        in 2..16 -> "$cores Cores"
        else -> "I want your machine"
	}
	println(result)
}

// 결과
8 Cores

 

⚫직접함수 반환처리

fun main() {
println(sys())
}

fun sys(): String = when (val cores = Runtime.getRuntime().availableProcessors()) {
    1 -> "1core"
    in 2..16 -> "$cores Cores"
    else -> "I want your machine"
}

// 결과
8 Cores

 

3. 예외

 

a. 예외처리 : try catch finally(얘는 무조건실행)

fun main() {
    try {
	add()
    } catch (e: Exception) {
	println(e)
    } finally {
	println("정상처리")
    }
}

fun add() = 100

//결과
정상처리

 

b. 예외 던지기 : throw

fun main() {
    try {
        throw Exception("예외 발생")
    } catch (e: Exception) {
	println(e)
    } finally {
	println("정상처리")
    }
}

//결과 

java.lang.Exception: 예외 발생
정상처리

 

c. 예외도 표현식이라 변수 할당이 가능하며, 예외처리는 비정상 처리를 정상 처리로 변환해준다.

fun main() {
    val x = try {
        100
    } catch (e: Exception) {
        200
    } finally {
        300
    }
    println(x)
}

//결과 
100

아래의 경우 “예외”라는 글자는 안나옴

fun main() {
    val y = try {
        add_ex()
    } catch (e: Exception) {
        200
    } finally {
        300
    }
    println(y)
}

fun add_ex(): Nothing = throw Exception("예외")

//결과 
200

3. 순환표현 알아보기

 

1. 범위객체 생성하기 

fun main() {
	val r1 = 1..10
	val r2 = 1.rangeTo(10)
	println(r1 == r2) // true
	println(r2.first) // 1
	println(r2.last) // 10

	val r3 = 1.until(10) // 마지막 항목 포함x 
	println(r3.first) // 1
	println(r3.last) // 9

	val r4 = 10.downTo(1)
	println(r4.first) // 10
	println(r4.last) // 1

	val r5 =1.rangeTo(10).step(2)
	println(r5.javaClass.kotlin) // class kotlin.ranges.IntProgression
	println(r5.first) // 1
	println(r5.last) // 9
	println(r5.step) // 2
}

 

2. 순환 

a. 순환조건은 in을 기준으로 앞에는 지역변수, 뒤에는 컬렉션 클래스의 객체인 범위, 배열, 집합, 맵, 등이 온다.

fun main() {
    for (i in 1..5)
        when (i) {
            in 1..4 ->print(i.toString() + ",")
            else ->print(i.toString())
        }
}
// 결과 

1,2,3,4,5

 

b. 범위의 시작과 끝이 같다면 한번만 실행 

fun main() {
    for (i in 5..5)
	println(i.toString() + ",") // 5,
    for (i in 5..5step3)
	println(i.toString() + ",") // 5,
}

var a =10

println(a + “,”) (x)

println( “,”+ a) (0)

 

c. for 역방향 순환

fun main() {
    for (i in 10..1)
	print(i.toString()) // 결과값 없음 
    println()
    
    for (i in 10.downTo(1))
	print(i.toString()) // 10987654321
    println()

    for (i in 10.downTo(10))
	print(i.toString()) // 10
}

 

d. 문자 범위 순환

문자도 아스키코드나 유니코드에 순서대로 저장되어 있어 문자범위를 만들 수 있다.

fun main() {
    for (ch in 'a'..'f')
	print(ch.toString()) // abcdef
    println()

    for (ch in 'f' downTo 'a')
	print(ch.toString()) // fedcba
}

 

e. continue / break : 가장가까운 순환문에서 다음으로 이동/ 가장가까운 순환문 종료

 

f.  break@레이블, continue@레이블 : 중첩된 순환문에서 한번에 외부순환문까지 (끝낸다.)빠져나올 수 있다. 

fun main() {
    loop@ for (i in 1..3) {
        for (j in 1..5) {
            if (j == 2) {
		println("break호출")
                break@loop
            }
		println("내포순환" + j.toString())
        }
    }
}
// 결과 

내포순환1
break호출

 

3. while(){} /do{}while() : 조건()을 만족하면 순환문 내부 실행/ 일단 한번 실행 후 조건() 확인  

 

a. break@레이블, continue@레이블은 여기서도 사용할 수 있다. 

fun main() {
    var n = 0
    var m = 0
    ug@ while (n++ < 3) {
	println("n : " + n)
        while (m++ < 5) {
	    println("m :" + m)
            if (m == 3) break@ug
        }
    }
}
// 결과값

n : 1
m :1
m :2
m :3

https://parkjuida.tistory.com/37

 

 

4. 반복자

 

a. 반복형(iterable) 클래스 : 여러개의 원소를 가진 자료형인 범위, 배열, 리스트 등을 객체로 만듦

 

b. iterator메서드 : 반복자(Iterator) 클래스의 객체로 변환 가능

반복형 → 반복자 자료형 변환 시 내부의 원소를 순환 할 수 있는 메서드 추가

 

c. 보통 내부 순환을 하는 forEach, map, filter, reduce등 메서드 제공

 

⚫ forEach{ }

반복자 생성 후 반복자.forEach{it. }

fun main() {

    val i = 1..10
    val c = 'a'..'z'
    // 반복자 객체 생성
    val iter1 = i.iterator()
    val iter2 = c.iterator()

    iter1.forEach{print(it.toString() + ",")}
    println()
    for (i in iter2) print(i + ",")
}
// 결과값
1,2,3,4,5,6,7,8,9,10,
a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,
fun main() {
    val r = ('a'..'c').iterator() // 반복자(Iterator) 처리
    while (r.hasNext())
	println(r.next())

    ('d'..'e').forEach(::print)
    println()
    ('f'..'g').forEach(::println)
}
// 결과값

a
b
c
de
f
g

 

⚫ map

반복자 생성없이 반복형에 바로 사용

map은 콜백이 객체를 반환할 것으로 예상하기 때문에 아래 코드 예제와 같이 완전히 새로운 객체를 만드는 데 사용할 수 있습니다.

배열의 모든 항목을 포함하지만 대신 콜백 함수가 이 경우 이름이 될 원래 동물 대신 새 배열에 넣을 변환된 개체를 반환할 것으로 예상.

map은 콜백이 객체를 반환할 것으로 예상하기 때문에 아래 코드 예제와 같이 완전히 새로운 객체를 만드는 데 사용할 수 있습니다. 

data class Animals(var name: String, var species: String)

var animals=arrayOf(
    Animals("Poppy", "rabbit"),
    Animals("Caro", "dog"),
    Animals("Teddy", "dog"),
    Animals("Molly", "fish"),
    Animals("Jimmy", "cat"),
    Animals("Harold", "fish")
)

fun main() {

    var names =animals.map(
        fun(animal: Animals): String {
            return animal.name
        })
    println(names)

}
// 출력값

[Poppy, Caro, Teddy, Molly, Jimmy, Harold]

 

⚫ filter 

반복자 생성없이 반복형에 바로 사용, 필터는 항목이 배열에 포함되어야 하는지 여부를 결정하는 참 또는 거짓 값을 반환하는 콜백 함수

fun main() {
    val r = (1..10) 
    val result = r.filter{ it% 3 == 0}
    println(result)
}
// 결과값 

[3, 6, 9]

map은 배열을 가져 와서 동일한 길이의 배열로 변환하지만 각각의 개별 항목이 변환됩니다.

filter는 배열을 더 작은 배열로 변환합니다.

 

⚫ reduce

반복자 생성없이 반복형에 바로 사용

varamounts=arrayOf(
    256,
    45,
    344,
    775,
    121,
    50
)

fun main() {
    var totalAmount =amounts.reduce(fun(sum, amount): Int {
        return sum + amount
    })
    println(totalAmount)
}
//결과값

 1591

val totalAmount = amounts.reduce { sum, amount -> sum + amount }

sum은 첫번째 콜백 인자이고 amout는 두번째 인자로 iterated item이다.

 

https://medium.com/mindorks/functional-programming-in-kotlin-part-2-map-reduce-ebdb3ebaa1f6

 


🌻🌻스터디 후기 🌻🌻(현재 추가중)

1.

when (cores) {
    1 -> println("1core")
    in 2..16 -> println("$cores Cores")
    else -> println("I want your machine")
}

+구문에서 else는 위에 설정된 모든 조건에 해당하지 않을 경우에 해당하며, 컴파일러는 else 가 있는지를 강력하게 확인하고, else 가 마지막이 아닌 부분에 오는 것은 허용하지 않습니다. 

버전에 따라 else를 안써도 됐었지만 지금 나오는 버전에선 써야한다. 

 

2. Unit/ Nothing의 차이

요약하면 Unit은 자바로는 void로 정상적인 종료인데 반환값 자체가 없을 때 쓰이고,

Nothing은 예외 처리나 에러를 던지는 상황에서 그 함수에서 아무것도 반환할 상황이 아닐때 쓰인다고 했는데 

제너릭관련해서도 얘기가 나와 추후 제너릭을 나가면 그때 포스팅을 할 것입니다!

-코드

 

 

3.  :: 언제 왜 쓸까? 

 

 

4.  코드 컨벤션 - 사람마다 다른부분이지만 멘토님은 이런식으로 쓰신다고 예를 들어주셨고

const val EXTRA_NAME = "name"
const val ARG_NAME = "name"

추가로 이렇게 상수로 선언해야하는 이유에 대해선 개발자들의 실수를 줄일 수 있기 때문이라고 설명해주셨다. 그래서 멘토님은 한번만 쓰이는 것이라도 상수로 선언하신다고 말해주셨다.

val intent = Intent()
intent.putExtra("name1", "hello") // name1을 누가 실수로 naaam으로 고칠 수도 있음.
intent.putExtra("KEY_NAME1", "hi")