Effective Java Reading Notes

Effective Java Reading Notes

Chapter 2: Creating and Destroying Objects

This chapter mainly discusses the creation and destruction of objects, including how to use static factory methods, avoid creating unnecessary objects, and release objects in time.

The core idea of ​​this chapter is: For creating and destroying objects, some basic rules should be followed, such as using static factory methods instead of using constructors directly, avoiding creating unnecessary objects, and destroying objects in time.

There are several reasons to use static factory methods instead of direct constructors:

Static factory methods can have more descriptive method names, which can help improve code readability and maintainability.

Static factory methods can control the creation of objects, such as caching objects that have already been created to avoid repeated creation, thereby improving performance.

Static factory methods can return objects of subclasses, but constructors cannot. This can provide convenient implementations for interfaces and abstract classes.

Also note:
Avoid creating unnecessary objects. For example, use a static factory method to create a singleton pattern object to avoid creating the same object repeatedly.
Destroy the object in a timely manner. For example, avoid keeping too many references to objects that take up too much memory space.

Close resources using the try-with-resources statement. For example, use the try-with-resources statement to close resources such as files and network connections to avoid resource leaks and performance problems.

Code example:

// Create objects using static factory methods
public class MyClass {
    private final String name;
    private final int age;

    private MyClass(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public static MyClass create(String name, int age) {
        return new MyClass(name, age);
    }
}

// Avoid creating unnecessary objects
String s = "hello";
// Do not do this
String t = new String(s);
// should do
String u = s;

// timely release of objects
MyClass myObj = new MyClass("John", 30);
// use myObj
myObj = null; // timely release of objects

Chapter 3: Methods common to all objects

The core content of the third chapter is about the best practices and design principles for the combination of classes and interfaces. This chapter provides some best practices and design principles to help Java programmers correctly design the hierarchy of classes and interfaces to avoid some common problems, such as too high coupling between classes and interfaces, and difficult code maintenance.

The core idea of ​​this chapter is: For the combination of classes and interfaces, some basic rules should be followed, such as using inheritance and implementation to express the relationship between classes and interfaces, avoiding too many levels in the class hierarchy, and using abstract classes and Interfaces to define types etc. Below we explain these rules and the reasons behind them in detail.

The following are some core content of this chapter:

Use inheritance and implementation to express the relationship between classes and interfaces. Inheritance means that a class is a type of another class, while implementation means that a class implements all the methods of an interface.

Avoid too many levels in the class hierarchy. If the class hierarchy is too deep, the code will be less readable and maintainable. The hierarchy should be kept as flat as possible.

Prefer composition over inheritance. Composition is a more flexible way of combining objects dynamically at runtime, whereas inheritance is static and must be determined at compile time.

Types are defined through abstract classes and interfaces. Abstract classes and interfaces can define a set of methods, thereby defining a type, which allows us to organize code more flexibly, while improving the readability and maintainability of the code.

Code example:

// Override the equals() and hashCode() methods
public class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

// Override the toString() method
public class Person {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Chapter 4: Classes and Interfaces

The core content of the fourth chapter is about the introduction of common Java process designing norms and idioms. These specifications and idioms cover many aspects of Java programming, such as classes and interfaces, methods, variables, exceptions, annotations, concurrency, and more. The purpose of these conventions and idioms is to write Java code that is clear, easy to understand and maintain, while improving code readability, reliability, and performance.

The following are some core content of this chapter:

Classes and interfaces: Make the design of classes and interfaces as simple, clear, and easy to understand and use as possible. A minimum number of methods and variables should be used to accomplish a specific task, and exposure of internal state should be avoided as much as possible.

Method: The method should be as short and concise as possible, clear and easy to understand. You should use concise parameter lists, clear naming and annotations, and clear return types and exception handling.

Variables: Variable naming should be as clear, concise, and meaningful as possible, while avoiding excessive and unnecessary abbreviations. The final modifier should be used as much as possible to make variables immutable, thereby improving the reliability and performance of the code.

Exceptions: You should try to use Java standard exceptions instead of custom exceptions. When catching exceptions, you should follow the principle of minimum capture, only capture necessary exceptions, and avoid catching all exceptions.

Comments: Comments should be concise, clear and understandable. The purpose of comments is to explain the intent and implementation details of the code to help other developers understand and maintain the code.

Concurrency: When writing multi-threaded programs, you should follow some best practices and idioms, such as using thread-safe classes, avoiding shared mutable state, using volatile and synchronized keywords to protect shared state, etc.

Code example:

// Try to use interfaces instead of abstract classes
public interface Animal {
    void makeSound();
}

// Avoid using non-final classes
public final class MyFinalClass {
    // class implementation
}

Chapter 5: Generics

Generics are an important feature of the Java language, allowing us to write more general, type-safe code. This chapter provides some best practices and design principles to help Java programmers use generics correctly to avoid some common problems, such as type conversion exceptions, compile-time errors, etc.

