1. 코틀린에서 Type을 다루는 방법
1.1 기본타입
val number1 = 3 // Int
val number2 = 3L // Long
val number3 = 3.0f // Float
val number4 = 3.0 // Double
코틀린에서는 선언된 기본값을 보고 타입을 추론한다
int number1 = 4;
long number2 = number1;
System.out.println(number1 + number2);
자바의 경우 number1이 long 타입으로 암시적 변환이 이루어진다.
암시적이라는것은 (long)이라던지 .toLong()이라던지와 같은 명시적 설정 없이 알아서 변환 된다는것
하지만 코틀린에서는
val number1 = 4
val number2: Long = number1
TypeMismatch 컴파일 에러가 발생한다.
코틀린에서는 메모리가 더 큰 타입이라도 암시적 변환이 되지않는다.
val number1 = 4
val number2: Long = number1.toLong()
.toLong()
.to변환타입()를 사용해서
명시적으로 변환해줘야한다.
예시2 [ nuallble 타입 변환처리 ]
var number1: Int? = 4
val number2: Long = number1?.toLong() ?: 0L
1.2 타입 캐스팅
예시)
자바코드
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public static void printAgeIfPerson(Object obj) {
if (obj instanceof Person) {
Person person = (Person) obj;
System.out.println(person.getAge());
}
}
Obejct 객체를 받아서 Person 타입이라면 형변환해서 age를 출력하는 코드이다.
[ instanceof : 변수가 주어진 타입이면 true, 그렇지 않으면 false ]
형변환해주는 저 메소드를 코틀린으로 작성해보면 다음과 같다.
fun printAgeIfPerson(obj: Any) {
if (obj is Person) {
val person = obj as Person
println(person.age)
}
}
코틀린에서 is 는 Java의 instanceof의 기능을 한다. [ 맞으면 true, 다르면 false ]
as Person은 Java의 (Person)과 같은 명시적 형변환 기능을 한다.
그리고 as 문법은 생략이 가능하다.
즉 다음과 같이 작성 가능하다.
fun printAgeIfPerson(obj: Any) {
if (obj is Person) {
println(obj.age)
}
}
이렇게 코틀린이 컨텍스트를 분석해서 자동 형변환 해주는것을 스마트 캐스트라고 한다.
[ Any에 대해서는 밑에서 설명한다. ]
instanceof의 반대를 진행하려면
자바의 경우
public static void printAgeIfPerson(Object obj) {
if (!(obj instanceof Person)) {
Person person = (Person) obj;
System.out.println(person.getAge());
}
}
!를 붙여서 진행하고
코틀린 또한 !를 붙여서 가능하지만
fun printAgeIfPerson(obj: Any) {
if (!(obj is Person)) {
println(obj.age)
}
}
is 앞에 !를 붙여서 not is를 만들 수 있다.
fun printAgeIfPerson(obj: Any) {
if (obj !is Person) {
println(obj.age)
}
}
위의 예시에서 obj에 null이 들어올 수 있다면?
fun main() {
printAgeIfPerson(null)
}
fun printAgeIfPerson(obj: Any?) {
val person = obj as Person
println(person.age)
}
이 상태에서 코드 실행시 NPE가 발생한다.
fun main() {
printAgeIfPerson(null)
}
fun printAgeIfPerson(obj: Any?) {
val person = obj as? Person // person 변수의 타입은 Person?
println(person?.age)
}
as 뒤에 ? 를 붙여주면 Safe Call 동작 방식과 비슷하게
obj가 null이 아닐때는 Person으로 타입캐스팅해주고, null일 경우는 null을 반환한다.
저렇게 코드를 작성하면 person 변수는 null이 들어갈 수 도있고 아닐수도 있으므로 타입은 Person?가 되고
person의 프로피터를 가져오거나, 함수를 실행하려면 Safe Call을 해야하므로 person?.age로 수정해줘야한다.
is, !is
as
as?
1.3. Kotlin의 3가지 특이한 타입
1.3.1. Any
- Java의 Object 역할 (모든 객체의 최상위 타입)
- 모든 Primitive Type의 최상위 타입도 Any이다.
-> 자바에서 Primitive Type의 최상위 타입은 Object가 아닌데 코틀린에서는 모든 Primitive Type의 최상위 타입도 Any이다.
-> 코틀린에서는 Primitive 타입 구분없이 Int, Long ,Double 을 사용하기 때문이다.
- Any 자체로는 null을 포함할 수 없어 null을 포함하고 싶다면, Any?로 표현한다.
- Any 에 equals / hashCode / toString 존재한다.
-> 자바의 Obejct와 비슷
1.3.2. Unit
- Unit은 Java의 void와 동일한 역할이다.
-> 반환할것이 없다면 void를 쓰듯이 코틀린에서는 Unit을 사용하면된다. 하지만 타입추론이 가능해서 생략도 가능하다.
- (살짝 어려운 내용) void와 다르게 Unit은 그 자체로 타입 인자로 사용 가능하다.
-> 자바에서는 Void로 사용해야하는데 Unit은 그냥 사용하면된다.
- 함수형 프로그래밍에서 Unit은 단 하나의 인스턴스만 갖는 타입을 의미. 즉, 코틀린의 Unit은 실제 존재하는 타입이라는 것을 표현
차이점 정리
- Java의 void:
- 반환값이 없음을 나타내는 키워드입니다.
- 실제로 타입(type)은 아닙니다.
- 메서드에서 값을 반환하지 않는 경우 사용됩니다.
- Kotlin의 Unit:
- 반환값이 없음을 나타내는 타입(type)입니다.
- Singleton 객체로 구현되어 있으며, 기본적으로 반환값으로 Unit 객체를 반환합니다.
- 타입 추론이 가능하므로 생략할 수 있습니다.
- Unit은 Kotlin에서 반환값이 없는 함수의 반환 타입을 명시할 때 사용됩니다.
- 반환 타입을 생략하면 컴파일러가 자동으로 Unit을 추론합니다.
- Unit은 Singleton 객체로, 내부적으로 단일 인스턴스를 반환합니다.
fun example1(): Unit { // 명시적으로 Unit을 반환
println("This function returns Unit")
}
fun example2() { // Unit은 생략 가능
println("This function also returns Unit")
}
함수형 프로그래밍에서 Unit의 의미
- Unit은 단일 값만 가질 수 있는 타입:
- 함수형 프로그래밍에서 Unit은 값이 하나뿐인 타입을 뜻합니다.
- 이 값은 "아무 의미 없는 값" 또는 "작동이 성공적으로 완료되었다"는 것을 나타냅니다.
- Kotlin의 Unit은 이러한 개념을 따르며, 딱 하나의 인스턴스만 존재합니다.
- Unit은 void와 달리 값으로 존재:
- Java의 void는 반환값이 없는 것으로 간주되며, 실제 값은 없습니다.
- 반면, Kotlin의 Unit은 값으로 존재하는 객체입니다.
- Kotlin의 Unit은 object 키워드로 정의된 Singleton 객체로, 단 하나의 인스턴스만 가질 수 있습니다.
fun printMessage(message: String): Unit {
println(message)
}
val result: Unit = printMessage("Hello")
// result는 `Unit` 객체를 참조합니다.
println(result) // 출력: kotlin.Unit
여기서 result는 Kotlin의 Unit 타입 객체를 가지며, 이 값은 단 하나의 인스턴스(Unit)입니다.
왜 Unit이 중요한가?
함수형 프로그래밍에서는 모든 함수가 반환값을 가진다는 개념이 중요합니다.
- 반환값이 없는 함수(void를 반환하는 함수)조차 "어떤 값"을 반환해야 하는데, 이 역할을 수행하는 것이 Unit입니다.
- Unit을 반환하는 함수는 "나는 값을 반환하지 않지만, 호출이 성공적으로 완료되었음을 의미한다"는 의사를 전달합니다.
Unit은 "진짜 타입"이다
Unit이 "실제 타입"이라는 것은:
- 타입 시스템 내에서 사용 가능:
- 다른 타입처럼 변수에 할당하거나 반환값으로 사용할 수 있음.
- Java의 void와 달리 타입으로서의 역할을 명확히 합니다.
- 함수형 프로그래밍 관점:
- 모든 함수는 반드시 반환값을 가지므로, 반환값이 없는 경우 Unit을 반환하도록 설계.
- 이를 통해 일관된 타입 체계를 유지.
- 단 하나의 값만 가짐:
- Unit은 항상 같은 객체를 반환하므로 추가 메모리 할당 없이 재사용됩니다.
결론
- Unit은 단 하나의 인스턴스만 가지는 타입으로, 함수형 프로그래밍에서 "반환값이 없음을 나타내는 값"의 역할을 합니다.
- Kotlin에서는 이를 Singleton 객체로 구현해 실제로 존재하는 타입으로 표현했으며, Java의 void와는 달리 타입 시스템에 포함될 수 있는 객체입니다.
1.3.2. Nothing
- Nothing은 함수가 정상적으로 끝나지 않았다는 사실을 표현하는 역할이다.
- 무조건 예외를 반환하는 함수 / 무한 루프 함수 등
실행되면 무조건 예외가 반환되는 로직이 들어있는 메소드의 반환타입에 적어주면 된다.
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
많이 사용되지않는 타입이다.
1.4. String Interpolation, String indexing
1.4.1 코틀린 문자열 가공
자바의 경우 위와 같이 문자열을 가공하여 사용했다.
하지만 코틀린에서는 위와 같이 문자열을 가공할 수 있다.
사용예시)
var person = Person("g", 10)
var str = "이름 : ${person.name} 이름2 : $person.name"
println(str)
$person.name의 잘못된 구문
- 문자열 템플릿에서 $ 기호를 사용하여 변수를 참조할 때, 복합적인 속성이나 메서드에 접근하려면 **중괄호 {}**를 사용해야 합니다.
- 코드에서 $person.name처럼 작성하면 $person 변수를 참조하려고 시도합니다. 하지만 person은 객체이고, 문자열로 변환될 때 기본적으로 객체의 toString() 메서드가 호출됩니다.
- toString() 메서드가 재정의되지 않은 경우, 객체의 기본 toString() 출력 형식은 클래스명@해시코드 형태입니다.
출력 결과의 구성
- 이름 : ${person.name}: 올바르게 작성된 부분으로, person.name의 값 g가 출력됩니다.
- 이름2 : $person.name: $person은 객체를 문자열로 변환한 결과(com.lannstark.lec03.Person@36d64342)가 출력되고, .name은 그대로 문자열로 취급됩니다
사용예시2)
fun main() {
var num = 10
var str = "숫자 : $num"
println(str)
1.4.2 여러줄의 문자열 생성
val name = "ABC"
val str =
"""
hi
my $name
""".trimIndent()
println(str)
코틀린에서는 """ """ 를 이용하여 여러줄의 문자열을 생성할 수 있다.
.trimIndent()로 인해 들여쓰기가 된 부분이나, 공백줄을 없애서 문자열을 깔끔하게 정리 할 수 있다.
1.4.3 문자열의 특정 문자 가져오기
val str = "ABC"
println(str[0])
println(str[1])
println(str[2])
2. 코틀린에서 연산자를 다루는 방법
2.1 단항 연산자 / 산술 연산자
2.2 비교 연산자와 동등성, 동일성
2.2.1 비교 연산자
자바와 코틀린 사용법은 동일하다
하지만
Java와 다르게 객체를 비교할때 비교 연산자를 사용하면 자동으로 compareTo를 호출해준다.
public class JavaMoney implements Comparable<JavaMoney> {
private final long amount;
public JavaMoney(long amount) {
this.amount = amount;
}
@Override
public int compareTo(@NotNull JavaMoney o) {
return Long.compare(this.amount, o.amount);
}
}
이런 자바클래스가 존재한다고 했을때
자바에서 두 JavaMoney 객체의 amount 비교할때는 구현한 compareTo 메서드를 이용하여
public static void main(String[] args) {
JavaMoney money1 = new JavaMoney(1_000L);
JavaMoney money2 = new JavaMoney(2_000L);
if (money1.compareTo(money2) > 0) {
System.out.println(" Money1 돈이 더크다.");
}
}
이런식으로 구현했다면
코틀린에서는
val money1 = JavaMoney(2_000L)
val money2 = JavaMoney(1_000L)
if (money1 > money2) {
println(" Money1 돈이 더크다.")
}
다음과 같이 구현 가능하다. [ > 과 같은 부등호에 자동으로 compareTo를 적용해준다. ]
2.2.2 동등성과 동일성
동등성(Equality) : 두 객체의 값이 같은가?
동일성(Identity) : 완전히 동일한 객체인가? 즉 주소가 같은가?
3개의 객체가 존재한다고 해보자
주소가 동일한 객체는 같은 객체로 판단된다. [ 동일성 ]
==를 이용해서 주소값 비교 [ 동일성 체크]
주소값이 다르지만 내부 값이 같다. [ 동등성 ]
equals 메소드를 이용하여 동등성 체크를 한다.
자바에서는 위와 같이 동일성,동등성 체크를 했지만
코틀린은 다르게 체크한다.
자바 예시)
public class JavaMoney {
private final long amount;
public JavaMoney(long amount) {
this.amount = amount;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JavaMoney javaMoney = (JavaMoney) o;
return amount == javaMoney.amount;
}
}
JavaMoney money1 = new JavaMoney(1_000L);
JavaMoney money2 = money1;
JavaMoney money3 = new JavaMoney(1_000L);
System.out.println(money1 == money3);
System.out.println(money1.equals(money3)); // Object의 기본 equals 구현은 주소값 비교이다.
equals를 구현해서 어떤식으로 동등성을 확인할지 구현해줘야한다.
[String 같은경우는 기본적으로 equals가 구현되어있다. ]
코틀린은 === 을 이용하여 주소값 비교 [ 동일성 ]
==를 이용하여 값을 비교한다. [ 동등성 ]
val money1 = JavaMoney(1_000L)
val money2 = money1
val money3 = JavaMoney(1_000L)
if (money1 === money2) {
println("동일성 체크 성공")
}
if (money1 == money3) {
println("동등성 체크 성공")
}
== 를 눌러보면 구현되어있는 equals 함수로 이동된다. 즉 ==가 알아서 해당 클래스에 구현되어있는 equals를 호출해준다.
2.3 논리 연산자 / 코틀린에 있는 특이한 연산자
2.3.1 논리 연산자
Lazy 연산 예시)
fun main() {
if (fun1() || fun2()) {
println("good")
}
}
fun fun1(): Boolean {
println("fun 1")
return true
}
fun fun2(): Boolean {
println("fun 2")
return true
}
if (fun1() || fun2())
fun1()이 true이므로 뒤에 fun2() 함수를 실행하지않고
바로
println("good")
를 실행하는것을 말한다.
자바에서도 똑같이 동작한다.
2.3.2 코틀린에 있는 특이한 연산자
in / !in
// 예시
if(1 in numbers){
}
컬렉션이나 범위에 포함되어 있다, 포함되어 있지 않다
a..b
//예시
val range = 1..5 // 1부터 5까지의 범위를 생성
println(range.toList()) // [1, 2, 3, 4, 5]
a부터 b 까지의 범위 객체를 생성한다
a[i]
//예시
val str = "ABC"
println(str[2]) // C
a에서 특정 Index i로 값을 가져온다
2.4 연산자 오버로딩
Kotlin에서는 객체마다 연산자를 직접 정의할 수 있다
말 그대로 연산자 +,-같은 연산자의 동작을 직접 정의할 수 있다.
예시코드)
data class Vector(val x: Int, val y: Int) {
operator fun plus(other: Vector): Vector {
return Vector(this.x + other.x, this.y + other.y)
}
}
fun main() {
val v1 = Vector(1, 2)
val v2 = Vector(3, 4)
val result = v1 + v2 // plus 연산자 호출
println(result) // 출력: Vector(x=4, y=6)
}
data 키워드
- data class는 Kotlin에서 데이터를 저장하기 위한 클래스
- 자동으로 equals, hashCode, toString 등의 메서드를 생성해 주기 때문에 데이터를 다루기 편리합니다.
operator 키워드
- Kotlin에서는 연산자(+, -, *, /, 등)를 특정 함수에 연결할 수 있습니다.
- operator 키워드는 이 함수가 연산자로 동작하게끔 만듭니다.
연산자 오버로딩은 Kotlin의 공식 문서에서도 다뤄지고 있으며, 다양한 연산자(-, *, /, %, == 등)를 커스터마이징할 수 있습니다.
주의: 남용하면 코드가 읽기 어려워질 수 있으니, 적절히 사용하는 것이 중요
'인프런 > 자바 개발자를 위한 코틀린 입' 카테고리의 다른 글
5) 코틀린에서 상속을 다루는 방법 (0) | 2025.01.08 |
---|---|
4) 코틀린에서 클래스를 다루는 방법 (0) | 2025.01.07 |
3) 코틀린에서 조건문,반복문,예외,함수를 다루는 방법 (0) | 2025.01.07 |
1) 코틀린에서 변수,null을 다루는 방법 (0) | 2025.01.06 |
댓글