Fundamentals 21 min read

27 Essential Java Programming Principles from Effective Java

This article distills the classic "Effective Java" book into twenty‑seven practical programming principles covering object construction, static factory methods, builders, enums for singletons, resource management, equals/hashCode, generics, method design, exception handling, and general coding best practices, providing clear guidance for Java developers.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
27 Essential Java Programming Principles from Effective Java
❝ Think, output, and solidify. Use plain language to describe technology so both you and others can benefit. Author: Yi Hang 😜 ❞

Preface

Effective Java is a classic work in the Java development field, worth every Java developer's study. The book provides more than ninety valuable programming principles, ideal for programmers who already have some Java experience and wish to deepen their understanding.

This article condenses the original ninety‑plus principles and selects twenty‑seven that have clear guidance value, hoping these experiences help you write better code.

Object Construction

Consider Static Factory Methods Instead of Constructors

Static factory methods have the following advantages over constructors:

They have a definite name. For example, BigInteger.probablePrime is clearer than BigInteger(int, int, Random) , because the name indicates it returns a probable prime.

They do not need to create a new object on each call. For instance, Boolean.valueOf(boolean) returns a cached instance.

They do not require the caller to know the concrete class in advance. A typical example is JDBC , where DriverManager.getConnection returns a connection implementation supplied by the specific database driver.

When a Constructor Has Many Parameters, Prefer the Builder Pattern

If a constructor has many optional parameters, providing numerous overloads becomes cumbersome and reduces readability. The Builder pattern solves this problem by allowing incremental construction of complex objects.

Implement Singleton with an Enum

In Java, using an enum to implement a singleton provides a simpler, safer, and more reliable way to create a single instance. Its advantages include:

Thread safety : Enum instances are inherently thread‑safe.

Protection against reflection : Enums cannot be instantiated via reflection, preventing singleton bypass.

Serialization safety : Enums are serializable by default, and you can implement readResolve to prevent new instances during deserialization.

Avoid Creating Unnecessary Objects and Clear Stale References Promptly

Do not create objects that are not needed. If an object is immutable, it can be reused.

For example, the following statement creates two String objects:

String s = new String("bikini");

Whereas the alternative creates only one:

String s = "bikini";

Java may also suffer from memory leaks, so stale references should be cleared to avoid such leaks.

Prefer try‑with‑resources Over try‑finally

In Java, try‑with‑resources (automatic resource management) is generally superior to the traditional try‑finally block for three key reasons:

Simplicity and readability : You declare and initialize resources directly in the try statement without a separate finally block.

Automatic resource closing : Resources are automatically closed when the try block exits, reducing the risk of leaks.

Exception handling : Exceptions are propagated correctly while still ensuring resources are closed.

Overall, try‑with‑resources provides a simpler, more readable, safer, and more powerful resource‑management mechanism, and is usually preferred over try‑finally .

Common Object Methods

Override equals and hashCode Together

In Java, the relationship between equals() and hashCode() is crucial because it directly affects how objects behave in collections such as HashSet and HashMap . When you override equals() , remember to also override hashCode() .

Interfaces and Classes

Control Access Scope

The key factor in good component design is whether it hides its internal data and implementation details from other components. Well‑designed components encapsulate all implementation details, which is a fundamental principle of software design.

Composition Over Inheritance

Composition reduces coupling between classes, making code more flexible and easier to maintain, which benefits flexibility, maintainability, and extensibility.

Prefer Interfaces Over Abstract Classes, Use Them Only to Define Types

Interfaces provide greater flexibility and loose coupling, allowing shared behavior definitions across different classes. Their primary role is to define a contract, making them ideal for type definition.

Class Hierarchies Are Better Than Tagged Classes

Class hierarchies have several advantages:

Clarity and readability : Relationships are explicit and easy to understand.

Type safety : Stronger compile‑time checks reduce runtime errors.

Extensibility : New functionality can be added by creating new subclasses.

Code reuse : Subclasses inherit attributes and methods from superclasses.

Maintainability : Clear responsibilities make locating and modifying code easier.

Testability : Well‑defined interfaces simplify unit testing.

