Comparable и Comparator

Два новых интерфейса java.lang.Comparable и java.util.Comparator были добавлены в версии Java 5. Использование данных интерфейcов в своих приложениях позволяет упорядочивать (сортировать) данные.

Интерфейс Comparable

В интерфейсе Comparable объявлен только один метод compareTo (Object obj), предназначенный для упорядочивания объектов класса. Данный метод удобно использовать для сортировки списков или массивов объектов.

Метод compareTo (Object obj) сравнивает вызываемый объект с obj. В отличие от метода equals, который возвращает true или false, compareTo возвращает:

  • 0, если значения равны;
  • Отрицательное значение (обычно -1), если вызываемый объект меньше obj;
  • Положительное значение (обычно +1), если вызываемый объект больше obj.

Если типы объектов не совместимы при сравнении, то compareTo (Object obj) может вызвать исключение ClassCastException. Необходимо помнить, что аргумент метода compareTo имеет тип сравниваемого объекта класса.

Обычные классы Byte, Short, Integer, Long, Double, Float, Character, String уже реализуют интерфейс Comparable.

Пример реализации интерфейса Comparable

package test;

import java.util.TreeSet;

class Compare implements Comparable<Object>
{
    String  str;
    int     num;
    String  TEMPLATE = "num = %d, str = '%s'";
    
    Compare(String str, int num)
    {
        this.str = str;
        this.num = num;
    }

    @Override
    public int compareTo(Object obj)
    {
        Compare entry = (Compare) obj;
        int result = str.compareTo(entry.str);
        if(result != 0)
            return result;

        result = num - entry.num;
        if(result != 0)
            return (int) result / Math.abs( result );

        return 0;
    }
    
    @Override
    public String toString()
    {
        return String.format(TEMPLATE, num, str);
    }
}

public class Example 
{
    public static void main(String[] args)
    {
        TreeSet<Compare> data = new TreeSet<Compare>();
        data.add(new Compare("Начальная школа"  , 234));
        data.add(new Compare("Начальная школа"  , 132));         
        data.add(new Compare("Средняя школа"    , 357));
        data.add(new Compare("Высшая школа"     , 246));
        data.add(new Compare("Музыкальная школа", 789));
        for (Compare e : data)
            System.out.println(e.toString());
    }
}

Результат выполнения программы:


num = 246, str = 'Высшая школа'
num = 789, str = 'Музыкальная школа'
num = 132, str = 'Начальная школа'
num = 234, str = 'Начальная школа'
num = 357, str = 'Средняя школа'
 

В примере значения сортируются сначала по полю str (по алфавиту), а затем по num в методе compareTo. Это хорошо видно по двум строкам с одинаковыми значения str и различными num. Чтобы изменить порядок сортировки значения str (в обратном порядке), необходимо внести небольшие изменения в метод compareTo.

