Kotlin의 기본 타입(Primitive Types 또는 Built-in Types)은 다른 언어들처럼 숫자, 문자, 불리언 등 자주 사용하는 데이터 타입들을 제공하지만, Kotlin에서는 이들을 모두 객체(클래스) 로 다룬다는 점이 특징입니다. Kotlin은 자바와 달리 모두가 객체로 취급되지만, 실제로는 JVM이 primitive로 최적화해서 컴파일합니다. 즉 스택에 할당됨.
| Kotlin 타입 | 설명 | JVM 타입 (컴파일 시) |
|---|---|---|
Byte | 8비트 정수 | byte |
Short | 16비트 정수 | short |
Int | 32비트 정수 | int |
Long | 64비트 정수 | long |
Float | 32비트 부동소수점 | float |
Double | 64비트 부동소수점 | double |
Char | 문자 (16비트) | char |
Boolean | true / false | boolean |
'A', '1', '#'Char는 숫자가 아닌 문자형 클래스임.== 으로 비교 가능, 유니코드 값 기반 연산도 가능String은 불변(immutable) 객체+ 연산자나 $변수 를 이용한 문자열 템플릿 지원val String 은 String Constant Pool 이라는 특수 영역에 저장하므로, 동일한 값이 있으면 재사용
print(a === b) // true
→ 힙에 있는 게 아니라, 클래스 로더에 의해 관리되는 상수 풀에 존재
StringBuilder 사용 (성능 최적화)
StringBuilder는 내부에서 하나의 버퍼를 유지하며 문자열을 조립문자열 리터럴 vs 동적 문자열
"Hello" → 상수 풀(String Constant Pool)에 저장됨 → 재사용 가능 → 메모리 효율적"Hel" + "lo" → 컴파일 타임 연산이므로 상수 풀"Hel" + name → 런타임 연산 → 힙에 새 문자열 생성JVM은 동일한 문자열 리터럴을 재사용하기 위해 String Pool을 운영하며, "Hello" 같은 리터럴은 메모리 최적화를 위해 같은 인스턴스로 공유됩니다.
| 항목 | String | StringBuilder |
|---|---|---|
| 변경 가능성 | ❌ 불변 (immutable) | ✅ 가변 (mutable) |
| 연산 방식 | 새 객체 생성 | 내부 char[]에 append |
| 속도 | 느림 (많은 객체 생성) | 빠름 (메모리 재사용) |
String은 불변이기 때문에 덧셈 시마다 새로운 객체가 생성되며,
문자열 조작이 많을 때는 StringBuilder를 사용하면 성능이 크게 향상됩니다.
true, false 만 가짐arrayOf() 함수로 생성arr[index] 또는 arr.get(index)arr[index] = value 또는 arr.set(index, value)| 타입 | 설명 |
|---|---|
Any | 모든 Kotlin 클래스의 최상위 타입 (자바의 Object) |
Unit | 반환 값이 없는 함수의 리턴 타입 (자바의 void와 유사) |
Nothing | 절대 반환되지 않음을 명시하는 타입 (예: 예외 던짐) |
| Kotlin 표현 | JVM 타입 | 박싱 여부 | 성능 |
|---|---|---|---|
Int | int | ❌ 박싱 안 됨 | 매우 빠름 |
Int? | Integer | ✅ 박싱됨 | 느림 (GC 부담) |
Int vs Int?Kotlin에서는 Int는 nullable하지 않은 원시 타입, Int?는 nullable이 가능한 박싱 타입입니다.
| Kotlin 타입 | JVM에서 컴파일되는 타입 | 특징 |
|---|---|---|
Int | int | primitive, 성능 좋음 |
Int? | java.lang.Integer | nullable, 박싱됨, 성능 저하 가능 |
Primitive 타입(예: int, double)을 객체 타입(예: Integer, Double)으로 감싸는 것
즉, 숫자 값처럼 단순한 데이터를 객체로 래핑(wrapping) 하는 거야.
Int?에 담기면 자동으로 객체로 박싱됨.객체 타입에서 원시 타입 값을 꺼내는 것
JVM에는 원래 두 가지 타입이 있어요:
| 종류 | 예시 | 저장 위치 | 특징 |
|---|---|---|---|
| Primitive | int, double, boolean | 스택 | 작고 빠름, 객체 아님 |
| Object (Wrapper) | Integer, Double, Boolean | 힙 | null 가능, 제네릭에서 사용 가능 |
| 비용 원인 | 설명 |
|---|---|
| 힙 할당 | 객체 생성 비용 (GC 부하 포함) |
| equals 비교 | == → 참조 비교, .equals() → 값 비교 차이 |
| null 처리 | primitive는 null 불가 → wrapper 사용 시 null 처리 가능하지만 주의 필요 |
| 상황 | boxing 발생 여부 |
|---|---|
| 제네릭 타입에 전달 | ✅ (제네릭은 primitive 허용 안 함) |
| Any, Object로 업캐스팅 | ✅ |
| List 등 컬렉션 사용 | ✅ (List는 객체만 저장 가능) |
| 단순 연산 (a + b 등) | ❌ (primitive 연산) |
class Dog(val name: String)
constructor 키워드로 선언, 주 생성자 호출 필요class Animal(val name: String) {
var age: Int = 0
constructor(name: String, age: Int): this(name) {
this.age = age
}
}
init block)init 블록은 주생성자의 파라미터를 이용한 초기화 코드를 작성하는 곳입니다.
Kotlin의 클래스는 기본적으로 final → 상속 불가 → 상속을 허용하려면 open 키워드 사용해야 함
| 항목 | 가능 여부 | 설명 |
|---|---|---|
val / var 프로퍼티 선언 | ✅ | 반드시 구현 클래스에서 초기화하거나, getter만 정의 |
backing field 사용 (field 키워드 등) | ❌ | 인터페이스는 저장공간이 없음 |
초기값 있는 변수 선언 (var count = 0) | ❌ | 초기값 저장 불가 |
abstract class 사용 시기interface 사용 시기Serializable, Clickable)| 항목 | class Hi(val name: String) | class Hi(name: String) |
|---|---|---|
| 프로퍼티 생성 | ✅ | ❌ |
외부 접근 가능 (hi.name) | ✅ | ❌ |
| 내부 메서드에서 사용 | ✅ | ✅ |
| 메모리에 저장됨 | ✅ (프로퍼티) | ❌ (파라미터일 뿐) |
class Hi(val name: String)name은 생성자 매개변수이자, 클래스의 프로퍼티(멤버 변수)가 됩니다.hi.name처럼 접근할 수 있어요.class Hi(name: String)name은 단순히 생성자 매개변수일 뿐, 클래스 내부에 저장되지 않음.hi.name처럼 접근할 수 없어요."딱 한 번만 생성되는 객체(싱글턴)"를 선언하고 즉시 사용할 수 있게 해주는 키워드
| 특징 | 설명 |
|---|---|
| 자동 싱글턴 | 클래스처럼 여러 번 생성되지 않고 한 번만 생성됨 |
| 인스턴스화 필요 없음 | 바로 이름.함수() 형태로 사용 |
| 상태(state) 저장 가능 | 내부에 val, var, 함수 등 자유롭게 정의 가능 |
| thread-safe | Kotlin이 보장하는 thread-safe 싱글턴 객체 |
| 생성자 없음 | constructor 사용 불가 |
object Config {
val version = "1.0.0"
fun printVersion() {
println("App Version: $version")
}
}
object : 인터페이스)val listener = object : View.OnClickListener {
override fun onClick(v: View?) {
println("Clicked!")
}
}
→ 일회성으로 사용할 객체를 정의하고 바로 사용
companion object (정적 멤버처럼 사용)class MyClass {
companion object {
val TAG = "MyClass"
fun log(message: String) {
println("[$TAG] $message")
}
}
}
object, companion object 는 GC 대상이 아님
클래스의 하위 타입 정의를 제한하여, 컴파일러가 모든 하위 클래스를 인식할 수 있도록 해주는 클래스
| 항목 | sealed class | abstract class | enum class |
|---|---|---|---|
| 상속 가능 | ✅ (같은 파일 내만) | ✅ (어디서든 가능) | ❌ |
| 타입 제한 | ✅ | ❌ | ✅ (고정된 값만 가능) |
| when 분기 완전성 체크 | ✅ | ❌ | ✅ |
| 목적 | 타입 분기 안전성 | 공통 로직 제공 | 고정 값 나열 |
| 값 포함 | 다양한 타입/데이터 가능 | 가능 | 제한적 |
데이터를 담기 위한 클래스를 간결하게 정의할 수 있도록 만들어진 Kotlin의 특수한 클래스입니다.
| 메서드 | 역할 |
|---|---|
equals() | 두 객체의 내용이 같은지 비교 |
hashCode() | 해시 값 생성 (Map, Set에서 사용됨) |
toString() | 객체 내용을 문자열로 표현 |
copy() | 동일한 객체를 복사하면서 일부 값만 변경 가능 |
componentN() | component1(), component2() → 구조 분해 선언에 사용 |
class NormalUser(val name: String, val age: Int)
val a = NormalUser("Polaris", 28)
val b = NormalUser("Polaris", 28)
println(a == b) // false (참조 비교)
→ 일반 클래스는 equals()를 override하지 않으면 참조 주소로 비교
→ data class는 내용이 같으면 자동으로 같다고 판단
객체를 복사하되, 내부에 포함된 참조형 객체는 복사하지 않고 참조만 공유하는 복사
객체를 복사하면서, 내부에 포함된 참조형 객체까지 모두 새로 복사하는 방식
| 구분 | 얕은 복사 (copy()) | 깊은 복사 (직접 구현) |
|---|---|---|
| 기본 제공 여부 | Kotlin의 copy()는 기본으로 제공 | 직접 수동 구현 필요 |
| 참조형 필드 | 공유됨 (같은 주소) | 별도 객체로 새로 복사됨 |
| 안전성 | 간편하지만 위험할 수 있음 | 안전하지만 구현 비용 있음 |
| 활용도 | 일반적인 UI 상태에 적합 | 완전한 복제가 필요한 로직에 적합 |
Kotlin에서 abstract class와 interface는 모두 다형성과 추상화를 제공하는 구조지만,
용도와 기능, 설계 의도에서 차이점이 뚜렷합니다.
공통점부터 말씀드리면,
두 구조 모두 구현이 없는 함수(추상 메서드)를 정의하여
서브 클래스가 해당 함수를 반드시 구현하도록 강제
할 수 있고,
Kotlin에서는 둘 다 기본 구현(default body)도 포함할 수 있습니다.
하지만 주요 차이점은 다음과 같습니다.
abstract class는 생성자와 필드를 가질 수 있어 상태를 보유할 수 있지만, interface는 backing field가 없기 때문에 상태를 저장할 수 없습니다. 선언된 프로퍼티는 항상 구현 클래스에서 override되거나, 커스텀 getter로 제공되어야 합니다.abstract class는 "공통된 상태와 동작을 묶어 하나의 기반 클래스를 제공"하는 데 적합하며, interface는 "특정 기능(역할)을 선언하고 구현하도록 강제"하는 데 적합합니다.Android에서 View 처럼 공통 속성과 메서드를 가진 기본 뷰 클래스는 abstract class 로,
반면에 OnClickListener 처럼 기능을 부여하는 형태의 객체는 interface로 구현됩니다.
컴파일 타임에 결정되는 상수(Constant Value)를 정의할 때 사용
즉, 코드가 컴파일되는 순간 값이 확정되어 있어야 함
const val vs val 차이| 항목 | const val | val |
|---|---|---|
| 상수 여부 | 컴파일 타임 상수 | 런타임 상수 |
| 사용 위치 | top-level, object, companion object | 어디서든 가능 |
| 초기화 시점 | 컴파일 시 | 런타임 시 |
| 초기화 값 조건 | 리터럴만 가능 (String, Int, 등) | 계산된 값도 가능 |
| 성능 | 매우 빠름 (코드에 삽입됨) | 접근 시 메모리 읽기 필요 |
const는 반드시 top-level, object, companion object 내부에서만 사용 가능| 키워드 | 용도 | 초기화 시점 | 위치 제한 | 성능 |
|---|---|---|---|---|
val | 변경 불가능한 변수 | 런타임 | 어디든 가능 | 일반 |
const val | 컴파일 타임 상수 | 컴파일 시 | top-level 또는 object 내부 | 빠름 |
Kotlin에서는 객체가 실제로 필요할 때까지 초기화를 미루는 방식을 제공해요. 대표적으로:
lateinit: var에 대해, 초기화를 나중에 할 수 있게 함lazy: val에 대해, 처음 접근 시에만 초기화하고 값을 저장| 상황 | 추천 방식 |
|---|---|
| 초기화 순서를 나중으로 미루고 싶음 | lateinit |
| 처음 한 번만 초기화하고 캐싱하고 싶음 | by lazy |
Delegates.notNull() 사용
변수 초기화 전 접근하면 IllegalStateException을 발생시킵니다.
var number: Int by Delegates.notNull()
| 타입 | 설명 | 중복 허용 | 순서 유지 |
|---|---|---|---|
List | 순차적 요소 모음 | ✅ 가능 | ✅ 유지 |
Set | 중복 없는 요소 집합 | ❌ 불가 | ❌ 기본은 없음 (LinkedHashSet 예외) |
Map | 키-값 쌍 저장 | ✅ (키는 중복 불가) | ✅ 유지 (LinkedHashMap 사용 시) |
==, ===, .equals() 차이| 표현 | 의미 | Java에서 대응 | 설명 |
|---|---|---|---|
== | 값 비교 | .equals() | Kotlin은 자동으로 .equals() 호출 |
=== | 참조 비교 | == | 동일한 객체인지 주소값 비교 |
.equals() | 값 비교 | .equals() | 수동 호출 |
기본형 Int, Double은 값 비교가 빠릅니다 (primitive → CPU 비교)
하지만 박싱된 Integer, Double은 객체이므로 equals() 호출 + 널 체크 + 타입 체크가 추가됩니다.
Kotlin에서 ==는 값 비교이지만, Boxing이 발생하면 .equals() 호출로 인해 성능 저하가 있을 수 있습니다.
특히 반복문, 컬렉션 내 비교에서 Boxed 타입이면 불필요한 객체 생성과 함수 호출이 발생하므로 주의해야 합니다.
List<Int> vs IntArray 성능 비교 및 최적화Kotlin의 List는 내부적으로 박싱된 Integer 객체이기 때문에, 반복, 연산, 비교 시 불필요한 GC 및 성능 저하가 발생할 수 있습니다.
반대로 IntArray는 primitive 기반 배열로, 메모리 효율과 속도 면에서 훨씬 뛰어납니다.
대량 처리/성능이 중요한 경우엔 항상 IntArray를 사용하는 것이 좋습니다.
수신 객체: it
반환 값: 람다의 마지막 표현식
주 용도: null-safe 처리, 임시 범위 생성, 체이닝
수신 객체: this
반환 값: 람다의 마지막 표현식
주 용도: 객체 초기화 + 결과 반환
수신 객체: this
반환 값: 람다의 마지막 표현식
주 용도: 외부 객체 설정
수신 객체: this
반환 값: 수신 객체 자신
주 용도: 객체 설정(builder 패턴)
수신 객체: it
반환 값: 수신 객체 자신
주 용도: 부수 효과(side effect), 디버깅, 로깅
Kotlin에서 is 연산자 또는 null-check 등을 사용한 후,
컴파일러가 자동으로 명확한 타입으로 캐스팅(cast)해주는 기능입니다.
Kotlin은 변수 선언 시 타입을 생략해도 컴파일러가 자동으로 타입을 유추합니다.
| Modifier | 클래스 외부 접근 | 서브클래스 접근 | 같은 파일 내부 | 모듈 내 |
|---|---|---|---|---|
public | ✅ | ✅ | ✅ | ✅ |
internal | ❌ | ❌ | ✅ | ✅ |
protected | ❌ | ✅ | ✅ | ❌ |
private | ❌ | ❌ | ✅ | ❌ |
Kotlin은 스마트 캐스트와 타입 추론을 통해 불필요한 형변환과 타입 선언을 줄이고,
코드의 간결성과 안정성을 동시에 제공합니다.
또한
private,internal,protected,public의 가시성 제어를 통해클래스, 함수, 프로퍼티 수준에서 모듈 안전성과 캡슐화를 유지할 수 있도록 설계되어 있습니다.
모든 클래스의 최상위 타입
모든 nullable 타입의 최상위 타입
반환값이 없음을 의미
절대 반환하지 않음 (예외 등)
| 항목 | enum class | sealed class |
|---|---|---|
| 상속 | ❌ 불가능 | ✅ 하위 클래스 가능 |
| 상태 데이터 | 거의 없음 | ✅ 다양한 상태/필드 표현 가능 |
| 하위 타입 제한 | ✅ 고정된 값 | ✅ 같은 파일 내 클래스만 허용 |
| 활용 사례 | 상태 상수, 타입 식별자 | 결과 표현, UI 상태 등 |
| 예시 | Direction, LogLevel | Success/Failure, UIState |
| 키워드 / 방식 | 대상 | 초기화 시점 | 특징 |
|---|---|---|---|
val | 불변 변수 | 선언 시 | 재할당 불가 |
var | 가변 변수 | 선언 시 | 값 변경 가능 |
lateinit | var only | 나중에 수동 초기화 | nullable 없이 지연 초기화 |
by lazy | val only | 최초 접근 시 | 1회 초기화 + 캐싱 |
init 블록 | 클래스 | 주 생성자 실행 직후 | 생성자 파라미터 기반 초기화 |
| 보조 생성자 | 클래스 | 별도 constructor() 호출 | 반드시 this(...)로 주생성자 호출 필요 |
클래스나 함수에서 사용할 데이터의 타입을 나중에 타입 파라미터(T)로 일반화하여 여러 타입에 대해 재사용 가능하게 만드는 문법입니다.
out (공변성: Produces)
out T: T 를 밖으로 반환하고, 넣지 않음
읽기 전용, 생산자 적할
in (반공변성: Consumes)
in T: T 를 받기만 하고, 반환하지 않음
쓰기 전용, 소비자 역할
제네릭 타입 T의 타입 정보를 런타임에도 사용하고 싶을 때
inline fun <reified T> Gson.fromJson(json: String): T {
return this.fromJson(json, T::class.java)
}
// 사용
val user: User = gson.fromJson(jsonString)
Kotlin의 기본 자료형(Primitive Type)은 어떻게 처리되나요?
Kotlin에서는 기본 자료형도 모두 객체처럼 다룰 수 있는 클래스입니다. 하지만 JVM 상에서는 자동으로 Java의 primitive 타입으로 최적화됩니다.
Kotlin에서 val과 var의 차이는 무엇인가요?
💡 val은 immutable 참조, var는 mutable 참조입니다.
Kotlin의 data class는 어떤 목적이고, 어떤 메서드를 자동 생성하나요?
data class는 데이터 보관용 클래스로, 값을 비교하거나 복사하는 기능이 필요한 경우 사용됩니다.
equals(), hashCode()toString()copy()componentN() (Destructuring)Kotlin에서 ==와 ===의 차이는 무엇인가요?
| 연산자 | 의미 | 비교 대상 |
|---|---|---|
== | 값 비교 | equals() 호출 |
=== | 참조 비교 | 객체 주소가 같은지 확인 |
Kotlin에서 lateinit과 by lazy의 차이를 설명해 주세요.
| 항목 | lateinit | by lazy |
|---|---|---|
| 용도 | 나중에 초기화할 수 있는 var | 처음 사용 시 초기화되는 val |
| 대상 | var + non-null 객체만 가능 | val + 지연 초기화가 필요한 값 |
| 초기화 시점 | 명시적으로 나중에 초기화 | 최초 접근 시 자동 초기화 |
| Null 허용 | ❌ | ✅ 가능 |
| 사용 위치 | 클래스 멤버 | 클래스 멤버, 전역 변수, 객체 등 |
lateinit은 주로 Android에서 ViewModel, ViewBinding 등에 사용되고,
by lazy는 비용이 큰 객체를 나중에 초기화할 때 사용합니다.
object, companion object, singleton의 차이를 설명해 주세요.object - 즉시 생성되는 싱글톤 객체
companion object - 클래스 내부의 정적 객체
Kotlin에서 object는 자체적으로 싱글톤 객체를 생성하는 키워드로, 전역 유틸성 객체에 자주 사용됩니다.
반면
companion object는 클래스 내부의 정적 객체로, Java의static대체 용도로 활용되며 클래스 로딩 시 한 번만 생성됩니다.디자인 패턴으로서 Singleton은 오직 하나의 인스턴스를 갖는 객체를 의미하며, Kotlin에서는
object를 통해 이 패턴을 언어 차원에서 간결하게 구현할 수 있습니다.
sealed class와 enum class의 차이와 용도는?when 표현식에서 모든 하위 타입을 컴파일 타임에 체크 가능in, out, reified 키워드를 제네릭에서 사용할 때 각각 어떤 의미인가요?enum class는 고정된 상수 집합을 표현할 때 사용하며, 각 항목은 데이터 없이 단일한 상태로 존재합니다. 반면 sealed class는 상속 가능한 클래스 계층을 표현하며, 각 하위 클래스는 서로 다른 형태의 데이터를 가질 수 있습니다.
enum은 대표적으로 방향, 성별, 로그 레벨 등 단순 상태 구분에 사용하고,sealed class는 UI 상태, 네트워크 응답 등 다양한 데이터 구조와 함께 분기 처리가 필요한 곳에 사용합니다.또한
sealed class는when구문에서 컴파일 타임에 exhaustive 체크가 가능해, 더 안전한 상태 분기 처리가 가능합니다.
Int, Boolean 등은 실제로 객체로 할당되나요? → boxing/unboxing 설명Kotlin에서 Int, Boolean 등은 문법적으로는 클래스처럼 사용되지만, 컴파일 시에는 JVM의 primitive 타입으로 최적화되어 할당됩니다.
단, 이 타입들이 nullable (
Int?,Boolean?)로 사용되거나 제네릭에 전달되면 JVM에서는 해당 타입을 객체(Integer,Boolean)로 boxing하여 처리하게 됩니다.이때 발생하는 추가 객체 할당이나 equals 비교, GC 비용 등이 성능 저하 요인이 될 수 있습니다.
따라서 실무에서는 가능한 한 primitive 타입을 유지하고, 컬렉션에서는
IntArray,BooleanArray등을 사용하여 boxing을 피하는 것이 성능적으로 유리합니다.
List<Int>와 IntArray의 성능 차이는 왜 발생하나요?List는 내부적으로 Integer 객체의 리스트이기 때문에, 각 요소가 박싱된 객체로 메모리에 저장됩니다.
반면
IntArray는 JVM의 primitive 배열(int[])로 컴파일되어 각 요소가 primitive로 메모리상에 연속 배치됩니다.이로 인해
IntArray는 메모리 할당, 캐시 적중률, 연산 속도 면에서List<Int>보다 훨씬 효율적입니다.반복이 많은 숫자 처리나 퍼포먼스가 중요한 연산에서는
List<Int>대신IntArray를 사용하는 것이 좋습니다.
String은 불변 객체인데 성능 최적화는 어떻게 하나요?Kotlin의 String은 Java와 동일하게 immutable이며, 변경 시마다 새로운 객체가 생성됩니다.
이로 인해 문자열 조작이 많은 경우에는 매번 메모리를 새로 할당하게 되며, GC 부담도 커질 수 있습니다.
성능 최적화를 위해 Kotlin에서는
StringBuilder를 사용하여 mutable한 방식으로 문자열을 누적하고, 최종적으로toString()을 호출하여String으로 변환합니다.또한 문자열 리터럴은 JVM의 String Constant Pool에 저장되어 중복 없이 재사용되어, 비교 연산(
===) 시에도 성능 이점이 있습니다.
const val은 컴파일 타임 상수(Constant)를 선언할 때 사용하는 키워드입니다.
일반적인
val은 런타임에 초기화되는 불변 값이지만,const val은 컴파일 시점에 값이 결정되어바이트코드에 바로 삽입되므로 더 빠르고 효율적입니다.
String, Int, Double 등 primitive 또는 String 리터럴만 가능object, 또는 companion object 안에서만 사용 가능it: 전달된 값 (매개변수 인자), let, also, filter, map 등
this: 람다 내부의 수신 객체, apply, run, with 등
hashCode()는 객체의 고유 식별값을 표현하는 해시 함수로, 컬렉션에서 객체를 빠르게 찾거나 비교할 때 중요한 역할을 합니다.
| 항목 | equals() | hashCode() |
|---|---|---|
| 목적 | 논리적 동등성 판단 | 해시 기반 컬렉션에서 탐색/분류 |
| 반환값 | true / false | 정수 (Int) |
| 기준 | 필드 값 비교 | 정수 연산으로 계산된 값 |
| 용도 | 객체 내용 비교 | 컬렉션 검색, 중복 체크 등 |
| 관계 | equals()가 같으면 hashCode()도 같아야 함 | 반대는 아님 (충돌 허용) |