“Live up to the time, the creation of non-stop, this article is participating in 2021 year-end summary essay competition”
Effective Java is the book by Josh Bloch, one of the world’s leading experts. As I mentioned in my previous post, programming is like martial arts, requiring external martial arts techniques (programming languages, tools, middleware, etc.). Also need to practice the mind (design patterns, source code, etc.) outstanding students, learning OR open
Personally, I have also been studied for nearly five years in the field of Java, the science of uniting the “internal strength” has also contacted some programming code through various channels, such as taishan the admins about alibaba, on the basis of reading the book is still under did a lot of impulse, let me learned a lot of the details behind the convention, and some made me appreciate the book’s point is that The explanations of the programming conventions in the book impressed me and made me want to use them in my work, reminding me to keep reading the JDK source code on my agenda.
Finally want to share my personal views at present, internal work practice is not as simple as learning a new tool, its purpose lies in the steadfast, exploring the underlying principle of process is slow and difficult, but once the enlightenment, for will break through the bottleneck, to achieve a higher level, it is far from me through a blog or two can be learned.
Here are my gains and reflections on this book.
In some cases it can be misleading. For example, when extends is an inherited keyword in the Java language, it doesn’t mean anything when translated into an extension. Therefore, it is suggested to compare the semantic understanding of the original English version.
For those of you who don’t have time to read the original, please refer to this article.
Static factory instead of constructor
This section also illustrates the advantages of the factory design pattern over constructors:
- Static factory methods have names
The static method bigInteger. probablePrime, for example, lets you see at a glance that the constructed object might be prime
- You don’t have to create a new object every time you call it
You can refer to Spring to create a singleton logic, the built instance stored in the cache return; We always create a new object when we call the constructor.
- A static factory can return a subclass of the original class
class A{
// The constructor can get only one object of the class
public A(a){... }// Static constructor can get subclasses of A
public staticA subclass XXXFactory(){... }}Copy the code
In addition, Spring source code BeanFactory is a good practice of factory design patterns, there are similar
- You can use static factory method inputs to control the type of object returned
For example, the static factory method of the EnumSet class can decide whether to return a RegularEnumSet instance or a JumboEnumSet instance based on the number of elements it takes in
- The class to which the object returned by the method belongs may not exist when writing the class containing the static factory method
This is a bit of a mouthful at first, but it actually says that the return value can be a subclass, for example: JDBC database Connection API: JDBC database Connection API: JDBC database Connection API: JDBC database Connection API: JDBC database Connection API But then the reason we all use it is because the database vendor did the docking.
After talking about the advantages of the static factory approach, we can talk about the disadvantages so that we can understand more why the author advocates us to do this:
- Classes that do not have a public or protected constructor cannot be subclassed.
In layman’s terms, the essence of a static factory method is to achieve flexibility by returning a subclass of the value returned by this function. But since a subclass inherits its parent class, the constructor of the subclass will call the parent class’s no-argument constructor by default. If it doesn’t, the constructor of the parent class needs to be explicitly called with the same argument. Therefore, if the parent class’s constructor is not common or protected, the subclass cannot be instantiated, and the static factory method makes no sense.
However, the authors suggest that this may be a “blessing in disguise” because it encourages programmers to use composition rather than inheritance
- They are not explicitly marked in the API documentation as constructors are.
A static factory annotation in the class’s comments can be addressed, and Javadoc will definitely add that in the future.
Consider using a constructor when a constructor has multiple arguments
Multi-argument constructors:
If you want to create an instance of a class whose constructor takes more than one parameter, code like this appears:
NutritionFacts cocaCola = new NutritionFacts(240.8.100.0.35.27);
Copy the code
This call usually requires a lot of arguments that you don’t want to set, but have to set, such as the third passed 0. This works, but if you have a lot of arguments, the client-side code will be difficult to write, and programmers will be wary of passing the wrong arguments.
LogUtil (LogUtil) is a new logging tool that can be used to create a LogUtil (LogUtil). The LogUtil (LogUtil) is a new logging tool that can be used to create a LogUtil (LogUtil).
The JavaBeans mode:
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
Copy the code
Calling the no-argument constructor to create the object and then calling setter methods to set each required parameter is more readable, but has serious drawbacks:
- The process of creating objects is not atomic, so inconsistent objects can be produced in high concurrency scenarios
- The JavaBeans pattern cannot make classes immutable because calling setter methods itself means “mutable.
Builder: SqlSessionFactoryBuilder in Mybatis is an example of the Builder model. In the definition of a class, Builder is usually a static member of the class, and the build method with no arguments is usually an immutable object.
NutritionFacts cocaCola = new NutritionFacts.Builder(240.8).calories(100).sodium(35).carbohydrate(27).build();
Copy the code
Builder mode is very flexible, which can use a single Builder to build multiple objects. The Builder parameters can be adjusted during the call to the Build method to create the object, and can even fill in fields such as creating new objects that automatically increment ordinal values.
3. Enhance the Singleton property with a private constructor or enumerated type
I believe that students who have been exposed to design patterns know that a classical way to implement singletons is the constructor of private. However, it was not until I saw the content of this chapter that I suddenly realized that enumeration types can also strengthen singletons. I feel ashamed that my ability to synthesize singletons needs to be strengthened.
Singleton method 1:
// Singleton with public final field
public class Elvis {
public static final Elvis INSTANCE= new Elvis();
private Elvis(a) {.. .}}Copy the code
This implementation has a downside is that can use reflection mechanism in AccessibleObject. SetAccessible to modify the constructor to become public
Singleton method 2:
// Singleton with static factory
public class Elvis {
private static finalElvis INSTAN zeta E =new Elvis();
private Elvis(a) {... }public static Elvis getInstance(a){return the INSTANCE; }}Copy the code
In a practical choice, the second implementation is preferred if one of the following advantages is used, otherwise the first singleton implementation is considered:
- Can easily be modified to be non-singleton (by modifying the return statement of getInstance)
- You can use
Elvis::instance
grammar
In addition, to prevent the singleton feature from being broken by creating a new instance with each serialization and deserialization, you need to add a method to the Elvis class:
private Object readResolve (a) {
return INSTANCE;
}
Copy the code
Implement singleton 3: This is another way I didn’t think of, declaring an enumeration type containing a single element:
public enum Elvis {
INSTANCE;
public void leaveTheBuilding(a) {.. .}}Copy the code
With Elvis. The INSTANCE can obtain singleton, Elvis. INSTANCE. LeaveTheBuilding (); Callback methods.
This approach is also highly recommended by the authors, providing a serialization mechanism for nothing and absolutely preventing multiple instantiations. Enumerated types of single elements are often the best way to implement singletons.
Use privete’s constructor to enforce the ability to not instantiate
This article focuses on the fact that when writing utility classes that you don’t want to instantiate, such as java.lang.Math, it’s a good idea to write a private constructor by hand.
The disadvantage is that this class cannot be subclassed, because subclasses have no superclass constructor to call.
5 Dependency injection is preferred when referencing resources
An example is referencing resources: the spellchecker relies on dictionaries, which are called resources.
There are two common ways to refer to a resource:
- Encapsulate as a static utility class
private static final Lexicon dictionary;
public static boolean isValid(String word){... };Copy the code
- Design as a singleton class
privateConstructor;private final Lexicon dictionary;
public boolean isValid(String word){... };Copy the code
In fact, both implementations are wrong because they assume that only one dictionary is available and that in the real world each language needs its own dictionary.
From the above discussion, we can solve this problem in the simplest way: each time you create a new instance, pass its dependent resources to the constructor.
public class SpellChecke {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {... }public boolean isValid(String word) {...}
}
Copy the code
This is a form of dependency injection.
A variant of dependency injection is to pass a resource factory as an argument to the constructor, such as Mosaic create(Supplier
tileFactory){… }
By the way, Spring is a classic dependency injection framework!
6 Avoid creating unnecessary objects
Literally, everyone knows that creating unnecessary objects is a bad idea. But the main purpose of this section is to remind us to avoid unintentional code writing that creates unnecessary objects.
Case 1:
String s = new String("abc");
Copy the code
The correct answer to this question is:
String s = "abc";
Copy the code
The reason is that the first method creates a new String instance each time it is executed, but these are all repetitions!
Example 2:
We use static factory methods in preference to constructors to avoid creating unnecessary objects, such as Boolean. ValueOf (String) is always used in preference to constructor Boolean(String). Because the constructor creates a new object each time it is called, static factories do not.
Example 3: When you create expensive objects, you should cache them.
Matches creates an instance of Pattern inside the String. This is expensive because regular expressions need to be compiled into finite state machines, so it should be cached:
public class RomanNumerals {
private static final Pattern ID = Pattern.compile("^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$");
static boolean isRomanNumeral(String s){
returnID.matcher(s).matches(); }}Copy the code
In this way, the same ID instance is reused every time isRomanNumeral is called
Example 4: The above Pattern instances are invariant, but in certain scenarios instances are mutable, which is where adapters can be considered. An adapter is an object that delegates functionality to a fallback object, providing the fallback object with an interface that replaces previous functionality.
The KeySet method of the Map interface, for example, returns the same Set instance every time it is called. Although Set instances are mutable, when one of them changes, the others change because they are themselves one.
Example 5: Base types are preferred over boxing types because of the following example:
private static long sum(a){
Long sum = 0L;
for(long i = 0; i <= Integer.MAX_VALUE; i ++)
sum += i;
return sum;
}
Copy the code
This program executes without any problems, but it is slower because sum is of type Long rather than Long, so the program constructs about 2^31 Long instances.
This point in my memory and work requirements are not consistent, so I specially read the Alibaba Java development manual, which is described as follows:It can be seen that the company gives priority to the business in this issue, so friends can consider the choice when using. I personally recommend using the packaging type.
One mistake to avoid: don’t get caught up in the logic that creating objects is very expensive after this chapter, instead it’s a mistake to maintain your own object pool to avoid creating objects. Because modern JVM implementations have highly optimized garbage collectors, performance can easily exceed that of lightweight object pools.
A good example is database connection pooling, because establishing a connection to a database is very expensive.
7 Remove expired object references
This tip focuses on avoiding memory leaks. Because a language like Java has a garbage collection mechanism, memory leaks are usually hidden.
Such as:
package com.wjw;
import java.util.Arrays;
import java.util.EmptyStackException;
/ * * * 2 *@Author: Mr. Wang * 3 *@Date: 2021/11/23 20:50
* 4
*/
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(a){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size ++] = e;
}
public Object pop(a){
if (size == 0)
throw new EmptyStackException();
return elements[-- size];
}
private void ensureCapacity(a) {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1); }}Copy the code
There is a memory leak in the above code. If you add elements to the stack and then pop them, the popup object is not recycled because the stack maintains expired references to the popup object.
The solution to this problem is simply to set the reference to the out-of-stack element to expire:
Other sources of memory leaks:
- The cache
The reason is that object references that are put into the cache tend to be forgotten. Taking advantage of the fact that the value of data stored in the cache is inversely proportional to the length of storage, a background thread can be started to clean up the invalid items in time.
- Listeners and other callbacks
The reason is that they pile up when clients register callbacks in the API we provide but do not cancel them. We can keep only their weak references (keys in a WeakHashMap) if we want callbacks to be reclaimed immediately.
A bit more about WeakHashMap: WeakHashMap is actually a weak-reference Map. Keys are stored as weak references and will be garbage collected during GC if there are no external strong references (when strong references corresponding to callback are not present). This feature is also used in caches. If there is no reference to a key, the item in the cache will be deleted automatically.
Such as: WeakHashMap is used to store BigImage instances. Key is of type ImageName and value is of BigImage instance. If you set ImageName = null, there are no strong references to this key and the BigImage instance will be reclaimed during GC
WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>();
BigImage bigImage = new BigImage("image_id");
UniqueImageName imageName = new UniqueImageName("name_of_big_image"); / / strong reference
map.put(imageName, bigImage);
assertTrue(map.containsKey(imageName));
imageName = null; // The values object in map becomes a weak reference object
System.gc(); // Initiate a GC
Copy the code
Avoid finalization and cleanup methods
Finalizer and cleaner are unpredictable and dangerous, so they should be avoided
Their disadvantages mainly include the following:
1. There is no guarantee that the finalizing method will be executed in a timely manner. The thread of the finalizing method will have a much lower priority than the other application threads, and the program may even terminate before it can be executed. So if you use them to release locks on shared resources, you can easily crash the system.
Normally, if an exception occurs, the program will print an exception message. However, if the exception occurs in the finalizing method, nothing will be printed and you cannot start debugging.
3. Finalization and cleanup methods have a significant performance penalty mainly because finalization prevents effective garbage collection.
There is a serious security problem with finalizing methods that hackers can use to launch attacks.
If the constructor throws an exception, the malicious subclass’s finalizer can run on the partially constructed object, preventing it from being garbage collected. This allows you to call methods on this object that would otherwise not be allowed here.
Normally, the constructor throws an exception and the object fails to be created, not with finalization methods. To protect against this attack, write an empty final Finalize method. If a resource encapsulated in an object really needs to be terminated, one way to bypass writing a termination or cleanup method is to have the class Implements AutoCloseable, and the client calls the close method for each instance when it is no longer needed.
Of course, there are two scenarios where finalization and cleanup methods are useful:
1. Act as a “safety net” in case you forget to call the close method
2. Terminate non-critical local resources The local peer is a Native object. Java objects are delegated to a local object through native methods, which cannot be reclaimed by the JVM.
9 try-with-resources takes precedence over try-finally
This is a way to close resources.
Prior to Java7, the try-finally statement was used to close a resource, but it had two obvious drawbacks:
- If you have multiple resources that need to be closed, the code can be ugly, like this:
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[10];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally{ out.close(); }}finally{ in.close(); }}Copy the code
- Exception information will be overwritten
static String firstLineOfFile(String path) throws Exception{
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally{ br.close(); }}Copy the code
If the underlying physical device is abnormal, br.readline (); An exception will be thrown, and a call to close will raise an exception, at which point the second exception will override the first exception, which can be cumbersome to debug because the first exception is the actual entry point to diagnose the problem.
These two problems were solved when Java7 introduced try-with-resources.
To use this syntax, you need to implement AutoCloseable, which is implemented by many classes in the Java and third-party libraries.
- When multiple resources need to be shut down
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)){
byte[] buf = new byte[10];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n); }}Copy the code
- When a method throws an exception
static String firstLineOfFile(String path) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader(path))) {
returnbr.readLine(); }}Copy the code
If both the readLine and close methods throw exceptions, the previous exception is still printed out and not overwritten.
- You can also use the catch clause to handle exceptions
static String firstLineOfFile(String path, String defaultVal) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
} catch (IOException e){
returndefaultVal; }}Copy the code
Instead of printing the stack, a default value is returned.