  1. When using generics, try to use wildcard types as much as possible. Wildcard types allow us to write more generic code while maintaining type safety.

  2. When using generics, try to avoid using primitive types. Raw types can bypass compile-time type checking and easily cause type conversion exceptions.

  3. When using generics, try to use type parameters as much as possible. Type parameters allow us to write more generic code while maintaining type safety.

  4. When using generics, try to avoid using primitive types in public API s. Raw types can make your code less robust, and at the same time make your code less readable and maintainable.

  5. When using generics, be aware of type erasure. Type erasure means that generic types are erased into primitive types at compile time, which may cause some problems, such as type conversion exceptions, generic array creation exceptions, etc.

Code example:

// Use restricted wildcards to increase API flexibility
public class MyList<E> {
    private List<E> list = new ArrayList<>();

    public void addAll(Collection<? extends E> c) {
        list.addAll(c);
    }
}

// Prioritize type-safe generic methods
public class Utils {
    public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {
        if (coll.isEmpty()) {
            throw new IllegalArgumentException("Collection is empty");
        }

        T result = null;
        for (T elem : coll) {
            if (result == null || comp.compare(elem, result) > 0) {
                result = elem;
            }
        }
        return result;
    }
}

Chapter 6: Enumerations and Annotations

The core content of Chapter 6 is about best practices and design principles for enumerated types. Enumerated types are an important feature in the Java language that allow us to write more readable and type-safe code. This chapter provides some best practices and design principles to help us use enumeration types correctly to avoid some common problems, such as null pointer exceptions, type conversion exceptions, etc.

The following are some core content of this chapter:

  1. Using enumerated types can make code clearer and easier to understand. Enumerated types can encapsulate constant values ​​and methods, thereby improving the readability and maintainability of the code.

  2. Enum types can be used to implement the singleton pattern. The singleton mode of the enumeration type can avoid thread safety problems, and it can also avoid reflection attacks.

  3. Enumerated types can be used to implement finite state machines. Finite state machine is an important programming model that can be used to describe complex system behavior, thereby improving the readability and maintainability of code.

  4. When using enumerated types, beware of null pointer exceptions. Each enumeration value in an enumeration type is an object, so a null pointer exception may occur.

When using enumerated types, be aware of type conversion exceptions. Each enumeration value in an enumeration type has a unique name and ordinal number, so type conversion exceptions may occur.

Code example:

// Use enums instead of constants
public enum Size {
    SMALL,
    MEDIUM,
    LARGE
}

// Add new method to enum type
public enum Operation {
    PLUS {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE {
        @Override
        public double apply(double x, double y) {
            return x / y;
        }
    };

    public abstract double apply(double x, double y);
}

Chapter 7: Methods

This chapter mainly introduces the use and design principles of the method, including designing the method as a static method as much as possible, avoiding the method being too long, etc.

Code example:

// Try to design the method as a static method
public class MyUtils {
    public static int max(int a, int b) {
        return a > b ? a : b;
    }
}

// avoid long methods
public class MyCalculator {
    public int calculate(int a, int b, int c, int d, int e) {
        int result = 0;
        // calculation process
        return result;
    }
}

Chapter 8: General Programming

The eighth chapter mainly introduces the principles and skills of general programming. General programming refers to writing program code that can be used in multiple application scenarios, and it is also an important basis for high-quality, reusable and easy-to-maintain code. This chapter mainly introduces the following contents:

