Skip to content

MSaifAsif/effective-java-study

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 

Repository files navigation

Effective Java

Part 1: Creating and Destroying Objects

  1. Consider static factory methods instead of constructors: Use static methods for better flexibility and readability.
  2. Consider a builder when faced with many constructor parameters: Simplify complex object creation using the builder pattern.
  3. Enforce the singleton property with a private constructor or an enum: Use singletons for classes requiring a single instance.
  4. Enforce noninstantiability with a private constructor: Prevent instantiation of utility classes.
  5. Prefer dependency injection to hardwiring resources: Decouple classes from dependencies using injection.
  6. Avoid creating unnecessary objects: Reuse objects to reduce overhead.
  7. Eliminate obsolete object references: Avoid memory leaks by cleaning up unused references.
  8. Avoid finalizers and cleaners: Use try-with-resources or close methods for resource management.
  9. Prefer try-with-resources to try-finally: Automatically manage resources using try-with-resources.

Part 2: Methods Common to All Objects

  1. Override equals judiciously: Ensure equals adheres to reflexivity, symmetry, transitivity, consistency, and null checks.
  2. Always override hashCode when you override equals: Ensure equal objects have consistent hash codes.
  3. Always override toString: Provide a clear and informative string representation of objects.
  4. Override clone judiciously: Prefer copy constructors or static factory methods over clone.
  5. Consider implementing Comparable: Define a natural order for your objects.

Part 3: Classes and Interfaces

  1. Minimize the accessibility of classes and members: Use encapsulation to reduce exposure.
  2. In public classes, use accessor methods, not public fields: Preserve flexibility by using getter and setter methods.
  3. Minimize mutability: Favor immutable objects for simplicity and thread safety.
  4. Favor composition over inheritance: Reduce fragility by composing objects instead of inheriting.
  5. Design and document for inheritance or else prohibit it: Clearly define how inheritance should be used or prevent it.
  6. Prefer interfaces to abstract classes: Interfaces provide greater flexibility for multiple implementations.
  7. Design interfaces for posterity: Ensure interfaces are forward-compatible and extensible.
  8. Use interfaces only to define types: Avoid constant interfaces.
  9. Prefer class hierarchies to tagged classes: Use polymorphism instead of type codes.
  10. Favor static member classes over nonstatic: Reduce coupling with static nested classes.
  11. Limit source files to a single top-level class: Simplify file organization and reduce conflicts.

Part 4: Generics

  1. Don’t use raw types: Always specify type parameters for type safety.
  2. Eliminate unchecked warnings: Resolve or suppress unchecked warnings for cleaner code.
  3. Prefer lists to arrays: Lists are type-safe, whereas arrays are not.
  4. Favor generic types: Use generics to create reusable and type-safe classes.
  5. Favor generic methods: Use generics to create flexible and type-safe methods.
  6. Use bounded wildcards to increase API flexibility: Support more use cases with ? extends and ? super.
  7. Combine generics and varargs judiciously: Avoid heap pollution when combining generics with varargs.
  8. Consider typesafe heterogeneous containers: Use generics and Class objects to create flexible containers.

Part 5: Enums and Annotations

  1. Use enums instead of int constants: Enums are safer, more expressive, and more powerful.
  2. Use instance fields instead of ordinals: Avoid using ordinal to store data; use fields instead.
  3. Use EnumSet instead of bit fields: Simplify set operations with EnumSet.
  4. Use EnumMap instead of ordinal indexing: Use EnumMap for mapping enums to values.
  5. Emulate extensible enums with interfaces: Combine enums with interfaces for extensibility.
  6. Prefer annotations to naming patterns: Use annotations for metadata and behavior tagging.
  7. Consistently use the @Override annotation: Prevent errors by ensuring methods override superclass methods.
  8. Use marker interfaces to define types: Marker interfaces provide compile-time type information.

Part 6: Lambdas and Streams

  1. Prefer lambdas to anonymous classes: Lambdas are more concise and readable.
  2. Prefer method references to lambdas: Use method references for clarity and simplicity.
  3. Favor the use of standard functional interfaces: Use Function, Supplier, Consumer, etc., for common tasks.
  4. Use streams judiciously: Streams are powerful but can reduce clarity for simple cases.
  5. Prefer side-effect-free functions in streams: Avoid modifying external state in stream operations.
  6. Prefer Collection to Stream as a return type: Collections offer more flexibility than streams.
  7. Use caution when making streams parallel: Ensure thread safety and avoid performance pitfalls in parallel streams.

