Navigating the intricacies of Java comparators can be tricky, especially when it comes to using instance variables. At COMPARE.EDU.VN, we aim to clarify whether you can leverage instance variables within a Java comparator, offering a comprehensive guide to help you make informed decisions. Discover the power and flexibility of Java comparators with compare.edu.vn. Learn about comparator implementations, custom comparison logic, and stateful comparators.
1. What Is A Comparator In Java?
A comparator in Java is an interface ( java.util.Comparator
) that defines a method for comparing two objects. It’s used to impose an ordering on collections of objects that do not have a natural ordering (i.e., they don’t implement Comparable
) or when you want to define a different ordering than the natural one. Understanding comparators is crucial for sorting and organizing data efficiently.
1.1. Purpose Of Comparators
Comparators serve the purpose of defining a specific order between two objects. This is particularly useful when dealing with collections like lists and sets that need to be sorted according to custom criteria. Comparators offer flexibility beyond the natural ordering provided by the Comparable
interface.
1.2. Comparator Interface
The Comparator
interface consists primarily of the compare(T o1, T o2)
method, which returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second. This method is the core of the comparison logic.
import java.util.Comparator;
public class MyObjectComparator implements Comparator<MyObject> {
@Override
public int compare(MyObject o1, MyObject o2) {
// Comparison logic here
return 0;
}
}
1.3. Natural Ordering Vs. Custom Ordering
Natural ordering is the inherent order of objects that implement the Comparable
interface. Custom ordering, on the other hand, is defined externally using a Comparator
. For instance, String
objects have a natural lexicographical order, but you might want to sort them case-insensitively, requiring a custom comparator.
2. Can Comparators Have Instance Variables?
Yes, comparators can have instance variables. This allows you to create stateful comparators that maintain and use internal state during the comparison process. The ability to include instance variables opens up possibilities for more complex and context-dependent comparison logic.
2.1. Stateful Comparators
Stateful comparators are comparators that hold state, typically through instance variables. This state can influence the comparison logic, making the comparator behave differently based on its internal conditions.
2.2. Advantages Of Using Instance Variables
- Contextual Comparison: Instance variables allow the comparator to consider external context or configuration during comparisons.
- Complex Logic: They can enable more intricate comparison algorithms that depend on historical or environmental data.
- Reusability: A stateful comparator can be configured once and then reused multiple times with the same configuration.
2.3. Disadvantages And Considerations
- Thread Safety: Stateful comparators are not inherently thread-safe. If multiple threads access and modify the comparator’s state concurrently, it can lead to race conditions and unpredictable behavior.
- Memory Management: Instance variables consume memory. Overuse or improper management can lead to memory leaks or increased memory footprint.
- Complexity: Introducing state can complicate the comparator’s logic, making it harder to understand and maintain.
3. How To Implement A Comparator With Instance Variables
To implement a comparator with instance variables, you declare the variables within the comparator class and use them within the compare
method. Here’s a step-by-step guide with examples.
3.1. Declaring Instance Variables
First, declare the instance variables that your comparator will use. These variables should represent the state or configuration that the comparator needs.
import java.util.Comparator;
public class MyObjectComparator implements Comparator<MyObject> {
private int threshold;
public MyObjectComparator(int threshold) {
this.threshold = threshold;
}
@Override
public int compare(MyObject o1, MyObject o2) {
// Comparison logic using the threshold
return 0;
}
}
3.2. Constructor Initialization
Initialize the instance variables in the constructor of your comparator class. This allows you to configure the comparator when it is created.
import java.util.Comparator;
public class MyObjectComparator implements Comparator<MyObject> {
private int threshold;
public MyObjectComparator(int threshold) {
this.threshold = threshold;
}
@Override
public int compare(MyObject o1, MyObject o2) {
// Comparison logic using the threshold
return 0;
}
}
3.3. Using Instance Variables In Compare Method
Utilize the instance variables within the compare
method to implement your comparison logic. The state held by these variables will influence how objects are compared.
import java.util.Comparator;
public class MyObjectComparator implements Comparator<MyObject> {
private int threshold;
public MyObjectComparator(int threshold) {
this.threshold = threshold;
}
@Override
public int compare(MyObject o1, MyObject o2) {
int diff1 = Math.abs(o1.getValue() - threshold);
int diff2 = Math.abs(o2.getValue() - threshold);
return Integer.compare(diff1, diff2);
}
}
3.4. Example: Sorting By Distance From A Threshold
Consider a scenario where you want to sort objects based on their distance from a specified threshold. Here’s how you can implement it:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class MyObject {
private int value;
public MyObject(int value) {
this.value = value;
}
public int getValue() {
return value;
}
@Override
public String toString() {
return "MyObject{" +
"value=" + value +
'}';
}
}
public class MyObjectComparator implements Comparator<MyObject> {
private int threshold;
public MyObjectComparator(int threshold) {
this.threshold = threshold;
}
@Override
public int compare(MyObject o1, MyObject o2) {
int diff1 = Math.abs(o1.getValue() - threshold);
int diff2 = Math.abs(o2.getValue() - threshold);
return Integer.compare(diff1, diff2);
}
public static void main(String[] args) {
List<MyObject> objects = new ArrayList<>();
objects.add(new MyObject(10));
objects.add(new MyObject(5));
objects.add(new MyObject(15));
int threshold = 8;
MyObjectComparator comparator = new MyObjectComparator(threshold);
Collections.sort(objects, comparator);
System.out.println(objects);
}
}
This example sorts a list of MyObject
instances based on how close their value
is to the threshold
.
4. Use Cases For Comparators With Instance Variables
Comparators with instance variables are useful in various scenarios where the comparison logic needs to be dynamic or context-aware. Let’s explore some common use cases.
4.1. Dynamic Sorting Criteria
When the sorting criteria need to change at runtime, instance variables can hold the current sorting preference. This allows you to switch between different sorting modes without creating new comparator instances.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Product {
private String name;
private double price;
private int quantity;
public Product(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public int getQuantity() {
return quantity;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + ''' +
", price=" + price +
", quantity=" + quantity +
'}';
}
}
enum SortBy {
NAME,
PRICE,
QUANTITY
}
class ProductComparator implements Comparator<Product> {
private SortBy sortBy;
public ProductComparator(SortBy sortBy) {
this.sortBy = sortBy;
}
public void setSortBy(SortBy sortBy) {
this.sortBy = sortBy;
}
@Override
public int compare(Product p1, Product p2) {
switch (sortBy) {
case NAME:
return p1.getName().compareTo(p2.getName());
case PRICE:
return Double.compare(p1.getPrice(), p2.getPrice());
case QUANTITY:
return Integer.compare(p1.getQuantity(), p2.getQuantity());
default:
return 0;
}
}
public static void main(String[] args) {
List<Product> products = new ArrayList<>();
products.add(new Product("Laptop", 1200.0, 10));
products.add(new Product("Keyboard", 75.0, 50));
products.add(new Product("Mouse", 25.0, 100));
ProductComparator comparator = new ProductComparator(SortBy.NAME);
Collections.sort(products, comparator);
System.out.println("Sorted by Name: " + products);
comparator.setSortBy(SortBy.PRICE);
Collections.sort(products, comparator);
System.out.println("Sorted by Price: " + products);
comparator.setSortBy(SortBy.QUANTITY);
Collections.sort(products, comparator);
System.out.println("Sorted by Quantity: " + products);
}
}
This example shows how a ProductComparator
can dynamically sort a list of Product
objects based on different criteria (name, price, quantity) using an instance variable sortBy
.
4.2. Filtering Based On External State
Comparators can use instance variables to filter elements based on an external state. For example, you might want to compare objects differently depending on whether a certain feature is enabled or not.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Task {
private String description;
private int priority;
public Task(String description, int priority) {
this.description = description;
this.priority = priority;
}
public String getDescription() {
return description;
}
public int getPriority() {
return priority;
}
@Override
public String toString() {
return "Task{" +
"description='" + description + ''' +
", priority=" + priority +
'}';
}
}
class TaskComparator implements Comparator<Task> {
private boolean prioritizeHigh;
public TaskComparator(boolean prioritizeHigh) {
this.prioritizeHigh = prioritizeHigh;
}
@Override
public int compare(Task t1, Task t2) {
if (prioritizeHigh) {
return Integer.compare(t2.getPriority(), t1.getPriority()); // Higher priority first
} else {
return t1.getDescription().compareTo(t2.getDescription()); // Sort by description
}
}
public static void main(String[] args) {
List<Task> tasks = new ArrayList<>();
tasks.add(new Task("Implement feature A", 2));
tasks.add(new Task("Fix bug B", 1));
tasks.add(new Task("Refactor module C", 3));
TaskComparator comparatorHigh = new TaskComparator(true);
Collections.sort(tasks, comparatorHigh);
System.out.println("Prioritize High: " + tasks);
TaskComparator comparatorName = new TaskComparator(false);
Collections.sort(tasks, comparatorName);
System.out.println("Sort by Name: " + tasks);
}
}
In this example, the TaskComparator
uses the prioritizeHigh
instance variable to determine whether to sort tasks by priority or by description.
4.3. Context-Specific Comparisons
In some cases, the comparison logic depends on external factors such as user preferences, system settings, or environmental conditions. Instance variables can store these factors and adapt the comparison accordingly.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Event {
private String name;
private long timestamp;
public Event(String name, long timestamp) {
this.name = name;
this.timestamp = timestamp;
}
public String getName() {
return name;
}
public long getTimestamp() {
return timestamp;
}
@Override
public String toString() {
return "Event{" +
"name='" + name + ''' +
", timestamp=" + timestamp +
'}';
}
}
class EventComparator implements Comparator<Event> {
private long currentTime;
public EventComparator(long currentTime) {
this.currentTime = currentTime;
}
@Override
public int compare(Event e1, Event e2) {
long diff1 = Math.abs(e1.getTimestamp() - currentTime);
long diff2 = Math.abs(e2.getTimestamp() - currentTime);
return Long.compare(diff1, diff2);
}
public static void main(String[] args) {
List<Event> events = new ArrayList<>();
events.add(new Event("Meeting", System.currentTimeMillis() - 3600000)); // 1 hour ago
events.add(new Event("Presentation", System.currentTimeMillis() + 7200000)); // 2 hours from now
events.add(new Event("Lunch", System.currentTimeMillis())); // Now
long currentTime = System.currentTimeMillis();
EventComparator comparator = new EventComparator(currentTime);
Collections.sort(events, comparator);
System.out.println("Sorted by proximity to current time: " + events);
}
}
Here, the EventComparator
sorts events based on their proximity to the current time, stored in the currentTime
instance variable.
5. Thread Safety Considerations
When using instance variables in comparators, thread safety becomes a critical concern. If a comparator is accessed by multiple threads concurrently, you need to ensure that its state is properly synchronized to avoid race conditions and data corruption.
5.1. Immutability
One way to ensure thread safety is to make the comparator immutable. This means that once the comparator is created, its state cannot be changed. Immutable comparators are inherently thread-safe because there is no mutable state to synchronize.
import java.util.Comparator;
class ImmutableComparator implements Comparator<Integer> {
private final int factor;
public ImmutableComparator(int factor) {
this.factor = factor;
}
@Override
public int compare(Integer a, Integer b) {
return Integer.compare(a * factor, b * factor);
}
}
In this example, the ImmutableComparator
has a factor
instance variable that is set in the constructor and never modified. This makes the comparator thread-safe.
5.2. Synchronization
If immutability is not an option, you can use synchronization to protect the comparator’s state. This involves using locks to ensure that only one thread can access and modify the state at a time.
import java.util.Comparator;
class SynchronizedComparator implements Comparator<Integer> {
private int threshold;
public SynchronizedComparator(int threshold) {
this.threshold = threshold;
}
public synchronized void setThreshold(int threshold) {
this.threshold = threshold;
}
@Override
public synchronized int compare(Integer a, Integer b) {
return Integer.compare(Math.abs(a - threshold), Math.abs(b - threshold));
}
}
In this example, the SynchronizedComparator
uses the synchronized
keyword to ensure that only one thread can access the setThreshold
and compare
methods at a time.
5.3. ThreadLocal
Another approach is to use ThreadLocal
variables to give each thread its own copy of the comparator’s state. This eliminates the need for synchronization because each thread is working with its own isolated state.
import java.util.Comparator;
class ThreadLocalComparator implements Comparator<Integer> {
private static final ThreadLocal<Integer> threshold = new ThreadLocal<>();
public ThreadLocalComparator(int initialThreshold) {
threshold.set(initialThreshold);
}
public void setThreshold(int value) {
threshold.set(value);
}
@Override
public int compare(Integer a, Integer b) {
Integer currentThreshold = threshold.get();
return Integer.compare(Math.abs(a - currentThreshold), Math.abs(b - currentThreshold));
}
}
In this example, the ThreadLocalComparator
uses a ThreadLocal
variable to store the threshold. Each thread gets its own copy of the threshold, so there is no need for synchronization.
6. Performance Implications
Using instance variables in comparators can have performance implications, especially in terms of memory usage and execution speed. It’s important to be aware of these implications and optimize your comparators accordingly.
6.1. Memory Overhead
Instance variables consume memory. If you create many comparator instances, each with its own set of instance variables, the memory overhead can become significant. To minimize memory usage, consider reusing comparator instances whenever possible.
6.2. Execution Speed
Accessing instance variables can be slower than accessing local variables or constants. If your comparator is used in a performance-critical section of code, the overhead of accessing instance variables can add up. To improve performance, consider caching frequently accessed instance variables in local variables within the compare
method.
6.3. Avoiding Unnecessary Object Creation
Creating new comparator instances can be expensive. If you only need a single comparator instance, create it once and reuse it. If you need multiple comparator instances with different configurations, consider using a factory method to create and cache them.
7. Best Practices For Using Comparators With Instance Variables
To effectively use comparators with instance variables, follow these best practices:
7.1. Keep It Simple
Keep the comparison logic as simple as possible. Complex comparison logic can be harder to understand, maintain, and optimize. If possible, break down complex comparisons into smaller, more manageable steps.
7.2. Document Your Code
Document your code thoroughly, especially the purpose and behavior of the instance variables. This will make it easier for others (and your future self) to understand and maintain the code.
7.3. Test Thoroughly
Test your comparators thoroughly, especially if they are stateful or thread-safe. Use unit tests to verify that the comparators behave as expected under different conditions.
7.4. Consider Immutability
Whenever possible, make your comparators immutable. Immutable comparators are inherently thread-safe and easier to reason about.
7.5. Use Lazy Initialization
If an instance variable is not always needed, consider using lazy initialization to create it only when it is first accessed. This can save memory and improve performance.
8. Alternatives To Instance Variables
If you want to avoid the complexities and potential pitfalls of using instance variables in comparators, there are several alternatives you can consider.
8.1. Using Lambda Expressions
Lambda expressions provide a concise way to create comparators without explicitly defining a class. You can capture variables from the enclosing scope and use them in the comparison logic.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
public class LambdaExample {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Alice", 30));
people.add(new Person("Bob", 25));
people.add(new Person("Charlie", 35));
int ageThreshold = 28;
Collections.sort(people, (p1, p2) -> {
if (p1.getAge() > ageThreshold && p2.getAge() <= ageThreshold) {
return -1;
} else if (p1.getAge() <= ageThreshold && p2.getAge() > ageThreshold) {
return 1;
} else {
return Integer.compare(p1.getAge(), p2.getAge());
}
});
System.out.println("Sorted by age, prioritizing those over " + ageThreshold + ": " + people);
}
}
In this example, the lambda expression captures the ageThreshold
variable from the enclosing scope and uses it in the comparison logic.
8.2. Passing Parameters To The Compare Method
Instead of using instance variables, you can pass the necessary parameters directly to the compare
method. This avoids the need for stateful comparators and simplifies the code.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + ''' +
", salary=" + salary +
'}';
}
}
class EmployeeComparator implements Comparator<Employee> {
private String sortOrder;
public EmployeeComparator(String sortOrder) {
this.sortOrder = sortOrder;
}
@Override
public int compare(Employee e1, Employee e2) {
return compareEmployees(e1, e2, sortOrder);
}
public static int compareEmployees(Employee e1, Employee e2, String sortOrder) {
if ("name".equalsIgnoreCase(sortOrder)) {
return e1.getName().compareTo(e2.getName());
} else if ("salary".equalsIgnoreCase(sortOrder)) {
return Double.compare(e1.getSalary(), e2.getSalary());
} else {
return 0; // Default case
}
}
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 60000.0));
employees.add(new Employee("Bob", 50000.0));
employees.add(new Employee("Charlie", 70000.0));
String sortOrder = "salary";
Collections.sort(employees, new EmployeeComparator(sortOrder));
System.out.println("Sorted by salary: " + employees);
sortOrder = "name";
Collections.sort(employees, new EmployeeComparator(sortOrder));
System.out.println("Sorted by name: " + employees);
}
}
In this example, the compareEmployees
method takes the sortOrder
as a parameter and uses it to determine the comparison logic.
8.3. Using Static Factory Methods
Static factory methods can be used to create comparator instances with different configurations. This allows you to reuse comparator instances and avoid unnecessary object creation.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Book {
private String title;
private String author;
private int year;
public Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public int getYear() {
return year;
}
@Override
public String toString() {
return "Book{" +
"title='" + title + ''' +
", author='" + author + ''' +
", year=" + year +
'}';
}
}
class BookComparator {
private BookComparator() {} // Prevent direct instantiation
public static Comparator<Book> byTitle() {
return Comparator.comparing(Book::getTitle);
}
public static Comparator<Book> byAuthor() {
return Comparator.comparing(Book::getAuthor);
}
public static Comparator<Book> byYear() {
return Comparator.comparingInt(Book::getYear);
}
public static void main(String[] args) {
List<Book> books = new ArrayList<>();
books.add(new Book("The Lord of the Rings", "J.R.R. Tolkien", 1954));
books.add(new Book("Pride and Prejudice", "Jane Austen", 1813));
books.add(new Book("1984", "George Orwell", 1949));
Collections.sort(books, BookComparator.byTitle());
System.out.println("Sorted by title: " + books);
Collections.sort(books, BookComparator.byAuthor());
System.out.println("Sorted by author: " + books);
Collections.sort(books, BookComparator.byYear());
System.out.println("Sorted by year: " + books);
}
}
In this example, the BookComparator
class provides static factory methods to create comparator instances for sorting books by title, author, or year.
9. Common Mistakes To Avoid
When working with comparators and instance variables, there are several common mistakes to avoid.
9.1. NullPointerExceptions
Failing to handle null values properly can lead to NullPointerException
errors. Always check for null values before accessing object properties in the compare
method.
import java.util.Comparator;
class StringComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
if (s1 == null && s2 == null) {
return 0;
} else if (s1 == null) {
return -1;
} else if (s2 == null) {
return 1;
} else {
return s1.compareTo(s2);
}
}
}
9.2. Inconsistent Comparisons
Comparators must be consistent, meaning that if compare(a, b)
returns a negative value, compare(b, a)
must return a positive value, and if compare(a, b)
returns 0, compare(b, a)
must also return 0. Inconsistent comparisons can lead to unpredictable sorting results.
9.3. Not Handling Edge Cases
Failing to handle edge cases, such as empty collections or objects with extreme values, can lead to incorrect sorting results or runtime errors.
9.4. Ignoring Performance
Ignoring performance considerations can lead to slow sorting algorithms, especially when working with large collections. Always optimize your comparators for speed and memory usage.
10. Advanced Techniques
For more advanced use cases, you can explore techniques such as chaining comparators and using reflection to access object properties dynamically.
10.1. Chaining Comparators
You can chain multiple comparators together to create a composite comparator that sorts objects based on multiple criteria. This is useful when you need to break ties based on secondary sorting criteria.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Student {
private String name;
private int grade;
private double gpa;
public Student(String name, int grade, double gpa) {
this.name = name;
this.grade = grade;
this.gpa = gpa;
}
public String getName() {
return name;
}
public int getGrade() {
return grade;
}
public double getGpa() {
return gpa;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", grade=" + grade +
", gpa=" + gpa +
'}';
}
}
public class ChainingComparators {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 10, 3.8));
students.add(new Student("Bob", 11, 3.5));
students.add(new Student("Charlie", 10, 4.0));
students.add(new Student("David", 11, 3.5));
Comparator<Student> gradeComparator = Comparator.comparingInt(Student::getGrade);
Comparator<Student> gpaComparator = Comparator.comparingDouble(Student::getGpa).reversed();
Comparator<Student> chainedComparator = gradeComparator.thenComparing(gpaComparator);
Collections.sort(students, chainedComparator);
System.out.println("Sorted by grade then GPA: " + students);
}
}
In this example, the chainedComparator
first sorts students by grade and then, for students with the same grade, sorts them by GPA in descending order.
10.2. Using Reflection
Reflection allows you to access object properties dynamically at runtime. This can be useful when you need to create generic comparators that can sort objects based on arbitrary properties.
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class ReflectionExample {
public static void main(String[] args) throws NoSuchFieldException {
List<Object> objects = new ArrayList<>();
objects.add("Charlie");
objects.add("Alice");
objects.add("Bob");
Field stringValue = String.class.getDeclaredField("value");
Comparator<Object> reflectiveComparator = Comparator.comparing(obj -> {
try {
return (Comparable) stringValue.get(obj);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
Collections.sort(objects, reflectiveComparator);
System.out.println("Sorted reflectively: " + objects);
}
}
Note: Using reflection can have performance implications and should be done with caution.
11. Practical Examples
To further illustrate the use of comparators with instance variables, let’s consider some practical examples.
11.1. Sorting Products By Discounted Price
Suppose you have a list of products and you want to sort them by their discounted price. The discount rate is stored in an instance variable of the comparator.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class DiscountedProduct {
private String name;
private double price;
public DiscountedProduct(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return "DiscountedProduct{" +
"name='" + name + ''' +
", price=" + price +
'}';
}
}
class DiscountedProductComparator implements Comparator<DiscountedProduct> {
private double discountRate;
public DiscountedProductComparator(double discountRate) {
this.discountRate = discountRate;
}
@Override
public int compare(DiscountedProduct p1, DiscountedProduct p2) {
double discountedPrice1 = p1.getPrice() * (1 - discountRate);
double discountedPrice2 = p2.getPrice() * (1 - discountRate);
return Double.compare(discountedPrice1, discountedPrice2);
}
public static void main(String[] args) {
List<DiscountedProduct> products = new ArrayList<>();
products.add(new DiscountedProduct("Laptop", 1200.0));
products.add(new DiscountedProduct("Keyboard", 75.0));
products.add(new DiscountedProduct("Mouse", 25.0));
double discountRate = 0.2; // 20% discount
DiscountedProductComparator comparator = new DiscountedProductComparator(discountRate);
Collections.sort(products, comparator);
System.out.println("Sorted by discounted price: " + products);
}
}
In this example, the DiscountedProductComparator
uses the discountRate
instance variable to calculate the discounted price of each product and sort them accordingly.
11.2. Sorting Files By Last Modified Time Within A Range
Suppose you have a list of files and you want to sort them by their last modified time, but only within a specific date range. The start and end dates are stored in instance variables of the comparator.
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
class FileByDateRangeComparator implements Comparator<File> {
private Date startDate;
private Date endDate;
public FileByDateRangeComparator(Date startDate, Date endDate) {
this.startDate = startDate;
this.endDate = endDate;
}
@Override
public int compare(File f1, File f2) {
long lastModified1 = f1.lastModified();
long lastModified2 = f2.lastModified();
if (lastModified1 < startDate.getTime()