Architect Diary - Talk about the practical skills that must be mastered for development | JD Cloud Technology Team

I. Introduction

Although software development has always been committed to the pursuit of efficient, readable, and easy-to-maintain features, these features are like an impossible triangle, intertwined with each other, and one is ebbing and the other is ebbing. Just like low-level languages ​​(such as assembly and C language) can maintain efficient running performance, but have shortcomings and disadvantages in readability and maintainability; while high-level languages ​​(such as Java and Python) have shortcomings and disadvantages in readability and maintainability. It performs well in terms of performance, but falls short in terms of execution efficiency.

Building the advantages of the language ecology and making up for its shortcomings has always been an evolution direction of programming languages.

Different programming languages ​​have different features and protocols. Let’s take the JAVA language as an example to break down the knowledge points and practical skills that are easily overlooked during the development process but must be mastered.

2 Basics

In 1999, NASA's Mars mission failed: In this mission, the flight system software on the Mars Climate Explorer used the metric unit Newton to calculate thruster power, while the direction correction amount and thrust input by ground personnel The detector parameters used the British unit of pound force, which caused the detector to enter the atmosphere at an incorrect height and eventually disintegrated.

This was an accident caused by the conflict between international standards (cattle) and localization (pounds). This leads to the topic that programs need to pay attention to maintainability. Since software production often requires the collaboration of multiple people, maintainability is an important part of collaborative consensus. Regarding this aspect, the two aspects that people most easily think of are naming and annotation. Let’s discuss them below.

2.1 About naming

According to reading habits, the variable naming method of the program needs to overcome the space problem between words, so as to connect different words together, and ultimately achieve the effect of creating a new "word" that is easy to read. Common naming methods include the following:

  • Snake case : Also called underline naming, use underscores and lowercase words, for example: my_system;
  • Camel case (camel case): Case-sensitive according to the first letter of the word, and can be subdivided into large camel case and small camel case, such as: MySystem, mySystem;
  • Hungarian nomenclature (HN case): attribute + type + description, such as: nLength, g_cch , hwnd;
  • Pascal case (Pascal case): all first letters are capitalized, which is equivalent to the big camel case nomenclature, such as: MySystem;
  • Spinal case : use a dash, for example: my-system;
  • Studly caps: mixed case, no concise rules, such as: mySYSTEM, MYSystem;

Ranked according to audience size and popularity, camel case nomenclature and snake nomenclature are more popular. After all, they have advantages in readability and ease of writing.

2.1.1 Naming dictionary

Knowing the meaning after seeing the name: A good naming is a kind of comment.

It is recommended that R&D students memorize the names of common business scenarios in the industry. Of course, someone has already summarized them for us, so I won’t go into too much explanation here. Here is an excerpt for your reference:

Management class naming : Bootstrap, Starter, Processor, Manager, Holder, Factory, Provider, Registrar, Engine, Service, Task

Propagation class naming : Context, Propagator

Callback class naming : Handler, Callback, Trigger, Listener, Aware

Monitoring class naming : Metric, Estimator, Accumulator, Tracker

Memory management class naming : Allocator, Chunk, Arena, Pool

Filter detection class naming : Pipeline, Chain, Filter, Interceptor, Evaluator, Detector

Structural class naming : Cache, Buffer, Composite, Wrapper, Option, Param, Attribute, Tuple, Aggregator, Iterator, Batch, Limiter

Common design pattern naming : Strategy, Adapter, Action, Command, Event, Delegate, Builder, Template, Proxy

Parsing class naming : Converter, Resolver, Parser, Customizer, Formatter

Network class naming : Packet, Encoder, Decoder, Codec, Request, Response

CRUD naming : Controller, Service, Repository

Auxiliary class naming : Util, Helper

Other class naming : Mode, Type, Invoker, Invocation, Initializer, Future, Promise, Selector, Reporter, Constants, Accessor, Generator

2.1.2 Naming practices

What are the common naming rules for projects ? Different languages ​​may have different habits. Take the camel case naming convention of the Java language as an example:

1. The project name must be in all lowercase letters;
2. Package names should be in all lowercase letters;
3. The first letter of the class name is capitalized, and the first letters of the remaining constituent words are capitalized in order;
4. The first letter of variable names and method names must be lowercase. If the name consists of multiple words, the first letter of each word except the first letter must be capitalized;
5. Constant names should be in all capital letters;

