How to correctly create and destroy Java objects

How to correctly create and destroy Java objects

Public number: Java Little Coffee Show , website:

Author: RonTech, link:

1. Introduction

Java was invented by Sun Microsystems and released in 1995. It is one of the most widely used programming languages in the world. Java is a general-purpose programming language. It has attracted many developers because of its powerful library, runtime, simple syntax, platform independence (Write Once, Run Anywhere-WORA), and awesome community.

In this series of articles, we will cover some advanced Java concepts. We assume that you already have some basic knowledge of the Java language. This series of articles is not a complete reference, but a detailed guide to take your Java skills to the next level.

In this series of articles, you will see some code snippets. In these code snippets, the syntax of java 7 and the syntax of java 8 will be used.

2. Instance Construction

Java is an object-oriented programming language, so the creation of new instances (objects) may be one of its most important concepts. Constructors play a very central role in the new class instance. Java provides many solutions for the definition of constructors.

2.1 Implicitly (implicitly) constructor

Java allows you to define a class without any constructor, but this does not mean that this class has no constructor. For example, let us look at the following class.

package; public class NoConstructor { } Copy code

There is no constructor for this class, but the Java compiler implicitly generates a constructor and is called when a new class instance is created using the new keyword.

final NoConstructor noConstructorInstance = new NoConstructor(); Copy code

2.2 Constructors without Arguments

The parameterless constructor is the easiest way to explicitly perform the work of the Java compiler.

