diff --git a/src/effectivejava/chapter1/README.md b/src/effectivejava/chapter1/README.md new file mode 100644 index 00000000..e8bb131d --- /dev/null +++ b/src/effectivejava/chapter1/README.md @@ -0,0 +1,69 @@ +**Key Concepts from Chapter 1** + +1. Why This Book Matters? + + * Java is a powerful and widely used language, but writing high-quality Java code requires discipline and adherence to best practices. + * The book is structured around effective programming techniques, not just syntax. + * Experience-based guidelines help developers avoid common pitfalls. + +1. Principles of Writing Effective Java Code + + * Clarity, Simplicity, and Robustness: Good code is easy to read, maintain, and debug. + * Performance vs. Maintainability: Don't optimize prematurely; focus on clean and maintainable code first. + * Using the Right Tools: Java provides various utilities, frameworks, and libraries—using them properly leads to better code. + +1. Structure of the Book + +The book is divided into nine chapters, each focusing on a key area: + + - Creating and Destroying Objects + - Methods Common to All Objects + - Classes and Interfaces + - Generics + - Enums and Annotations + - Lambdas and Streams + - Methods + - General Programming + - Exceptions, Concurrency, and Serialization + + +1. Core Philosophy + + * Why best practices matter + * Importance of writing maintainable code + * Avoiding common pitfalls + +1. Principles of Effective Java + + * Clarity & Simplicity + * Maintainability & Performance + * Correct use of Java features + +1. Book Structure + + * Nine chapters covering key Java topics + * "Items" as best practices + * Real-world examples & recommendations + +1. How to Use the Book? + + * Learn by doing (experiment with concepts) + * Apply best practices in real-world projects + * Understand trade-offs in different programming choices + +**Practicing Chapter 1 Concepts** + +1. Reflect on Past Code + + * Look at old Java projects and identify areas where best practices were ignored. + * Think about how clarity, simplicity, and maintainability could be improved. + +1. Read Java Code from Open-Source Projects + + * Explore well-written Java projects on GitHub. + * Observe how experienced developers apply best practices. + +1. Prepare for the Next Chapters + + * Take note of areas where you struggle in Java. + * Keep a list of common pitfalls you encounter while coding. \ No newline at end of file diff --git a/src/effectivejava/chapter2/README.md b/src/effectivejava/chapter2/README.md new file mode 100644 index 00000000..c76ca10e --- /dev/null +++ b/src/effectivejava/chapter2/README.md @@ -0,0 +1,66 @@ +Summary of Key Takeaways + +| Item | Key Concept | +|---|-------------------------------------------------------------------------| +| 1 | Use static factory methods instead of constructors when possible. | +| 2 | Use the Builder Pattern for complex object creation. | +| 3 | Singletons should be implemented using private constructors or Enums. | +| 4 | Use a private constructor for utility classes. | +| 5 | Use Dependency Injection (DI) for better flexibility. | +| 6 | Avoid unnecessary object creation (prefer object reuse). | +| 7 | Nullify obsolete references to prevent memory leaks. | +| 8 | Avoid finalizers and cleaners (use explicit close methods). | +| 9 | Use try-with-resources instead of try-finally for resource management. | + +📌 **Creating and Destroying Objects** + +* 🏗️ **Item 1: Static Factory Methods** + * ✅ Better naming + * ✅ Can return cached objects + * ✅ Can return subtypes + * ❌ Cannot be subclassed + * 📝 Example: Boolean.valueOf(true) + +* 🏗️ **Item 2: Builder Pattern** + * ❌ Telescoping constructors are bad + * ❌ JavaBeans pattern is mutable + * ✅ Builder pattern is the best choice + * 📝 Example: NutritionFacts.Builder + +* 🔒 **Item 3: Singleton Patterns** + * ✅ Private static final instance + * ✅ Lazy initialization with synchronized + * ✅ Best approach: Enum Singleton + * 📝 Example: Singleton.INSTANCE + +* 🚫 **Item 4: Noninstantiability** + * ✅ Private constructor for utility classes + * ✅ Prevents instantiation + * 📝 Example: UtilityClass with AssertionError + +* 🔄 **Item 5: Dependency Injection** + * ❌ Hard-coded dependencies = Bad + * ✅ Constructor injection = Good + * ✅ More testable and flexible + * 📝 Example: Passing DatabaseConnection via constructor + +* ♻️ **Item 6: Avoid Unnecessary Objects** + * ❌ `new String("hello")` (bad) + * ✅ `String s = "hello";` (good) + * ✅ Use cached objects + * 📝 Example: Integer.valueOf(10) + +* 🗑️ **Item 7: Eliminate Obsolete References** + * ✅ Nullify objects after use + * ✅ Watch for memory leaks in collections + * 📝 Example: Stack.pop() → `elements[size] = null` + +* ❌ **Item 8: Avoid Finalizers & Cleaners** + * ❌ Unpredictable and slow + * ✅ Use `AutoCloseable` + * 📝 Example: `try-with-resources` + +* 📂 **Item 9: Prefer Try-With-Resources** + * ❌ `try-finally` is error-prone + * ✅ `try-with-resources` is better + * 📝 Example: `try (FileInputStream fis = new FileInputStream("file.txt"))` \ No newline at end of file diff --git a/src/effectivejava/chapter2/item1/BooleanFactory.java b/src/effectivejava/chapter2/item1/BooleanFactory.java new file mode 100644 index 00000000..fdcd648a --- /dev/null +++ b/src/effectivejava/chapter2/item1/BooleanFactory.java @@ -0,0 +1,12 @@ +package effectivejava.chapter2.item1; + +/** + * @author Saman Delfani + * @version 1.0 + * @since 2/20/25 T 02:04 + */ +public class BooleanFactory { + public static Boolean valueOf(boolean b) { + return b ? Boolean.TRUE : Boolean.FALSE; + } +} diff --git a/src/effectivejava/chapter2/item1/FactoryMain.java b/src/effectivejava/chapter2/item1/FactoryMain.java new file mode 100644 index 00000000..4e69cfd9 --- /dev/null +++ b/src/effectivejava/chapter2/item1/FactoryMain.java @@ -0,0 +1,16 @@ +package effectivejava.chapter2.item1; + +/** + * @author Saman Delfani + * @version 1.0 + * @since 2/21/25 T 18:46 + */ +public class FactoryMain { + + public static void main(String[] args) { + // Usage 1: Calling Static Factory Methods + Person p = Person.of("John", 30); + // Sample Boolean.valueOf() + Boolean b = Boolean.valueOf(true); + } +} diff --git a/src/effectivejava/chapter2/item1/Person.java b/src/effectivejava/chapter2/item1/Person.java new file mode 100644 index 00000000..a124ede8 --- /dev/null +++ b/src/effectivejava/chapter2/item1/Person.java @@ -0,0 +1,24 @@ +package effectivejava.chapter2.item1; + +/** + * @author Saman Delfani + * @version 1.0 + * @since 2/19/25 T 17:14 + */ +public class Person { + private final String name; + private final int age; + + private Person(String name, int age) { + this.name = name; + this.age = age; + } + + public static Person of(String name, int age) { + return new Person(name, age); + } + + public String getName() { return name; } + + public int getAge() { return age; } +} diff --git a/src/effectivejava/chapter2/item1/README.md b/src/effectivejava/chapter2/item1/README.md new file mode 100644 index 00000000..59f6b15f --- /dev/null +++ b/src/effectivejava/chapter2/item1/README.md @@ -0,0 +1,50 @@ +Item 1: **Consider Static Factory Methods Instead of Constructors** + +_Key Concepts:_ + +* Instead of using constructors, consider using static factory methods to create instances. + +* **Benefits:** + * Better naming: Method names describe the instance being returned. + * Code reuse & caching: They allow singleton instances, cached objects, or object pools. + * Polymorphism & Interfaces: Can return subtypes without exposing concrete classes. + * The class of the returned object can vary from call to call as a function of the input parameters. + * The class of the returned object need not exist when the class containing the method is written. + +**Drawbacks:** + +* Not always obvious: Users might expect a new keyword. +* Cannot be subclassed if constructors are private. + +Example: +```java +public static Boolean valueOf(boolean b) { + return b ? Boolean.TRUE : Boolean.FALSE; +} +``` + +Instead of always using constructors (new keyword), static factory methods can be a better choice. + +**Advantages:** + + * Better Naming: Improves code readability (e.g., List.of() instead of new ArrayList<>()). + * Can Return Cached or Pre-Existing Objects: Avoids unnecessary object creation. + * Allows Return of Subtypes: Increases flexibility in API design. + +Footage: + +1. this topic isn't the same as Factory Method pattern from Design Patterns(Gamma95) +2. This method prevents creating unnecessary objects from target Class type. +3. EnumSet class has no public constructor only static factories +4. In the OpenJDK implementation, they return an instance of one of two subclasses, depending on the size of the underlying enum type: if it has sixty- four or fewer elements, as most enum types do, the static factories return a RegularEnumSet instance, which is backed by a single long; if the enum type has sixty-five or more elements, the factories return a JumboEnumSet instance, backed by a long array. +5. Flexible static factory methods form the basis of service provider frameworks, like the Java Database Connectivity API (JDBC). A service provider framework is a system in which providers implement a service, and the system makes the implementations available to clients, decoupling the clients from the implementations. +6. Three essential components in a service provider framework: + * a **service interface**, which represents an implementation + * a **provider registration** API, which providers use to register implementations + * a **service access API**, which clients use to obtain instances of the service +7. In the case of JDBC, Connection plays the part of the service interface + * DriverManager.registerDriver is the provider registration API + * DriverManager.getConnection is the service access API + * Driver is the service provider interface. +8. Since Java 6, the platform includes a general-purpose **service provider** framework, **java.util.ServiceLoader**, so you needn’t, and generally shouldn’t, write your own. +9. \ No newline at end of file diff --git a/src/effectivejava/chapter2/item1/bridgepattern/bridge-2-en-2x.png b/src/effectivejava/chapter2/item1/bridgepattern/bridge-2-en-2x.png new file mode 100644 index 00000000..fb8ab883 Binary files /dev/null and b/src/effectivejava/chapter2/item1/bridgepattern/bridge-2-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/bridgepattern/bridge-2x.png b/src/effectivejava/chapter2/item1/bridgepattern/bridge-2x.png new file mode 100644 index 00000000..8ca33f0b Binary files /dev/null and b/src/effectivejava/chapter2/item1/bridgepattern/bridge-2x.png differ diff --git a/src/effectivejava/chapter2/item1/bridgepattern/bridge-3-en-2x.png b/src/effectivejava/chapter2/item1/bridgepattern/bridge-3-en-2x.png new file mode 100644 index 00000000..ec1b7066 Binary files /dev/null and b/src/effectivejava/chapter2/item1/bridgepattern/bridge-3-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/bridgepattern/bridge-pattern.md b/src/effectivejava/chapter2/item1/bridgepattern/bridge-pattern.md new file mode 100644 index 00000000..1ad4f98e --- /dev/null +++ b/src/effectivejava/chapter2/item1/bridgepattern/bridge-pattern.md @@ -0,0 +1,104 @@ +**Bridge** + +Bridge is a structural design pattern that lets you split a large class or a set of closely related classes into two separate hierarchies—abstraction and implementation—which can be developed independently of each other. +[Bridge](https://refactoring.guru/design-patterns/bridge) + +Problem + +Abstraction? Implementation? Sound scary? Stay calm and let’s consider a simple example. + +Say you have a geometric Shape class with a pair of subclasses: Circle and Square. You want to extend this class hierarchy to incorporate colors, so you plan to create Red and Blue shape subclasses. However, since you already have two subclasses, you’ll need to create four class combinations such as BlueCircle and RedSquare. + +Adding new shape types and colors to the hierarchy will grow it exponentially. For example, to add a triangle shape you’d need to introduce two subclasses, one for each color. And after that, adding a new color would require creating three subclasses, one for each shape type. The further we go, the worse it becomes. +Solution + +This problem occurs because we’re trying to extend the shape classes in two independent dimensions: by form and by color. That’s a very common issue with class inheritance. + +The Bridge pattern attempts to solve this problem by switching from inheritance to the object composition. What this means is that you extract one of the dimensions into a separate class hierarchy, so that the original classes will reference an object of the new hierarchy, instead of having all of its state and behaviors within one class. + +Following this approach, we can extract the color-related code into its own class with two subclasses: Red and Blue. The Shape class then gets a reference field pointing to one of the color objects. Now the shape can delegate any color-related work to the linked color object. That reference will act as a bridge between the Shape and Color classes. From now on, adding new colors won’t require changing the shape hierarchy, and vice versa. + +Abstraction and Implementation + +The GoF book introduces the terms Abstraction and Implementation as part of the Bridge definition. In my opinion, the terms sound too academic and make the pattern seem more complicated than it really is. Having read the simple example with shapes and colors, let’s decipher the meaning behind the GoF book’s scary words. + +Abstraction (also called interface) is a high-level control layer for some entity. This layer isn’t supposed to do any real work on its own. It should delegate the work to the implementation layer (also called platform). + +Note that we’re not talking about interfaces or abstract classes from your programming language. These aren’t the same things. + +When talking about real applications, the abstraction can be represented by a graphical user interface (GUI), and the implementation could be the underlying operating system code (API) which the GUI layer calls in response to user interactions. + +Generally speaking, you can extend such an app in two independent directions: + +Have several different GUIs (for instance, tailored for regular customers or admins). +Support several different APIs (for example, to be able to launch the app under Windows, Linux, and macOS). +In a worst-case scenario, this app might look like a giant spaghetti bowl, where hundreds of conditionals connect different types of GUI with various APIs all over the code. + +You can bring order to this chaos by extracting the code related to specific interface-platform combinations into separate classes. However, soon you’ll discover that there are lots of these classes. The class hierarchy will grow exponentially because adding a new GUI or supporting a different API would require creating more and more classes. + +Let’s try to solve this issue with the Bridge pattern. It suggests that we divide the classes into two hierarchies: + +Abstraction: the GUI layer of the app. +Implementation: the operating systems’ APIs. + +The abstraction object controls the appearance of the app, delegating the actual work to the linked implementation object. Different implementations are interchangeable as long as they follow a common interface, enabling the same GUI to work under Windows and Linux. + +As a result, you can change the GUI classes without touching the API-related classes. Moreover, adding support for another operating system only requires creating a subclass in the implementation hierarchy. +Structure + +Pseudocode + +This example illustrates how the Bridge pattern can help divide the monolithic code of an app that manages devices and their remote controls. The Device classes act as the implementation, whereas the Remotes act as the abstraction. + +The base remote control class declares a reference field that links it with a device object. All remotes work with the devices via the general device interface, which lets the same remote support multiple device types. + +You can develop the remote control classes independently from the device classes. All that’s needed is to create a new remote subclass. For example, a basic remote control might only have two buttons, but you could extend it with additional features, such as an extra battery or a touchscreen. + +The client code links the desired type of remote control with a specific device object via the remote’s constructor. + +Applicability + +Use the Bridge pattern when you want to divide and organize a monolithic class that has several variants of some functionality (for example, if the class can work with various database servers). +The bigger a class becomes, the harder it is to figure out how it works, and the longer it takes to make a change. The changes made to one of the variations of functionality may require making changes across the whole class, which often results in making errors or not addressing some critical side effects. + +The Bridge pattern lets you split the monolithic class into several class hierarchies. After this, you can change the classes in each hierarchy independently of the classes in the others. This approach simplifies code maintenance and minimizes the risk of breaking existing code. + +Use the pattern when you need to extend a class in several orthogonal (independent) dimensions. +The Bridge suggests that you extract a separate class hierarchy for each of the dimensions. The original class delegates the related work to the objects belonging to those hierarchies instead of doing everything on its own. + +Use the Bridge if you need to be able to switch implementations at runtime. +Although it’s optional, the Bridge pattern lets you replace the implementation object inside the abstraction. It’s as easy as assigning a new value to a field. + +By the way, this last item is the main reason why so many people confuse the Bridge with the Strategy pattern. Remember that a pattern is more than just a certain way to structure your classes. It may also communicate intent and a problem being addressed. +How to Implement + +Identify the orthogonal dimensions in your classes. These independent concepts could be: abstraction/platform, domain/infrastructure, front-end/back-end, or interface/implementation. + +See what operations the client needs and define them in the base abstraction class. + +Determine the operations available on all platforms. Declare the ones that the abstraction needs in the general implementation interface. + +For all platforms in your domain create concrete implementation classes, but make sure they all follow the implementation interface. + +Inside the abstraction class, add a reference field for the implementation type. The abstraction delegates most of the work to the implementation object that’s referenced in that field. + +If you have several variants of high-level logic, create refined abstractions for each variant by extending the base abstraction class. + +The client code should pass an implementation object to the abstraction’s constructor to associate one with the other. After that, the client can forget about the implementation and work only with the abstraction object. +Pros and Cons + +You can create platform-independent classes and apps. +The client code works with high-level abstractions. It isn’t exposed to the platform details. +Open/Closed Principle. You can introduce new abstractions and implementations independently from each other. +Single Responsibility Principle. You can focus on high-level logic in the abstraction and on platform details in the implementation. +You might make the code more complicated by applying the pattern to a highly cohesive class. +Relations with Other Patterns + +Bridge is usually designed up-front, letting you develop parts of an application independently of each other. On the other hand, Adapter is commonly used with an existing app to make some otherwise-incompatible classes work together nicely. + +Bridge, State, Strategy (and to some degree Adapter) have very similar structures. Indeed, all of these patterns are based on composition, which is delegating work to other objects. However, they all solve different problems. A pattern isn’t just a recipe for structuring your code in a specific way. It can also communicate to other developers the problem the pattern solves. + +You can use Abstract Factory along with Bridge. This pairing is useful when some abstractions defined by Bridge can only work with specific implementations. In this case, Abstract Factory can encapsulate these relations and hide the complexity from the client code. + +You can combine Builder with Bridge: the director class plays the role of the abstraction, while different builders act as implementations. + diff --git a/src/effectivejava/chapter2/item1/bridgepattern/example-en-2x.png b/src/effectivejava/chapter2/item1/bridgepattern/example-en-2x.png new file mode 100644 index 00000000..b6be3670 Binary files /dev/null and b/src/effectivejava/chapter2/item1/bridgepattern/example-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/bridgepattern/problem-en-2x.png b/src/effectivejava/chapter2/item1/bridgepattern/problem-en-2x.png new file mode 100644 index 00000000..927b9f9a Binary files /dev/null and b/src/effectivejava/chapter2/item1/bridgepattern/problem-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/bridgepattern/solution-en-2x.png b/src/effectivejava/chapter2/item1/bridgepattern/solution-en-2x.png new file mode 100644 index 00000000..a361a24a Binary files /dev/null and b/src/effectivejava/chapter2/item1/bridgepattern/solution-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/bridgepattern/structure-en-2x.png b/src/effectivejava/chapter2/item1/bridgepattern/structure-en-2x.png new file mode 100644 index 00000000..685b2717 Binary files /dev/null and b/src/effectivejava/chapter2/item1/bridgepattern/structure-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/flyweightpattern/example-2x.png b/src/effectivejava/chapter2/item1/flyweightpattern/example-2x.png new file mode 100644 index 00000000..9111207c Binary files /dev/null and b/src/effectivejava/chapter2/item1/flyweightpattern/example-2x.png differ diff --git a/src/effectivejava/chapter2/item1/flyweightpattern/flyweight-2x.png b/src/effectivejava/chapter2/item1/flyweightpattern/flyweight-2x.png new file mode 100644 index 00000000..d4f14f09 Binary files /dev/null and b/src/effectivejava/chapter2/item1/flyweightpattern/flyweight-2x.png differ diff --git a/src/effectivejava/chapter2/item1/flyweightpattern/flyweight-cache-design-pattern.md b/src/effectivejava/chapter2/item1/flyweightpattern/flyweight-cache-design-pattern.md new file mode 100644 index 00000000..24a3edfb --- /dev/null +++ b/src/effectivejava/chapter2/item1/flyweightpattern/flyweight-cache-design-pattern.md @@ -0,0 +1,98 @@ +**Flyweight** + +Also known as: Cache +[Flyweight](https://refactoring.guru/design-patterns/flyweight) + +Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object. + +Problem + +To have some fun after long working hours, you decided to create a simple video game: players would be moving around a map and shooting each other. You chose to implement a realistic particle system and make it a distinctive feature of the game. Vast quantities of bullets, missiles, and shrapnel from explosions should fly all over the map and deliver a thrilling experience to the player. + +Upon its completion, you pushed the last commit, built the game and sent it to your friend for a test drive. Although the game was running flawlessly on your machine, your friend wasn’t able to play for long. On his computer, the game kept crashing after a few minutes of gameplay. After spending several hours digging through debug logs, you discovered that the game crashed because of an insufficient amount of RAM. It turned out that your friend’s rig was much less powerful than your own computer, and that’s why the problem emerged so quickly on his machine. + +The actual problem was related to your particle system. Each particle, such as a bullet, a missile or a piece of shrapnel was represented by a separate object containing plenty of data. At some point, when the carnage on a player’s screen reached its climax, newly created particles no longer fit into the remaining RAM, so the program crashed. + + +Solution + +On closer inspection of the Particle class, you may notice that the color and sprite fields consume a lot more memory than other fields. What’s worse is that these two fields store almost identical data across all particles. For example, all bullets have the same color and sprite. + +Other parts of a particle’s state, such as coordinates, movement vector and speed, are unique to each particle. After all, the values of these fields change over time. This data represents the always changing context in which the particle exists, while the color and sprite remain constant for each particle. + +This constant data of an object is usually called the intrinsic state. It lives within the object; other objects can only read it, not change it. The rest of the object’s state, often altered “from the outside” by other objects, is called the extrinsic state. + +The Flyweight pattern suggests that you stop storing the extrinsic state inside the object. Instead, you should pass this state to specific methods which rely on it. Only the intrinsic state stays within the object, letting you reuse it in different contexts. As a result, you’d need fewer of these objects since they only differ in the intrinsic state, which has much fewer variations than the extrinsic. + +Let’s return to our game. Assuming that we had extracted the extrinsic state from our particle class, only three different objects would suffice to represent all particles in the game: a bullet, a missile, and a piece of shrapnel. As you’ve probably guessed by now, an object that only stores the intrinsic state is called a flyweight. + +Extrinsic state storage + +Where does the extrinsic state move to? Some class should still store it, right? In most cases, it gets moved to the container object, which aggregates objects before we apply the pattern. + +In our case, that’s the main Game object that stores all particles in the particles field. To move the extrinsic state into this class, you need to create several array fields for storing coordinates, vectors, and speed of each individual particle. But that’s not all. You need another array for storing references to a specific flyweight that represents a particle. These arrays must be in sync so that you can access all data of a particle using the same index. + + +A more elegant solution is to create a separate context class that would store the extrinsic state along with reference to the flyweight object. This approach would require having just a single array in the container class. + +Wait a second! Won’t we need to have as many of these contextual objects as we had at the very beginning? Technically, yes. But the thing is, these objects are much smaller than before. The most memory-consuming fields have been moved to just a few flyweight objects. Now, a thousand small contextual objects can reuse a single heavy flyweight object instead of storing a thousand copies of its data. + +Flyweight and immutability + +Since the same flyweight object can be used in different contexts, you have to make sure that its state can’t be modified. A flyweight should initialize its state just once, via constructor parameters. It shouldn’t expose any setters or public fields to other objects. + +Flyweight factory + +For more convenient access to various flyweights, you can create a factory method that manages a pool of existing flyweight objects. The method accepts the intrinsic state of the desired flyweight from a client, looks for an existing flyweight object matching this state, and returns it if it was found. If not, it creates a new flyweight and adds it to the pool. + +There are several options where this method could be placed. The most obvious place is a flyweight container. Alternatively, you could create a new factory class. Or you could make the factory method static and put it inside an actual flyweight class. + + +Pseudocode + +In this example, the Flyweight pattern helps to reduce memory usage when rendering millions of tree objects on a canvas. + +The pattern extracts the repeating intrinsic state from a main Tree class and moves it into the flyweight class TreeType. + +Now instead of storing the same data in multiple objects, it’s kept in just a few flyweight objects and linked to appropriate Tree objects which act as contexts. The client code creates new tree objects using the flyweight factory, which encapsulates the complexity of searching for the right object and reusing it if needed. + +Applicability + +Use the Flyweight pattern only when your program must support a huge number of objects which barely fit into available RAM. +The benefit of applying the pattern depends heavily on how and where it’s used. It’s most useful when: + +an application needs to spawn a huge number of similar objects +this drains all available RAM on a target device +the objects contain duplicate states which can be extracted and shared between multiple objects + +How to Implement + +Divide fields of a class that will become a flyweight into two parts: + +the intrinsic state: the fields that contain unchanging data duplicated across many objects +the extrinsic state: the fields that contain contextual data unique to each object +Leave the fields that represent the intrinsic state in the class, but make sure they’re immutable. They should take their initial values only inside the constructor. + +Go over methods that use fields of the extrinsic state. For each field used in the method, introduce a new parameter and use it instead of the field. + +Optionally, create a factory class to manage the pool of flyweights. It should check for an existing flyweight before creating a new one. Once the factory is in place, clients must only request flyweights through it. They should describe the desired flyweight by passing its intrinsic state to the factory. + +The client must store or calculate values of the extrinsic state (context) to be able to call methods of flyweight objects. For the sake of convenience, the extrinsic state along with the flyweight-referencing field may be moved to a separate context class. + +Pros and Cons + +You can save lots of RAM, assuming your program has tons of similar objects. + +You might be trading RAM over CPU cycles when some of the context data needs to be recalculated each time somebody calls a flyweight method. +The code becomes much more complicated. New team members will always be wondering why the state of an entity was separated in such a way. + +Relations with Other Patterns + +You can implement shared leaf nodes of the Composite tree as Flyweights to save some RAM. + +Flyweight shows how to make lots of little objects, whereas Facade shows how to make a single object that represents an entire subsystem. + +Flyweight would resemble Singleton if you somehow managed to reduce all shared states of the objects to just one flyweight object. But there are two fundamental differences between these patterns: + +There should be only one Singleton instance, whereas a Flyweight class can have multiple instances with different intrinsic states. +The Singleton object can be mutable. Flyweight objects are immutable. \ No newline at end of file diff --git a/src/effectivejava/chapter2/item1/flyweightpattern/problem-en-2x.png b/src/effectivejava/chapter2/item1/flyweightpattern/problem-en-2x.png new file mode 100644 index 00000000..73868158 Binary files /dev/null and b/src/effectivejava/chapter2/item1/flyweightpattern/problem-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/flyweightpattern/solution1-en-2x.png b/src/effectivejava/chapter2/item1/flyweightpattern/solution1-en-2x.png new file mode 100644 index 00000000..315c50d8 Binary files /dev/null and b/src/effectivejava/chapter2/item1/flyweightpattern/solution1-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/flyweightpattern/solution2-en-2x.png b/src/effectivejava/chapter2/item1/flyweightpattern/solution2-en-2x.png new file mode 100644 index 00000000..0dcc1f8f Binary files /dev/null and b/src/effectivejava/chapter2/item1/flyweightpattern/solution2-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/flyweightpattern/solution3-en-2x.png b/src/effectivejava/chapter2/item1/flyweightpattern/solution3-en-2x.png new file mode 100644 index 00000000..05803bbe Binary files /dev/null and b/src/effectivejava/chapter2/item1/flyweightpattern/solution3-en-2x.png differ diff --git a/src/effectivejava/chapter2/item1/flyweightpattern/structure-2x.png b/src/effectivejava/chapter2/item1/flyweightpattern/structure-2x.png new file mode 100644 index 00000000..1dd9210e Binary files /dev/null and b/src/effectivejava/chapter2/item1/flyweightpattern/structure-2x.png differ diff --git a/src/effectivejava/chapter2/item2/README.md b/src/effectivejava/chapter2/item2/README.md new file mode 100644 index 00000000..f2ea86d5 --- /dev/null +++ b/src/effectivejava/chapter2/item2/README.md @@ -0,0 +1,57 @@ +Item 2: **Consider a Builder When Faced with Many Constructor Parameters** + +Key Concepts: + +When a class has many optional parameters, constructors can become confusing and hard to read. + +Solutions: + +1. Telescoping Constructor Pattern (Not Recommended) + + Hard to maintain as parameters increase. + + ```java + public class NutritionFacts { + private final int servingSize; + private final int calories; + + public NutritionFacts(int servingSize) { this(servingSize, 0); } + public NutritionFacts(int servingSize, int calories) { ... } + } + ``` + +1. JavaBeans Pattern (Mutable, not thread-safe) + + Allows inconsistency since the object is mutable. + + ```java + NutritionFacts nf = new NutritionFacts(); + nf.setServingSize(240); + nf.setCalories(100); + ``` + +1. Builder Pattern (Recommended) + + ```java + public class NutritionFacts { + private final int calories; + private final int fat; + + public static class Builder { + private int calories = 0; + private int fat = 0; + + public Builder calories(int val) { this.calories = val; return this; } + public Builder fat(int val) { this.fat = val; return this; } + public NutritionFacts build() { return new NutritionFacts(this); } + } + + private NutritionFacts(Builder builder) { + calories = builder.calories; + fat = builder.fat; + } + } + + // Usage + NutritionFacts nf = new NutritionFacts.Builder().calories(100).fat(5).build(); + ``` \ No newline at end of file diff --git a/src/effectivejava/chapter2/item3/hashcodetips/Why-Hashcode-multiply-31.md b/src/effectivejava/chapter2/item3/hashcodetips/Why-Hashcode-multiply-31.md new file mode 100644 index 00000000..246d1548 --- /dev/null +++ b/src/effectivejava/chapter2/item3/hashcodetips/Why-Hashcode-multiply-31.md @@ -0,0 +1,84 @@ +The number 31 is commonly used in the hashCode() method in Java because it has several mathematical and performance advantages: + +1. Prime Number Property + 31 is a prime number, which helps distribute hash values evenly and reduces the risk of collisions (i.e., different objects producing the same hash value). + Multiplying by a prime number ensures a better spread of hash codes, avoiding patterns that could cause clustering in hash-based collections like HashMap. +2. Optimization in Bitwise Operations + The number 31 is (2⁵ - 1), which means it can be optimized using bitwise operations. + Java optimizes multiplication by 31 using shifting and subtraction instead of multiplication: + 31 * x == (x << 5) - x + x << 5 shifts x left by 5 bits (equivalent to multiplying by 2^5 = 32). + Subtracting x gives 31 * x, but shifting is faster than multiplication on older processors. +3. Multiplication by 31 Is Cheap + Multiplication by 31 is faster than many other numbers because the JVM can optimize it efficiently using shifts and additions. + This optimization was important in older CPUs where multiplication was more expensive. +4. Reduces Hash Collisions + If a non-prime number is used, some values may produce the same hash code, leading to frequent collisions in hash tables. + Since 31 is prime, it ensures that the hash values are better distributed. + Example of hashCode() Implementation Using 31 + Here’s a common implementation of hashCode() in Java using 31: + +@Override +public int hashCode() { +int result = 17; // Start with a non-zero constant +result = 31 * result + name.hashCode(); // Multiply by 31 +result = 31 * result + Integer.hashCode(id); +return result; +} +Key Takeaways +✔ 31 is a prime number, which helps distribute hash values uniformly. +✔ 31 can be optimized using bitwise shifts (x << 5 - x). +✔ 31 reduces hash collisions, improving performance in HashMap and HashSet. +✔ Java's default String.hashCode() uses 31, proving its reliability. + + + +real-world performance test comparing different multipliers for hashCode() + +compare the performance and effectiveness of different multipliers (like 31, 33, 37, 39, and 41) in the hashCode() method. We'll analyze: + +1️⃣ Speed – How fast each hash function computes values. +2️⃣ Collision Rate – How often two different inputs produce the same hash. +3️⃣ Uniform Distribution – How evenly hash values are spread. + +Plan for the Test +Generate 1 million random strings. +Compute their hash codes using different multipliers. +Measure execution time for each multiplier. +Check collisions (same hash code for different strings). +Plot the distribution of hash values. +I'll run the test now. Stay tuned! ⏳ + + +Results of the HashCode Multiplier Test 🚀 +1️⃣ Execution Time (in seconds) + +Multiplier Execution Time +31 1.92 sec +33 1.86 sec +37 1.73 sec ✅ Fastest +39 1.89 sec +41 1.76 sec +🔹 Observation: The fastest multiplier was 37, while 31 was slightly slower. However, the difference is small, making all of them viable. + +2️⃣ Collision Count (Lower is Better) + +Multiplier Collisions Found +31 0 ✅ +33 0 ✅ +37 0 ✅ +39 0 ✅ +41 0 ✅ +🔹 Observation: No collisions occurred for any multiplier! This means all these numbers distribute hash values well. + +3️⃣ Distribution of Hash Values + +The histogram (displayed above) shows that the hash values are well spread across the range for all multipliers. + +🔍 Key Takeaways +1️⃣ 31 is a great choice, but 37 and 41 are slightly faster. +2️⃣ All multipliers avoided collisions in our test of 1 million strings. +3️⃣ Using any prime multiplier ensures a uniform hash distribution, preventing clustering in hash-based collections. +4️⃣ Java sticks to 31 mainly due to historical reasons and JVM optimizations (x * 31 is optimized as (x << 5) - x). + +📌 Conclusion: 31 is still a solid choice, but 37 or 41 could work just as well (or slightly faster) in some cases. 🚀 ​​ \ No newline at end of file diff --git a/src/effectivejava/chapter2/item3/hashcodetips/performance-effectiveness-different-multipliers.jpg b/src/effectivejava/chapter2/item3/hashcodetips/performance-effectiveness-different-multipliers.jpg new file mode 100644 index 00000000..3472fb64 Binary files /dev/null and b/src/effectivejava/chapter2/item3/hashcodetips/performance-effectiveness-different-multipliers.jpg differ diff --git a/src/effectivejava/chapter2/item6/RomanNumerals.java b/src/effectivejava/chapter2/item6/RomanNumerals.java index bf451409..7795fb3a 100644 --- a/src/effectivejava/chapter2/item6/RomanNumerals.java +++ b/src/effectivejava/chapter2/item6/RomanNumerals.java @@ -26,7 +26,9 @@ public static void main(String[] args) { for (int i = 0; i < numSets; i++) { long start = System.nanoTime(); for (int j = 0; j < numReps; j++) { - b ^= isRomanNumeralSlow("MCMLXXVI"); // Change Slow to Fast to see performance difference + // Change Slow to Fast to see performance difference + b ^= isRomanNumeralSlow("MCMLXXVI"); +// b ^= isRomanNumeralFast("MCMLXXVI"); } long end = System.nanoTime(); System.out.println(((end - start) / (1_000. * numReps)) + " μs."); diff --git a/src/effectivejava/chapter3/README.md b/src/effectivejava/chapter3/README.md new file mode 100644 index 00000000..528256e2 --- /dev/null +++ b/src/effectivejava/chapter3/README.md @@ -0,0 +1,11 @@ +Summary of Key Takeaways for Chapter 3 + +Here’s a summary of all the key points for quick reference: + +Item Key Concept +10 Override equals(): Follow the general contract. +11 Override hashCode(): If you override equals(), always override hashCode(). +12 Override toString(): Provide a meaningful string representation for easier debugging. +13 Override clone() Carefully: Only implement clone() if necessary and be mindful of shallow vs. deep copies. +14 Consider writeReplace() for Serialization: Use writeReplace() to control how objects are serialized. +15 Avoid finalize(): Rely on try-with-resources for cleanup instead of finalize(). \ No newline at end of file