@Override
public int compareTo(Object obj)
{
    int result = ((Comp)obj).str.compareTo(str);
    if(result != 0)
        return result;

    result = entry.number - number;
    
    if(result != 0) {
        return (int) result / Math.abs( result );
    return 0;
}

Интерфейс Comparator : compare, compareTo

В интерфейсе Comparator объявлен метод compare (Object obj1, Object obj2), который позволяет сравнивать между собой два объекта. На выходе метод возвращает значение 0, если объекты равны, положительное значение или отрицательное значение, если объекты не тождественны.

Метод может вызвать исключение ClassCastException, если типы объектов не совместимы при сравнении. Простой пример реализации интерфейса Comparator:

package test;

import java.util.TreeSet;
import java.util.Iterator;
import java.util.Comparator;

class Compare implements Comparator<String>
{
    @Override
    public int compare(String obj1, String obj2)
    {
        return obj1.compareTo(obj2);
    }
}

public class Example
{
    public static void main(String[] args)
    {
        TreeSet<String> data = new TreeSet<String>();
        data.add(new String("Змей Горыныч"     ));
        data.add(new String("Баба Яга"         ));
        data.add(new String("Илья Муромец"     ));
        data.add(new String("Алеша Попович"    ));
        data.add(new String("Соловей Разбойник"));
            
        Iterator<String> i = data.iterator();
            
        while(i.hasNext())
            System.out.println(i.next(););
    }
}

Результат выполнения программы:


Алеша Попович
Баба Яга
Змей Горыныч
Илья Муромец
Соловей Разбойник
 

Усложним пример, и реализуем несколько видов сортировки. Для этого создадим класс Product с полями name, price и quantity.

class Product
{
    private String name;
    private float  price;
    private float  quantity;
    
    public Product (String name, float price, float quantity)
    {
        this.name     = name;
        this.price    = price;
        this.quantity = quantity;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public float getPrice() {
        return price;
    }
    public void setPrice(float price) {
        this.price = price;
    }
    public float getQuantity() {
        return quantity;
    }
    public void setQuantity(float quantity) {
        this.quantity = quantity;
    }
    @Override
    public String toString()
    {
        return "Наименование '" + name + "', цена - " + String.valueOf (price) + 
               ", количество - " + String.valueOf (quantity);
    }
}

Создадим два класса (SortedByName, SortedByPrice), реализующих интерфейс Comparator для сортировки объектов по названию и по цене :

// сортировка по названию
class SortedByName implements Comparator<Product>
{
    public int compare(Product obj1, Product obj2)
    {
        String str1 = obj1.getName();
        String str2 = obj2.getName();
            
        return str1.compareTo(str2);
    }
}

// сортировка по цене
class SortedByPrice implements Comparator<Product>
{
    public int compare(Product obj1, Product obj2)
    {
        float price1 = obj1.getPrice();
        float price2 = obj2.getPrice();

        if (price1 > price2) {
            return 1;
        } else if (price1 < price2) {
            return -1;
        } else {
            return 0;
        }
    }
}

Пример использования Arrays.sort :

public class Example
{
    public static void main(String[] args)
    {
        Product[] products = new Product[3];

        // заполним объект Product содержимым
        products[0] = new Product("Молоко", (float) 35.56, (float)900.00);
        products[1] = new Product("Кофе"  , (float)199.50, (float) 90.00);
        products[2] = new Product("Чай"   , (float) 78.50, (float)150.00);

        // выведем данные без сортировки
        System.out.println("~~~~~ без сортировки ~~~~~");
        for(Product product : products)
            System.out.println(product.toString());

        // Сортировка по цене
        Arrays.sort(products, new SortedByPrice());
        System.out.println("\n~~~~~ сортировка по цене ~~~~~");
            
        for(Product product : products)
            System.out.println(product.toString());

        // Сортировка по названию
        Arrays.sort(products, new SortedByName());
        System.out.println("\n~~~~~ сортировка по названию ~~~~~");

        for(Product product : products)
            System.out.println(product.toString());
    }
}

Результат выполнения программы:

~~~~~ без сортировки ~~~~~
Наименование 'Молоко', цена - 35.56, количество - 900.0
Наименование 'Кофе', цена - 199.5, количество - 90.0
Наименование 'Чай', цена - 78.5, количество - 150.0

~~~~~ сортировка по цене ~~~~~
Наименование 'Молоко', цена - 35.56, количество - 900.0
Наименование 'Чай', цена - 78.5, количество - 150.0
Наименование 'Кофе', цена - 199.5, количество - 90.0

~~~~~ сортировка по названию ~~~~~
Наименование 'Кофе', цена - 199.5, количество - 90.0
Наименование 'Молоко', цена - 35.56, количество - 900.0
Наименование 'Чай', цена - 78.5, количество - 150.0
 

Для сортировки объектов были реализованы два независимых компаратора по наименованию и по цене (SortedByName и SortedByPrice). Сортировка выполняется с помощью класса Arrays, у которого есть метод sort. Данный метод в качестве второго аргумента принимает тип компаратора.

Arrays.sort(T[] arg1, Comparator<? super T> arg2);

Можно использовать также метод sort класса Collections, который в качестве первого входного аргумента принимает список объектов:

Collections.sort(List<T> arg1, Comparator<? super T> arg2);

Отличие интерфейсов Comparator и Comparable

Интерфейс Comparable используется только для сравнения объектов класса, в котором данный интерфейс реализован. Т.е. interface Comparable определяет логику сравнения объекта определенного ссылочного типа внутри своей реализации (по правилам разработчика).

Comparator представляет отдельную реализацию и ее можно использовать многократно и с различными классами. Т.е. interface Comparator позволяет создавать объекты, которые будут управлять процессом сравнения (например при сортировках).

  Рейтинг@Mail.ru