Best Practices

This page is a wrap-up checklist for the entire Core Java track: naming, encapsulation, exception handling, interfaces, loops vs. streams, equals/hashCode, and method design — habits worth carrying into every Java project you build from here on.

1) Naming Conventions

Consistent naming makes code predictable to read, even before you understand what it does.

public class OrderProcessor { // class: PascalCase

  private static final int MAX_RETRIES = 3; // constant: ALL_CAPS

  public void processOrder(Order pendingOrder) { // method & variable: camelCase
    // ...
  }
}
          
  • Classes and interfaces: PascalCase — OrderProcessor, Runnable.
  • Methods and variables: camelCase — processOrder, pendingOrder.
  • Constants (static final): ALL_CAPS with underscores — MAX_RETRIES.

2) Favor Encapsulation

Keep fields private and expose behavior through methods, rather than letting outside code reach in and change state directly. See Encapsulation.

public class BankAccount {
  private double balance; // private field - no direct outside access

  public void deposit(double amount) {
    if (amount > 0) this.balance += amount; // validated through a method
  }
}
          
  • Mark fields private by default; only widen access when there's a real reason to.
  • Validate input inside setters/methods instead of trusting whoever calls them.
  • Expose only what other code actually needs — not every field needs a getter and setter.

3) Handle Exceptions Meaningfully

Never catch an exception just to silence it. See Exception Handling.

try {
  Files.readAllLines(path);
} catch (IOException e) {
  logger.error("Failed to read config file", e); // logged, not swallowed
}
          
  • Never leave a catch block empty — at minimum, log what went wrong.
  • Catch the most specific exception type you can handle correctly; avoid blanket catch (Exception e).
  • Only catch an exception where you can actually do something useful about it.

4) Prefer Interfaces Where It Makes Sense

Coding against an interface, not a concrete class, keeps your code flexible to swap implementations later. See Interfaces.

List<String> names = new ArrayList<>(); // declared as the interface List
          
  • Declare variables and parameters using the interface type (List) instead of the concrete class (ArrayList) when the caller only needs the interface's behavior.
  • This lets you swap ArrayList for LinkedList later without touching calling code.
  • Don't force interfaces where there's genuinely only one sensible implementation — flexibility for its own sake adds noise.

5) Prefer Enhanced for-Loops and Streams for Clarity

Manual index-based loops are still useful, but readability usually wins when you don't need the index itself.

// Manual index loop - fine, but easy to get the bounds wrong
for (int i = 0; i < names.size(); i++) { /* ... */ }

// Enhanced for-loop - clearer when you just need each element
for (String name : names) { /* ... */ }
          
  • Use the enhanced for-loop (for (Type item : collection)) when you don't need the index.
  • Reach for streams (see Lambdas & Streams) when transforming or filtering data reads better as a pipeline.
  • Keep the manual index loop for cases where you genuinely need the index or fine control over iteration.

6) Override equals() and hashCode() Together

If you override one, override the other — hash-based collections like HashSet and HashMap rely on both being consistent.

@Override
public boolean equals(Object o) { /* compare fields */ }

@Override
public int hashCode() { /* derive from the same fields */ }
          
  • Two objects considered equals() must return the same hashCode().
  • Base both methods on the same set of fields to keep them consistent.
  • Skipping hashCode() after overriding equals() causes subtle bugs in HashMap/HashSet lookups.

7) Keep Methods Short and Focused

A method should do one thing and do it clearly — if you struggle to name it without using "and", it's probably doing too much.

// Doing too much: validating, saving, AND sending an email
public void validateSaveAndNotify(Order order) { /* ... */ }

// Better: three focused methods, one job each
public void validate(Order order) { /* ... */ }
public void save(Order order) { /* ... */ }
public void notifyCustomer(Order order) { /* ... */ }
          
  • Aim for one clear responsibility per method — this also makes methods easier to test on their own.
  • Short methods are easier to read, reuse, and reason about than long ones.
  • If a method's name needs "and" to describe it, consider splitting it.
AreaOne-line rule
NamingPascalCase for classes, camelCase for methods/variables, ALL_CAPS for constants.
EncapsulationKeep fields private; expose behavior, not raw state.
ExceptionsNever catch and ignore — log or handle meaningfully.
InterfacesCode against the interface type when flexibility is worth it.
Loops/streamsEnhanced for-loop or streams over manual indices when clarity wins.
equals/hashCodeOverride both together, based on the same fields.
MethodsOne responsibility per method; keep them short.
Congratulations! You've completed the Core Java track — from JVM basics and syntax all the way through OOP, collections, generics, multithreading, file I/O, lambdas, streams, enums, and wrapper classes. The Spring and Hibernate tracks (coming soon) build directly on these fundamentals. For now, click Next to carry that momentum into the Python tutorial.