Sign In
Sign In

Overriding the hashCode Method in Java

Overriding the hashCode Method in Java
Hostman Team
Technical writer
Java
08.07.2024
Reading time: 6 min

Each object in a program can be represented as an integer. This process is called hashing, and its result is a hash. The hashCode() method in Java generates a hash for objects, making them easier to sort and search.

The generated hash code helps organize quick searches and access to objects in associative arrays or hash tables. This method can be compared to a surjection, meaning every element of set B is the image of at least one element of set A. The following illustration clearly depicts this.

Image1

For all elements in set B, there is at least one corresponding element in set A. The only difference between the hashCode() method and surjection is that any object can be processed by this method.

When using this method, sometimes different objects may have the same hash. For instance, objects C and V are mapped to the same element in the illustration above. These cases are called collisions. The equals() method, closely related to the hashCode() method, helps resolve these collisions.  Another article on Hostman covers more details about equals().

If you plan to use an associative array in your project and use objects as keys, it is recommended to override the hashCode() method for faster and more accurate operation. It is also necessary to override it whenever the equals() method is overridden. We will describe how to correctly override the hashCode() method in Java further in this article.

Requirements for Implementing the hashCode() Method

The declaration of the hashCode() method looks like this:

public int hashCode() {
   // ...
}

To correctly implement this method in Java, the following requirements are defined:

  1. If the hashCode() method is called multiple times on the same object, it must return the same value each time, provided the object's fields are not modified.

  2. If the method is called on two equivalent objects, it must return the same result for both. The equality of such objects can be verified using the equals() method, which should return true if the objects are equal.

  3. If the results returned by the method for two objects are identical, it does not guarantee their equality. However, if they are different, it guarantees that the objects are not equal.

These requirements highlight the importance of using the hashCode() method together with equals() for object comparison. The latter compares objects, while the former indicates if an object's state has changed. This again confirms that when overriding the equals() method, the hashCode() method should also be overridden.

Overriding the hashCode() Method

The first idea that might come to mind for overriding hashCode() in Java is to return a constant.

@Override
public int hashCode() {
   return 7;
}

Implementing the override this way is categorically wrong for several reasons:

  • This implementation meets the requirements and will return the same number for two equal objects, but if the state of one changes, its hash code will not change, violating the requirements mentioned above.

  • Such an implementation guarantees collisions.

Given these drawbacks, this approach to overriding the method should be avoided.

There are two correct ways to override the hashCode() method in Java:

  1. Use an existing algorithm for your own implementation.

  2. Use auxiliary methods to generate the hash code.

The first approach involves following these rules for overriding hashCode() in Java:

  • Exclude all redundant fields that are not involved in equals().

  • Choose a base - a starting number for calculating the object's hash code and assign it to a variable total. Developers often use the number 31, but you can choose a different value. Many IDEs generate hash codes using this number.

  • Next, calculate the hash for each remaining field. The table below shows the rules for calculating possible field types:

Field Type

Rule

boolean

(f ? 1 : 0)

char, short, byte, or int

(int) f

float

Float.floatToIntBits(f)

double

Double.doubleToLongBits(f) then (int)(f ^ (f >>> 32))

long

(int)(f ^ (f >>> 32))

Reference to another object

Recursive call to hashCode()

Array

Process each element of the array as a separate field of the object

null

return 0

  • The next rule is to add the calculated hash of each field (let's say it's the variable compute) to the variable total:

total = 31 * total + compute;
  • Finally, return the final value of the variable total after all calculations.

Below is an example of implementing the override for the Staff class, using the rules listed above:

public class Staff {
    private String FCs;
    private String city;
    private int experience;
    private double wage;
    private String department;

    public Staff(String FCs, String city, int experience, double wage, String department) {
        this.FCs = FCs;
        this.city = city;
        this.experience = experience;
        this.wage = wage;
        this.department = department;
    }

