0% found this document useful (0 votes)
40 views

Lecture18 - Good vs. Bad Practices Part 1 - CreatingObjects

Creating and Destroying Objects discusses several techniques for creating objects in Java, including: 1) Static factory methods have advantages over constructors such as named methods and allowing immutable classes to reuse instances. However, classes without public constructors cannot be subclassed. 2) Singletons can be implemented by making the constructor private and having a public static final field, or with a private constructor and public static factory method. Enums are preferred to prevent reflection attacks. 3) Builder pattern is better than long constructors when there are many optional parameters, as it improves readability and prevents errors from incorrect parameter order.

Uploaded by

Karim Ghaddar
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views

Lecture18 - Good vs. Bad Practices Part 1 - CreatingObjects

Creating and Destroying Objects discusses several techniques for creating objects in Java, including: 1) Static factory methods have advantages over constructors such as named methods and allowing immutable classes to reuse instances. However, classes without public constructors cannot be subclassed. 2) Singletons can be implemented by making the constructor private and having a public static final field, or with a private constructor and public static factory method. Enums are preferred to prevent reflection attacks. 3) Builder pattern is better than long constructors when there are many optional parameters, as it improves readability and prevents errors from incorrect parameter order.

Uploaded by

Karim Ghaddar
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

Creating and Destroying

Objects
Note: In the following slides, the item numbers correspond to their numbers in the
book (we will not cover all the items which is why the numbers are not consecutive)

Effective Java by Joshua Bloch


Item 1: static factory methods vs. constructors
➢To obtain an instance of a class, a public constructor is typically used
➢A class can also provide a public static factory method, which is simply a static
method that returns an instance of the class. This has advantages and
disadvantages:

+1: static factory methods have names:


1) Better readability
– BigInteger(int bitLength, int certainty, Random rnd): Constructs a randomly
generated positive BigInteger that is probably prime, with the specified bitLength
– Would have been better expressed as a static factory method named
BigInteger.probablePrime(int bitLength, int certainty, Random rnd)
2) Overcomes the restriction that we cannot have multiple constructors
with the same signature
Item 1: static factory methods vs. constructors (cont)
+2: Allows classes to control how many instances get created
– Singletons or limited number of instances
– Allows immutable classes to use preconstructed instances, or to cache instances as
they’re constructed, and dispense them repeatedly to avoid creating unnecessary
duplicate objects.
– Example: Boolean.valueOf(boolean)

-1: classes without public or protected constructors cannot be subclassed (since


the subclass must call super)
(might not be that bad… since it discourages inheritance!)
Item 2: Singletons using private constructors or enum types
➢ Approach1:
// Singleton with public final field
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public int data;
}
The private constructor is called only once, to initialize the public static final field Elvis.INSTANCE
The lack of a public or protected constructor guarantees that exactly one Elvis instance will exist

➢ Approach2:
// Singleton with public static factory
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public static Elvis getInstance() { return INSTANCE; }
public int data;
}
All calls to Elvis.getInstance() return the same object reference, and no other Elvis instance will ever be created

Approach1 is simpler, however Approach2 gives you the flexibility to change your mind about whether the class is a
singleton without changing its API
Item 2: Singletons (cont)
Using reflection, a user can violate the singleton property and create 2 instances of the object:

Elvis elvis1 = Elvis.INSTANCE; // or call the static factory


Constructor constructor = elvis1.getClass().getDeclaredConstructor();
constructor.setAccessible(true);
Elvis elvis2 = (Elvis) constructor.newInstance();

➢ Approach3 [the recommended approach]:


Reflection cannot be used to instantiate new instances of an enum, so we can declare a single-element enum as
follows:

// Enum singleton - the preferred approach


public enum Elvis {
INSTANCE;
public int data;
}

Elvis theElvis = Elvis.INSTANCE;


System.out.println(theElvis.data);
Item 3: Use a builder when having many constructor params
Given many parameters, programmers typically provide a constructor with 0 param, 1 param, 2
params, 3 params …. All params. The latter is called by the former versions.
public class NutritionFacts {
private final int servingSize; // (mL) required
private final int servings; // (per container) required
private final int calories; // (per serving) optional
private final int fat; // (g/serving) optional
private final int sodium; // (mg/serving) optional
private final int carbohydrate; // (g/serving) optional

public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); }


public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); }
public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); }
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); }
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize; this.servings = servings;
this.calories = calories; this.fat = fat;
this.sodium = sodium; this.carbohydrate = carbohydrate;
}
}

