A Method To Compare The Types In Java allows developers to implement total ordering on collections of objects. Comparators facilitate precise control over sort order and influence the arrangement of data structures. compare.edu.vn is a valuable resource for understanding and implementing effective type comparison techniques in Java. The insights here can aid in making informed decisions in Java development and utilizing ordering methods, equivalence relations, and data structures with type comparisons.
1. Understanding Comparators in Java
In Java, a comparator is an interface that defines a method for comparing two objects. This interface, part of the Java Collections Framework, offers a flexible way to define custom sorting logic for collections of objects. Comparators are particularly useful when you need to sort objects in an order different from their natural ordering (defined by the Comparable
interface) or when the objects don’t have a natural ordering at all. Understanding comparators is crucial for effectively managing and manipulating data in Java applications.
1.1. What is a Comparator?
A comparator is an object that implements the java.util.Comparator
interface. This interface consists primarily of the compare(Object o1, Object o2)
method, which determines the order of two objects. The method returns a negative integer, zero, or a positive integer if the first argument is less than, equal to, or greater than the second, respectively.
public interface Comparator<T> {
int compare(T o1, T o2);
}
This fundamental concept enables developers to specify how objects should be sorted or ordered based on custom criteria. Comparators can be used with various methods, such as Collections.sort()
and Arrays.sort()
, to sort collections and arrays according to the defined comparison logic. For example, consider a scenario where you have a list of Employee
objects and you want to sort them based on their salaries. You can create a comparator that compares the salaries of two employees and use it to sort the list.
1.2. Natural Ordering vs. Comparator Ordering
Java objects can have a natural ordering defined by implementing the java.lang.Comparable
interface. This interface includes the compareTo(Object o)
method, which allows an object to define its inherent comparison logic. However, there are cases where you might want to sort objects in a different order or when the objects don’t have a natural ordering. This is where comparators come into play.
- Natural Ordering: Defined by the
Comparable
interface, it provides a default way to compare objects of a class. - Comparator Ordering: Defined by the
Comparator
interface, it provides a flexible way to define custom sorting logic.
For instance, the String
class in Java has a natural ordering based on lexicographical order. However, if you want to sort strings ignoring case sensitivity, you can use a comparator that performs case-insensitive comparison. Comparators offer the flexibility to handle diverse sorting requirements without modifying the original class definition.
1.3. Implementing the Comparator Interface
To implement a comparator, you need to create a class that implements the java.util.Comparator
interface and provide an implementation for the compare(Object o1, Object o2)
method. This method should return a negative integer, zero, or a positive integer based on the comparison of the two objects.
import java.util.Comparator;
public class EmployeeSalaryComparator implements Comparator<Employee> {
@Override
public int compare(Employee e1, Employee e2) {
return Double.compare(e1.getSalary(), e2.getSalary());
}
}
In this example, EmployeeSalaryComparator
compares two Employee
objects based on their salaries. The Double.compare()
method is used to handle the comparison of double values, ensuring correct ordering. Once the comparator is implemented, it can be used with sorting methods to arrange collections of Employee
objects by salary.
1.4. Using Comparators with Sorting Methods
Comparators are commonly used with the Collections.sort()
and Arrays.sort()
methods to sort collections and arrays. These methods accept a comparator as an argument, allowing you to specify the sorting logic.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ComparatorExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 50000));
employees.add(new Employee("Bob", 60000));
employees.add(new Employee("Charlie", 45000));
Collections.sort(employees, new EmployeeSalaryComparator());
for (Employee employee : employees) {
System.out.println(employee.getName() + ": " + employee.getSalary());
}
}
}
In this example, the Collections.sort()
method is used with the EmployeeSalaryComparator
to sort the list of Employee
objects by salary. The output will display the employees in ascending order of their salaries. Similarly, Arrays.sort()
can be used to sort arrays with a custom comparator.
1.5. Anonymous Comparators
Anonymous comparators are a concise way to define comparators inline without creating a separate class. This is particularly useful when you need a comparator only once and don’t want to create a named class for it.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AnonymousComparatorExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 50000));
employees.add(new Employee("Bob", 60000));
employees.add(new Employee("Charlie", 45000));
Collections.sort(employees, (e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
for (Employee employee : employees) {
System.out.println(employee.getName() + ": " + employee.getSalary());
}
}
}
Here, an anonymous comparator is defined using a lambda expression to compare employees by salary. This approach reduces the amount of code and improves readability, especially for simple comparison logic. Anonymous comparators are a powerful tool for writing clean and efficient Java code.
1.6. Comparator.comparing() Method
Java 8 introduced the Comparator.comparing()
method, which provides a convenient way to create comparators based on a key extraction function. This method simplifies the creation of comparators by allowing you to specify the property to compare directly.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Comparator;
public class ComparingMethodExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 50000));
employees.add(new Employee("Bob", 60000));
employees.add(new Employee("Charlie", 45000));
Collections.sort(employees, Comparator.comparing(Employee::getSalary));
for (Employee employee : employees) {
System.out.println(employee.getName() + ": " + employee.getSalary());
}
}
}
In this example, Comparator.comparing(Employee::getSalary)
creates a comparator that compares Employee
objects based on their salaries using the getSalary()
method as the key extraction function. This method enhances code readability and reduces boilerplate code, making it easier to define comparators.
1.7. Chaining Comparators with thenComparing()
Sometimes, you need to sort objects based on multiple criteria. The thenComparing()
method allows you to chain comparators, providing a way to define a secondary sorting order when the primary comparison results in a tie.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Comparator;
public class ThenComparingExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 50000, 30));
employees.add(new Employee("Bob", 60000, 25));
employees.add(new Employee("Charlie", 50000, 35));
Collections.sort(employees, Comparator.comparing(Employee::getSalary).thenComparing(Employee::getAge));
for (Employee employee : employees) {
System.out.println(employee.getName() + ": " + employee.getSalary() + ", " + employee.getAge());
}
}
}
In this example, employees are first sorted by salary and then by age. If two employees have the same salary, they are then sorted by their age. The thenComparing()
method provides a flexible way to define complex sorting logic by combining multiple comparison criteria.
1.8. Handling Null Values in Comparators
When comparing objects, you might encounter null values. It’s essential to handle null values properly in your comparators to avoid NullPointerException
errors. Java provides methods like Comparator.nullsFirst()
and Comparator.nullsLast()
to handle null values gracefully.
Comparator.nullsFirst(Comparator<? super T> comparator)
: Returns a comparator that considers null values as smaller than non-null values.Comparator.nullsLast(Comparator<? super T> comparator)
: Returns a comparator that considers null values as larger than non-null values.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Comparator;
public class NullHandlingExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 50000, 30));
employees.add(null);
employees.add(new Employee("Bob", 60000, 25));
Comparator<Employee> nullsFirstComparator = Comparator.nullsFirst(Comparator.comparing(Employee::getSalary));
Collections.sort(employees, nullsFirstComparator);
for (Employee employee : employees) {
System.out.println(employee == null ? "Null Employee" : employee.getName() + ": " + employee.getSalary());
}
}
}
In this example, Comparator.nullsFirst()
is used to ensure that null employees are placed at the beginning of the list. Handling null values properly ensures that your comparators are robust and don’t throw unexpected exceptions.
1.9. Best Practices for Writing Comparators
Writing effective comparators involves following certain best practices to ensure correctness, readability, and performance. Here are some key guidelines:
- Ensure Consistency: Comparators should provide consistent results. If
compare(a, b)
returns a negative value,compare(b, a)
should return a positive value, and vice versa. - Handle Null Values: Always handle null values appropriately to avoid
NullPointerException
errors. UseComparator.nullsFirst()
orComparator.nullsLast()
as needed. - Use Standard Comparison Methods: Utilize standard comparison methods like
Integer.compare()
,Double.compare()
, andString.compareTo()
to ensure correct ordering. - Keep It Simple: Keep the comparison logic simple and focused. Avoid complex calculations or side effects within the
compare()
method. - Consider Performance: Be mindful of the performance implications of your comparators. Avoid unnecessary object creation or expensive operations within the comparison logic.
- Test Thoroughly: Test your comparators thoroughly with various inputs to ensure they produce the correct ordering.
1.10. Real-World Examples of Comparator Usage
Comparators are used extensively in various real-world scenarios. Here are a few examples:
- Sorting a List of Students by GPA: You can use a comparator to sort a list of
Student
objects based on their GPA. - Sorting a List of Products by Price: You can use a comparator to sort a list of
Product
objects based on their price. - Sorting a List of Dates: You can use a comparator to sort a list of
Date
objects in chronological order. - Custom Sorting in Data Structures: Comparators can be used to define the order of elements in sorted sets and sorted maps.
- Dynamic Sorting in UI Applications: Comparators can be used to implement dynamic sorting in UI applications, allowing users to sort data based on different criteria.
By understanding and applying comparators effectively, you can enhance the functionality and flexibility of your Java applications.
2. Methods for Type Comparison in Java
Type comparison in Java is a fundamental aspect of ensuring type safety and correctness in your code. Java provides several methods and techniques for comparing types, each with its own use cases and considerations. Understanding these methods is crucial for writing robust and reliable Java applications.
2.1. Using the instanceof
Operator
The instanceof
operator is a fundamental tool in Java for checking whether an object is an instance of a particular class or interface. It returns true
if the object is an instance of the specified type, and false
otherwise.
public class InstanceOfExample {
public static void main(String[] args) {
Object obj = new String("Hello");
if (obj instanceof String) {
System.out.println("obj is an instance of String");
}
if (obj instanceof Object) {
System.out.println("obj is an instance of Object");
}
if (obj instanceof Integer) {
System.out.println("obj is an instance of Integer");
}
}
}
In this example, the instanceof
operator is used to check if the obj
object is an instance of String
, Object
, and Integer
. The output shows that obj
is an instance of String
and Object
, but not Integer
. The instanceof
operator is particularly useful when you need to perform different actions based on the type of an object.
2.2. The getClass()
Method
The getClass()
method, inherited from the Object
class, returns the runtime class of an object. This method is useful for obtaining the exact class of an object, which can be used for precise type comparisons.
public class GetClassExample {
public static void main(String[] args) {
Object obj = new String("Hello");
Class<?> clazz = obj.getClass();
System.out.println("Class of obj: " + clazz.getName());
if (clazz == String.class) {
System.out.println("obj is a String");
}
}
}
In this example, the getClass()
method is used to get the class of the obj
object. The clazz.getName()
method returns the fully qualified name of the class. The if
statement checks if the class of obj
is equal to String.class
, ensuring a precise type comparison.
2.3. Using equals()
Method for Object Comparison
The equals()
method is used to compare two objects for equality. It is defined in the Object
class and can be overridden by subclasses to provide custom equality logic. When comparing types, it’s essential to ensure that the equals()
method is properly implemented.
public class EqualsExample {
public static void main(String[] args) {
String str1 = new String("Hello");
String str2 = new String("Hello");
if (str1.equals(str2)) {
System.out.println("str1 and str2 are equal");
}
Integer num1 = new Integer(10);
Integer num2 = new Integer(10);
if (num1.equals(num2)) {
System.out.println("num1 and num2 are equal");
}
}
}
In this example, the equals()
method is used to compare two String
objects and two Integer
objects. The output shows that both pairs of objects are equal because the equals()
method in both String
and Integer
classes is overridden to compare the content of the objects.
2.4. Type Casting and Type Conversion
Type casting and type conversion are techniques used to change the type of a variable or object. Type casting is used to convert an object from one type to another, while type conversion is used to convert a primitive data type from one type to another.
public class TypeCastingExample {
public static void main(String[] args) {
Object obj = new String("Hello");
if (obj instanceof String) {
String str = (String) obj; // Type casting
System.out.println("String value: " + str);
}
double numDouble = 10.5;
int numInt = (int) numDouble; // Type conversion
System.out.println("Integer value: " + numInt);
}
}
In this example, type casting is used to convert an Object
to a String
, and type conversion is used to convert a double
to an int
. Type casting requires checking the type of the object using instanceof
to avoid ClassCastException
. Type conversion can result in data loss, so it should be used carefully.
2.5. Using Generics for Type Safety
Generics provide a way to add type safety to your code by allowing you to specify the type of objects that a class or method can work with. This helps prevent type-related errors at compile time rather than runtime.
import java.util.ArrayList;
import java.util.List;
public class GenericsExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// stringList.add(10); // Compile-time error
for (String str : stringList) {
System.out.println(str);
}
}
}
In this example, the List<String>
declaration specifies that the list can only contain String
objects. Attempting to add an Integer
to the list results in a compile-time error, ensuring type safety. Generics are a powerful tool for writing type-safe and reusable code.
2.6. Reflection and Type Introspection
Reflection is a powerful feature in Java that allows you to inspect and manipulate classes, interfaces, fields, and methods at runtime. Reflection can be used for type introspection, which involves examining the type of an object at runtime.
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
Object obj = new String("Hello");
Class<?> clazz = obj.getClass();
System.out.println("Class name: " + clazz.getName());
Field[] fields = clazz.getDeclaredFields();
System.out.println("Fields:");
for (Field field : fields) {
System.out.println(" " + field.getName() + ": " + field.getType().getName());
}
Method[] methods = clazz.getDeclaredMethods();
System.out.println("Methods:");
for (Method method : methods) {
System.out.println(" " + method.getName());
}
}
}
In this example, reflection is used to get the class of a String
object and then to retrieve its fields and methods. Reflection is a powerful tool for dynamic type analysis and manipulation, but it should be used carefully because it can reduce performance and bypass type safety checks.
2.7. Using Interfaces for Polymorphism
Interfaces define a contract that classes can implement, allowing you to treat objects of different classes uniformly. This is known as polymorphism, which is a key concept in object-oriented programming.
interface Shape {
double getArea();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double getArea() {
return width * height;
}
}
public class InterfaceExample {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
System.out.println("Circle area: " + circle.getArea());
System.out.println("Rectangle area: " + rectangle.getArea());
}
}
In this example, the Shape
interface defines the getArea()
method, which is implemented by the Circle
and Rectangle
classes. This allows you to treat Circle
and Rectangle
objects uniformly through the Shape
interface, demonstrating polymorphism.
2.8. Type Annotations and Static Analysis Tools
Type annotations and static analysis tools can be used to perform advanced type checking and analysis. Type annotations provide additional information about the type of a variable or expression, while static analysis tools can analyze your code for type-related errors without running it.
import org.checkerframework.checker.nullness.qual.NonNull;
public class AnnotationExample {
public static void main(String[] args) {
@NonNull String str = "Hello";
// str = null; // Compile-time error
System.out.println(str.length());
}
}
In this example, the @NonNull
annotation from the Checker Framework is used to indicate that the str
variable cannot be null. Attempting to assign null
to str
results in a compile-time error. Static analysis tools can help you catch type-related errors early in the development process, improving the reliability of your code.
2.9. Dynamic Typing with Scripting Languages
Java can be integrated with scripting languages like JavaScript and Python, which support dynamic typing. Dynamic typing allows you to defer type checking to runtime, providing more flexibility but also requiring more careful testing.
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class ScriptingExample {
public static void main(String[] args) throws Exception {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
engine.eval("var x = 10; var y = 'Hello'; print(x + y);");
}
}
In this example, a JavaScript engine is used to execute a script that performs dynamic typing. The script defines a variable x
as a number and y
as a string, and then concatenates them. Dynamic typing can be useful for certain types of applications, but it’s important to be aware of the potential for runtime type errors.
2.10. Best Practices for Type Comparison
Effective type comparison involves following certain best practices to ensure correctness, readability, and maintainability. Here are some key guidelines:
- Use
instanceof
Carefully: Useinstanceof
only when necessary, as it can reduce code flexibility. - Prefer
equals()
for Object Comparison: Use theequals()
method for comparing objects for equality, and ensure that it’s properly implemented. - Use Generics for Type Safety: Use generics to add type safety to your code and prevent type-related errors at compile time.
- Handle Type Casting Carefully: Handle type casting carefully, and always check the type of an object using
instanceof
before casting. - Use Interfaces for Polymorphism: Use interfaces to define contracts and achieve polymorphism, allowing you to treat objects of different classes uniformly.
- Use Static Analysis Tools: Use static analysis tools to catch type-related errors early in the development process.
- Test Thoroughly: Test your code thoroughly with various inputs to ensure that type comparisons are correct.
By understanding and applying these methods and best practices, you can write robust and reliable Java applications that handle type comparisons effectively.
3. Practical Examples of Comparing Types in Java
Understanding the theoretical aspects of type comparison in Java is essential, but seeing practical examples can significantly enhance your comprehension. This section provides several real-world scenarios where type comparison is crucial and demonstrates how to implement it effectively.
3.1. Sorting a List of Custom Objects
One of the most common use cases for type comparison is sorting a list of custom objects. This involves creating a Comparator
that defines the sorting logic based on the object’s properties.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Product {
private String name;
private double price;
public Product(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 "Product{" +
"name='" + name + ''' +
", price=" + price +
'}';
}
}
public class ProductSortingExample {
public static void main(String[] args) {
List<Product> products = new ArrayList<>();
products.add(new Product("Laptop", 1200.0));
products.add(new Product("Keyboard", 75.0));
products.add(new Product("Mouse", 25.0));
products.add(new Product("Monitor", 300.0));
// Sort products by price
Collections.sort(products, Comparator.comparingDouble(Product::getPrice));
System.out.println("Products sorted by price:");
for (Product product : products) {
System.out.println(product);
}
// Sort products by name
Collections.sort(products, Comparator.comparing(Product::getName));
System.out.println("nProducts sorted by name:");
for (Product product : products) {
System.out.println(product);
}
}
}
In this example, the Product
class represents a product with a name and a price. The Collections.sort()
method is used with Comparator.comparingDouble()
and Comparator.comparing()
to sort the list of products by price and name, respectively. This demonstrates how to use comparators to sort custom objects based on their properties.
3.2. Implementing a Custom equals()
Method
The equals()
method is used to compare two objects for equality. Implementing a custom equals()
method allows you to define what it means for two objects of your class to be equal.
class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Point point = (Point) obj;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
import java.util.Objects;
public class PointEqualsExample {
public static void main(String[] args) {
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 2);
Point p3 = new Point(3, 4);
System.out.println("p1 equals p2: " + p1.equals(p2));
System.out.println("p1 equals p3: " + p1.equals(p3));
}
}
In this example, the Point
class represents a point in a 2D plane. The equals()
method is overridden to compare two Point
objects based on their x
and y
coordinates. The hashCode()
method is also overridden to ensure that equal objects have the same hash code. This demonstrates how to implement a custom equals()
method to define equality for your objects.
3.3. Using instanceof
to Handle Different Types
The instanceof
operator is used to check if an object is an instance of a particular class or interface. This is useful when you need to handle different types of objects in a uniform way.
interface Animal {
void makeSound();
}
class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
}
public class AnimalSoundExample {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
makeAnimalSound(animal1);
makeAnimalSound(animal2);
}
public static void makeAnimalSound(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.makeSound();
} else if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.makeSound();
} else {
System.out.println("Unknown animal");
}
}
}
In this example, the Animal
interface defines the makeSound()
method, which is implemented by the Dog
and Cat
classes. The makeAnimalSound()
method uses the instanceof
operator to check the type of the Animal
object and then calls the appropriate makeSound()
method. This demonstrates how to use instanceof
to handle different types of objects in a uniform way.
3.4. Using Generics for Type-Safe Collections
Generics provide a way to add type safety to your code by allowing you to specify the type of objects that a collection can store. This helps prevent type-related errors at compile time.
import java.util.ArrayList;
import java.util.List;
public class GenericListExample {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
// names.add(123); // Compile-time error
for (String name : names) {
System.out.println("Name: " + name);
}
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
for (int number : numbers) {
System.out.println("Number: " + number);
}
}
}
In this example, the List<String>
and List<Integer>
declarations specify that the lists can only contain String
and Integer
objects, respectively. Attempting to add an object of a different type results in a compile-time error. This demonstrates how to use generics to create type-safe collections.
3.5. Implementing a Custom Comparator with thenComparing()
The thenComparing()
method allows you to chain comparators, providing a way to define a secondary sorting order when the primary comparison results in a tie.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
class Student {
private String name;
private int age;
private double gpa;
public Student(String name, int age, double gpa) {
this.name = name;
this.age = age;
this.gpa = gpa;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getGpa() {
return gpa;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", age=" + age +
", gpa=" + gpa +
'}';
}
}
public class StudentSortingExample {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
students.add(new Student("Alice", 20, 3.8));
students.add(new Student("Bob", 22, 3.5));
students.add(new Student("Charlie", 20, 4.0));
students.add(new Student("David", 22, 3.5));
// Sort students by GPA, then by age
Collections.sort(students, Comparator.comparing(Student::getGpa).thenComparing(Student::getAge));
System.out.println("Students sorted by GPA, then by age:");
for (Student student : students) {
System.out.println(student);
}
}
}
In this example, the Student
class represents a student with a name, age, and GPA. The Collections.sort()
method is used with Comparator.comparing()
and thenComparing()
to sort the list of students by GPA and then by age. This demonstrates how to use thenComparing()
to define a secondary sorting order.
3.6. Handling Null Values in a Comparator
When comparing objects, you might encounter null values. It’s essential to handle null values properly in your comparators to avoid NullPointerException
errors.
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 +
'}';
}
}
public class EmployeeSortingExample {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("Alice", 50000.0));
employees.add(new Employee("Bob", 60000.0));
employees.add(new Employee("Charlie", null));
employees.add(new Employee("David", 55