The specification is relatively abstract. Let’s first take a look at what are the bad names?

1. Variable name with built-in obfuscation function: String zhrmghg = "Extreme abbreviation";
2. Meaningless universal variable name: String a,b,c="Love so and so type";
3. Long string pinyin variable name: String HuaBuHua = "Archaeological type";
4. Mixed use of various symbols: String $my_first_name_ = "It's too hard to remember";
5. Confusing capitalization, numbers, and abbreviations: String waitRPCResponse1 = "Extremely error-prone";

In addition to standard specifications, there are also some actual cases that trouble us during the actual development process.

1. When defining a member variable, should you use a wrapper type or a basic data type?

The default values ​​of wrapper classes and basic data types are different. The former is null, and the default values ​​of the latter are different depending on the type. From a data rigorous perspective, the null value of the wrapper class can represent additional information, making it safer. For example, it can avoid the NPE risk caused by basic types of automatic unboxing and the risk of abnormal business logic processing. Therefore, member variables must use wrapper data types, and basic data types are used in the context of local variables.

2. Why is it not recommended that Boolean type member variables start with is?

There are actually clear regulations on the definition of getter/setter methods in Java Beans. According to the JavaBeans(TM) Specification, if it is an ordinary parameter named propertyName, its setter/getter needs to be defined in the following way:

    public <PropertyType> get<PropertyName>();
    public void set<PropertyName>(<PropertyType> p)

However, the Boolean type variable propertyName follows a different set of naming principles:

    public boolean is<PropertyName>();
    public void set<PropertyName>(boolean p)

Due to differences in the way various RPC frameworks and object serialization tools handle Boolean type variables, it is easy to cause code portability problems. There are compatibility issues between the most common json serialization libraries Jackson and Gson. The former traverses all getter methods in the class through reflection and obtains the properties of the object through method name interception, while the latter directly uses reflection Iterate over the properties in this class. In order to avoid the impact of this difference on business, it is recommended that all member variables do not start with is to prevent unpredictable serialization results.

3. See what side effects can be caused by word case?

The JAVA language itself is case-sensitive, but when operating files with file paths and file names, the file names and paths here are not case-sensitive. This is because the file system is not case-sensitive. A typical scenario is that when we use a code management platform such as git and change the uppercase file name in the package path to lowercase, git cannot be updated. In order to avoid unnecessary trouble, it is recommended to use lowercase words in the package path. Multiple words are defined through a path hierarchy.

4. Will classes in different jar packages conflict with each other?
1. One category is that there are multiple different versions of the same jar package. The application selects the wrong version, causing the jvm to fail to load the required classes or to load the wrong version of the class; (it is relatively easy to solve with the help of maven management tools)
2. The other type is that classes with the same class path appear in different jar packages, and the same class appears in different dependent jars. Due to the order in which the jars are loaded, the JVM loads the wrong version of the class; (more difficult to solve)

Here we focus on the second situation. This situation is likely to occur when the system is split and reconstructed. The original project is copied and then deleted, resulting in some tools or enumeration classes having the same paths and names as the original ones. , when third-party callers rely on these two systems at the same time, it is easy to lay a trap for future iterations. To avoid such problems, you must create a unique package path for the system.

Supplement: If you rely on third-party libraries and there is a class conflict, you can solve the jar package conflict by introducing the third-party library jarjar.jar and modifying the package name of one of the conflicting jar files.

5. How to make a trade-off between the readability of variable naming and the resources (memory, bandwidth) occupied?

You can use object serialization tools as a breakthrough, taking the common Json (Jackson) serialization method as an example:

public class SkuKey implements Serializable {
    @JsonProperty(value = "sn")
    @ApiModelProperty(name = "stationNo", value = " 门店编号", required = true)
    private Long stationNo;
    @JsonProperty(value = "si")
    @ApiModelProperty(name = "skuId", value = " 商品编号", required = true)
    private Long skuId;
    // 省略get/set方法
}

The function of the @JsonProperty annotation is to rename the common properties in JavaBean to the specified new name during serialization. This implementation has no impact on business implementation. The original naming operation still prevails. It only takes effect when external RPC requires serialization and deserialization. In this way, the conflict between readability and resource occupation is better solved.

6. To provide input and output parameters for external services, should we use class objects or Map containers?

