Facebook Pixel
Searching...
English
EnglishEnglish
EspañolSpanish
简体中文Chinese
FrançaisFrench
DeutschGerman
日本語Japanese
PortuguêsPortuguese
ItalianoItalian
한국어Korean
РусскийRussian
NederlandsDutch
العربيةArabic
PolskiPolish
हिन्दीHindi
Tiếng ViệtVietnamese
SvenskaSwedish
ΕλληνικάGreek
TürkçeTurkish
ไทยThai
ČeštinaCzech
RomânăRomanian
MagyarHungarian
УкраїнськаUkrainian
Bahasa IndonesiaIndonesian
DanskDanish
SuomiFinnish
БългарскиBulgarian
עבריתHebrew
NorskNorwegian
HrvatskiCroatian
CatalàCatalan
SlovenčinaSlovak
LietuviųLithuanian
SlovenščinaSlovenian
СрпскиSerbian
EestiEstonian
LatviešuLatvian
فارسیPersian
മലയാളംMalayalam
தமிழ்Tamil
اردوUrdu
Listen

Key Takeaways

1. Static Factories Over Constructors: Naming, Control, and Flexibility

Providing a static factory method instead of a public constructor has both advantages and disadvantages.

Named Advantages. Static factory methods offer descriptive names, unlike constructors, enhancing code readability and usability. This is especially useful when constructor parameters don't clearly indicate the object being created. For example, a method named BigInteger.probablePrime() is more descriptive than a constructor BigInteger(int, int, Random).

Instance Control. Static factories aren't obligated to create new objects on each invocation, allowing immutable classes to cache instances and avoid unnecessary object creation. This can significantly improve performance, especially for frequently requested objects. The Boolean.valueOf(boolean) method exemplifies this, as it always returns the same Boolean.TRUE or Boolean.FALSE instances.

Subtype Flexibility. Static factories can return objects of any subtype of their return type, enabling APIs to return objects without making their classes public. This promotes a compact API and allows for varying the returned class based on input parameters or even across different releases, enhancing maintainability and flexibility. This is the basis of service provider frameworks like the Java Cryptography Extension (JCE).

2. Singleton Implementation: Private Constructors and Public Access

A singleton is simply a class that is instantiated exactly once [Gamma98, p. 127].

Ensuring Uniqueness. Singletons, representing unique system components, are enforced by making the constructor private and providing a public static member for access. This guarantees only one instance exists. There are two common approaches: using a final field or a static factory method.

Final Field Approach. The public static member is a final field initialized with the singleton instance. This approach is straightforward and makes it clear that the class is a singleton. For example:

  • public static final Elvis INSTANCE = new Elvis();

Static Factory Approach. A public static factory method returns the singleton instance. This offers flexibility to change the implementation without altering the API. For example:

  • public static Elvis getInstance() { return INSTANCE; }

Serialization Consideration. To maintain the singleton property during serialization, a readResolve method must be provided to return the existing instance, preventing the creation of new instances upon deserialization. This ensures that the singleton remains unique even after serialization and deserialization.

3. Utility Classes: Enforcing Non-Instantiability

Such utility classes were not designed to be instantiated: An instance would be nonsensical.

Purpose of Utility Classes. Utility classes, grouping static methods and fields, serve to organize related functionalities on primitive values, arrays, or objects implementing specific interfaces. They are not designed for instantiation, as instances would be meaningless. Examples include java.lang.Math and java.util.Arrays.

Preventing Instantiation. To enforce non-instantiability, a simple idiom involves including a single, explicit private constructor. This prevents the compiler from generating a public, parameterless default constructor, ensuring that the class cannot be instantiated from outside.

Side Effects. This idiom also prevents subclassing, as subclasses would lack an accessible constructor to invoke the superclass constructor. It's wise to include a comment explaining the purpose of the private constructor, as it might seem counterintuitive.

4. Object Reuse: Efficiency and Immutability

It is often appropriate to reuse a single object instead of creating a new functionally equivalent object each time it is needed.

Benefits of Reuse. Reusing objects, especially immutable ones, is more efficient and stylish than creating new ones repeatedly. This is because object creation and garbage collection can be expensive, especially for heavyweight objects. For example, using String s = "silly"; is better than String s = new String("silly");

Immutable Objects. Immutable objects can always be reused safely. For example, Boolean.valueOf(String) is preferable to Boolean(String) because the former may reuse existing instances.

Mutable Objects. Mutable objects can be reused if they are known not to be modified. For example, using static initialization to create Calendar and Date instances once, rather than creating them every time a method is invoked, can significantly improve performance.

