The ‘Eight-Legged Essay’ You Memorized May Be Outdated

The 'Eight-Legged Essay' You Memorized May Be Outdated

AliMei’s Guide

With the continuous updates and iterations of technology, some viewpoints and methods that were once considered “standard answers” are no longer suitable for current needs and are even regarded as outdated practices. In the new JDK versions, many new features, tools, and methods have been introduced, making Java programming more concise, efficient, and powerful. Therefore, it is time to clean up and update the knowledge base of the “Eight-Legged Essay”.

1. No longer using char[] in String

The 'Eight-Legged Essay' You Memorized May Be Outdated

Before JDK9, String internally stored character data using char arrays (char[]). However, after JDK9, the internal implementation of String changed to use byte arrays (byte[]). The main reason for this change is to save memory space, as using byte arrays to store a large number of Latin series characters (such as English, numbers, common punctuation, etc.) can save half the space compared to using char arrays.At the same time, the internal String class also introduced a byte-type field named coder. This field is used to identify what character encoding the data stored in the byte array uses. In the new implementation of the String class, there are two possible character encodings: ISO-8859-1 (one character occupies one byte) and UTF-16 (one character occupies two bytes). For strings encoded in ISO-8859-1, the value of coder is 0, while for strings encoded in UTF-16, the value of coder is 1. Thus, by checking the value of the coder field, it is possible to know what encoding method should be used to process the data stored in the byte array, thereby avoiding processing errors caused by different character encodings.

2. The types supported by switch are no longer limited to basic types and String

Before discussing this point, it is essential to understand what pattern matching is. Pattern matching is a language feature used to check whether a value matches a certain pattern and to execute corresponding code based on the result. In Scala and Haskell, pattern matching is a core feature, while in Java, the concept of pattern matching was introduced after JDK14.In simple terms, pattern matching allows you to check whether the type of a variable or value meets certain predefined rules (patterns). If it does/does not, specific operations can be executed. For example, through pattern matching, you can specify the following operation (JDK14):

    Object obj = "hello";    if (obj instanceof String str) {        System.out.println(str.length());    }

In this example, “String str” is a pattern that simultaneously performs type checking (instanceof) and downcasting (assigning to the variable str), making the code more concise. In JDK17, switch also supports this feature:

  Object obj = 10L;    switch (obj) {        case String str -> System.out.println("str: " + str);        case Integer intNum -> System.out.println("int: " + intNum);        case Long longNum -> System.out.println("long: " + longNum);        default -> throw new IllegalStateException("Unexpected value");    }

However, the current pattern matching is mainly applied to automatic type conversion during type checking (somewhat rudimentary). In other languages, pattern matching can also achieve various functionalities, such as simultaneously extracting values from complex data structures:

val list = List(1, 2, 3)list match {  case head :: tail => println(s"head: $head, tail: $tail")  case Nil => println("empty list")}

In this example, head :: tail is a pattern that places the head and tail of the queue into head and tail objects, respectively, for subsequent operations.

3. The biased lock of synchronized has been deprecated

First, let’s review what a biased lock is. A biased lock is an optimization technique for the synchronized keyword in Java, based on the idea that repeated access by the same thread does not require locking. The main goal is to eliminate synchronization operations when there is no contention, thereby improving runtime performance. In practice, if a thread acquires the lock, the lock enters a biased mode, recording the thread ID. When this thread requests the lock again, no further synchronization operations are needed, thus saving a lot of operations related to lock acquisition.However, in reality, biased locks do not always bring the expected performance advantages. On the contrary, in some cases (such as multi-core processor environments), the revocation of biased locks requires entering a global safepoint (where the virtual machine pauses all threads), which can lead to significant pause times.The idea of biased locks is good, but it adds complexity to the JVM and does not provide performance improvements for all applications. Therefore, in JDK15, biased locks are disabled by default, and in JDK18, biased locks have been completely deprecated (cannot be enabled via command line).

4. After G1 was introduced, the generational garbage collection strategy also changed

This is relatively familiar, as the G1 garbage collector was introduced in JDK7 and set as the default garbage collector in JDK9. In G1, there is no strict division between young and old generations; instead, it is divided into multiple independent regions of the same size, each of which may play different roles at different times.

5. No need to consider the relationship between JDK and JRE anymore

JDK and JRE are both essential components of Java, but their roles and uses are different. JDK is the Java Development Kit, primarily used for developing Java applications. It includes the JRE and provides additional tools such as the compiler (javac), debugger (jdb), etc. The JRE is the environment required to run Java applications. It includes the Java Virtual Machine (JVM) and Java class libraries, which are the core classes and other support files needed for running Java applications.Before JDK 9, Oracle provided separate JRE and JDK for users to download. This means you could install only the JRE to run Java programs or install the JDK to develop Java programs.However, starting from JDK 9, Oracle no longer releases JRE separately. Instead, the jlink tool can be used to generate custom runtime images. This approach simplifies the deployment of Java applications, as you only need to distribute a package containing your application and the custom runtime image, without needing to install the JRE separately.

6. Generics may no longer be just “syntactic sugar”

When it comes to Java generics, many people immediately think of “syntactic sugar” and “type erasure”. Type erasure is a mechanism for implementing generics in Java. This means that at compile time, generic types are replaced with their bounded types (if not explicitly specified, it is Object), and generic information is not retained in the bytecode.For example, if you have a generic class like this:

public class Box<T> {    private T object;    public void set(T object) { this.object = object; }    public T get() { return object; }}

After compilation, this class will become:

public class Box {    private Object object;    public void set(Object object) { this.object = object; }    public Object get() { return object; }}

The biggest advantage of type erasure is to ensure compatibility with older versions of Java code, as Java code before the introduction of generics did not have generic information. However, type erasure is a necessary compromise and has some drawbacks, such as inability to perform certain type checks, leading to method signature conflicts, etc.Then, the Valhalla project emerged. The Valhalla project is a long-term project of OpenJDK, aimed at introducing some improvements and new features to Java, including specialization of generics. Currently, Java’s implementation of generics uses type erasure, meaning that generic information only exists at compile time and is erased at runtime. Specialization of generics means that generic information can be retained at runtime, allowing specific code to be generated based on type parameters, improving runtime efficiency and achieving safer types.However, the Valhalla project has made significant strides but is progressing slowly, with many people saying the project is “dead”. Nevertheless, many new features have already been implemented in preview versions and will appear in the official version in the future.

7. Java can define private methods in interfaces

The purpose of interfaces in Java is to define public APIs, not to implement method details, so before JDK8, default and static methods were not supported. However, for convenience, JDK8 introduced support for default method implementations, allowing for iteration of APIs without breaking existing implementations when an interface has many implementing classes.The default implementation of interface classes in JDK8 solved many problems in model abstraction, but it also meant that if a default method needed to share some code segments, those segments could only be abstracted into a new function. If this function is static (JDK8 supports defining static functions in interfaces), it cannot access other non-static APIs; if it is an API with a default implementation, it allows implementing classes to freely override it. Most importantly, regardless of the approach, APIs will be exposed, which is not what developers want to see.Ultimately, JDK9 allowed developers to define private methods in interfaces, enabling extraction and encapsulation of common code in default methods and reducing code redundancy.

Welcome to join the 【Aliyun Developer WeChat Group】

This is a dedicated communication space for readers of the “Aliyun Developer” public account.💡 Here you can discuss technology and practices, and we will also regularly release group benefits and activities~Feel free to scan the code or add WeChat: argentinaliu to join us👇

The 'Eight-Legged Essay' You Memorized May Be Outdated

Leave a Comment