1️⃣ 오늘의 Keyword
- implicit boxing (암묵적 박싱)
2️⃣ 이론 공부
- 아래의 결과를 정확히 설명하고, 결과를 예측할 수 있는가??
//일반 객체... class User val user1 = User() val user2: User? = User() val user3: User? = user1 println("${user1 == user2}, ${user1 === user2}, ${user1 == user3}, ${user1 === user3}") //false, false, true, true //일반 객체는 non-null, nullable 모두 겍체비교이다. ==, === 모두 객체비교이다 //기초 타입 - non-null val data1 = 1000 val data2 = 1000 println("${data1 == data2}, ${data1 === data2}") //기초 타입의 객체가 non-null 로 선언되면.. 자바로 변형시에 primitive type 으로 변형된다..객체가 아니다 //값을 비교할 수 밖에 없다 //기초 타입 - nullable val data3: Int? = 1000 val data4: Int? = 1000 println("${data3 == data4}, ${data3 === data4}") //true, false //? 로 선언..null 대입 가능. 자바로 변형시에 객체로 변형할 수 밖에 없다. Integer //Int 를 wrapper 클래스인 Integer 로 변형시켜서 Boxing 효과. //값을 비교하고 싶을 경우 (==), 객체를 비교하고 싶은 경우 (===) val data5: Int? = 10 val data6: Int? = 10 println("${data5 == data6}, ${data5 === data6}") //true, true val data7: Int = 100 val data8: Int? = 100 println("${data7 == data8}, ${data7 === data8}") //true, true val data9: Int = 1000 val data10: Int? = 1000 println("${data9 == data10}, ${data9 === data10}") //true, true
- 코틀린에서 ===의 동일성 비교
- primitive 값은 객체가 아니기 때문에 원래 참조값이 없음
- 그러면 아래처럼 === 참조비교를 하려고 하면??
val data1 = 1000 val data2 = 1000 println("${data1 == data2}, ${data1 === data2}")
억지로 둘다 원시타입인 값을 둘다 객체로 만들어서 당연히 각각 다른 주소값을 가질텐데
꼭 둘다 원시타입의 값인 것을 참조비교 해야겠어? 권장하지 않아.
그래서 사용이 불가라고 표현.
⇒ identity equality for arguments of types ‘Int' and 'Int' is deprecated
boxing하면 Integer 객체를 만들고 힙 할당 부담이 생긴다.
=== 참조비교 한번 할 때마다 객체를 2개씩 만들어서 당연히 false인 값을 드려?
⇒ 쓸데없는 boxing으로 성능저하 / 쓸데없는 항상 false 확인을 피함.
val data5: Int = 1500 val data6: Int? = 1500 println("${data5 == data6}, ${data5 === data6}") //true, true
박싱해서 당연히 다른 객체가 생성되는데, 항상 false 인데??
- JVM의 Integer 캐싱 규칙
- JVM 구현체에 따라 다를 수 있음
- HotSpot (Oracle, OpenJDK)의 경우
-128 ~ 127 범위의 Integer 객체를
JVM 런타임 초기화단계에서 미리 만들어 두고 재사용함
그 마저도 캐시 범위 설정 옵션이 또 있음 - 이것이 Kotlin이 말하는 **“unstable”**의 정체
- 즉, ‘현재의 JVM이 어떤 최적화를 쓰고 있느냐”에 결과를 맡기는게 됨. 그래서
로컬에선 됐다.
서버에선 다르게 나왔다.
이런 경우가 생길 수 있음.
- 사실상 실무 결론
- 숫자, Boolean, Char → 절대 === 쓰지 마라
- 객체 identity가 의미 있는 경우만 ===
3️⃣ 예제 실습 - 도서 대출, 반납 프로그램
//[ Main Loop ] //|-- 1. 대출 메뉴 -> [ rentBook Loop ] //| |-- 도서 선택 -> 상태 변경 & 리스트 추가 //| |-- 0 입력 -> 메뉴 탈출 (break) //|-- 2. 반납 메뉴 -> [ returnBook Loop ] //| |-- 대여 목록 확인 -> 상태 변경 & 리스트 제거 //| |-- 0 입력 -> 메뉴 탈출 (break) //|-- 3. 종료 package com.example.kotlin_test.study_alone.test1.cli class Book(val bookTitle: String, var hasBook: Boolean = true) //최초의 도서 목록 val initialBookList: Array<Book> = arrayOf( Book("Kotlin 기초"), Book("베이직 자바"), Book("이웃집 백만장자"), Book("AI와 코딩공부"), ) //가변 도서 목록 val bookList = mutableListOf(*initialBookList) //대출 도서 목록 val returnBookList = mutableListOf<Book>() fun main() { //연습 삼아 도서 목록에 도서 추가 bookList.add(Book("코틀린의 정석")) while (true) { println("\n1.도서 대출\t2.도서 반납\t0.프로그램 종료") //사용자 메뉴 입력 값 받기 //입력값이 없거나 숫자가 아닐 때 예외처리 : 기본값 -1 when (val menuInput = readlnOrNull()?.toIntOrNull() ?: -1) { 1 -> rentBook() //도서 대출 함수 2 -> returnBook() //도서 반납 함수 0 -> break // 종료 else -> { println("잘못 된 입력입니다.\n") continue } } } } //도서 목록 + 대출 정보 출력 fun showAllBooks() { for ((index, book) in bookList.withIndex()) { //bookList에서 index와 book을 한쌍씩 묶어서 다 꺼낼 수 있을 때까지 반복 when (book.hasBook) { // book.hasbook의 값이... true -> println("${index + 1}. ${book.bookTitle}\t\t(대출 가능)") false -> println("${index + 1}. ${book.bookTitle}\t\t(대출 중)") } } } //대출 목록 출력 fun showAllReturnBooks() { for ((index, book) in returnBookList.withIndex()) { println("${index + 1}. ${book.bookTitle}") } } fun rentBook() { while (true) { println("================================") showAllBooks() println("================================") println("대여할 책을 선택해주세요.\t나가기 : 0") val userInput = readlnOrNull()?.toIntOrNull() ?: -1 if (userInput == -1) { println("잘못 된 입력입니다.\n") continue } else if (userInput == 0) break else if (userInput > bookList.size) { println("잘못 된 입력입니다.\n") continue } else { val selectedBook = bookList[userInput - 1] when (selectedBook.hasBook) { true -> { selectedBook.hasBook = false returnBookList.add(selectedBook) println("\"${selectedBook.bookTitle}\" 도서를 대여하였습니다.\n") } false -> { println("이미 대여중인 책입니다.\n") } } } } } fun returnBook() { while (true) { if (returnBookList.isEmpty()) { println("대출 목록이 없습니다") break } println("================================") showAllReturnBooks() println("================================") println("반납할 책을 선택해주세요.\t나가기 : 0") val userInput = readlnOrNull()?.toIntOrNull() ?: -1 if (userInput == -1) { println("잘못 된 입력입니다.\n") continue } else if (userInput == 0) break else if (userInput > returnBookList.size) { println("잘못 된 입력입니다.\n") continue } else { val selectedBook = returnBookList[userInput -1] println("\"${selectedBook.bookTitle}\" 도서를 반납하였습니다.\n") //도서 목록에 hasBook 상태 변경 selectedBook.hasBook = true returnBookList.removeAt(userInput - 1) } } }
//클래스 주 생성자 선언 (매개변수에 val, var을 명시하여 클래스 멤버변수로 적용)
class Book(val bookTitle: String, var hasBook: Boolean = true)
class Book(val bookTitle: String, var hasBook: Boolean = true)
//사용자 입력값에 Nullable처리 -> String to Int? 처리 -> null일 경우 초기값 -1 (빈 입력, 문자열 입력이 -1로 치환됨)
val menuInput = readlnOrNull()?.toIntOrNull() ?: -1
val menuInput = readlnOrNull()?.toIntOrNull() ?: -1
//bookList에서 index와 book을 한쌍씩 묶어서 다 꺼낼 수 있을 때까지 반복
for ((index, book) in bookList.withIndex())
for ((index, book) in bookList.withIndex())
<<중요한점>>
bookList 와 returnBookList 둘다 Book이라는 타입의 객체만 담을 수 있는 리스트이다.
그러므로 리스트에는 책의 제목이 아니라!!! Book타입의 객체 주소값이 담긴다.
그래서 실제 담기는 Book의 멤버변수, 여기서는 hasBook의 Boolean 값을 변경하기 위해서
어느 리스트에서든 객체 주소가 담겨만 있다면 해당 객체의 변수에 접근 가능
bookList 와 returnBookList 둘다 Book이라는 타입의 객체만 담을 수 있는 리스트이다.
그러므로 리스트에는 책의 제목이 아니라!!! Book타입의 객체 주소값이 담긴다.
그래서 실제 담기는 Book의 멤버변수, 여기서는 hasBook의 Boolean 값을 변경하기 위해서
어느 리스트에서든 객체 주소가 담겨만 있다면 해당 객체의 변수에 접근 가능
