The goal of this course is to help you use Java more effectively. There are some advanced topics discussed, including object creation, concurrency, serialization, reflection, and other advanced features. This course will guide your journey of Java proficiency.
1. Introduction
In the TIOBE programming language ranking, the Java language developed by Sun in 1995 is one of the most widely used programming languages in the world. As a general programming language, Java language is very attractive to software development engineers due to its powerful toolkit and runtime environment, simple syntax, rich platform support (written at once, run everywhere) and extremely active community support.
In this series of articles, advanced content related to Java is covered, so it is assumed that the reader already has basic language knowledge. This is not a complete reference manual, but an exhaustive guide to taking your skills to the next level.
This course contains a large number of code snippets. In order to make comparisons, some other parties will provide examples of Java 7 and Java 8 at the same time.
2. Example construction
As an object-oriented language, object creation is perhaps one of the most important concepts in the Java language. A constructor plays an important role in the initialization of object instances, and Java provides a variety of ways to define constructors.
2.1 Implicit (generated) construction method
Java allows no constructors to be declared when defining classes, and this does not mean that there is no constructor. Let's look at the definition of the following class:
package com.javacodegeeks.advanced.construction;public class NoConstructor {}This class does not define a constructor, but the Java compiler will implicitly generate one for it, allowing us to use the new keyword to create new object instances.
final NoConstructor noConstructorInstance = new NoConstructor();
2.2 Parameterless construction method
The parameterless constructor is the easiest way to replace Java compilation and generate constructors by explicit declarations.
package com.javacodegeeks.advanced.construction;public class NoArgConstructor { public NoArgConstructor() { // Constructor body here }}When creating a new object instance using the new keyword, the above constructor will be called.
2.3 Parameter-like construction method
The parameter construction method is the most interesting and widely used, and the creation of new instances is customized by specifying parameters. The following example defines a constructor with two parameters.
package com.javacodegeeks.advanced.construction;public class ConstructorWithArguments { public ConstructorWithArguments(final String arg1,final String arg2) { // Constructor body here }}In this scenario, when using the new keyword to create an instance, two parameters defined on the construction method need to be provided at the same time.
final ConstructorWithArguments constructorWithArguments = new ConstructorWithArguments( "arg1", "arg2" );
Interestingly, constructors can be called to each other through this keyword. In practice, it is recommended to chain multiple constructors by using this to reduce code duplication and to have a single initialization entry based on the object. As an example, the following code defines a constructor with only one parameter.
public ConstructorWithArguments(final String arg1) { this(arg1, null);}2.4 Initialize the code block
In addition to constructing methods, Java also provides the logic to initialize by initializing code blocks. Although this usage is rare, it is not harmful to know more about it.
package com.javacodegeeks.advanced.construction;public class InitializationBlock { { // initialization code here }}On the other hand, initializing code blocks can also be regarded as implicit construction methods without parameters. In a specific class, multiple initialization code blocks can be defined, and they are called in the order they are in the code when executed, as shown in the following code:
package com.javacodegeeks.advanced.construction;public class InitializationBlocks { { // initialization code here } { // initialization code here }}Really initializing code blocks are not to replace the constructor, but they can appear simultaneously. But remember that the initialization code will be executed before the constructor method call is soon.
package com.javacodegeeks.advanced.construction;public class InitializationBlockAndConstructor { { // initialization code here } public InitializationBlockAndConstructor() { }}2.5 Ensure the construction of default values
Java provides a definite initialization guarantee, and programmers can use the initialization result directly. Uninitialized instances and class variables (statics) will be automatically initialized to the corresponding default values.
Type default value
booleanFalse
byte0
short0
int0
long0L
char/u0000
float0.0f
double0.0d
Object reference null
Table 1
We use the following example to verify the default values in the above table:
package com.javacodegeeks.advanced.construction;public class InitializationWithDefaults { private boolean booleanMember; private byte byteMember; private short shortMember; private int intMember; private long 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 ); }}After instantiating the object using the new keyword:
final InitializationWithDefaults initializationWithDefaults = new InitializationWithDefaults(),
You can see the output result from the console as follows:
booleanMember = falsebyteMember = 0shortMember = 0intMember = 0longMember = 0charMember = 0floatMember = 0.0doubleMember = 0.0referenceMember = null
2.6 Visibility
The constructor follows Java's visibility rules, and can determine whether the constructor can be called in other classes through access control modifiers.
Modifier package visibility subclass visibility public visibility
public visible visible visible visible visible visible
protected visible visible, visible invisible invisible
<No modifier>visible, not visible, not visible
Private Invisible Invisible Invisible Invisible Table 2
2.7 Garbage recycling
Java (JVM to be precise) has an automatic garbage collection mechanism. Simply put, when a new object is created, it will automatically allocate its intrinsics; then when the object is no longer referenced, they will be automatically destroyed and the corresponding memory will be recycled.
Java garbage collection adopts a generational recycling mechanism and is based on the assumption that "most objects have short life" (that is, they will not be recited soon after the object is created, so they can be safely destroyed). Most programmers habitually believe that object creation in Java is very inefficient, so they should avoid the creation of new objects as much as possible. In fact, this understanding is wrong. The overhead of creating objects in Java is quite low and fast. The huge overhead of the real generation is unnecessary long-term survival objects, so they will eventually be migrated to the old age and cause stop-the-world to occur.
2.8 Object Finalizers
We have talked about the topics related to construction methods and object initialization, but we have not mentioned their negative side: object destruction. Mainly because Java uses garbage collection mechanisms to manage the life cycle of objects, destroying unnecessary objects and freeing required memory becomes the responsibility of garbage collection.
However, Java still provides another feature similar to a destructor finalizer, which assumes the responsibility of cleaning up multiple resources. Finalizer is generally seen as a dangerous thing (because it can bring a variety of side effects and performance issues). Usually, finalizer is not required so try to avoid it (except in rare scenarios that contain a large number of native objects). The try-with-resources syntax and AutoCloseable interface introduced in Java 7 can be used as alternatives to finalizers, and the following concise code can be written:
try ( final InputStream in = Files.newInputStream( path ) ) { // code here}3. Static initialization
Above we learned about the construction and initialization of class instances. In addition, Java also supports class-level initialization construction, called static initialization. Static initialization is similar to the initialization code block described above, except that there are additional static keyword modifications. It should be noted that static initialization will only be performed once when the class is loaded. Examples are as follows:
Similar to initializing code blocks, multiple static initialization blocks can be defined in a class, and their position in the class determines the order in which they are executed at initialization. Examples are as follows;
package com.javacodegeeks.advanced.construction;public class StaticInitializationBlocks { static { // static initialization code here } static { // static initialization code here }}Because static initialization blocks can be triggered by multiple threads executing in parallel (when the class is initially loaded), the JVM runtime ensures that the initialized code is executed only once in a thread-safe way.
4. Constructor mode
A variety of easy-to-understand constructor (creator) patterns have been introduced to the Java community over the years. Below we will learn a few of the more popular ones: singleton mode, auxiliary class mode, factory mode, and dependency injection (also known as control inversion).
4.1 Singleton Mode
Singleton is a long history but controversial model in the software development community. The core concept of singleton pattern is to ensure that at any time a given class is created only one object. While it sounds simple, there is a lot of discussion about how to create objects in a correct and thread-safe way. The following code shows a simple version of the singleton pattern implementation:
package com.javacodegeeks.advanced.construction.patterns;public class NaiveSingleton { private static NaiveSingleton instance; private NaiveSingleton() { } public static NaiveSingleton getInstance() { if( instance == null ) { instance = new NaiveSingleton(); } return instance; }}There is at least one problem with the above code: multiple objects may be created in a multi-threaded concurrency scenario. A reasonable way to implement (but not delayed loading) is to use the static`final` property of the class. as follows:
final property of the class.package com.javacodegeeks.advanced.construction.patterns;public class EagerSingleton { private static final EagerSingleton instance = new EagerSingleton(); private EagerSingleton() { } public static EagerSingleton getInstance() { return instance; }}If you don't want to waste valuable resources and want singleton objects to be created only when they are really needed, then you need to use an explicit synchronization method. This method may reduce concurrency in multi-threaded environments (more details about Java concurrency will be described in Java Advanced 9-Concurrency Best Practices).
package com.javacodegeeks.advanced.construction.patterns;public class LazySingleton { private static LazySingleton instance; private LazySingleton() { } public static synchronized LazySingleton getInstance() { if( instance == null ) { instance = new LazySingleton(); } return instance; }}Nowadays, singleton patterns are no longer considered a good choice in many scenarios because they make the code less easy to test. In addition, the generation of dependency injection mode also makes singleton mode unnecessary.
4.2 Tools/Auxiliary Classes
The tool class/help class pattern is quite popular among Java developers. Its core concept is to use non-institialized classes (by declaring private constructors), optional final (more details about declaring final classes will be introduced in Java Advanced 3-class and interface design) keywords and static methods. Examples are as follows:
package com.javacodegeeks.advanced.construction.patterns;public final class HelperClass { private HelperClass() { } public static void helperMethod1() { // Method body here } public static void helperMethod2() { // Method body here }}Many experienced developers believe that this pattern will make tool classes a container for various irrelevant methods. Because some methods do not have a suitable placement but need to be used by other classes, they will be mistakenly placed into the tool class. This design should also be avoided in most scenarios: there will always be better ways to reuse the code, keeping the code clear and concise.
4.3 Factory model
The factory model has proven to be an extremely powerful tool for developers, and there are many ways to implement it in Java: factory methods and abstract factories. The easiest example is to use the static method to return an instance of a specific class (factory method), as follows:
package com.javacodegeeks.advanced.construction.patterns;public class Book { private Book( final String title) { } public static Book newBook( final String title ) { return new Book( title ); }}Although using this method can improve the readability of the code, it is often controversial that it is difficult to give the newBook factory method richer scenarios. Another way to implement the factory pattern is to use interfaces or abstract classes (abstract factories). As follows, we define a factory interface:
public interface BookFactory { Book newBook();}Depending on the picture gallery, we can have many different newBook implementations:
public class Library implements BookFactory { @Override public Book newBook() { return new PaperBook(); }}public class KindleLibrary implements BookFactory { @Override public Book newBook() { return new KindleBook(); }}Now, different implementations of BookFactory block the differences in specific books, but provide a general newBook method.
4.4 Dependency injection
Dependency injection (also known as control inversion) is considered by class designers as a good design practice: if some class instances depend on instances of other classes, those instances that are dependent should be provided (injected) through constructor methods (or setter methods, policies, etc.), rather than created by the instance itself. Let's take a look at the following code:
package com.javacodegeeks.advanced.construction.patterns;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 ); }}The Dependant class requires an instance of the DateFormat class and is obtained by DateFormat.getDateInstance() when instantiating the object. A better way should do the same thing by constructing the parameters of the method:
package com.javacodegeeks.advanced.construction.patterns;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 ); }}In the example above, all dependencies of the class instance are provided externally, making it easy to tweak DateFormat and easy to write test code.