Effective Java Flashcards
What is the difference between final, finally, and finalize
- final: Class cannot be overriden. No methods within the class can be overriden.
- final: method cannot be overriden.
- final: variable can not be changed.
- finally: happens after a try block completes after all catch’s are done, even if no exception is thrown
- finalize: override this function, and the code will be executed just before garbage collection
What are the Access Modifiers?
- public - Accessible from any other class
- private - Accessible only within the same class
- protected - Accessible within the package and subclasses
- package-private - Accessible only within the package
What are the threading modifiers?
- volatile - Indicates the variable may be changed by multiple threads
- synchronized - Restricts access to one thread at a time
What are other visibility modifiers?
- default - Provides a default method implementation in interfaces
- static - Belongs to the class rather than instances
- final - Prevents inheritance, overriding, and reassignment
- abstract - Declares that the class cannot be instantiated or method has no body
- sealed / non-sealed / permits - sealed class can only be extended by classes it permits.
What are the lesser used modifiers?
- native - Declares a method implemented in a non-Java language
- strictfp - guarantees that calculations strictly adhere to the IEEE 754 floating-point standard
- transient - Prevents serialization of the field
What are boxed primitives?
Primitive - Wrapper (Boxed) Class
int - Integer
long - Long
float - Float
double - Double
boolean - Boolean
char - Character
byte - Byte
short - Short
What happens when comparing and object that didn’t override the equals function?
It will only equal itself.
What are varargs?
- Variable length of arguments
- e.g.
public static int sum(int... numbers) {}
What are mixins?
- A mixin is a way to add reusable functionality to a class without establishing a formal inheritance relationship
- In JavaScript, you can do this by adding functions from one class to another as mixin functions.
What are the steps to make a class immutable?
- Don’t provide methods that modify the object’s state (known as
mutators). - Ensure that the class can’t be extended (make class final or private constructor)
- Make all fields final.
- Make all fields private.
- Ensure exclusive access to any mutable components.
How does inheritance break enacpsulation?
A subclass depends on the implementation details of its superclass for its proper function. The superclass’s implementation may change from release to release, and if it does, the subclass may break, even though its code has not been touched. As a consequence, a subclass must evolve in tandem with its superclass, unless the superclass’s authors have designed and documented it specifically for the purpose of being extended.
Snyder, Alan. 1986. “Encapsulation and Inheritance in Object-Oriented Programming Languages.” In Object-Oriented Programming Systems, Languages, and Applications Conference Proceedings, 38–45. New York, NY: ACM Press.
What are the 4 kinds of nested classes?
- static member classes: Defined as a static nested class within another class.
- nonstatic member classes: Defined as a non-static nested class within another class.
- anonymous classes: A special type of local class without a name, often used for one-off implementations, especially for interfaces or abstract classes (e.g. Runnable)
- local classes: Defined within a method, constructor, or initializer block of the enclosing class.
What are the important Generic terms.
Term, Example
Parameterized type, List<String>
Actual type parameter, String
Generic type, List<E>
Formal type parameter, E
Unbounded wildcard type, List<?>
Raw type, List
Bounded type parameter, <E>
Recursive type bound, <T extends Comparable<T>>
Bounded wildcard type, List<? extends Number>
Generic method, static <E> List<E> asList(E[] a)
Type token, String.class</E></E></T></E></E></String>
Item 1: Consider static factory methods instead of constructors
- Can use named functions
- Do not have to create a new object with each invocation
- Can return an object of any type/subtype
- Input parameters can determine type/subtype
- The type/subtype returned doesn’t even have to exist today.
Fun Facts
- Since Java 8, interfaces can have static functions and default implementations. Static functions don’t need to be default since they are part of the interface. Java.util.Collections has 45 separate public classes, one for each convenience implementation. These could now just be part of the interfaces.
Item 2: Consider a builder when faced with many constructor parameters
- The Builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameter
- More easily thread safe because you can create the instance in a single call by passing the building into the constructor.
Item 3: Enforce the singleton property with a private constructor or an enum type
Fun Facts
- private contructor singleton can be broken when implementing serializable. Variables must be labeled transient.
Item 4: Enforce noninstantiability with a private constructor
- When using something like a static helper class that you never want to be instantiated, remember to mark the constructor private.
Item 5: Prefer dependency injection to hardwiring resources
- Allow a client to provide a type instead of choosing up front.
- Dependency Injection can mean something as simple as passing in an instance in a constructor.
Item 6: Avoid creating unnecessary objects
- Do not do “String s = new String(foobar)”
- Do this instead: String s = “foobar”
- This makes it immutable and reusable.
- Also beware of accidental autoboxing (i.e. compared an unboxed primitive to a boxed object will automatically create a boxed object from the primitive).
Example Problem
- This actually creates a new Pattern instance, uses it once, and then making it available for garbage collection.
- Instead compile your regex into a Pattern instance so it only happens once, and you can resuse.
static boolean isRomanNumeral(String s) { return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); }
Item 7: Eliminate obsolete object references
- Basically, even though there is garbage collection, memory leaks can still happen.
Item 8: Avoid finalizers and cleaners
- Finalizers have security and performance issues, just don’t use them.
- Cleaners are new in Java 9 but still have performance issues.
- C++ engineers might think they are the same as destructors, but this isn’t necessary since Java has garbage collection.
Item 9: Prefer try-with-resources to try-finally
- try-with-resources came in Java 7 and it allows types of AutoCloseable and Closeable.
- This allows object owners to ensure resources are properly closed without losing the original error / stacktrace.
Item 10: Obey the general contract when overriding
equals
- If it is ok for your object to only equal itself (i.e. the exact instance), then do NOT override equals.
- How To:
- Use == operator to check if it is the same instance
- Use instanceof operator to check if they are the same type.
- Cast the argument to correct type.
- Check each significant field in the class
Item 11: Always override hashCode when you override equals
- If you don’t do this, then it is possible for two equal objects, to not have the same hashcode
- This breaks contract for hashcode.
Item 12: Always override toString
- Not mission critical
- providing a good toString implementation makes your class much more pleasant to use and makes systems using the class easier to debug.
Item 13: Override clone judiciously
- Clone doesn’t deep a deep copy
- Prefer constructors or static factory methods.
Item 14: Consider implementing Comparable
- when implemented, allows consistent and predictable sorting, which enhances usability and interoperability in Java’s collection frameworks.
Item 15: Minimize the accessibility of classes and members
- best practice is to make each class or member as inaccessible as possible.
- If hidden you can:
- decorate functions
- change internal implementations that still meet function definitions
- can guarantee thread safety
Item 16: In public classes, use accessor methods, not public fields
- If a class is accessible outside its package, provide accessor methods to preserve the flexibility to change the class’s internal representation
Item 17: Minimize mutability
- Immutable objects are inherently thread-safe; they require no synchronization
- Immutable objects can be shared freely, even their internals
- Immutable objects make great building blocks for other objects
- Immutable objects provide failure atomicity for free
- The major disadvantage of immutable classes is that they require a separate object for each distinct value.
Item 18: Favor composition over inheritance
Unlike method invocation, inheritance violates encapsulation
Item 19: Design and document for inheritance or else prohibit it
- Designing a class for inheritance requires great effort and places substantial limitations on the class
- The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed.
- If you want to allow inheritance remember:
- The class must document its self-use of overridable methods.
- Constructors must not invoke overridable methods
- The only way to test a class designed for inheritance is to write subclasses.
- You must test your class by writing subclasses before you release it.
Item 20: Prefer interfaces to abstract classes
- Java allows single inheritance (i.e. one class can implement many interfaces but can only be extended by one class)
- Existing classes can easily be retrofitted to implement a new interface.
- Interfaces are ideal for defining mixins.
- Interfaces allow for the construction of nonhierarchical type frameworks.
Item 21: Design interfaces for posterity
Carefully design interfaces with the future in mind, as they are difficult to change once published. Since interfaces define a contract that implementing classes must follow, adding or modifying methods later can break backward compatibility. To create robust interfaces that can stand the test of time:
- Minimize Initial Methods: Start with a small, essential set of methods to avoid overloading the interface with unnecessary functionality.
- Use Default Methods Judiciously: Since Java 8, default methods can be added to interfaces, allowing you to introduce new methods without breaking existing implementations, but overuse can lead to bloated and confusing interfaces.
- Consider Potential Implementations: Think about how future classes might implement the interface, ensuring flexibility and extensibility without forcing changes.
The goal is to create a minimal, stable, and flexible interface that can accommodate future needs without frequent modifications.
Item 22: Use interfaces only to define types
interfaces in Java should be used solely to define types, not to hold constant values or utility methods. Using interfaces for constants or utilities can lead to confusing and cluttered code, as these interfaces often end up being implemented by unrelated classes simply to access the constants, violating the purpose of interfaces as contracts for behavior.
Key points include:
- Avoid Constant Interfaces: Defining constants in interfaces and implementing them across classes is considered a bad practice because it pollutes the implementing class’s namespace. Instead, constants should be placed in utility classes or enums.
- Use Interfaces as Contracts for Behavior: Interfaces should represent capabilities or behaviors that can be implemented by various classes, following the principle of polymorphism.
In summary, interfaces should focus on defining the behaviors expected from implementing classes, not as a means to share constants or unrelated static methods.
Item 23: Prefer class hierarchies to tagged classes
- eg, don’t have a Shape class that can take an Enum that defines which type of shape it becomes.
- Instead, just have a subclass for different shapes
Reasons to Avoid Tagged Classes:
- Complexity and Error-Prone Code: Tagged classes require extensive conditional logic (e.g., if or switch statements) to handle the different cases, increasing the chance of errors and making code harder to read and maintain.
- Reduced Extensibility: Modifying or adding a new “type” in a tagged class usually requires changes in multiple places, which violates the open-closed principle (classes should be open for extension but closed for modification).
Item 24: Favor static member classes over nonstatic
- If a nested class doesn’t need access to the outer classes members, and only needs access to static members, then use static member class.
- Using non-static member classes can be a cause for memory leak if not careful.
Item 25: Limit source files to a single top-level class
- When you do this, the ordering of the classes determines which is the actual definition of the source file.
- Following this rule guarantees that you can’t have multiple definitions for a single class at compile time.
- This in turn guarantees that the class files generated by compilation, and the behavior of the resulting program, are independent of the order in which the source files are passed to the compiler.
Item 26: Don’t use raw types
- They exist primarily for compatibility with pre-generics code.
- See Item 29 for reasons to use generic types
Item 27: Eliminate unchecked warnings
- Generics will generate a lot of warnings.
- Eliminate every unchecked warning that you can
- If you can’t eliminate a warning, but you can prove that the code that provoked the warning is typesafe, then (and only then) suppress the warning with an @SuppressWarnings(“unchecked”) annotation
- Always use the SuppressWarnings annotation on the smallest scope possible.
- Every time you use a @SuppressWarnings(“unchecked”) annotation, add a comment saying why it is safe to do so.
Item 28: Prefer lists to arrays
- Lists allow generics, arrays to do not.
- See Item 29 for reasons to use generic types