/* 본 게시물은 참고자료의 내용을 토대로 작성되었습니다. */
/* 본 글은 개인적으로 공부한 내용을 정리한 글이므로 오류가 있을 수 있습니다. */
참고 자료
[쾌락 코딩] 코틀린 동등성 연산 : https://wooooooak.github.io/kotlin/2019/02/24/kotiln_%EB%8F%99%EB%93%B1%EC%84%B1%EC%97%B0%EC%82%B0/
[NATION OF 6KIKI] ==, compareTo(), equals() 서로간의 차이점에 대해 알아보자 : https://6kkki.tistory.com/9
[김성일] Comparable / Comparator 인터페이스 차이점 : https://dev-daddy.tistory.com/23
객체에서 숫자, 문자열 비교를 하려면 어떻게 해야할까? 객체를 비교하기 위해서는 동등성의 개념과 Comparable 인터페이스의 이해가 필요하다. 우선, 코틀린의 동등성 연산에 대해 알아보자.
코틀린 동등성 연산(== / ===)
코틀린에서의 == 는 자바와는 조금 다른 부분이 있다. 원시 타입 두개를 비교할 때는 == 연산자는 동일(값을 비교)하지만, 참조 타입을 비교할 때는 다르게 동작한다. 자바와 코틀린 코드의 예시를 보면서 이해해보자.
자바
String a = "hello world!" // 주소값 : 1
String b = "hello world!" // 주소값 : 2
System.out.println(a==b) // false
System.out.println(a.equals(b)) // true
코틀린
val a: String = "hello world!"
val b: String = "hello world!"
println(a == b) // true
println(a === b) // true
코틀린에서 == 는 내부적으로 equals 를 호출한다. 따라서 참조 타입인 두개의 String을 == 연산으로 비교하면 주소값이 아닌 값(동등성)비교를 한다.
그렇다면 주소값을 비교하고 싶을 때는 어떻게 해야할까?
=== 연산자를 이용하면 된다. 참조 비교를 할 때는 === 연산자를 사용한다. 즉, 자바의 주소 값 비교인 == 와 코틀린의 === 가 동일한 역할을 한다.
여기서 잠깐!
그러면 String 객체 a,b 에 대하여 a === b 는 false 가 되어야하지 않나요? 라고 생각할 수 있다. 하지만 Stirng 객체는 String pool 이라는 힙의 영역이 따로 존재하여 값을 String pool에 저장한다. 만약 이미 String pool 에 같은 값이 존재한다면 같은 주소를 참조하는 방식이다. 그래서 a === b 값이 true를 반환하게 된다. 즉, String class 에는 equals 를 객체의 equals 를 override(재정의) 한 것이다. String 자료형에 대한 자세한 내용은 이 글을 확인하자.
class A
val c = A()
val d = A()
println(c == d) // false
println(c === d) // false
* 객체에서 equals 는 주소 비교 와 같다. 객체의 값을 비교하려면 equals 를 override 해야 한다.
다음 코드를 이해하면 코틀린의 동등성은 완벽히 이해했다고 보면 된다. (코드 출처)
class UserA(var id: String)
var user1 = UserA("0")
var user2 = UserA("0")
if (user1 == user2) >> false
if (user1 === user2) >> false
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()
}
}
var user3 = UserB("1")
var user4 = UserB("1")
if (user3 == user4) >> true
if (user3 === user4) >> false
*equals를 override 하기 위해서는 hashCode 도 override 해야됨.
Comparable 인터페이스
객체에서 원하는 자료형으로 비교하기 위해, Comparable 인터페이스를 사용할 수 있다. Comparable 인터페이스는 같은 자료형의 다른 객체 하나를 인자로 받아와 비교하는 compareTo 함수를 사용한다. a.comparable(b) 는 a가 b보다 작을 때는 0보다 작은 수, a와 b가 같으면 0, a가 b보다 크면 0보다 큰 수를 반환한다. Comparable 인터페이스는 다음과 같이 구현되어 있다.
public interface Comparable<in T> {
public operator fun compareTo(other: T): Int
}
Comparable을 상속받는 클래스를 한 번 만들어보자.
class User(var id: Int, var name: String) : Comparable<User> {
// id로 비교
override fun compareTo(other: User): Int =
when {
this.id > other.id -> 1
this.id < other.id -> -1
else -> 0
}
}
fun main() {
val user1 = User(1, "민수")
val user2 = User(2, "영희")
println(user1 > user2) // false
println(user1 < user2) // true
println(user1 == user2) // false
println(user1.compareTo(user2)) // -1
}
Comparable 를 상속받아서 compareTo 를 override 한 경우 객체간의 >, < 비교를 지원한다.
String 자료형에서 재정의되있는 compareTo 는 주소값에 상관없이 값만을 비교하며 같은 때는 0, 다를 땐 같은 인덱스에서 다른 문자가 처음 나온 문자들의 차이의 값(앞파벳 순으로 기준 값이 큰 경우 양수, 작으면 음수)을 return한다.
그 외에도 Boolean, Byte, Int, Duration 등 Comparable 을 상속받기 때문에 연산자를 통해 비교가 가능하다. (관련 링크)
Comparator 인터페이스
Comparator 는 Comparable 와 마찬가지로 비교(정렬) 규칙을 설정하기 위해 사용하는 목적은 동일하다. 둘의 차이점은 기본적으로 인터페이스가 영향을 미치는 범위와 그에 따른 사용목적이 다르다는 점이다. Comparable 의 예시 코드를 확인하면 Comparable 인터페이스는 객체 스스로에게 상속하여 한 가지 기본 정렬 규칙을 설정한다. 위 예시처럼 id 로 비교하는 방법 말고 이름으로 비교하는 방법을 추가할 수는 없을까? 이 때 Comparator를 사용하면 된다. Comparator는 정렬 규칙을 정한 클래스로, 기본 정렬 규칙과 다르게 정렬 순서를 지정하고 싶을 때 사용한다. 코드를 통해 알아보자.
object SortByName : Comparator<User>{
override fun compare(user1: User, user2: User): Int {
// 오름차순
return user1.name.compareTo(user2.name)
// 내림차순
//return user2.name.compareTo(user1.name)
}
}
fun main(){
val user1 = User(1, "영희")
val user2 = User(2, "민수")
val user3 = User(3, "가가")
arrayListOf<User>(user1, user2, user3).apply{
println("sort by age")
this.sort()
this.forEach{
println("${it.id} ${it.name}")
}
}
arrayListOf<User>(user1, user2, user3).apply {
println("sort by name")
this.sortWith(SortByName)
this.forEach {
println("${it.id} ${it.name}")
}
}
}
*sortWith 의 파라미터로 Comparator 객체를 넘겨줘야한다.
*compare() 는 compareTo() 와 다르게 파라미터로 기준 객체와 비교 객체가 필요하다.
첫 번째는 arrayList 에 정의되어 있는 Comparable 를 이용해 정렬되었다. 두 번째는 Comparator를 이용해 오름차순의 이름으로 정렬되었다.
정리
Kotlin 에서 equals, == 는 내부적으로 동일하며 주소값에 상관없이 값만을 가지고 비교하며 같을 땐 true, 다를 땐 false를 return 한다. (*단, Object.equals() 는 주소 값 비교)
compareTo는 주소값에 상관없이 값만을 가지고 비교하며 같을 땐 0을, 다를 땐 같은 인덱스에서 다른 문자가 처음 나온 문자들의 차이의 값을 return 한다.
Comparable 는 이를 상속받아 구현한 객체 스스로에게 부여하는 한 가지 기본 정렬 규칙을 설정하는 목적으로 사용한다.
Comparator 는 상속받은 클래스는 정렬 규칙 그 자체를 의미하며, 기본 정렬 규칙과 다른 정렬순서를 지정하고 싶을 때 사용한다.