From a flexibility perspective, Map containers are stable and more flexible. From the perspective of stability and readability, the Map container is a black box. You don’t know what is inside. You must have auxiliary detailed documentation to collaborate. Since the actions of maintaining documents are often separated from the engineering code, the mechanism will As a result, it is difficult to ensure the accuracy and timeliness of information. Therefore, it is still recommended to use class structure objects to maintain the incoming and outgoing parameter structures.

2.2 About comments

Comments are an important means of communication between programmers and readers. They are explanations and explanations of the code. Good comments can improve the readability of the software and reduce the cost of maintaining the software.

2.2.1 Good comments

Hierarchical : Annotate and explain each with its own focus according to different granularities such as system, package, class, method, code block, code line, etc.
1. System notes : The macroscopic functions and architecture are realized through the README.md file;
2. Package annotations : The module responsibility boundaries are reflected through the package-info file. In addition, the file also supports the declaration of friendly classes, package constants, and provides convenience for annotations (Annotations) marked on the package;
3. Class annotations : mainly reflect functional responsibilities, version support, author attribution, application examples and other related information;
4. Method comments : pay attention to input parameters, output parameters, exception handling statements, usage scenario examples and other related content;
5. Code blocks and code line comments : mainly reflect logical intentions, warnings for closing pits, planning TODO, amplification of concerns and other details;
There are specifications : good code is better than a lot of comments, which is the same reason as we often say "convention is greater than configuration". It is a good standard way to use third-party libraries such as swagger to implement annotations, that is, interface documents ;

2.2.2 Bad comments

In order to make comments accurately and clearly express functional logic, the maintenance of comments has considerable maintenance costs, so the more comments, the more detailed the better. Here are some bad annotation scenarios to assist understanding:

1. Redundancy : If the reader can easily read the meaning of a function, the comments are redundant;
2. Wrong formula : If the annotation is unclear or even ambiguous, it is better not to write it;
3. Signature style : Comments like "add by liuhuiqing 2023-08-05" are easy to expire and are not trustworthy (there is no guarantee that everyone will comment in this way every time), and its functions can be completely controlled by git code management tools to achieve;
4. Long-form comments : Code blocks are mixed with long comments, which not only affects code reading, but also makes maintenance difficult;
5. Non-local comments : Comments should be placed closest to the code implementation. For example, the called method comments are maintained by the method itself, and the caller does not need to provide a detailed explanation of the method;
6. Commented out code : Useless code should be deleted, not commented out. Historical records are maintained by code management tools such as git;

2.3 About layering

The main purpose of system layered design is to reduce the complexity of the system by separating concerns, while improving reusability and reducing maintenance costs. Therefore, if you understand the concept of layering, the maintainability of the system will have a skeleton to a large extent.

2.3.1 System layering

Before ISO (International Standardization Organization) formulated the seven-layer network communication model (Open System Interconnection Reference Model, OSI/RM) in 1981, there were many architectures in computer networks, among which IBM's SNA (System Network Architecture) Structure) and DEC's DNA (Digital Network Architecture) digital network architecture are the most famous.

Previously, the different standards proposed by each manufacturer were based on their own equipment. Users could only use products from the same company when choosing products, because different companies have different standards and may have different working methods. The result is that there may be incompatibility between network products from different manufacturers. If the products of the same company can meet the needs of users, then it depends on which company is stronger, stronger, and more user-friendly. Naturally, users will not say anything. The problem is that one company is not right. All products excel. This will lead to painful suffering for both manufacturers and users. By analogy with the current mobile phone charging interface protocols (Micro USB interface, Type-c interface, Lightning interface), if you always have various charging cables on hand, you can deeply understand the meaning of the standard.

2.3.2 Software scalability

Software scalability refers to the ability of a software system to maintain its original performance and expand to support more tasks when facing load pressure.

Scalability can have two aspects, vertical scalability and horizontal scalability. Vertical scalability improves the throughput of the system by adding resources in the same business unit, such as increasing the number of server CPUs, increasing server memory, etc. Horizontal scalability is achieved by adding multiple business unit resources so that all business units logically act like one unit. For example, the ejb distributed component model, microservice component model, etc. all belong to this method.

Software systems need to consider effective scalability design when designing to ensure sufficient performance support when facing load pressure.

