[JAVA] Comparable compareTo(), Comparator compare()

KwangYong·2022년 3월 7일
0

JAVA

목록 보기
4/5
post-thumbnail

참고 : https://st-lab.tistory.com/243

Comparable, Comparator을 이용하여 java 객체를 정렬한다.
이 둘은 인터페이스로써 사용하고자 한다면 인터페이스 내에 선언된 메소드를 반드시 구현해야한다.

Comparable 인터페이스의 compareTo(T o) 메소드가 선언되어있다. 이 메소드를 재정의(Override)을 해주어야한다.

Comparator 인터페이스에서는 compare(T o1, T o2)를 구현해야한다. 다른 메소드들도 선언되어있지만 Java8부터는 Interface에서도 일반 메소드를 구현할 수 있도록 변경되었다. default나 static으로 선언된 메소드들을 보면 함수를 구현하고 있다. 즉, default나 static으로 선언된 메소드가 아니면 이는 추상 메소드라는 의미로 반드시 재정의를 해주어야 한다. default로 선언된 메소드는 재정의 할 수 있고, static은 재정의 할 수 없다.

두 Interface는 객체를 비교할 수 있도록 만든다.

  • Comparable은 "자기 자신과 매개변수 객체를 비교" -> 파라미터 하나, lang패키지에 있기 때문에 import를 해줄 필요가 없음.
  • Comparator은 "두 매개변수 객체를 비교" -> 파라미터 두 개, util패키지에 있음.

Comparable compareTo()

compareTo 메소드를 보면 int값을 반환하도록 되어있다. 자기 자신을 기준으로 삼아 '값'을 비교해서 대소관계를 파악하고 양수 or 0 or 음수 정수를 반환한다. 따라서 두 비교대상의 값 차이를 반환하므로써 간편하게 처리할 수 있다.

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반
		
		
		int isBig = a.compareTo(b);	// a자기자신과 b객체를 비교한다.
		
		if(isBig > 0) {
			System.out.println("a객체가 b객체보다 큽니다.");
		}
		else if(isBig == 0) {
			System.out.println("두 객체의 크기가 같습니다.");
		}
		else {
			System.out.println("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;
	}
}

하지만 주의해야 할 점이 있다. 두 수의 대소비교를 두 수의 차를 통해 음수, 0, 양수로 구분하여 구했지만, 뺄샘 과정에서 자료형의 범위를 넘어버리는 경우가 발생할 수 있기 때문이다. 쉽게 말하자면 -2,147,483,648 - 1 = -2,147,483,649 일 것이다. 하지만, int 자료형에서 표현할 수 없는 수로 2,147,483,647으로 int형의 최댓값으로 반환한다. 이렇게 주어진 범위의 하한선을 넘어버리는 것을 'Underflow' 라고 한다.

따라서 대소비교에 있어 이러한 Overflow 혹은, Underflow가 발생할 여지가 있는지를 반드시 확인하고 사용해야 한다. 특히 primitive 값에 대해 위와 같은 예외를 만약 확인하기 어렵다면 <, >, == 으로 대소비교를 해주는 것이 안전하며 일반적으로 권장되는 방식이다.

	@Override
	public int compareTo(Student o) {
    
		// 자기자신의 age가 o의 age보다 크다면 양수
		if(this.age > o.age) {
			return 1;
		}
		// 자기 자신의 age와 o의 age가 같다면 0
		else if(this.age == o.age) {
			return 0;
		}
		// 자기 자신의 age가 o의 age보다 작다면 음수
		else {
			return -1;
		}
	}
}

Comparator compare()

Comparator은 자기 자신이 아니라 파라미터(매개변수)로 들어오는 두 객체를 비교하는 것이다.

import java.util.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반
			
		// a객체와는 상관 없이 b와 c객체를 비교한다.
		int isBig = a.compare(b, c);
		
		if(isBig > 0) {
			System.out.println("b객체가 c객체보다 큽니다.");
		}
		else if(isBig == 0) {
			System.out.println("두 객체의 크기가 같습니다.");
		}
		else {
			System.out.println("b객체가 c객체보다 작습니다.");
		}
		
	}
}
 
class Student implements Comparator<Student> {
 
	int age;			// 나이
	int classNumber;	// 학급
	
	Student(int age, int classNumber) {
		this.age = age;
		this.classNumber = classNumber;
	}
	
	@Override
	public int compare(Student o1, Student o2) {
		return o1.classNumber - o2.classNumber;
	}
}

만약에 a.compare 메소드에서 a와 비교하고 싶다면 다음과 같이 해주면 되는 것이다.
a.compare(a, b); 즉, 객체 자체와는 상관 없이 독립적으로 매개변수로 넘겨진 두 객체를 비교하는 것이 포인트다.
이처럼 Comparator를 통해 compare 메소드를 사용려면 결국에는 compare메소드를 활용하기 위한 객체가 필요하게 된다. 그리고 호출하는 객체는 어떤 객체이든 상관없다.
비교만을 위한 객체를 하나 더 생성하게 되면 해당 클래쓰의 쓸모가 없는 변수까지 생성이 되어버린다.

즉, Comparator 비교 기능만 따로 두고 싶고 이는 익명 객체(클래스)를 활용함으로써 가능하다.

익명객체는 '이름이 정의되지 않은 객체'를 의미한다. 지금처럼 특정 구현 부문만 따로 사용한다거나, 부분적으로 기능을 일시적으로 바궈야할 경우가 생길 때 익명객체를 사용한다.