  1. Follow standard programming conventions and styles. Programming conventions and styles help us write code that is readable, understandable, and maintainable.

  2. Design and follow API specifications. API specification refers to writing an interface that can be used by other developers. Following the API specification can make our code more general and reusable.

  3. Write generic code using interfaces and abstract classes. Interfaces and abstract classes are important tools for general programming to make code more flexible and extensible.

  4. Write reusable generic code. Generic code allows us to write generic, type-safe and reusable code.

  5. Separate creation and use of objects. Separating object creation and use makes code more flexible, extensible, and reusable.

  6. Write type-safe code using enumerated types. Enumerated types are a type-safe, reusable and easy-to-maintain code implementation.

Code example:

// Protector Robustness
public class MyCalculator {
    public int divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return a / b;
    }
}

// Make programs easy to understand and modify
public class MyUtils {
    public static String formatDate(Date date) {
        // date formatting process
        return formattedDate;
    }
}

Chapter 9: Exceptions

This chapter mainly introduces the use and design principles of exceptions, including using checked exceptions, not ignoring exceptions, and not over-catching exceptions, etc.

Code example:

// Use checked exceptions
public class MyCalculator {
    public int divide(int a, int b) throws IllegalArgumentException {
        if (b == 0) {
            throw new IllegalArgumentException("Division by zero");
        }
        return a / b;
    }
}

// don't ignore exceptions
public class MyFileUtils {
    public static void copyFile(File source, File dest) throws IOException {
        try (InputStream in = new FileInputStream(source);
             OutputStream out = new FileOutputStream(dest)) {
            // file copy process
        }
    }
}

// Don't overcatch exceptions
public class MyCalculator {
    public int divide(int a, int b) throws IllegalArgumentException {
        try {
            return a / b;
        } catch (ArithmeticException e) {
            throw new IllegalArgumentException("Division by zero", e);
        }
    }
}

Chapter 10: Concurrency

The core content of Chapter 10 is about best practices and design principles for concurrent programming. Concurrent programming is a very important field in modern software development, but it is also a challenging one. In concurrent programming, multiple threads accessing shared resources at the same time may cause a series of problems, such as race condition, deadlock, starvation, etc. Therefore, writing correct, efficient, and robust concurrent code is a very important task.

The following are some core content of this chapter:

Use synchronization mechanisms to protect shared resources. The synchronization mechanism includes using the synchronized keyword, the Lock interface, the Atomic class, etc. The use of synchronization mechanisms can ensure the correctness and consistency of shared resources.

Avoid excessive synchronization. Excessive synchronization will reduce concurrency performance and may cause problems such as deadlocks. When using the synchronization mechanism, the scope of synchronization should be minimized to improve concurrency performance.

Get familiar with concurrency libraries. Java provides a rich concurrency library, including Executor framework, concurrent collections, atomic variables, etc. Familiarity with concurrency libraries can help us write more efficient and robust concurrent code.

Understand thread safety. Thread safety means that shared resources behave as expected in a multithreaded environment. Understanding different types of thread safety like immutable, thread safe, conditional thread safe, thread unsafe, etc. can help us properly design concurrent code.

Avoid deadlock and starvation. Deadlock and starvation are common problems in concurrent programming, which can be avoided by some technical means. For example, avoid nested locks, avoid lock order deadlocks, etc.

Code example:

// Use synchronized code blocks to ensure thread safety
public class MyCounter {
    private int count = 0;

    public void increment() {
        synchronized (this) {
            count++;
        }
    }

    public int getCount() {
        synchronized (this) {
            return count;
        }
    }
}

// Use concurrent collections to optimize concurrency performance
public class MyConcurrentMap {
    private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

    public void put(String key, String value) {
        map.put(key, value);
    }

    public String get(String key) {
        return map.get(key);
    }
}

Tags: Java Singleton pattern programming language

Posted by magic123 on Mon, 20 Mar 2023 04:38:53 +0530