From a scalability perspective, system layering falls more into the category of horizontal scalability. In J2EE system development, we generally adopt a layered architecture, which is generally divided into presentation layer, business layer and persistence layer. After layering is adopted, because communication between layers will cause additional overhead, what will be brought to our software system is that the processing overhead of each business will increase.

Since layering will bring additional overhead, why do we need to layer it?

This is because there is an upper limit to improving software performance and throughput by simply relying on vertical scaling of heap hardware resources, and as the system scale expands, the cost of vertical scaling will also become very expensive. When layering is adopted, although communication overhead is incurred between layers, it is beneficial to the horizontal scalability of each layer, and each layer can be independently scaled without affecting other layers. That is to say, when the system needs to cope with a larger amount of access, we can increase the system throughput by adding multiple business unit resources.

2.4 Summary

This chapter mainly talks about the need to achieve a unified consensus on naming and annotation during the development process from the aspects of readability and maintainability. In addition to consensus, the isolation of concerns also needs to be done at the design level. This includes the splitting of system responsibilities, the division of module functions, the convergence of class capabilities, and the relationship between entity structures all need to be well planned.

Three Practice Chapters

Let’s learn from those classic practical cases from several important quality indicators such as program scalability, maintainability, security and performance.

3.1 Class definition

3.1.1 Constant definition

A constant is a fixed value that does not change during program execution. You can use enumerations (Enum) or classes (Class) to define constants.

If you need to define a group of related constants, then using an enumeration is more appropriate. Enumerations have greater advantages in terms of safety and operability (supporting traversal and function definition).

public enum Color {
 RED, GREEN, BLUE;
}

If you only need to define one or a few read-only constants, it is more concise and convenient to use class constants.

public class MyClass {
 public static final int MAX_VALUE = 100;
}

3.1.2 Tools

Tool classes usually contain common public methods in a certain non-business field. They do not require supporting member variables and are only used as tool methods. Therefore, it is most appropriate to make it a static method, which does not require instantiation and can only be used to obtain the definition of the method and call it.

The reason why the tool class is not instantiated is to save memory space, because the tool class provides static methods that can be called through the class, and there is no need to instantiate the tool class object.

public abstract class ObjectHelper {
    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }
}

In order to achieve constraints that do not require instantiated objects, it is best to add the abstract keyword to qualify the declaration when defining the class . This is why most open source tool classes such as spring are modified with the abstract keyword.

3.1.3 JavaBean

There are two common implementation methods for JavaBean definition: manual writing and automatic generation.

public class Person {
 private String name;
 private int age;
 
 public Person(String name, int age) {
 this.name = name;
 this.age = age;
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public int getAge() {
 return age;
 }
 
 public void setAge(int age) {
 this.age = age;
 }
}

Use the lombok plug-in to enhance Java code writing through annotations, and dynamically generate get and set methods during compilation.

import lombok.Data;

@NoArgsConstructor
@Data
@Accessors(chain = true)
public class Person {
    private String name;
    private int age;
}

The plug-in package also provides practical chain programming capabilities such as @Builder and @Accessors, which can improve coding efficiency to a certain extent.

3.1.4 Immutable classes

In some scenarios, in order to ensure the stability and consistency of its functions and behaviors, classes are designed so that they cannot be inherited and overridden.

The way to define it is to add the final keyword to the class, example:

public final class String implements Serializable, Comparable<String>, CharSequence {

}

The following are some classes that cannot be inherited and overridden, which are used in some underlying middleware:

java.lang.String
java.lang.Math
java.lang.Boolean
java.lang.Character
java.util.Date
java.sql.Date
java.lang.System
java.lang.ClassLoader

3.1.5 Anonymous inner classes

Anonymous inner classes are usually used to simplify code. Its definition and use usually occur in the same place. Its usage scenarios are as follows:

1. Directly passed as a parameter to the method or constructor;
2. An anonymous instance used to implement an interface or abstract class;
public class Example {
    public static void main(String[] args) {
        // 创建一个匿名内部类
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, World!");
            }
        };
        // 调用匿名内部类的方法
        runnable.run();
    }
}

3.1.6 Declaring a class

A declaration class is a basic type or interface in the Java language, used to define the behavior or characteristics of the class. Some are even just a declaration without specific method definitions.