public class Anonymous {
	public static void main(String[] args) {
	
		Rectangle a = new Rectangle();
		
		// 익명 객체 1 
		Rectangle anonymous1 = new Rectangle() {
		
			@Override
			int get() {
				return width;
			}
		};
		
		System.out.println(a.get());
		System.out.println(anonymous1.get());
		System.out.println(anonymous2.get());
	}
	
	// 익명 객체 2
	static Rectangle anonymous2 = new Rectangle() {
		
		int depth = 30;
		@Override
		int get() {
			return width * height * depth;
		}
	};
}
 
class Rectangle {
	
	int width = 10;
	int height = 20;
	
	int get() {	
		return height;
	}
}

객체를 구현한다는 것은 Rectangle 클래스처럼 일반적인 클래스 구현 방식, interface클래스를 implements하여 interface의 메소드를 재정의하거나, class를 상속(extends)하여 부모의 메소드, 필드를 사용 또는 재정의 하는 것들 모두 객체를 수현하는 것이다. 이 때, 구현을 하는 클래스들은 모두 '이름'이 존재한다.

그러나 위의 Rectangle anonymous2 = new Rectangle() {...}에서 변수를 선언하고, Rectangle클래스의 메소드 get을 재정의했다. 즉, Rectangle을 상속받은 하나의 새로운 class라는 것이다.

public class Anonymous {
	public static void main(String[] args) {
 
		Rectangle a = new Rectangle();
		ChildRectangle child = new ChildRectangle();
 
		System.out.println(a.get());		// 20
		System.out.println(child.get());	// 10 * 20 * 40
	}
}
 
class ChildRectangle extends Rectangle {
	
	int depth = 40;
	
	@Override
	int get() {
		return width * height * depth;
	}
}
 
class Rectangle {
 
	int width = 10;
	int height = 20;
 
	int get() {
		return height;
	}
}

위 코드에선 Rectangle 클래스를 상속받아 ChildRectangle이라는 이름으로 정의된 자식 클래스를 이용했다. depth란 필드도 새로 생성했고, get()메소드를 재정의했다.

익명 객체 코드에서도 ChildRectangle클래스를 만든 것과 같지만, 이름이 정의되어있지 않고 anonymous라는 이름으로 객체만 생성되어있다.
이렇게 클래스 이름으로 정의되지 않는 객체를 바로 익명 객체라 한다. 이름이 정의되지 않기 때문에 특정 타입이 존재하는 것이 아니기 때문에 반드시 익명 객체의 경우는 상속할 대상이 있어야 한다.
이때, 상속이라 함은 class의 extends뿐만 아니라 interface의 implements또한 마찬가지다.

public class Anonymous {
	public static void main(String[] args) {
 
		Rectangle a = new Rectangle();
		
		Shape anonymous = new Shape() {
			int depth = 40;
			
			@Override
			public int get() {
				return width * height * depth;
			}
		};
 
		System.out.println(a.get());			// Shape 인터페이스를 구현한 Rectangle 객체
		System.out.println(anonymous.get());	// Shape 인터페이스를 구현한 익명 객체
	}
 
}
 
class Rectangle implements Shape {
	int depth = 40;
	
	@Override
	public int get() {
		return width * height * depth;
	}
}
 
interface Shape {
 
	int width = 10;
	int height = 20;
 
	int get();
}

지금 원하는 것은 Comparator의 compareTo만 사용하고 싶은 것이다. 분명 Comparator라는 interface가 존재한다. 구현(상속)할 대상이 존재한다는 것이고 이는 익명객체로 만들 수 있다는 것이다.

import java.util.Comparator;
 
public class Test {
	public static void main(String[] args) {
    
		// 익명 객체 구현방법 1
		Comparator<Student> comp1 = new Comparator<Student>() {
			@Override
			public int compare(Student o1, Student o2) {
				return o1.classNumber - o2.classNumber;
			}
		};
	}
 
	// 익명 객체 구현 2 (main함수 밖에서 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;
	}
 
}

익명 객체의 경우 필요에 따라 main함수 밖에 정적(static) 타입으로 선언해도 되고, main안에 지역변수처럼 non-static으로 생성해도 된다. 이렇게 외부에서 Comparator을 구현하는 익명객체가 생성되었기 때문에, Student 클래스 내부에서 우린 Comparator을 구현해줄 필요가 없어졌다.
즉, 이 전에 a.compare(b, c) 이런식이 아니라, 위에서 생성한 익명객체를 가리키는 comp 를 통해 comp.compare(b, c) 이런 식으로 해주면 된다는 것이다.

클래스를 상속(구현)할 때, 이름만 다르게 하면 몇개던 생성할 수 있듯이, 익명객체또한 마찬가지다. 익명 객체를 가리키는 변수명만 달리하면 몇개든 생성할 수 있다. 익명객체를 통해 여러가지 비교 기준을 정의할 수 있다.

import java.util.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반
			
		// 학급 기준 익명객체를 통해 b와 c객체를 비교한다.
		int classBig = comp.compare(b, c);
		
		if(classBig > 0) {
			System.out.println("b객체가 c객체보다 큽니다.");
		}
		else if(classBig == 0) {
			System.out.println("두 객체의 크기가 같습니다.");
		}
		else {
			System.out.println("b객체가 c객체보다 작습니다.");
		}
		
		// 나이 기준 익명객체를 통해 b와 c객체를 비교한다.
		int ageBig = comp2.compare(b, c);
		
		if(ageBig > 0) {
			System.out.println("b객체가 c객체보다 큽니다.");
		}
		else if(ageBig == 0) {
			System.out.println("두 객체의 크기가 같습니다.");
		}
		else {
			System.out.println("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;
	}
	
}
profile
바른 자세로 코딩합니다 👦🏻💻

0개의 댓글