← Kursa Dön
📄 Text · 10 min

Comparator Interface

Nesneleri sıralamak programlamanın en temel ihtiyaçlarından biri. Sayıları küçükten büyüğe, isimleri alfabetik, tarihleri eskiden yeniye, ürünleri fiyata göre... Java'da bu sıralamanın iki yolu var: Comparable ve Comparator.

Analoji: Bir yarışma düzenliyorsun. Comparable, yarışmacının kendi üzerinde taşıdığı numara — "doğal sıram bu." Comparator ise jüri — dışarıdan bakıp "şu kritere göre sıralayalım" diyen kişi. Yarışmacı değişmez ama jüri kriterleri her seferinde farklı olabilir.

Comparable: Doğal Sıralama

Comparable<T> interface'i, sınıfın kendi doğal sırasını tanımlar. Tek bir method'u var: compareTo().

public class Student implements Comparable<Student> {
    private String name;
    private double gpa;
    
    public Student(String name, double gpa) {
        this.name = name;
        this.gpa = gpa;
    }
    
    @Override
    public int compareTo(Student other) {
        // Negatif: this < other
        // Sıfır: this == other
        // Pozitif: this > other
        return this.name.compareTo(other.name); // İsme göre doğal sıra
    }
    
    @Override
    public String toString() {
        return name + "(" + gpa + ")";
    }
}
List<Student> students = new ArrayList<>(List.of(
    new Student("Zeynep", 3.8),
    new Student("Ali", 3.5),
    new Student("Mehmet", 3.9)
));

Collections.sort(students); // compareTo() kullanılır
System.out.println(students); // [Ali(3.5), Mehmet(3.9), Zeynep(3.8)]

Java'daki birçok sınıf zaten Comparable'dır:

  • String → alfabetik

  • Integer, Double → sayısal

  • LocalDate, LocalDateTime → kronolojik

  • BigDecimal → sayısal

// String'ler zaten Comparable
List<String> names = new ArrayList<>(List.of("Zeynep", "Ali", "Can"));
Collections.sort(names); // [Ali, Can, Zeynep]

// Integer'lar da
List<Integer> nums = new ArrayList<>(List.of(5, 2, 8, 1));
Collections.sort(nums); // [1, 2, 5, 8]

compareTo() Kuralları

// compareTo kontratı:
// 1. sgn(x.compareTo(y)) == -sgn(y.compareTo(x))  (simetri)
// 2. x.compareTo(y) > 0 && y.compareTo(z) > 0  →  x.compareTo(z) > 0  (geçişlilik)
// 3. x.compareTo(y) == 0  →  sgn(x.compareTo(z)) == sgn(y.compareTo(z))
// 4. İdeal: (x.compareTo(y) == 0) == x.equals(y)  (tutarlılık)

⚠️ Dikkat: compareTo() ile çıkarma yapmak (return this.age - other.age) integer overflow'a neden olabilir. Integer.compare() kullan.

// HATALI — overflow riski
public int compareTo(Student other) {
    return this.age - other.age; // Integer.MIN_VALUE - 1 = ???
}

// DOĞRU
public int compareTo(Student other) {
    return Integer.compare(this.age, other.age);
}

Comparator: Dışarıdan Sıralama

Comparator<T> interface'i, sıralama mantığını sınıfın dışında tanımlar. Aynı koleksiyonu farklı kriterlere göre sıralayabilirsin.

List<Student> students = getStudents();

// İsme göre sırala
students.sort(new Comparator<Student>() {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.getName().compareTo(s2.getName());
    }
});

// Lambda ile daha temiz (Java 8+)
students.sort((s1, s2) -> s1.getName().compareTo(s2.getName()));

// Method reference ile daha da temiz
students.sort(Comparator.comparing(Student::getName));

Comparator.comparing() — Modern Yaklaşım

Java 8 ile gelen Comparator.comparing() factory method'u hayat kurtarıcı:

List<Student> students = getStudents();

// İsme göre
students.sort(Comparator.comparing(Student::getName));

// GPA'ya göre
students.sort(Comparator.comparingDouble(Student::getGpa));

// Yaşa göre
students.sort(Comparator.comparingInt(Student::getAge));

// Ters sıra
students.sort(Comparator.comparing(Student::getName).reversed());

Primitive tipler için özel method'lar var:

  • Comparator.comparingInt() → int field için

  • Comparator.comparingLong() → long field için

  • Comparator.comparingDouble() → double field için

Bunlar autoboxing'den kaçınır, daha performanslı.

Chained Comparators — Çoklu Kriter

Birden fazla kritere göre sıralama yapmak çok kolay:

// Önce GPA'ya göre (azalan), eşitse isme göre (artan)
students.sort(
    Comparator.comparingDouble(Student::getGpa).reversed()
              .thenComparing(Student::getName)
);

// Önce departman, sonra yaş, sonra isim
employees.sort(
    Comparator.comparing(Employee::getDepartment)
              .thenComparingInt(Employee::getAge)
              .thenComparing(Employee::getName)
);