AutoCloseable: Indicates that the class that implements this interface can be automatically closed, usually used for resource management.
Comparable: Indicates that a class that implements this interface can be compared with other objects that implement this interface.
Callable: Indicates that a class that implements this interface can be passed to the thread pool as a parameter and return the result.
Cloneable: Indicates that classes that implement this interface can be cloned.
Enum: Indicates that the class that implements this interface is an enumeration type.
Iterable: Indicates that the class that implements this interface can be iterated.
Runnable: Indicates that a class that implements this interface can run as a thread.
Serializable: Indicates that classes that implement this interface can be serialized and deserialized.
interface: Indicates that the class that implements the interface is an interface and can contain method declarations.
Annotation: Indicates that the class that implements this interface is an annotation and can be used for metadata description.

3.1.7 Record class

The Record class was previewed in Java14 and was not officially released until Java17. According to the description of JEP395, the Record class is a carrier of immutable data, similar to various model, dto, vo and other POJO classes that are widely used today, but the record itself is no longer assignable after construction. All record classes inherit from java.lang.Record. The Record class provides a full field constructor, attribute access, and equals, hashcode, and toString methods by default. Its function is very similar to the lombok plug-in .

Defined way

/**
 * 关键定义的类是不可变类
 * 将所有成员变量通过参数的形式定义
 * 默认会生成全部参数的构造方法
 * @param name
 * @param age
 */
public record Person(String name, int age) {
    public Person{
        if(name == null){
            throw new IllegalArgumentException("提供紧凑的方式进行参数校验");
        }
    }
    /**
     * 定义的类中可以定义静态方法
     * @param name
     * @return
     */
    public static Person of(String name) {
        return new Person(name, 18);
    }
}

Usage

Person person = new Person("John", 30);
// Person person = Person.of("John");
String name = person.name();
int age = person.age();

scenes to be used

Construct a temporary storage object through Record and sort the Person array objects according to age.

public List<Person> sortPeopleByAge(List<Person> people) {
    
    record Data(Person person, int age){};

    return people.stream()
                .map(person -> new Data(person, computAge(person)))
                .sorted((d1, d2) -> Integer.compare(d2.age(), d1.age()))
                .map(Data::person)
                .collect(toList());
}
public int computAge(Person person) {
    return person.age() - 1;
}

3.1.8 Sealing type

The new feature Sealed Classes introduced in Java 17 is mainly used to limit the inheritance of classes. We know that there are two main types of restrictions on class inheritance functions:

1. Final modifies the class so that the class cannot be inherited;
2. The package-private class can control that it can only be inherited by classes under the same package;

But obviously, these two restrictions are very rough, and sealed classes are more fine-grained control of class inheritance.

sealed class SealedClass permits SubClass1, SubClass2 {

}

class SubClass1 extends SealedClass {

}

class SubClass2 extends SealedClass {
   
}

In the above example, SealedClass is a sealed class which contains two subclasses SubClass1 and SubClass2. In the definition of SubClass1 and SubClass2, you must use the extends keyword to inherit from SealedClass, and use the permits keyword to specify which subclasses they allow to inherit. By using a sealed class, you can ensure that only subclasses that meet certain conditions can inherit or implement the protocol or specification.

3.2 Method definition

3.2.1 Construction method

A constructor is a special method used to create and initialize objects. The name of the constructor must be the same as the class name and have no return type. When creating an object, you can call the constructor by using the new keyword.

public class MyClass {
    private int myInt;
    private String myString;

    // 构造方法
    public MyClass(int myInt, String myString) {
        this.myInt = myInt;
        this.myString = myString;
    }
}

An important feature of implementing the singleton pattern is that users are not allowed to create (new) objects at will. How to achieve security control? Declare the constructor as private is an essential step.

3.2.2 Method rewriting

Method overriding refers to redefining a method with the same name in the parent class in the subclass. Method overriding allows a subclass to override the method implementation in the parent class to implement its own behavior based on the needs of the subclass.

class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myCat = new Cat();
        myCat.makeSound(); // 输出 "Meow"
    }
}

One of the three major characteristics of object-oriented polymorphism, method rewriting is its core.

3.2.3 Method overloading

Multiple methods are defined in a class with the same name but different parameter lists. Method overloading allows us to use the same method name to perform different operations and execute different code logic based on the parameters passed to the method.

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public double add(double a, double b) {
        return a + b;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        int result1 = calculator.add(2, 3);
        double result2 = calculator.add(2.5, 3.5);
        System.out.println(result1); // 输出 5
        System.out.println(result2); // 输出 6.0
    }
}