5. Memory Management: Eliminating Obsolete References

An obsolete reference is simply a reference that will never be dereferenced again.

Memory Leaks. Memory leaks in garbage-collected languages occur when object references are unintentionally retained, preventing garbage collection. This can lead to reduced performance, increased memory footprint, and even OutOfMemoryError.

Identifying Memory Leaks. A common source of memory leaks is classes that manage their own memory, such as a Stack class. When an element is freed, any object references contained in the element should be nulled out.

Solutions. To prevent memory leaks, null out references once they become obsolete. Also, be mindful of caches, which can retain object references long after they become irrelevant. Use WeakHashMap for caches where entries are relevant only as long as there are references to their keys outside the cache.

6. Equals Contract: Reflexivity, Symmetry, and Transitivity

The equals method implements an equivalence relation.

Equivalence Relation. When overriding the equals method, it's crucial to adhere to its general contract, which defines an equivalence relation. This contract includes reflexivity (x.equals(x) must return true), symmetry (x.equals(y) must return true if and only if y.equals(x) returns true), and transitivity (if x.equals(y) and y.equals(z) are true, then x.equals(z) must be true).

Symmetry Violation. Violating symmetry can lead to unpredictable behavior when objects are used in collections. For example, a CaseInsensitiveString class that attempts to interoperate with ordinary strings can violate symmetry.

Transitivity Violation. Violating transitivity can occur when extending an instantiable class and adding an aspect that affects equals comparisons. To avoid this, favor composition over inheritance.

Non-nullity. The equals method must return false for any non-null reference value x when x.equals(null) is invoked. The instanceof operator handles this automatically.

7. HashCode Override: Consistency with Equals

You must override hashCode in every class that overrides equals.

Importance of HashCode. Overriding hashCode is essential when overriding equals to ensure that equal objects have equal hash codes. Failure to do so violates the general contract for Object.hashCode and prevents the class from functioning properly in hash-based collections like HashMap and HashSet.

Legal but Poor Hash Function. A trivial hashCode method that always returns the same value is legal but results in poor performance for hash tables, as all objects hash to the same bucket.

Recipe for a Good Hash Function. A good hash function tends to produce unequal hash codes for unequal objects. A simple recipe involves:

  • Initializing an int variable result to a constant nonzero value (e.g., 17).
  • For each significant field f in the object, compute a hash code c and combine it into result using result = 37 * result + c;.
  • Return result.

8. ToString Override: Concise and Informative Representation

The toString method is automatically invoked when your object is passed to println, the string concatenation operator (+), or, as of release 1.4, assert.

Importance of ToString. Overriding the toString method provides a concise and informative string representation of an object, making the class more pleasant to use. This is especially useful for debugging and logging.

Content of ToString. When practical, the toString method should return all of the interesting information contained in the object. If the object is large or contains state that is not conducive to string representation, a summary should be returned.

Specifying Format. When implementing toString, decide whether to specify the format of the return value in the documentation. Specifying the format provides a standard, unambiguous representation but limits flexibility for future changes. If you specify the format, provide a matching String constructor or static factory.

9. Cloneable Interface: Implement Judiciously

The Cloneable interface was intended as a mixin interface (Item 16) for objects to advertise that they permit cloning.

Flaws of Cloneable. The Cloneable interface lacks a clone method, and Object's clone method is protected. This makes it difficult to invoke the clone method on an object merely because it implements Cloneable.

Clone Contract. The general contract for the clone method is weak, stating that it creates and returns a copy of the object. However, it does not specify how this copy should be made or whether constructors should be called.

Implementing Cloneable. To implement Cloneable properly, a class must override the clone method with a public method that first calls super.clone and then fixes any fields that need fixing. This typically means copying any mutable objects that comprise the internal "deep structure" of the object.

Alternatives to Cloneable. A fine approach to object copying is to provide a copy constructor or static factory. These approaches do not rely on a risk-prone extralinguistic object creation mechanism and do not conflict with the proper use of final fields.

10. Comparable Interface: Natural Ordering

By implementing Comparable, a class indicates that its instances have a natural ordering.

Benefits of Comparable. Implementing the Comparable interface allows a class to interoperate with generic algorithms and collection implementations that depend on ordering, such as Arrays.sort and TreeSet.

CompareTo Contract. The general contract for the compareTo method is similar to that of the equals method, requiring reflexivity, transitivity, and symmetry. It is strongly recommended that (x.compareTo(y)==0) == (x.equals(y)).