Part 7: Methods

  1. Check parameters for validity: Validate inputs to avoid runtime errors.
  2. Make defensive copies when needed: Protect internal state by copying mutable inputs and outputs.
  3. Design method signatures carefully: Choose method names and parameter types thoughtfully.
  4. Use overloading judiciously: Avoid ambiguous overloaded methods.
  5. Use varargs judiciously: Validate input and avoid excessive use of varargs.
  6. Return empty collections or arrays, not nulls: Simplify client code by avoiding null returns.
  7. Return Optionals judiciously: Use Optional for potentially absent values, but not excessively.

Part 8: General Programming

  1. Adhere to generally accepted naming conventions: Follow consistent naming standards for clarity.
  2. Avoid unnecessary use of checked exceptions: Use unchecked exceptions for programming errors.
  3. Favor the use of standard exceptions: Use exceptions like IllegalArgumentException for common scenarios.
  4. Throw exceptions appropriate to the abstraction: Avoid exposing implementation details in exceptions.
  5. Document all exceptions thrown by each method: Help users handle exceptions properly.
  6. Include failure-capture information in exceptions: Provide detailed information in exception messages.
  7. Strive for failure atomicity: Ensure operations leave objects in a consistent state on failure.
  8. Don’t ignore exceptions: Always handle exceptions appropriately.

Part 9: Performance

  1. Consider using APIs over handwritten code: Leverage standard libraries for efficiency and maintainability.
  2. Refer to objects by their interfaces: Use interfaces for flexibility and better design.
  3. Prefer interfaces to reflection: Avoid reflection unless necessary for dynamic behavior.
  4. Use native methods judiciously: Prefer Java code over native methods for portability and maintainability.
  5. Optimize judiciously: Optimize only when performance is a verified issue.

Part 10: Serialization

  1. Adhere to the general contract when overriding equals: Ensure consistent behavior in equality checks.
  2. Always override hashCode when you override equals: Ensure consistent behavior in hash-based collections.
  3. Always override toString: Provide meaningful string representations for debugging and logging.
  4. Override clone judiciously: Prefer alternatives to clone for copying objects.
  5. Implement Serializable judiciously: Serialization introduces risks; consider alternatives.

Part 11: Concurrency

  1. Prefer atomic variables to synchronized variables: Use atomic variables for better performance and clarity.
  2. Avoid excessive synchronization: Minimize synchronized blocks to reduce contention.
  3. Prefer executors and tasks to threads: Use the Executor framework for scalable concurrency.
  4. Use concurrent collections judiciously: Prefer ConcurrentHashMap and other utilities over manual synchronization.
  5. Document thread safety: Clearly indicate if a class is thread-safe, not thread-safe, or conditionally thread-safe.
  6. Avoid thread-local variables when possible: Use sparingly to avoid memory leaks and complexity.

Part 12: General Good Practices

  1. Avoid using Java serialization: Prefer modern serialization frameworks like JSON or Protocol Buffers.
  2. Use dependency injection judiciously: Inject dependencies for better decoupling.
  3. Avoid reflection for performance and security: Use reflection sparingly to avoid performance and maintainability issues.
  4. Follow Java conventions for serialization: Follow established practices when implementing Serializable.
  5. Prefer enums to singletons: Enums are the best way to implement singletons.

Part 13: Additional Best Practices

  1. Use lazy initialization judiciously: Avoid premature or excessive use of lazy initialization.
  2. Minimize scope of local variables: Declare variables in the narrowest possible scope.
  3. Prefer immutable objects: Reduce complexity with immutable classes.
  4. Avoid instance pooling: Favor object creation for simplicity and performance.
  5. Document thread safety assumptions: Ensure proper usage by explicitly documenting thread safety.

Part 14: Concurrency and Performance

  1. Use dependency injection to improve flexibility: Decouple dependencies for easier testing and maintenance.
  2. Avoid excessive synchronization: Overuse of synchronized blocks can degrade performance and cause deadlocks.
  3. Prefer concurrency utilities to low-level synchronization: Use frameworks like java.util.concurrent.
  4. Be cautious with parallel streams: Ensure proper thread safety when using parallel streams.

Part 15: General Practices

  1. Use enums for singletons: Simplify singleton design by using enums.
  2. Use atomic variables instead of locks: For single variables, atomic classes are simpler and faster.
  3. Use ThreadLocal variables with care: Avoid misuse to prevent memory leaks and unnecessary complexity.

Part 16: Final Notes

  1. Avoid Java serialization: Serialization introduces many risks; consider safer alternatives like JSON or Protocol Buffers.
  2. Prefer alternatives to clone: Use copy constructors or factory methods instead of clone.
  3. Use interfaces for types: Always prefer interfaces for type definition and flexibility.
  4. Avoid raw types: Use generics for type-safe and maintainable code.
  5. 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

Languages