3.2.4 Anonymous methods

Java 8 introduced Lambda expressions, which can be used to implement functions similar to anonymous methods. A lambda expression is an anonymous function that can be passed as a parameter to a method or used directly as a standalone expression.

public static void main(String args[]) {
    List<String> names = Arrays.asList("hello", "world");
    // 使用 Lambda 表达式作为参数传递给 forEach 方法
    names.forEach((String name) -> System.out.println("Name: " + name));

    // 使用 Lambda 表达式作为独立表达式使用
    Predicate<String> nameLengthGreaterThan5 = (String name) -> name.length() > 5;
    boolean isLongName = nameLengthGreaterThan5.test("John");
    System.out.println("Is long name? " + isLongName);
}

3.3 Object definition

3.3.1 Singleton object

A singleton object is an object that can be used repeatedly, but has only one instance. It has the following functions:

1. Control the use of resources: Control concurrent access to resources through thread synchronization.
2. Control the number of instances generated: to achieve the purpose of saving resources.
3. Used as a communication medium: that is, data sharing, it can enable communication between two unrelated threads or processes without establishing a direct correlation.

For example, use an enumeration to implement the singleton pattern:

public enum Singleton {
 INSTANCE;
 
 public void someMethod() {
 // ...其他代码...
 }
}

3.3.2 Immutable objects

Immutable objects in Java are objects whose state cannot be modified once created. Immutable objects are a very useful object because they ensure that the object's state is consistent at all times, thus avoiding problems caused by modifying the object's state. There are several ways to implement immutable objects:

1. Store the state of the object in an immutable object: String, Integer, etc. are built-in immutable object types;
2. Store the state of the object in a final variable: a final variable cannot be modified once it is assigned a value;
3. Set all properties of the object as immutable objects: This ensures that the entire object is immutable;

Some container class operations also have corresponding wrapper classes to implement the immutability of container objects, such as defining immutable array objects:

Collections.unmodifiableList(new ArrayList<>());
When an object in the domain is passed out as an input parameter, it is defined as an immutable object. This is very important in maintaining data consistency. Otherwise, the unpredictability of object attribute changes will be very troublesome when locating problems.

3.3.3 Tuple object

Tuple is a common concept in functional programming languages. A tuple is an immutable object that can store multiple objects of different types in a type-safe form. It is a very useful data structure that allows developers to process multiple data elements more conveniently and efficiently. However, the native Java standard library does not provide tuple support, and we need to implement it ourselves or with the help of a third-party class library.

Two-tuple implementation

public class Pair<A,B> {
    public final A first;
    public final B second;

    public Pair(A a, B b) {
        this.first = a;
        this.second = b;
    }

    public A getFirst() {
        return first;
    }

    public B getSecond() {
        return second;
    }
}

Triplet implementation

public class Triplet<A,B,C> extends Pair<A,B>{
    public final C third;

    public Triplet(A a, B b, C c) {
        super(a, b);
        this.third = c;
    }

    public C getThird() {
        return third;
    }

    public static void main(String[] args) {
        // 表示姓名,性别,年龄
        Triplet<String,String,Integer> triplet = new Triplet("John","男",18);
        // 获得姓名
        String name = triplet.getFirst();
    }

}

Multiple group implementation

public class Tuple<E> {
    private final E[] elements;

    public Tuple(E... elements) {
        this.elements = elements;
    }

    public E get(int index) {
        return elements[index];
    }

    public int size() {
        return elements.length;
    }
    public static void main(String[] args) {
        // 表示姓名,性别,年龄
        Tuple<String> tuple = new Tuple<>("John", "男", "18");
        // 获得姓名
        String name = tuple.get(0);
    }
}

Tuple mainly has the following functions:

1. Store multiple data elements: Tuple can store multiple different types of data elements, which can be basic types, object types, arrays, etc.;

2. Simplify the code: Tuple can make the code more concise and reduce the writing of repeated code. Through Tuple, we can pack multiple variables into one object, thereby reducing the amount of code;

3. Improve code readability: Tuple can improve code readability. Through Tuple, we can pack multiple variables into one object, making the code more readable;

4. Support functions returning multiple values: Tuple can support functions returning multiple values. In Java, a function can only return one value, but through Tuple, we can package multiple values ​​into an object and return it;