In short, class hierarchies improve readability, maintainability, and extensibility while reducing type errors.

Generics

Prefer Using Generics

Using generics in Java provides type safety, code reuse, readability, and performance benefits. Generics reduce programming errors, improve tool support, and serve as self‑documenting code, thereby enhancing overall code quality.

Use Bounded Wildcards to Increase API Flexibility

Bounded wildcards allow writing more generic code that works with a range of types. They come in two forms:

Upper‑bounded wildcard ( <? extends T> ): Accepts T and its subclasses. Example: public double sum(List numbers) { double total = 0; for (Number number : numbers) { total += number.doubleValue(); } return total; }

Lower‑bounded wildcard ( <? super T> ): Accepts T and its supertypes. Example: public void addIntegers(List list) { list.add(1); list.add(2); }

Bounded wildcards make APIs more flexible while preserving type safety.

Methods

Validate Method Parameters

Always be skeptical of incoming parameters; validate them to avoid null references and other common errors.

Return Empty Arrays or Collections Instead of null

Returning null forces callers to perform null checks, which is error‑prone. Prefer returning zero‑length arrays or collections, as demonstrated by MyBatis returning an empty list when no data is found.

Document All Exported Methods

Write documentation comments promptly! Important things should be said three times!

Design Method Signatures Carefully

A good method signature helps quickly understand its purpose. Effective Java recommends choosing clear method names, avoiding long parameter lists, and preferring interfaces or superclasses for parameter types.

Choose method names carefully.

Avoid long parameter lists.

Prefer interfaces or superclasses over concrete types for parameters.

(For more details, see the author's previous post on code refactoring.)

Exception Handling

Use Checked Exceptions for Recoverable Situations, Runtime Exceptions for Programming Errors

Java provides three throwable categories: checked exceptions, runtime exceptions, and errors. Use checked exceptions when callers can reasonably recover; otherwise, throw a runtime exception.

Prefer Standard Exceptions

Standard JDK exceptions (e.g., IllegalArgumentException , IllegalStateException , NullPointerException , IndexOutOfBoundsException , ConcurrentModificationException , UnsupportedOperationException ) cover most API error cases, reduce memory footprint, and improve performance.

Throw Exceptions That Match the Abstraction Level

Wrapping low‑level exceptions in unrelated runtime exceptions obscures the cause. For example: public void demo() { try { io.read(); } catch (IOException ex) { throw new RuntimeException(); } } This hides the original IOException and makes debugging harder.

Include Failure Details in Exception Messages

When an uncaught exception causes a failure, record the stack trace and request parameters to facilitate rapid problem diagnosis.

General Programming Practices

Minimize Variable Scope

Declare a variable at its first point of use. Declaring it earlier adds unnecessary noise and reduces readability.

Prefer Enhanced for‑each Loops Over Traditional for Loops

The enhanced for loop ("for‑each") hides iterator or index handling, eliminating many sources of bugs.

Avoid float and double for Precise Calculations

float and double are designed for scientific and engineering calculations using binary floating‑point arithmetic, which cannot represent decimal fractions like 0.1 exactly. They are unsuitable for precise domains such as monetary calculations; use BigDecimal instead.

Avoid String When a More Appropriate Type Exists

Strings should represent textual data only. For numeric data, convert to int , float , BigInteger , etc. For boolean answers, use boolean . Prefer the most suitable type whenever possible.

If an appropriate value type exists—whether primitive or object reference—it should be used; otherwise, define a suitable type.

Reference Objects via Interfaces

Prefer using interfaces rather than concrete classes when declaring variables, parameters, or return types. This enhances flexibility.

Follow Common Naming Conventions

Package and module names should be hierarchical, lower‑case, and optionally include numbers; they often start with the organization’s domain.

Class and interface names (including enums and annotations) should be in PascalCase.

Method and field names follow camelCase, starting with a lower‑case letter.

Conclusion

The book contains many more valuable insights; this article lists the twenty‑seven recommendations the author considers most important, hoping they prove helpful.

❝ If you found this useful, consider bookmarking; as the saying goes, bookmarking equals learning. ❞
design patternsJavaBest Practicesprogramming principlesEffective Java
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.