Scala

Java의 Comparator/Comparable, Scala의 Ordered/Ordering

partner_jun 2017. 3. 15. 15:26

자바에서는 비교 가능한 클래스의 구현을 위해 두 가지 인터페이스를 사용한다.


첫 번째로 자바빈 클래스에 구현해 '다른 객체와 비교' 하는데 사용하는 Comparable다.

public class Fruit implements Comparable<Fruit> {

public String name;
public int price;

public Fruit(String name, int price) {
this.name = name;
this.price = price;
}

@Override
public String toString() {
return this.name + " " + this.price;
}

@Override
public int compareTo(Fruit that) {
return this.price - that.price;
}
}

이렇게 이 객체(this)와 다른 객체(that)에 대한 비교를 자바빈 클래스에 구현해 둠으로써 비교나 정렬이 가능하다.


public static void main(String[] args) {
Fruit banana = new Fruit("Banana", 2000);
Fruit apple = new Fruit("Apple", 2500);
Fruit strawberry = new Fruit("Strawberry", 3000);
List<Fruit> fruits = Arrays.asList(banana, apple, strawberry);

Collections.sort(fruits);
System.out.println(fruits); // [Banana 2000, Apple 2500, Strawberry 3000]
}



두 번째로 '비교해 주는' 클래스/메소드를 구현하기 위한 Comparator 인터페이스다.

public class Fruit {

public String name;
public int price;

// toString 메소드 생략

public Fruit(String name, int price) {
this.name = name;
this.price = price;
}
}

public static class FruitComparator implements Comparator<Fruit> {
@Override
public int compare(Fruit fruit1, Fruit fruit2) {
return fruit1.price - fruit2.price;
}
}

이렇게 클래스에 Comparator를 구현하고 이 클래스의 인스턴스를 사용하거나 람다식, 혹은 익명 클래스(Anonymous Class)등으로 사용할 수 있다.


// Comparator가 구현된 FruitComparator Class의 인스턴스 사용
FruitComparator fruitComparator = new FruitComparator();
Collections.sort(fruits, fruitComparator);
fruits.sort(fruitComparator);

// 람다식 사용
// Comparator.comparingInt(f -> f.price)와 같음.
Collections.sort(fruits, (f1, f2) -> f1.price - f2.price);
fruits.sort((f1, f2) -> f1.price - f2.price);

// 익명 클래스 사용
Collections.sort(fruits, new Comparator<Fruit>() {
@Override
public int compare(Fruit f1, Fruit f2) {
return f1.price - f2.price;
}
});


두 인터페이스의 차이는 Override한 메소드를 보면 쉽게 이해 할 수 있다. 

Comparable 인터페이스는 이 객체(this)다른 객체(that)를 비교하는 compareTo 메소드, 

Comparator 인터페이스는 객체 A객체 B를 비교하는 compare 메소드가 선언되어 있다.





스칼라도 자바와 마찬가지로 클래스간의 비교를 위한 트레잇이 구현되어 있다.

Comparable을 구현한 OrderedComparator를 구현한 Ordering이다.

  * @author Geoffrey Washburn
* @version 0.9.5, 2008-04-15
* @since 2.7
* @see [[scala.math.Ordered]], [[scala.util.Sorting]]
*/
@annotation.implicitNotFound(msg = "No implicit Ordering defined for ${T}.")
trait Ordering[T] extends Comparator[T] with PartialOrdering[T] with Serializable {
 *  @author  Martin Odersky
* @version 1.1, 2006-07-24
*/
trait Ordered[A] extends Any with java.lang.Comparable[A] {


기본적인 사용법은 자바와 같다.


Ordered (Java의 Comparable)

case class Fruit(name: String, price: Int) extends Ordered[Fruit] {
override def compare(that: Fruit): Int = this.price - that.price
}

val banana = Fruit("Banana", 2000)
val apple = Fruit("Apple", 2500)
val strawberry = Fruit("Strawberry", 3000)

val fruits = List(banana, apple, strawberry)
println(fruits.sorted) // List(Fruit(Banana,2000), Fruit(Apple,2500), Fruit(Strawberry,3000))


Ordering (Java의 Comparator)

case class Fruit(name: String, price: Int)

object FruitOrdering extends Ordering[Fruit] {
override def compare(f1: Fruit, f2: Fruit): Int = f1.price - f2.price
}

val banana = Fruit("Banana", 2000)
val apple = Fruit("Apple", 2500)
val strawberry = Fruit("Strawberry", 3000)
val fruits = List(banana, apple, strawberry)

println(fruits.sorted(FruitOrdering)) // List(Fruit(Banana,2000), Fruit(Apple,2500), Fruit(Strawberry,3000))


implicit 키워드로 Ordering 오브젝트를 생략 할 수 있다.

case class Fruit(name: String, price: Int)

implicit object FruitOrdering extends Ordering[Fruit] {
override def compare(f1: Fruit, f2: Fruit): Int = f1.price - f2.price
}

val banana = Fruit("Banana", 2000)
val apple = Fruit("Apple", 2500)
val strawberry = Fruit("Strawberry", 3000)
val fruits = List(banana, apple, strawberry)

println(fruits.sorted) // FruitOrdering을 암시적으로 사용





Comparator와 Comparable의 차이는 아주 미묘하다. 

Comparator가 있으면 Comparable을 구현 할 수 있기 때문이다.

compare(this, that)와 this.compareTo(that)는 같은 결과를 가지는 다른 구현일 뿐이다.


그래서인지 스칼라의 Ordered 오브젝트에 Ordering[T]을 Ordered[T]로 만드는 암시적 메소드가 있다.

orderingToOrdered 메소드다.

object Ordered {
/** Lens from `Ordering[T]` to `Ordered[T]` */
implicit def orderingToOrdered[T](x: T)(implicit ord: Ordering[T]): Ordered[T] =
new Ordered[T] { def compare(that: T): Int = ord.compare(x, that) }
}

이 암시적 메소드 덕분에 타입 T에 대한 Ordering이 구현되어 있으면 타입 T를 Ordered로 변환해 비교하는데 사용 할 수 있다.

(Ordered에서 compareTo와 compare는 같은 메소드다.)


case class Fruit(name: String, price: Int)

implicit object FruitOrdering extends Ordering[Fruit] {
override def compare(f1: Fruit, f2: Fruit): Int = f1.price - f2.price
}

val banana = Fruit("Banana", 2000)
val apple = Fruit("Apple", 2500)
val strawberry = Fruit("Strawberry", 3000)

val orderedBanana: Ordered[Fruit] = banana
println(orderedBanana.compare(apple)) // - 500




마지막으로 compare를 통한 정렬은 compare 메소드 리턴 값이 음수면 오름차순, 양수면 내림차순이다.

Arrays.java의 mergeSort 메소드에서 그 놀라운 이유를 알 수 있다.

// Merge sorted halves (now in src) into dest
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}

conquer단계에서 그냥 그렇게 하게 구현되어 있다.