To create an instance, you use the constructor with the shortest parameter list containing all the parameters you want to set:
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27); // you care about all 6 params except ‘fat’
Item 3: Use a builder … (cont)
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27); // don’t care about the value of fat

What if we have more parameters and we don’t care about many parameters?
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 0, 0, …, 8, 0…, 27);
This approach is bad for readability and writability, a source of runtime bugs.

The Builder pattern is a good alternative. The client:


– Calls a constructor with all of the minimally required parameters and gets a builder object
– Calls setter-like methods on the builder object to set each optional parameter of interest.
– Calls a parameterless build method to generate the object.
Item 3: Use a builder pattern… (cont)
public class NutritionFacts { public Builder calories(int val) {
private final int servingSize; calories = val; return this; // this = builder object
private final int servings; }
private final int calories; public Builder fat(int val) {
private final int fat; fat = val; return this; // this = builder object
private final int sodium; }
private final int carbohydrate; public Builder sodium(int val) {
sodium = val; return this;
public static class Builder { }
// Required parameters public Builder carbohydrate(int val) {
private final int servingSize; carbohydrate = val; return this;
private final int servings; }
// Optional parameters - initialized to default values public NutritionFacts build() {
private int calories = 0; return new NutritionFacts(this);
private int fat = 0; }
private int sodium = 0; } // end Builder
private int carbohydrate = 0;
private NutritionFacts(Builder builder) {
public Builder(int servingSize, int servings) { servingSize = builder.servingSize; servings = builder.servings;
this.servingSize = servingSize; calories = builder.calories; fat = builder.fat;
this.servings = servings; sodium = builder.sodium; carbohydrate = builder.carbohydrate;
} }
Client code: }
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
[ creating a Builder object ] [creating a NutritionFacts object]
Item 3: Use a builder … (cont)
NutritionFacts cocaCola =
new NutritionFacts.Builder(240,8).calories(100).sodium(35).carbohydrate(27).build();

+1: This client code is easy to write and, more importantly, easy to read.