In addition to customization, third-party class libraries that implement the tuple concept include: Google Guava, Apache Commons Lang, JCTools, Vavr, etc.

The Google Guava library's Tuple provides more functionality and is widely used. For example, in order to make the meaning of tuples more clear, Guava provides the concept of named tuples. By giving tuples names, you can express the meaning of each element more clearly. Example:

NamedTuple namedTuple = Tuples.named("person", "name", "age");

3.3.4 Temporary objects

Temporary objects refer to objects that are temporarily needed during program execution but have a short life cycle. These objects typically exist only briefly during use and do not require long-term storage or reuse.

Optimization suggestions for temporary objects are as follows:

1. Try to reuse objects. Since the system not only takes time to generate objects, it may also take time to garbage collect and process these objects in the future. Therefore, generating too many objects will have a great impact on the performance of the program. Strategies for reusing objects include caching Objects can also be optimized for specific scenarios, such as using StringBuffer instead of string splicing;
2. Try to use local variables. The parameters passed when calling the method and the temporary variables created during the call are saved on the stack, which is faster. Other variables, such as static variables, instance variables, etc., are created in the heap and are slower;
3. Collect by generation. The generational garbage collection strategy is based on the fact that the life cycles of different objects are different. Therefore, objects with different life cycles can be collected in different ways to improve recycling efficiency;

3.3.5 Valhalla

As a high-level language, there has always been a big gap in performance between Java and the lower-level C language and assembly language. To bridge this gap, the Valhalla project was launched in 2014 with the goal of bringing more flexible flat data types to JVM-based languages.

We all know that Java supports both native types and reference types. Native data types are passed by value. Assignment and function parameter passing will copy the value. After copying, there is no correlation between the two copies. Reference types are passed pointers in any case, and modifying the content pointed to by the pointer will affect All references. Valhalla introduced value types, a concept between primitive types and reference types.

Since most of the Java data structures in an application are objects, we can consider Java as a pointer-intensive language. This pointer-based object implementation is used to enable object identification, which itself is used for language features such as polymorphism, mutability, and locking. By default, these properties apply to every object regardless of whether they are actually needed. This is where value types come into play.

The concept of value types is to represent pure data aggregation, which removes the functionality of regular objects. So we have pure data, no identity. Of course, this means that we also lose the functionality that can be achieved using object identification. Since we no longer have an object identity, we can abandon pointers and change the general memory layout of value types. Let's compare object reference and value type memory layout.



The object header information is removed, and the value type saves 16 bytes of space in the object header in a 64-bit operating system. At the same time, it also means giving up the object's unique identity (Identity) and initialization security. The previous keywords or methods such as wait(), notify(), synchronized(obj), System.identityHashCode(obj), etc. will be invalid and cannot be used.

Valhalla will significantly improve performance and reduce leaky abstractions:

Performance enhancements are addressed by flattening the object graph and removing indirection. This results in a more efficient memory layout and fewer allocations and garbage collections.
When used as a generic type, primitives and objects have more similar behavior, which is a better abstraction.

As of September 2023, the Valhalla project is still in progress and no official version has been released yet. This innovative project is worth looking forward to.

Four summary

This article summarizes the basic common sense often used in the software development process, and is divided into two chapters: basics and practice. The basics focuses on the naming conventions of classes, methods, variables, and the criteria for judging the quality of code comments. In the practical chapter, common technical concepts and implementation practices are analyzed from the three levels of classes, methods and objects. I hope that this common sense can bring some thinking and help to readers.

Author: JD Retail Emily Liu

Source: JD Cloud Developer Community Please indicate the source when reprinting

Lei Jun: The official version of Xiaomi's new operating system ThePaper OS has been packaged. The pop-up window on the lottery page of Gome App insults its founder. Ubuntu 23.10 is officially released. You might as well take advantage of Friday to upgrade! Ubuntu 23.10 release episode: The ISO image was urgently "recalled" due to containing hate speech. A 23-year-old PhD student fixed the 22-year-old "ghost bug" in Firefox. RustDesk remote desktop 1.2.3 was released, enhanced Wayland to support TiDB 7.4 Release: Official Compatible with MySQL 8.0. After unplugging the Logitech USB receiver, the Linux kernel crashed. The master used Scratch to rub the RISC-V simulator and successfully ran the Linux kernel. JetBrains launched Writerside, a tool for creating technical documents.
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10120062