Writing a CompareTo Method. When writing a compareTo method, compare object reference fields by invoking the compareTo method recursively. Compare primitive fields using the relational operators < and >. If a class has multiple significant fields, compare them in order of significance.

11. Minimize Accessibility: Information Hiding

The single most important factor that distinguishes a well-designed module from a poorly designed one is the degree to which the module hides its internal data and other implementation details from other modules.

Benefits of Information Hiding. Information hiding, or encapsulation, decouples modules, allowing them to be developed, tested, optimized, and modified independently. This speeds up system development, eases maintenance, increases software reuse, and decreases risk.

Access Levels. The Java programming language provides access modifiers (private, package-private, protected, and public) to aid information hiding. The rule of thumb is to make each class or member as inaccessible as possible.

Public Fields. Public classes should rarely, if ever, have public fields (as opposed to public methods). The exception is for public static final fields containing primitive values or references to immutable objects.

12. Favor Immutability: Simplicity and Thread Safety

An immutable class is simply a class whose instances cannot be modified.

Advantages of Immutability. Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure.

Rules for Immutability. To make a class immutable:

  • Don't provide any methods that modify the object (mutators).
  • Ensure that no methods may be overridden (make the class final or use private constructors and static factories).
  • Make all fields final.
  • Make all fields private.
  • Ensure exclusive access to any mutable components (make defensive copies).

Disadvantages of Immutability. The only real disadvantage of immutable classes is that they require a separate object for each distinct value. To cope with this, provide public static final constants for frequently used values and consider providing a public mutable companion class.

Last updated:

Review Summary

4.51 out of 5
Average of 7k+ ratings from Goodreads and Amazon.

Effective Java is highly regarded as an essential read for Java developers, praised for its clear explanations and practical advice. Readers appreciate Bloch's expertise and the book's focus on writing high-quality, maintainable code. Many consider it a must-read for both beginners and experienced programmers. The book covers various Java features, best practices, and common pitfalls. While some find certain sections outdated or too advanced, most agree that it significantly improves coding skills and understanding of Java's intricacies.

Your rating:

About the Author

Joshua Bloch is a renowned computer scientist and author specializing in Java programming. He has made significant contributions to the Java language and its libraries, including designing and implementing the Java Collections Framework. Bloch's expertise stems from his extensive experience in software development and his involvement in shaping modern Java features. His writing style is praised for being clear, precise, and thorough, making complex concepts accessible to readers. Bloch's work, particularly "Effective Java," has become a cornerstone reference for Java developers worldwide, offering invaluable insights into best practices and effective programming techniques.

Download PDF

To save this Effective Java summary for later, download the free PDF. You can print it out, or read offline at your convenience.
Download PDF
File size: 0.29 MB     Pages: 13

Download EPUB

To read this Effective Java summary on your e-reader device or app, download the free EPUB. The .epub digital book format is ideal for reading ebooks on phones, tablets, and e-readers.
Download EPUB
File size: 2.97 MB     Pages: 13
0:00
-0:00
1x
Dan
Andrew
Michelle
Lauren
Select Speed
1.0×
+
200 words per minute
Create a free account to unlock:
Requests: Request new book summaries
Bookmarks: Save your favorite books
History: Revisit books later
Ratings: Rate books & see your ratings
Try Full Access for 7 Days
Listen, bookmark, and more
Compare Features Free Pro
📖 Read Summaries
All summaries are free to read in 40 languages
🎧 Listen to Summaries
Listen to unlimited summaries in 40 languages
❤️ Unlimited Bookmarks
Free users are limited to 10
📜 Unlimited History
Free users are limited to 10
Risk-Free Timeline
Today: Get Instant Access
Listen to full summaries of 73,530 books. That's 12,000+ hours of audio!
Day 4: Trial Reminder
We'll send you a notification that your trial is ending soon.
Day 7: Your subscription begins
You'll be charged on Feb 27,
cancel anytime before.
Consume 2.8x More Books
2.8x more books Listening Reading
Our users love us
50,000+ readers
"...I can 10x the number of books I can read..."
"...exceptionally accurate, engaging, and beautifully presented..."
"...better than any amazon review when I'm making a book-buying decision..."
Save 62%
Yearly
$119.88 $44.99/year
$3.75/mo
Monthly
$9.99/mo
Try Free & Unlock
7 days free, then $44.99/year. Cancel anytime.
Settings
Appearance
Black Friday Sale 🎉
$20 off Lifetime Access
$79.99 $59.99
Upgrade Now →