thenComparing() zinciri: önceki kriter eşitlik verdiğinde bir sonraki kritere geçer. SQL'deki ORDER BY dept, age, name gibi.

// Örnek: Ürünleri önce kategoriye, sonra fiyata, sonra isme göre sırala
List<Product> products = getProducts();
products.sort(
    Comparator.comparing(Product::getCategory)
              .thenComparingDouble(Product::getPrice)
              .thenComparing(Product::getName)
);

null Handling

null değerler sıralama sırasında NullPointerException verebilir. Comparator.nullsFirst() ve nullsLast() bunu çözer:

List<String> names = new ArrayList<>(Arrays.asList("Ali", null, "Zeynep", null, "Can"));

// null'ları başa koy
names.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
// [null, null, Ali, Can, Zeynep]

// null'ları sona koy
names.sort(Comparator.nullsLast(Comparator.naturalOrder()));
// [Ali, Can, Zeynep, null, null]

// Nesne field'ı null olabilir
students.sort(
    Comparator.comparing(
        Student::getDepartment,
        Comparator.nullsLast(Comparator.naturalOrder())
    )
);

Comparable vs Comparator

ÖzellikComparableComparator
Interfacejava.lang.Comparablejava.util.Comparator
MethodcompareTo(T o)compare(T o1, T o2)
Tanım yeriSınıfın içindeSınıfın dışında
Sıralama sayısıTek (doğal sıra)İstediğin kadar
Sınıfı değiştirmekGerekirGerekmez
KullanımCollections.sort(list)Collections.sort(list, comp)
// Comparable — sınıfın doğal sırası
public class Temperature implements Comparable<Temperature> {
    private double celsius;
    
    @Override
    public int compareTo(Temperature other) {
        return Double.compare(this.celsius, other.celsius);
    }
}

// Comparator — farklı sıralama kriterleri
Comparator<Temperature> byCelsius = Comparator.comparingDouble(Temperature::getCelsius);
Comparator<Temperature> byFahrenheit = Comparator.comparingDouble(Temperature::getFahrenheit);

💡 İpucu: Sınıfın tek bir "doğal" sırası varsa (sayılar → büyüklük, tarihler → kronoloji) Comparable implement et. Birden fazla sıralama kriteri gerekiyorsa Comparator kullan. İkisi birlikte de olabilir.

Comparator ve Collections/Arrays

// Collections.sort
Collections.sort(list, Comparator.reverseOrder());

// List.sort (Java 8+)
list.sort(Comparator.comparing(Student::getName));

// Stream.sorted
list.stream()
    .sorted(Comparator.comparingDouble(Student::getGpa).reversed())
    .collect(Collectors.toList());

// Arrays.sort
String[] arr = {"Zeynep", "Ali", "Can"};
Arrays.sort(arr, Comparator.reverseOrder());

// TreeSet / TreeMap ile
Set<Student> sorted = new TreeSet<>(Comparator.comparing(Student::getName));
Map<Student, String> map = new TreeMap<>(Comparator.comparingDouble(Student::getGpa));

Gerçek Dünya: E-ticaret Ürün Sıralaması

public class ProductSorter {
    
    public static final Comparator<Product> BY_PRICE_ASC = 
        Comparator.comparingDouble(Product::getPrice);
    
    public static final Comparator<Product> BY_PRICE_DESC = 
        BY_PRICE_ASC.reversed();
    
    public static final Comparator<Product> BY_RATING = 
        Comparator.comparingDouble(Product::getRating).reversed();
    
    public static final Comparator<Product> BY_NAME = 
        Comparator.comparing(Product::getName, String.CASE_INSENSITIVE_ORDER);
    
    public static final Comparator<Product> BY_RELEVANCE = 
        BY_RATING.thenComparing(BY_PRICE_ASC);
    
    public static List<Product> sort(List<Product> products, String criteria) {
        Comparator<Product> comparator = switch (criteria) {
            case "price_asc"  -> BY_PRICE_ASC;
            case "price_desc" -> BY_PRICE_DESC;
            case "rating"     -> BY_RATING;
            case "name"       -> BY_NAME;
            default           -> BY_RELEVANCE;
        };
        
        return products.stream()
            .sorted(comparator)
            .collect(Collectors.toList());
    }
}

Özet

  • Comparable sınıfın doğal sırasını tanımlar — compareTo() method'u, sınıfın içinde implement edilir

  • Comparator dışarıdan sıralama mantığı verir — aynı koleksiyonu farklı kriterlere göre sıralayabilirsin

  • Comparator.comparing() factory method'u ile lambda yazmadan temiz comparator oluştur

  • Chained comparators: thenComparing() ile çoklu kriter — "önce X'e göre, eşitse Y'ye göre"

  • null handling: Comparator.nullsFirst() / nullsLast() ile NullPointerException'dan kaçın

  • Integer karşılaştırmada çıkarma yapma (a - b) — Integer.compare(a, b) kullan (overflow riski)