    @Override
    public int hashCode() { 
        int total = 31; 
 
        total = total * 31 + (FCs == null ? 0 : FCs.hashCode()); 
        total = total * 31 + (city == null ? 0 : city.hashCode()); 
        total = total * 31 + experience; 
        long lwage = Double.doubleToLongBits(wage); 
        total = total * 31 + (int)(lwage ^ (lwage >>> 32)); 
        total = total * 31 + (department == null ? 0 : department.hashCode()); 
 
        return total; 
    }

    // Overriding equals()
    // ...
}

The @Override annotation before the method declaration checks that the overridden method exists in the parent class. All string fields (FCs, city, and department) are checked for null before calling their hashCode() method to ensure safety from NullPointerException.

The second approach involves using auxiliary methods to generate the hash code, available through the java.util.Objects class starting from Java 8+. An example of this implementation is shown below:

@Override
public int hashCode() {
   return Objects.hash(FCs, city, experience, department);
}

All standard reference data types in Java (String, Integer, Double, etc.) already have correctly overridden equals() and hashCode() methods. Therefore, they can be safely integrated into collections like HashMap, HashSet, and others.

Managed solution for Backend development

Key Points to Remember

  • A hash is a number generated for an object using hash functions, including hashCode().

  • The Java hashCode() method returns an integer hash code value for a selected object.

  • If you plan to use an associative array in your project and use objects as keys, it is recommended to override the hashCode() method.

  • A well-chosen implementation of the hashCode() method will speed up the performance of associative arrays.

  • When overriding equals(), do not forget to override hashCode(), and vice versa.

  • An incorrect implementation of the Java hashCode() method will result in many collisions.

Java
08.07.2024
Reading time: 6 min

Similar

Java

Exception Handling in Java

