Repository files navigation
Part 1: Creating and Destroying Objects
Consider static factory methods instead of constructors : Use static methods for better flexibility and
readability.
Consider a builder when faced with many constructor parameters : Simplify complex object creation using the
builder pattern.
Enforce the singleton property with a private constructor or an enum : Use singletons for classes requiring a
single instance.
Enforce noninstantiability with a private constructor : Prevent instantiation of utility classes.
Prefer dependency injection to hardwiring resources : Decouple classes from dependencies using injection.
Avoid creating unnecessary objects : Reuse objects to reduce overhead.
Eliminate obsolete object references : Avoid memory leaks by cleaning up unused references.
Avoid finalizers and cleaners : Use try-with-resources or close methods for resource management.
Prefer try-with-resources to try-finally : Automatically manage resources using try-with-resources.
Part 2: Methods Common to All Objects
Override equals judiciously : Ensure equals adheres to reflexivity, symmetry, transitivity, consistency, and
null checks.
Always override hashCode when you override equals : Ensure equal objects have consistent hash codes.
Always override toString : Provide a clear and informative string representation of objects.
Override clone judiciously : Prefer copy constructors or static factory methods over clone.
Consider implementing Comparable : Define a natural order for your objects.
Part 3: Classes and Interfaces
Minimize the accessibility of classes and members : Use encapsulation to reduce exposure.
In public classes, use accessor methods, not public fields : Preserve flexibility by using getter and setter
methods.
Minimize mutability : Favor immutable objects for simplicity and thread safety.
Favor composition over inheritance : Reduce fragility by composing objects instead of inheriting.
Design and document for inheritance or else prohibit it : Clearly define how inheritance should be used or
prevent it.
Prefer interfaces to abstract classes : Interfaces provide greater flexibility for multiple implementations.
Design interfaces for posterity : Ensure interfaces are forward-compatible and extensible.
Use interfaces only to define types : Avoid constant interfaces.
Prefer class hierarchies to tagged classes : Use polymorphism instead of type codes.
Favor static member classes over nonstatic : Reduce coupling with static nested classes.
Limit source files to a single top-level class : Simplify file organization and reduce conflicts.
Don’t use raw types : Always specify type parameters for type safety.
Eliminate unchecked warnings : Resolve or suppress unchecked warnings for cleaner code.
Prefer lists to arrays : Lists are type-safe, whereas arrays are not.
Favor generic types : Use generics to create reusable and type-safe classes.
Favor generic methods : Use generics to create flexible and type-safe methods.
Use bounded wildcards to increase API flexibility : Support more use cases with ? extends and ? super.
Combine generics and varargs judiciously : Avoid heap pollution when combining generics with varargs.
Consider typesafe heterogeneous containers : Use generics and Class objects to create flexible containers.
Part 5: Enums and Annotations
Use enums instead of int constants : Enums are safer, more expressive, and more powerful.
Use instance fields instead of ordinals : Avoid using ordinal to store data; use fields instead.
Use EnumSet instead of bit fields : Simplify set operations with EnumSet.
Use EnumMap instead of ordinal indexing : Use EnumMap for mapping enums to values.
Emulate extensible enums with interfaces : Combine enums with interfaces for extensibility.
Prefer annotations to naming patterns : Use annotations for metadata and behavior tagging.
Consistently use the @Override annotation : Prevent errors by ensuring methods override superclass methods.
Use marker interfaces to define types : Marker interfaces provide compile-time type information.
Part 6: Lambdas and Streams
Prefer lambdas to anonymous classes : Lambdas are more concise and readable.
Prefer method references to lambdas : Use method references for clarity and simplicity.
Favor the use of standard functional interfaces : Use Function, Supplier, Consumer, etc., for common tasks.
Use streams judiciously : Streams are powerful but can reduce clarity for simple cases.
Prefer side-effect-free functions in streams : Avoid modifying external state in stream operations.
Prefer Collection to Stream as a return type : Collections offer more flexibility than streams.
Use caution when making streams parallel : Ensure thread safety and avoid performance pitfalls in parallel
streams.
Check parameters for validity : Validate inputs to avoid runtime errors.
Make defensive copies when needed : Protect internal state by copying mutable inputs and outputs.
Design method signatures carefully : Choose method names and parameter types thoughtfully.
Use overloading judiciously : Avoid ambiguous overloaded methods.
Use varargs judiciously : Validate input and avoid excessive use of varargs.
Return empty collections or arrays, not nulls : Simplify client code by avoiding null returns.
Return Optionals judiciously : Use Optional for potentially absent values, but not excessively.
Part 8: General Programming
Adhere to generally accepted naming conventions : Follow consistent naming standards for clarity.
Avoid unnecessary use of checked exceptions : Use unchecked exceptions for programming errors.
Favor the use of standard exceptions : Use exceptions like IllegalArgumentException for common scenarios.
Throw exceptions appropriate to the abstraction : Avoid exposing implementation details in exceptions.
Document all exceptions thrown by each method : Help users handle exceptions properly.
Include failure-capture information in exceptions : Provide detailed information in exception messages.
Strive for failure atomicity : Ensure operations leave objects in a consistent state on failure.
Don’t ignore exceptions : Always handle exceptions appropriately.
Consider using APIs over handwritten code : Leverage standard libraries for efficiency and maintainability.
Refer to objects by their interfaces : Use interfaces for flexibility and better design.
Prefer interfaces to reflection : Avoid reflection unless necessary for dynamic behavior.
Use native methods judiciously : Prefer Java code over native methods for portability and maintainability.
Optimize judiciously : Optimize only when performance is a verified issue.
Adhere to the general contract when overriding equals : Ensure consistent behavior in equality checks.
Always override hashCode when you override equals : Ensure consistent behavior in hash-based collections.
Always override toString : Provide meaningful string representations for debugging and logging.
Override clone judiciously : Prefer alternatives to clone for copying objects.
Implement Serializable judiciously : Serialization introduces risks; consider alternatives.
Prefer atomic variables to synchronized variables : Use atomic variables for better performance and clarity.
Avoid excessive synchronization : Minimize synchronized blocks to reduce contention.
Prefer executors and tasks to threads : Use the Executor framework for scalable concurrency.
Use concurrent collections judiciously : Prefer ConcurrentHashMap and other utilities over manual
synchronization.
Document thread safety : Clearly indicate if a class is thread-safe, not thread-safe, or conditionally
thread-safe.
Avoid thread-local variables when possible : Use sparingly to avoid memory leaks and complexity.
Part 12: General Good Practices
Avoid using Java serialization : Prefer modern serialization frameworks like JSON or Protocol Buffers.
Use dependency injection judiciously : Inject dependencies for better decoupling.
Avoid reflection for performance and security : Use reflection sparingly to avoid performance and maintainability
issues.
Follow Java conventions for serialization : Follow established practices when implementing Serializable.
Prefer enums to singletons : Enums are the best way to implement singletons.
Part 13: Additional Best Practices
Use lazy initialization judiciously : Avoid premature or excessive use of lazy initialization.
Minimize scope of local variables : Declare variables in the narrowest possible scope.
Prefer immutable objects : Reduce complexity with immutable classes.
Avoid instance pooling : Favor object creation for simplicity and performance.
Document thread safety assumptions : Ensure proper usage by explicitly documenting thread safety.
Part 14: Concurrency and Performance
Use dependency injection to improve flexibility : Decouple dependencies for easier testing and maintenance.
Avoid excessive synchronization : Overuse of synchronized blocks can degrade performance and cause deadlocks.
Prefer concurrency utilities to low-level synchronization : Use frameworks like java.util.concurrent.
Be cautious with parallel streams : Ensure proper thread safety when using parallel streams.
Part 15: General Practices
Use enums for singletons : Simplify singleton design by using enums.
Use atomic variables instead of locks : For single variables, atomic classes are simpler and faster.
Use ThreadLocal variables with care : Avoid misuse to prevent memory leaks and unnecessary complexity.
Avoid Java serialization : Serialization introduces many risks; consider safer alternatives like JSON or Protocol
Buffers.
Prefer alternatives to clone : Use copy constructors or factory methods instead of clone.
Use interfaces for types : Always prefer interfaces for type definition and flexibility.
Avoid raw types : Use generics for type-safe and maintainable code.
Prefer immutability : Immutable objects are simpler, safer, and easier to use in multithreaded environments.
About
My study guide book for Effective Java
Topics
Resources
Stars
Watchers
Forks
You can’t perform that action at this time.