package; public class NoArgConstructor { public NoArgConstructor() { //Constructor body here } } Copy code

This constructor will be called when using the new keyword to create a new instance of this class.

final NoArgConstructor noArgConstructor = new NoArgConstructor () ; duplicated code

2.3 Constructors with Arguments

The parameterized constructor is a very interesting and useful method for parameterized creation of class instances. The following class defines a constructor with two parameters.

package; public class ConstructorWithArguments { public ConstructorWithArguments(final String arg1,final String arg2) { //Constructor body here } } Copy code

In this case, when using the new keyword to create a class instance, both construction parameters must be provided.

final ConstructorWithArguments constructorWithArguments = new ConstructorWithArguments( "arg1", "arg2" ); Copy code

It is very interesting that using this keyword, the constructors can call each other. This way of connecting constructors is a very good practice in terms of reducing code duplication, and it can make a class have only one initialization entry point. Following the example above, we add a constructor with only one parameter.

public ConstructorWithArguments(final String arg1) { this(arg1, null); } Copy code

2.4 Initialization Blocks (Initialization Blocks)

Java also provides another way to implement initialization logic using initialization blocks. This feature is rarely used but it is very necessary to understand its existence.

package; public class InitializationBlock { { //initialization code here } } Copy code

In some cases, the initialization block can make up for the shortcomings of the anonymous parameterless constructor. Some special classes may have many initialization blocks and they will be called in the order they are defined in the code, such as:

package; public class InitializationBlocks { { //initialization code here } { //initialization code here } } Copy code

Initialization blocks are not a replacement constructor and they can exist independently of the constructor. But the most important thing to mention is that the initialization block will be executed before any constructor is called.

package; public class InitializationBlockAndConstructor { { //initialization code here } public InitializationBlockAndConstructor() { } } Copy code

2.5 Construction guarantee

Java provides some initialization guarantees that developers rely on. Uninitialized instance and class parameters are automatically initialized to their default values.

Let us use the following example to confirm these default values. (Search the public account for Java Friend, reply "2021", and give you a collection of Java interview questions)

package; public class InitializationWithDefaults { private boolean booleanMember; private byte byteMember; private short shortMember; private int intMember; private long longMember; private char charMember; private float floatMember; private double doubleMember; private Object referenceMember; public InitializationWithDefaults() { System.out.println( "booleanMember = "+ booleanMember ); System.out.println( "byteMember = "+ byteMember ); System.out.println( "shortMember = "+ shortMember ); System.out.println( "intMember = "+ intMember ); System.out.println( "longMember = "+ longMember ); System.out.println( "charMember = "+ Character.codePointAt( new char[] {charMember }, 0) ); System.out.println( "floatMember = "+ floatMember ); System.out.println( "doubleMember = "+ doubleMember ); System.out.println( "referenceMember = "+ referenceMember ); } } Copy code

Once instantiated using the new keyword:

inal InitializationWithDefaults initializationWithDefaults = new InitializationWithDefaults(); Copy code

The following results will be output in the console:

booleanMember = false byteMember = 0 shortMember = 0 intMember = 0 longMember = 0 charMember = 0 floatMember = 0.0 doubleMember = 0.0 referenceMember = null Copy code

2.6 Visibility

Constructors are subject to Java visibility rules and can have access control modifiers to determine whether other classes can call specific constructors.

2.7 Garbage collection

Java (especially JVM) uses an automatic garbage collection mechanism. In short, when new objects are created, the JVM will automatically allocate memory for these newly created objects. Therefore, when these objects have no references, they will be destroyed and the memory they occupied will be reclaimed.

Java garbage collection is generational. Based on this assumption (generational assumption), most objects are unreachable at a very young age (there are no references within a short period of time after they are created and they are safely destroyed ). Most developers used to believe that creating objects in Java is slow and should avoid the instantiation of new objects as much as possible.

In fact, this is not true: the overhead of creating objects in Java is very small and fast. Even so, there is no need to create objects with a long life cycle, because creating too many long-lived objects may eventually fill up the space of the old generation and cause stop-the-world garbage collection. In this way, the overhead will be relatively large.

2.8 Finalizers

So far, we have talked about constructors and object initialization, but we haven't actually mentioned anything about object destruction. This is because Java uses the garbage collector to manage the life cycle of objects, and the responsibility of the garbage collector is to destroy useless objects and reclaim the memory occupied by these objects.

However, there is a special feature called Finalizers in Java, which is somewhat similar to a destructor, but it solves a different intent when performing resource cleanup. Finalizers are considered to solve some dangerous features (such as problems that can cause countless side effects and performance problems).

Generally speaking, they are not necessary and should be avoided (except in very rare cases, mainly related to local objects). The Java 7 language introduces a better alternative called try-with-resources and the AutoCloseable interface, which allows to write code cleanly like the following:

try (final InputStream in = Files.newInputStream( path)) { //code here } Copy code

3. Static initialization

So far, we have talked about constructors and object initialization. But Java also supports class-level initialization construction, which we call static initialization.

Static initialization is a bit similar to the initialization block, except that the static keyword needs to be added. Note that static initialization is executed only once every time the class is loaded. such as:

package; public class StaticInitializationBlock { static { //static initialization code here } } Copy code

Similar to the initialization block, you can include any number of initialization blocks in the class definition, and they will be executed in the order in which they appear in the class code, for example:

package; public class StaticInitializationBlocks { static { //static initialization code here } static { //static initialization code here } } Copy code

Because the Static initialization block can be triggered from multiple parallel threads (the first class load occurs), the Java runtime guarantees that it will be executed only once under the premise of thread safety.

4. Construction Patterns

In the past few years, many constructive patterns that are easy to understand and widely used have appeared in the Java community. We will introduce some of the more common: Singleton (singleton), helper (helpers), factory mode (factory), injection dependency (dependency injection) - controls well known inversion (inversion of control).

4.1 Singleton mode (Singleton)

The singleton model is one of the oldest and most controversial models in the software developer community. Basically, its main idea is to ensure that only one instance of the class is created at any time. The idea is so simple, but the singleton pattern has triggered a lot of discussions about how to make it right, especially thread safety. The following is an example of the native version of the singleton pattern:

package; public class NaiveSingleton { private static NaiveSingleton instance; private NaiveSingleton() { } public static NaiveSingleton getInstance() { if( instance == null) { instance = new NaiveSingleton(); } return instance; } } Copy code

At least one problem with this code is that if multiple threads are called at the same time, then this class can create multiple instances. One way to design a suitable singleton pattern is to use the static final attribute of the class.

final property of the class. package; public class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return instance; } } Copy code

If you do not want to waste resources and want to delay the creation of the singleton object when it is really needed, this requires explicit synchronization, which may lead to reduced concurrency in a multithreaded environment (details about concurrency) The content will be discussed in a follow-up article).

package; public class LazySingleton { private static LazySingleton instance; private LazySingleton() { } public static synchronized LazySingleton getInstance() { if( instance == null) { instance = new LazySingleton(); } return instance; } } Copy code

Nowadays, the singleton mode is not considered as a good choice in most cases, mainly because the singleton mode will make the code difficult to test. The dependency injection pattern makes the singleton pattern unnecessary.

4.2 Utility/Helper class

The utility or helper class is a very popular pattern used by many developers. Basically, it represents a non-instantiable class (the constructor is defined as private), and you can only choose to define the method as final (how to define the class will be introduced later) or static. such as;

package; public final class HelperClass { private HelperClass() { } public static void helperMethod1() { //Method body here } public static void helperMethod2() { //Method body here } } Copy code

From the developer's point of view, the helpers class often plays the role of a container. This container contains a lot of unrelated methods that are not found elsewhere but need to be shared and used by other classes. This design determines to avoid use in many cases: always find another way to reuse the required functions, keeping the code concise and clear.

4.3 Factory mode (Factory)

The factory model proved to be a very useful technology in the hands of software developers. Therefore, Java has several styles of factory patterns, from factory methods to abstract factories. The simplest example of the factory pattern is a static method (factory method) that returns a new instance of a specific class. E.g:

package; public class Book { private Book( final String title) { } public static Book newBook( final String title) { return new Book( title ); } } Copy code

One might argue that there is no point in introducing the newBook factory method, but using this pattern usually makes the code more readable. Another change in the factory pattern involves interfaces or abstract classes (abstract factories). For example, let us define a factory interface:

public interface BookFactory { Book newBook(); } Copy code

Depending on the library type, several different implementations are completed:

public class Library implements BookFactory { @Override public Book newBook() { return new PaperBook(); } } public class KindleLibrary implements BookFactory { @Override public Book newBook() { return new KindleBook(); } } Copy code

Now, the specific class of Book is hidden after the BookFactory interface is implemented, and BookFactory still provides a general way to create books.

4.4 Dependency Injection

Dependency injection (inversion of control) is considered by class designers to be a good practice: if an instance of some class depends on an instance of another class, the dependent instance should be constructed (for example, through setters-setters, or Strategies strategies, patterns, etc.) are provided to dependent instances instead of being created by dependent instances. Take a look at the following situation:

package; import java.text.DateFormat; import java.util.Date; public class Dependant { private final DateFormat format = DateFormat.getDateInstance(); public String format( final Date date) { return format.format( date ); } } Copy code

The class Dependant requires an instance of DateFormat, and it is only created by calling DateFormat.getDateInstance() at construction time. The best design solution should be to accomplish the same thing in the form of constructor parameters.

package; import java.text.DateFormat; import java.util.Date; public class Dependant { private final DateFormat format; public Dependant( final DateFormat format) { this.format = format; } public String format( final Date date) { return format.format( date ); } } Copy code

According to this scheme, all dependencies of the class are provided externally, so that it is easy to modify the date format and write test cases for the class.

In this part of this series of articles, we have been studying classes and class instance construction and initialization techniques, covering several widely used patterns. In the next part, we will analyze the usage of the Object class and its well-known methods: equals, hashCode, toString and clone.