-1: The Builder pattern is more verbose than the original approach, so it should be used only if
there are enough parameters to make it worthwhile, say 4 or more.
Item 6: Avoid creating unnecessary objects
➢An object can always be reused if it is immutable
➢So why not reuse it?
String s = new String(“Hello"); // DON'T DO THIS!
This statement unnecessarily creates a new String instance each time it is executed

String s = “Hello";
This version reuses the same String instance, each time it is executed
Furthermore, it is guaranteed that the object will be reused by any other code running in the
same virtual machine that happens to contain the same string literal (String keeps a pool of
used string literals)
Item 6: Avoid creating unnecessary objects (cont)
➢ Autoboxing is a source of creating unnecessary objects
➢ Method that calculates the sum of all the positive int values:

private static long sum() {// Very slow! Can you spot the object creation?
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
The variable sum is declared as a Long instead of a long, which means that the program constructs about 231
unnecessary Long instances (roughly one for each time the long i is added to the Long sum). Changing the
declaration of sum from Long to long will tremendously reduce the runtime.

However:
➢ Creating additional objects to enhance the clarity, simplicity, or power of a program is generally a good thing.
➢ Avoiding object creation by maintaining your own object pool is sometimes risky (especially if they are
mutable).
➢ Creating object pools on its own could be heavyweight
Item 7: Eliminate obsolete object references
Garbage collection gives you the false impression that you don’t have to think about memory

public class Stack { // Can you spot the "memory leak"?


private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}

Item 7: Eliminate obsolete object references (cont)
➢ If a stack grows and then shrinks, the objects that were popped off the stack will not be garbage collected,
even if the program using the stack has no more references to them

public Object pop() { // corrected version of the pop method


if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}

➢Another common source of memory leaks is caches. Once you put an object reference into a
cache, it’s easy to forget that it’s there and leave it in the cache long after it becomes irrelevant

➢Consider creating your cache as a WeakHashMap. Entries will be removed automatically after
they become obsolete.
WeakHashMap
A WeakHashMap cooperates with the garbage collector to remove key/value pairs
when the only reference to the key is the one from the hash table entry:

➢The WeakHashMap uses weak references to hold keys.


➢A WeakReference object holds a reference to a hash table key.
➢WeakReferences are treated in a special way by the garbage collector.
– Normally, if the garbage collector finds that a particular object has no
references to it, it simply deletes it.
– However, if the object is reachable only by a WeakReference, the garbage
collector does not directly delete it, but places the weak reference pointed to
it in a queue. The WeakHashMap periodically check that queue in order to
remove the associated key/value entries.
import java.lang.ref.*;
class Dummy {
private String s;
public Dummy(String s) { this.s= s;}
public void finalize() {System.out.println("Finalized " + s);}

public static void main(String [ ] args) {


Dummy d1 = new Dummy("object1");
Dummy alias = d1; // try commenting this
d1 = null;
System.gc(); System.out.println("GC");

Dummy d2 = new Dummy("object2");


WeakReference<Dummy> weakRef = new WeakReference<Dummy>(d2); // weak alias
d2 = null;
System.gc(); System.out.println("GC");
}
}

Output:
GC
GC
Finalized object2
// Created HashMap and WeakHashMap objects
Map<String, String> hashmapObject = new HashMap<>();
Map<String, String> weakhashmapObject = new WeakHashMap<>();

// Created HashMap and WeakHashMap keys


String hashmapKey = new String ("hashmapkey"); String weakhashmapKey = new String ("weakhashmapkey");

// Created HashMap and WeakHashMap values


String hashmapValue = "hashmapvalue"; String weakhashmapValue = "weakhashmapvalue";

// Putting key and value in HashMap and WeakHashMap Object


hashmapObject.put(hashmapKey ,hashmapValue); weakhashmapObject.put(weakhashmapKey ,weakhashmapValue);

// Print HashMap and WeakHashMap Object : Before Garbage Collection


System.out.println("HashMap before Garbage Collected :" + hashmapObject); // {hashmapkey=hashmapvalue}
System.out.println("WeakHashMap before Garbage Collected :" + weakhashmapObject); // {weakhashmapkey= weakhashmapvalue}

// Set HashMap and WeakHashMap Object keys to null


hashmapKey = null; weakhashmapKey = null;

// Calling Garbage Collection (Note that gc is a parallel process and it is not guaranteed (but likely)
//that it will execute directly after calling it
System.gc();

// Print HashMap and WeakHashMap Object : After Garbage Collection


System.out.println("HashMap after Garbage Collected :" + hashmapObject);// {hashmapkey=hashmapvalue}
System.out.println("WeakHashMap after Garbage Collected :" + weakhashmapObject);// {}
Item 8: Avoid finalizers
➢Finalizers are unpredictable, and mostly unnecessary
➢C++ destructors are the normal way to reclaim the resources associated with an object, a
necessary counterpart to constructors. In Java, it is the job of the garbage collector
➢C++ destructors are also used to reclaim other non-memory resources (files, connections).
In Java, a try-with-resources or try-finally block is used for this purpose
➢Finalizers are not guaranteed to execute promptly: the finalizer thread could run at a
lower priority than other application threads
➢System.gc() and System.runFinalization() may increase the odds of finalizers getting
executed, but they don’t guarantee it
➢An uncaught exception thrown during finalization is ignored, and finalization of that
object terminates
➢Finalizers have two legitimate uses:
– To act as a safety net in case the owner of a resource neglects to call its close() method
– To clean native (non-Java) peers, since the garbage collector doesn’t know about natives
[Optional Reading] Item 9: Prefer try-with-resources to try-finally
static String firstLineOfFile(String path) throws IOException { // try-finally- for closing a resource
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
}
finally {
br.close();
}
}
static void copy(String src, String dst) throws IOException { // but things can get worse when you add a second resource
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n; while ((n = in.read(buf)) >= 0) out.write(buf, 0, n);
}
finally {
out.close();
}
}
finally {
in.close();
}
}
[Optional Reading] Item 9: Prefer try-with-resources to try-finally (cont)
➢ try-with-resources statement solves the above problem, given that the resource implements the AutoCloseable
interface (which consists of a single void-returning close() method)

static String firstLineOfFile(String path) throws IOException { // try-with-resources – closing one resource
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}

static void copy(String src, String dst) throws IOException { // try-with-resources – closing multiple resources
try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0) out.write(buf, 0, n);
}
}
[Optional Reading] Item 9: Prefer try-with-resources to try-finally (cont)

➢try-with-resources is more readable and provides better diagnostics

Assume that exceptions are thrown by both the readLine call and the close call, the
latter exception is suppressed in favor of the former
static String firstLineOfFile(String path) throws IOException { // try-finally- for closing a resource
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
}
finally {
br.close();
}
}

However, using try-with-resources, the suppressed exceptions are not merely


discarded; they are printed in the stack trace with a notation saying that they were
suppressed

You might also like