Exception handling is a cornerstone of robust software development in Java, serving as the bridge between theoretical correctness and practical resilience. While most developers grasp the basics of try-catch blocks, the true art of exception management lies in balancing technical precision, architectural foresight, and performance optimization. This article dives deep into Java exception handling, exploring not only core concepts but also advanced patterns, anti-patterns, and strategies for integrating error management into modern architectures such as microservices, reactive systems, and cloud-native applications. The Philosophy of Exception Handling: Beyond Syntax Java’s exception handling mechanism is more than just a syntax requirement—it embodies a philosophy of controlled failure. Unlike languages that rely on error codes or silent failures, Java enforces a structured approach to unexpected scenarios, ensuring developers confront errors explicitly. This design choice reflects two principles: Fail-fast: Identify and address issues at the earliest possible stage. Separation of Concerns: Decouple business logic from error recovery. Understanding these principles is critical for designing systems where exceptions are not merely "handled" but strategically managed to enhance reliability. class FailFastExample { public static void main(String[] args) { int age = -5; // Simulate invalid input // Fail-fast: Validate input and throw exception if invalid if (age < 0) { throw new IllegalArgumentException("Age cannot be negative"); } System.out.println("Age: " + age); // This line won't execute if exception is thrown } } The Anatomy of Java Exceptions Java’s exception hierarchy is rooted in the Throwable class, with three primary categories: Checked Exceptions (Exception subclasses): Enforced by the compiler (e.g., IOException, SQLException). Represent recoverable errors (e.g., file not found, network issues). Require explicit handling via try-catch or propagation using throws. Unchecked Exceptions (RuntimeException subclasses): Not enforced by the compiler (e.g., NullPointerException, IllegalArgumentException). Often indicate programming errors (e.g., invalid arguments, logic flaws). Errors (Error subclasses): Severe, non-recoverable issues (e.g., OutOfMemoryError, StackOverflowError). Typically arise from JVM or system-level failures. The distinction between checked and unchecked exceptions is often debated. Modern frameworks like Spring have largely moved away from checked exceptions, favoring runtime exceptions to reduce boilerplate and improve code readability. import java.io.FileInputStream; import java.io.FileNotFoundException; class ExceptionTypesDemo { public static void main(String[] args) { // Unchecked exception (ArithmeticException) try { int result = 10 / 0; // Division by zero } catch (ArithmeticException ex) { System.out.println("Unchecked error: " + ex.getMessage()); } // Checked exception (FileNotFoundException) try { // Attempt to open a non-existent file new FileInputStream("ghost.txt"); } catch (FileNotFoundException ex) { System.out.println("Checked error: " + ex.getMessage()); } } } Custom Exceptions: Crafting Domain-Specific Errors While Java provides a rich set of built-in exceptions, custom exceptions enable domain-specific error signaling. For example, an e-commerce app might define: // Custom exception class class InvalidInputException extends RuntimeException { public InvalidInputException(String message) { super(message); // Pass the error message to the parent class } } class CustomExceptionDemo { public static void main(String[] args) { try { processInput(""); // Simulate empty input } catch (InvalidInputException ex) { System.out.println("Custom error: " + ex.getMessage()); } } // Method to validate input static void processInput(String input) { if (input.isEmpty()) { throw new InvalidInputException("Input cannot be empty"); } } } Best Practices for Custom Exceptions: Immutable State: Ensure exception objects are immutable to prevent unintended side effects. Rich Context: Include metadata (e.g., timestamps, error codes) to aid debugging. Avoid Overuse: Reserve custom exceptions for scenarios where standard exceptions are insufficient. Advanced Exception Handling Patterns Pattern 1: Exception Translation Wrap lower-level exceptions in higher-level abstractions to avoid leaking implementation details. For instance, convert a SQLException into a DataAccessException in a DAO layer: // Custom exception for wrapping low-level exceptions class CalculationException extends RuntimeException { public CalculationException(String message, Throwable cause) { super(message, cause); // Pass message and cause to the parent class } } class ExceptionTranslationDemo { public static void main(String[] args) { try { calculate(); // Perform calculation } catch (CalculationException ex) { System.out.println("Translated error: " + ex.getMessage()); System.out.println("Root cause: " + ex.getCause().getMessage()); } } // Method to simulate a calculation static void calculate() { try { int result = 10 / 0; // Division by zero } catch (ArithmeticException ex) { // Wrap the low-level exception in a custom exception throw new CalculationException("Calculation failed", ex); } } } Pattern 2: Circuit Breakers In distributed systems, use frameworks like Resilience4j to prevent cascading failures: class SimpleCircuitBreaker { private int failureCount = 0; // Track number of failures private static final int MAX_FAILURES = 2; // Maximum allowed failures public void execute() { // If failures exceed the limit, open the circuit if (failureCount >= MAX_FAILURES) { throw new RuntimeException("Circuit open: Service halted"); } try { // Simulate a failing service throw new RuntimeException("Service error"); } catch (RuntimeException ex) { failureCount++; // Increment failure count System.out.println("Failure #" + failureCount); } } public static void main(String[] args) { SimpleCircuitBreaker cb = new SimpleCircuitBreaker(); for (int i = 0; i < 3; i++) { try { cb.execute(); // Attempt to execute the service } catch (RuntimeException ex) { System.out.println(ex.getMessage()); } } } } Pattern 3: Global Exception Handlers In Spring Boot, use @ControllerAdvice to centralize exception handling: class GlobalHandlerDemo { public static void main(String[] args) { // Set a global exception handler for uncaught exceptions Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> { System.out.println("Global handler caught: " + ex.getMessage()); }); // Simulate an uncaught exception throw new RuntimeException("Unexpected error occurred!"); } } Performance Considerations Exception handling incurs overhead, particularly when stack traces are generated. Optimize with these strategies: class LightweightException extends RuntimeException { @Override public Throwable fillInStackTrace() { return this; // Skip stack trace generation for better performance } } class PerformanceDemo { public static void main(String[] args) { try { throw new LightweightException(); // Throw a lightweight exception } catch (LightweightException ex) { System.out.println("Caught lightweight exception"); } } } Avoid Exceptions for Control Flow: Using exceptions for non-error scenarios (e.g., looping) is inefficient. Lazy Initialization of Stack Traces: Use Throwable constructors that skip stack trace generation (Java 7+): Logging Wisely: Avoid logging the same exception multiple times across layers. Tools and Libraries Lombok: Simplify exception logging with @SneakyThrows. Guava Preconditions: Validate inputs and throw standardized exceptions. ELK Stack (Elasticsearch, Logstash, Kibana): Centralize exception monitoring. Sentry: Real-time error tracking with context-rich reports. Conclusion Exception handling is not an afterthought but a foundational design discipline. By embracing principles like context preservation, strategic logging, and architectural alignment, developers can transform error management from a chore into a competitive advantage. As Java continues to evolve—integrating with cloud platforms, reactive systems, and AI-driven observability tools—exception handling will remain a critical skill for building software that thrives in the face of uncertainty.
06 February 2025 · 8 min to read
Java

Java Date Format

Handling dates and times effectively is a critical aspect of many software applications. In Java, the SimpleDateFormat class from the java.text package offers developers a robust mechanism for formatting Date objects into strings and parsing strings back into Date objects. This guide explores the features, use cases, and best practices for leveraging SimpleDateFormat in your Java projects. Overview of SimpleDateFormat A method for creating unique patterns for date and time data representation is offered by SimpleDateFormat. These patterns are versatile, allowing developers to adapt date formats to their specific application requirements. Here’s an introductory example: import java.text.SimpleDateFormat; import java.util.Date; public class SimpleDateFormatDemo { public static void main(String[] args) { SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); Date currentDate = new Date(); System.out.println("Formatted Date: " + formatter.format(currentDate)); } } This sample produces a string that is formatted in accordance with the given pattern and contains the current date and time: Formatted Date: 07/01/2025 14:35:00 Pattern Syntax for Formatting Dates The SimpleDateFormat class employs a symbolic pattern language to precise how dates and times should appear. Below is a table summarizing some key symbols: Symbol Description Example y Year 2025 (yyyy), 25 (yy) M Month 01 (MM), Jan (MMM) d Day of the month 07 (dd) H Hour (0-23) 14 (HH) h Hour (1-12) 02 (hh) m Minute 35 (mm) s Second 00 (ss) a AM/PM marker PM E Day of the week Tue (EEE), Tuesday (EEEE) z Time zone PST (z), Pacific Standard Time (zzzz) d Day of the month 07 (dd) H Hour (0-23) 14 (HH) h Hour (1-12) 02 (hh) m Minute 35 (mm) s Second 00 (ss) a AM/PM marker PM E Day of the week Tue (EEE), Tuesday (EEEE) z Time zone PST (z), Pacific Standard Time (zzzz) Combining these symbols allows developers to create highly tailored date and time formats. Customizing Date Formats Using SimpleDateFormat, you can craft custom formats to suit various requirements. Here’s an example demonstrating three distinct patterns: import java.text.SimpleDateFormat; import java.util.Date; public class DateFormatExamples { public static void main(String[] args) { Date currentDate = new Date(); SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat verboseFormat = new SimpleDateFormat("EEEE, MMMM dd, yyyy"); SimpleDateFormat timeFormat = new SimpleDateFormat("hh:mm a z"); System.out.println("ISO Format: " + isoFormat.format(currentDate)); System.out.println("Verbose Format: " + verboseFormat.format(currentDate)); System.out.println("Time Format: " + timeFormat.format(currentDate)); } } Sample Output: ISO Format: 2025-01-07 Verbose Format: Tuesday, January 07, 2025 Time Format: 02:35 PM PST Parsing Strings to Date Objects SimpleDateFormat also facilitates converting string representations of dates back into Date objects. This is especially useful for handling user input or reading data from external sources. import java.text.SimpleDateFormat; import java.util.Date; public class DateParsingDemo { public static void main(String[] args) { String inputDate = "07-01-2025 14:35:00"; SimpleDateFormat parser = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); try { Date parsedDate = parser.parse(inputDate); System.out.println("Parsed Date: " + parsedDate); } catch (Exception e) { System.out.println("Parsing failed: " + e.getMessage()); } } } Expected Output: Parsed Date: Tue Jan 07 14:35:00 PST 2025 Incorporating Time Zones The setTimeZone method in SimpleDateFormat allows for explicit handling of different time zones. Here’s an example: import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; public class TimeZoneHandling { public static void main(String[] args) { SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); formatter.setTimeZone(TimeZone.getTimeZone("UTC")); System.out.println("UTC Time: " + formatter.format(new Date())); formatter.setTimeZone(TimeZone.getTimeZone("America/New_York")); System.out.println("New York Time: " + formatter.format(new Date())); } } Output Example: UTC Time: 2025-01-07 22:35:00 UTC New York Time: 2025-01-07 17:35:00 EST Locale-Aware Formatting The SimpleDateFormat class supports locale-specific date formatting. By specifying a Locale, you can adapt your application for different regions and languages: import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class LocaleFormatting { public static void main(String[] args) { Date today = new Date(); SimpleDateFormat usFormatter = new SimpleDateFormat("EEEE, MMMM dd, yyyy", Locale.US); SimpleDateFormat frFormatter = new SimpleDateFormat("EEEE, MMMM dd, yyyy", Locale.FRANCE); System.out.println("US Format: " + usFormatter.format(today)); System.out.println("French Format: " + frFormatter.format(today)); } } Output Example: US Format: Tuesday, January 07, 2025French Format: mardi, janvier 07, 2025 Working with Legacy Code For projects that rely on legacy systems, SimpleDateFormat can be instrumental in ensuring compatibility with older data formats. By crafting patterns that match the specific requirements of legacy systems, developers can seamlessly bridge modern and older systems. However, it is critical to perform rigorous testing when working with legacy code. Edge cases like leap years, daylight saving time adjustments, or unusual formats often surface in older implementations. Developers should document the specific formats being used and verify the results using multiple test cases. In certain situations, refactoring legacy systems to use modern libraries like DateTimeFormatter may offer long-term benefits. While it might require upfront effort, the enhanced performance and reduced bug risk in newer libraries often justify the transition. Date Validation Techniques Validating dates is a common requirement in applications that accept user input. With SimpleDateFormat, you can ensure that input strings conform to the expected format before further processing. For example: import java.text.SimpleDateFormat; import java.util.Date; public class DateValidation { public static void main(String[] args) { String dateString = "31-02-2025"; SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy"); formatter.setLenient(false); try { Date validatedDate = formatter.parse(dateString); System.out.println("Valid Date: " + validatedDate); } catch (Exception e) { System.out.println("Invalid Date: " + e.getMessage()); } } } Output Example: Invalid Date: Unparseable date: "31-02-2025" Using the setLenient(false) method ensures that only logically valid dates are accepted, reducing the risk of errors in downstream processes. Common Pitfalls and Recommendations Thread Safety: Avoid sharing SimpleDateFormat instances across multiple threads. Use ThreadLocal or Java’s DateTimeFormatter for thread-safe alternatives. Error Handling: Always handle potential ParseException errors when parsing strings. ISO Standards: Utilize ISO 8601 formats (e.g., yyyy-MM-dd'T'HH:mm:ss'Z') for better interoperability. Dynamic Time Zones: Refrain from hardcoding time zones; instead, fetch them dynamically when necessary. Input Validation: Before parsing, make sure the input strings have the correct format. Transitioning to Modern Alternatives With the introduction of the java.time package in Java 8, many developers prefer DateTimeFormatter over SimpleDateFormat for its enhanced features and thread safety. import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class ModernDateFormatting { public static void main(String[] args) { LocalDateTime currentDateTime = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); System.out.println("Formatted Date-Time: " + currentDateTime.format(formatter)); } } Sample Output: Formatted Date-Time: 2025-01-07 14:35:00 Additionally, DateTimeFormatter supports advanced features such as optional sections, localized styles, and predefined constants for ISO-compliant formats. These features make it a more versatile and robust choice for modern applications. Final Thoughts SimpleDateFormat remains a practical choice for date and time handling in Java. However, it is essential to understand its limitations, especially in terms of thread safety and modern compatibility. By adopting best practices and considering newer options like DateTimeFormatter, you can ensure robust and efficient date manipulation in your Java applications. Whether you are working with legacy systems, processing user inputs, or developing new features, a thorough knowledge of Java's date formatting features will empower you to handle date and time operations with confidence and precision. In addition, check out our app platform to find cloud apps, such as React, Angular, Vue and more.
15 January 2025 · 8 min to read
Java

Java main() Method

The main method in Java is a fundamental component for any standalone application. It serves as the entry point where the program begins execution. Understanding the syntax and functionality of this method is crucial for every Java programmer. This article delves into the specifics of the main method, explaining its syntax, the role of command-line arguments, and providing practical examples to illustrate its use. Syntax Explanation: public static void main(String[] args) The Java main method is defined as: public static void main(String[] args) Breaking down the syntax: public: This is an access modifier, meaning the method is accessible from anywhere. static: This keyword means the method belongs to the class rather than an instance of the class. It can be invoked without creating an object of the class. void: This signifies that the method does not return any value. main: This is the name of the method. The Java runtime environment looks for this specific method signature to start execution. String[] args: This parameter is an array of String objects, which stores command-line arguments passed to the program. Understanding the String[] args Parameter The String[] args parameter in the main method is crucial for passing information to the program at runtime. Each element in this array is a command-line argument provided when the program is executed. For example, if a program is run with java MyClass arg1 arg2, args[0] would be arg1 and args[1] would be arg2. public class MyClass { public static void main(String[] args) { for (String arg : args) { System.out.println(arg); } } In the above code, each command-line argument passed to the program is printed to the console. How to Use Command-Line Arguments Command-line arguments allow users to pass data into a Java program at the time of execution, making the program more flexible and dynamic. Here's a simple example of how to use command-line arguments in a Java program: public class SumArgs { public static void main(String[] args) { int sum = 0; for (String arg : args) { sum += Integer.parseInt(arg); } System.out.println("Sum: " + sum); } } If this program is run with java SumArgs 10 20 30, it will output Sum: 60. This demonstrates how command-line arguments can be used to input data without modifying the program's source code. Typical Use Cases and Examples Configuration Parameters: Command-line arguments are often used to pass configuration settings to a program. For example, specifying the path to a configuration file or setting a debug mode. public class ConfigLoader { public static void main(String[] args) { if (args.length > 0) { String configFilePath = args[0]; System.out.println("Loading configuration from: " + configFilePath); // Load and process the configuration file } else { System.out.println("No configuration file path provided."); } } } File Processing: Programs that perform operations on files often accept file paths as command-line arguments. public class FilePrinter { public static void main(String[] args) { if (args.length > 0) { String filePath = args[0]; // Code to read and print file content } else { System.out.println("Please provide a file path."); } } } Testing and Debugging: Command-line arguments can be used to quickly test and debug different scenarios by changing the input parameters without altering the source code. Conclusion The public static void main(String[] args) method is an essential part of any Java application. Its syntax and functionality are designed to provide a standardized entry point for program execution. Understanding how to leverage String[] args for command-line arguments can significantly enhance the flexibility and utility of your programs. By mastering the main method, Java developers can build more dynamic and configurable applications, making their code more versatile and easier to manage.
31 July 2024 · 4 min to read

Do you have questions,
comments, or concerns?

Our professionals are available to assist you at any moment,
whether you need help or are just unsure of where to start.
Email us
Hostman's Support