Language/Java

Comparable과 Comparator

olsohee 2024. 1. 15. 11:27

Comparable과 Comparator

자바의 Comparable과 Comparator는 모두 인터페이스이다. 즉 Comparable 혹은 Comparator을 사용하려면 인터페이스 내에 선언된 메서드를 반드시 구현(오버라이드, 재정의)해야 한다.

Comparable

public interface Comparable<T> {
    
    public int compareTo(T o);
}

Comparator

@FunctionalInterface
public interface Comparator<T> {
    
    int compare(T o1, T o2);

    ...
}
[참고] Comparator 인터페이스에는 compare() 메소드 외에 여러 메소드가 있는데 compare() 메소드만 오버라이딩하면 된다. 나머지 메소드는 default 또는 static 메소드이기 때문이다. 참고로 default 메소드는 반드시 오버라이딩할 필요는 없으나 필요에 따라 오버라이딩이 가능하고, static 메소드는 오버라이딩이 불가능하다.

 

공통점

  • Comparable과 Comparator는 둘 다 "객체의 비교"를 위해 사용된다. 객체는 사용자가 기준을 정해주지 않는 이상 어떤 객체가 더 높은 우선순위를 갖는지 판단할 수 없다. 따라서 우선순위의 기준을 정해주기 위해 Comparable 또는 Comparator가 사용된다.

차이점

  • Comparable은 "자기 자신과 매개변수 객체를 비교"하고, Comparator는 "두 매개변수 객체를 비교"한다는 점이 다르다.
  • Comparable은 자기 자신과 매개변수로 들어오는 객체를 비교하고, Comparator는 자기 자신의 상태가 어떻든 상관없이 매개변수로 들어오는 두 객체를 비교한다. 즉 둘 다 비교한다는 것은 같지만, 비교 대상이 다르다.
  • Comparable의 compareTo는 선행 원소가 자기 자신이 되고, 후행 원소가 매개변수로 들어오는 o가 된다.
  • Comparator의 compare는 선행 원소가 o1이 되고, 후행 원소가 o2가 된다. 즉 o1과 o2를 비교하는데 있어서 자기 자신은 영향을 끼치지 않는다는 것이다. 따라서 만약 a.compare(b, c)와 같이 비교한다면, 이는 b와 c를 비교하는 것일 뿐 a와는 관련이 없다. 만약 a와 b를 비교하고 싶다면 a.compare(a, b)와 같이 적어주면 된다.
class Student implements Comparable<Student> {
 
	int age;		// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
	
	@Override
	public int compareTo(Student o) {
		return this.age - o.age;
	}
}

익명 객체를 통한 Comparator 활용

Comparator의 compare 메서드를 사용하려면 결국에는 객체가 필요하다. 예를 들어, 다음과 같이 어떤 두 객체간 비교를 하려면 어느 한 객체를 통해 compare 메서드를 사용해야 한다는 것이다. 아래 코드에서 a를 통해 compare 메서드를 사용하든, b를 통해 compare 메서드를 사용하든 상관없다.

public class Test {
	public static void main(String[] args)  {
 
		Student a = new Student(17, 2);	// 17살 2반
		Student b = new Student(18, 1);	// 18살 1반
		Student c = new Student(15, 3);	// 15살 3반
			
		int isBig = a.compare(a, b);
        
		int isBig2 = a.compare(b, c);
		
		int isBig3 = b.compare(a, c);
	}
}

 

다음과 같이 비교만을 위해 사용될 객체를 생성해주는 방법도 있다. 하지만 Student 클래스에서 변수로 둔 age와 classNumber는 굳이 0으로 초기화되며 생성된다는 단점이 있다.

public class Test {
	public static void main(String[] args)  {
 
		Student a = new Student(17, 2);	// 17살 2반
		Student b = new Student(18, 1);	// 18살 1반
		Student c = new Student(15, 3);	// 15살 3반
		Student comp = new Student(0, 0); // 비교만을 위해 사용할 객체
			
		int isBig = comp.compare(a, b);
        
		int isBig2 = comp.compare(b, c);
		
		int isBig3 = comp.compare(a, c);
	}
}

 

그렇다면 Comparator의 비교 기능만 따로 두고싶다면 어떻게 해야할까? 바로 익명 객체를 활용하는 것이다.

public class Test {
	public static void main(String[] args) {
    
		// 익명 객체 구현 1 (non static)
		Comparator<Student> comp1 = new Comparator<Student>() {
			@Override
			public int compare(Student o1, Student o2) {
				return o1.classNumber - o2.classNumber;
			}
		};
	}
 
	// 익명 객체 구현 2 (static)
	public static Comparator<Student> comp2 = new Comparator<Student>() {
		@Override
		public int compare(Student o1, Student o2) {
			return o1.classNumber - o2.classNumber;
		}
	};
}
 
 
// 외부에서 익명 객체로 Comparator가 생성되기 때문에 클래스에서 Comparator을 구현 할 필요가 없어진다.
class Student {
 
	int age;			
	int classNumber;	
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
 
}

 

  • 익명 객체는 필요에 따라 정적(static) 타입으로 선언해도 되고, 지역변수처럼 non-static으로 선언해도 된다.
  • 외부에서 Comparator를 구현하는 익명 객체가 생성되었기 때문에, Student 내부에서는 Comparator를 구현해줄 필요가 없다.

익명 객체를 사용하면 좋은 점이 하나 더 있는데, 익명 객체는 이름만 없을 뿐 객체와 마찬가지이기 때문에 여러 개를 생성할 수 있다. 따라서 익명 객체를 가리키는 변수명만 다르게 하여 여러 개 생성하면, 여러 기준으로 비교할 수 있다. 만약 학급 기준으로도 비교하고 나이를 기준으로도 비교하고 싶다면 다음과 같이 익명 객체를 두 개 생성하면 된다. 즉 익명 객체를 통해 여러가지 비교 기준을 정의할 수 있다.

public class Test {
	public static void main(String[] args)  {
 
		Student a = new Student(17, 2);	// 17살 2반
		Student b = new Student(18, 1);	// 18살 1반
		Student c = new Student(15, 3);	// 15살 3반
			
		// 학급을 기준으로 비교
		int classBig = comp.compare(b, c);
        
		// 나이를 기준으로 비교
		int ageBig = comp2.compare(b, c);
	}
	
	// 학급 대소 비교 익명 객체
	public static Comparator<Student> comp = new Comparator<Student>() {
		@Override
		public int compare(Student o1, Student o2) {
			return o1.classNumber - o2.classNumber;
		}
	};
	
	// 나이 대소 비교 익명 객체
	public static Comparator<Student> comp2 = new Comparator<Student>() {
		@Override
		public int compare(Student o1, Student o2) {
			return o1.age - o2.age;
		}
	};
}
 
class Student {
 
	int age;			
	int classNumber;	
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
	
}

 

[참고] Comparable도 익명 객체를 사용할까?
그렇다면 Comparable도 익명 객체를 통해 구현할 수 있을까? 물론 익명 객체 생성은 가능하다. 그런데 Comparable로 익명 객체를 생성하면 익명 객체의 compareTo 메서드는 익명 객체와 매개변수와의 비교이다. 즉 Student와 Student를 비교하는 것이 아니라 익명 객체와 Student를 비교하는 것이기 때문에 우리가 원하는 바가 아니다.

Reference