Урок 27. Интерфейс Comparable в Java


На этом уроке из серии уроков Java для начинающих я расскажу об интерфейсе Comparable Java.

Для чего используется интерфейс Comparable?

Как мы должны сравнивать и сортировать вещи? Теперь это может показаться странным вопросом, но я хочу, чтобы вы действительно подумали об этом. Допустим, у нас есть набор яблок, возникают следующие вопросы:

Как мы хотим их отсортировать? Хотим ли мы отсортировать их по весу? Если да, то сортируем ли мы их от самых легких к самым тяжелым или от самых тяжелых к самым легким? Когда мы сортируем их, нам нужно многократно сравнивать веса двух яблок, пока все яблоки не окажутся в правильном порядке. Является ли Apple 1 тяжелее, чем Apple 2? Он тяжелее, чем Apple 3? Нам нужно продолжать делать это до тех пор, пока список не будет отсортирован. Comparable интерфейс помогает нам достичь этой цели. Comparable не может сортировать объекты самостоятельно, но интерфейс определяет метод int compareTo(T).



Как работает CompareTo()

Давайте начнем с использования метода compareTo(), чтобы увидеть, какие яблоки тяжелее.

Метод compareTo() работает, возвращая значение int, которое является положительным, отрицательным или нулевым. Он сравнивает объект, вызывая объект, который является аргументом. Отрицательное число означает, что объект, выполняющий вызов, “меньше”, чем аргумент. Если бы мы сравнивали яблоки по размеру, приведенный выше вызов вернул бы отрицательное число, скажем, -400, потому что красное яблоко меньше зеленого яблока. Если бы два яблока имели одинаковый вес, вызов вернул бы 0. Если бы красное яблоко было тяжелее, compareTo() вернул бы положительное число, скажем, 68.

Гибкость compareTo()

Если бы мы неоднократно вызывали метод compareTo(), описанный выше, мы могли бы сортировать наши яблоки по размеру, что здорово, но это еще не конец истории. Что, если мы хотим отсортировать яблоки по цвету? Или вес? Мы тоже могли бы это сделать. Ключ в том, что наш клиент, назовем его Fatty Farmer, должен точно определить, как нужно сортировать яблоки, прежде чем мы сможем начать разработку.

Он может сделать это, ответив на эти два вопроса:

  • Как он хочет, чтобы яблоки были отсортированы? Какую характеристику он хотел бы, чтобы мы сравнили?
  • Что означают "меньше", "равно" и "больше" в этом контексте?
Также возможно использовать несколько характеристик, как мы увидим немного позже.

Пример 1: Сортировка яблок по весу

Для нашего первого примера мы собираемся отсортировать наши яблоки по весу. Для этого требуется всего одна строка кода.

Collections.sort(apples);
Приведенная выше строка кода может выполнить всю сортировку за нас, если мы заранее определили, как сортировать яблоки (вот где нам понадобится больше одной строки).

Давайте начнем с написания класса apple.

public class Apple implements Comparable {
	private String variety;
	private Color color;
	private int weight;
	@Override
	public int compareTo(Apple other) {
		if (this.weight < other.weight) {
			return -1;
		}
		if (this.weight == other.weight) {
			return 0;
		}
		return 1;
	}
}
Это наша первая версия class Apple. Поскольку мы используем метод compareTo и сортируем яблоки, я реализовал интерфейс. В этой первой версии мы сравниваем объекты по их весу. В нашем методе compareTo() мы пишем условие if, в котором говорится, что если вес яблока меньше, чем у другого яблока, верните отрицательное число, чтобы упростить его, мы скажем -1. Помните, это означает, что это яблоко легче, чем яблоко ‘другое’. В нашем втором операторе if мы говорим, что если яблоки имеют одинаковый вес, возвращаем 0. Теперь, если это яблоко не легче, и оно не того же веса, тогда оно должно быть больше, чем другое яблоко. В этом случае мы возвращаем положительное число, скажем, 1.

Пример 2: Сортировка яблок по нескольким характеристикам

Как я упоминал ранее, мы также можем использовать compareTo() для сравнения нескольких характеристик. Допустим, мы хотим сначала отсортировать яблоки по сорту, но если два яблока одного сорта, мы должны отсортировать их по цвету. Наконец, если обе эти характеристики совпадают, мы будем сортировать по весу. Хотя мы могли бы сделать это вручную, полностью, как я сделал в последнем примере, на самом деле мы можем сделать это гораздо более чистым способом. Как правило, лучше повторно использовать существующий код, чем писать свой собственный. Мы можем использовать методы compareTo в классах Integer, String и enum для сравнения наших значений. Поскольку мы не используем целочисленные объекты, а используем целые числа, мы должны использовать статический вспомогательный метод из класса Integer wrapper для сравнения двух значений.

public class Apple implements Comparable {
	private String variety;
	private Color color;
	private int weight;
	@Override
	public int compareTo(Apple other) {
		int result = this.variety.compareTo(other.variety);
		if (result != 0) {
			return result;
		}
		if (result == 0) {
			result = this.color.compareTo(other.color);
		}
		if (result != 0) {
			return result;
		}
		if (result == 0) {
			result = Integer.compare(this.weight, other.weight);
		}
		return result;
	}
}
В коде выше мы сравниваем первое качество яблок, которым наш клиент отдал предпочтение, их сорт. Если результат этого вызова compare To() отличен от нуля, мы возвращаем значение. В противном случае мы делаем еще один вызов, пока не получим ненулевое значение, или мы сравнили все три характеристики. Хотя этот код работает, это не самое эффективное или чистое решение. В примере ниже мы реорганизуем наш код, чтобы сделать его еще проще.

@Override
public int compareTo(Apple other) {
     int result = this.variety.compareTo(other.variety);
     if (result == 0) {
          result = this.color.compareTo(other.color);
     }
     if (result == 0) {
          result = Integer.compare(this.weight, other.weight);
     }
     return result;
}
Как вы можете видеть, это значительно сокращает наш код и позволяет нам проводить каждое сравнение только в одной строке. Если результат вызова compareTo() равен нулю, мы просто переходим к следующему “раунду” сравнений в рамках того же оператора if. Это, кстати, хороший пример того. Обычно вы не сразу пишете чистый код; вы начинаете с приблизительной идеи, заставляете ее работать, а затем постоянно улучшаете ее, пока не сделаете настолько чистой, насколько сможете.

Comparable, HashCode и Equals

Вы можете заметить, что compareTo() немного похож на методы hashCode() и equals(). Однако есть одно важное отличие. Для hashCode() и equals() порядок, в котором вы сравниваете отдельные атрибуты, не влияет на возвращаемое значение, однако в compareTo() порядок объектов определяется порядком, в котором вы сравниваете объекты.

Вывод

В заключение я просто хочу подчеркнуть, насколько важен интерфейс Comparable. Он используется как в java.util, так и в java.util.Arrays и java.util.Collections для сортировки элементов и поиска элементов в отсортированных коллекциях. С такими коллекциями, как TreeSet и TreeMap, это еще проще – они автоматически сортируют свои элементы, которые должны реализовывать Comparable интерфейс.
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегистатьи IT, уроки по java, java, интерфейс




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.




Урок 1. Концепции объектно-ориентированного программирования C#
Урок 29. Вызов функции JavaScript
Начало работы с PhantomJS