Sorting algorithm 9

P1: Classification of sorting algorithms

Sorting algorithms can be divided into internal sorting and external sorting. Sorting in memory is called internal sorting. When the amount of data to be sorted is very large, it cannot be copied to memory, so it needs to use external memory for sorting.

Internal sort includes comparison sort and non-comparison sort. Comparison sort includes insertion sort, selection sort, swap sort and merge sort. Non-comparison sort includes counting sort, radix sort and bucket sort. Insertion sort includes direct insertion sort and Hill sort, selection sort includes direct selection sort and heap sort, and swap sort includes bubble sort and quick sort.


P2: Direct insertion sort

Direct insertion sort belongs to insertion sort, which is a stable sort. The average time complexity and the worst time complexity are O(n²), and the best time complexity is O(n) and space complexity is O(1) when the elements are basically ordered.

The basic principle is to insert one record to be sorted into the appropriate position of the sorted set of records by the size of its keywords at each trip until all the records to be sorted are inserted. This mode applies to the case where the records to be sorted are few or basically in order.


`public` `void` `insertionSort(``int``[] nums) {`

`for` `(``int` `i =` `1``; i < nums.length; i++) {` `int` `insertNum = nums[i]; ` `int` `insertIndex; ` `for` `(insertIndex = i -` `1``; insertIndex >=` `0` `&& nums[insertIndex] > insertNum; insertIndex--) {` `nums[insertIndex +` `1``] = nums[insertIndex]; ` `}` `nums[insertIndex +` `1``] = insertNum; ` ` `} ` `}Copy the code

** Optimization: ** Direct insertion does not take advantage of the order of the sequence to be inserted. When inserting the ith element, the position to be inserted can be found through binary search, and then all elements between the first 1 bit of the ith element and the insertion position can be moved back to place the ith element at the target position.


`public` `void` `binaryInsertionSort(``int``[] nums) {`

`for` `(``int` `index =` `1``; index < nums.length; index++) {` `int` `insertNum = nums[index]; ` `int` `insertIndex = -``1``; ` `int` `start =` `0``; ` `int` `end = index -` `1``; ` `while` `(start <= end) {` `int` `mid = start + (end - start) /` `2``; ` `if` `(insertNum > nums[mid])` `start = mid +` `1``; ` `else` `if` `(insertNum < nums[mid])` `end = mid -` `1``; ` `else` `{` `insertIndex = mid +` `1``; ` `break` `; ` `} ` `} ` `if` `(insertIndex == -``1``)` `insertIndex = start; ` `if` `(index - insertIndex >=` `0``)` `System.arraycopy(nums, insertIndex, nums, insertIndex +` `1``, index - insertIndex); ` `nums[insertIndex] = insertNum; ` ` `} ` `}Copy the code

P3: Hill sort

Hill sort belongs to insertion sort, also known as reduced increment sort, is an improvement of direct insertion sort, and is an unstable sort, the average time complexity is O(n^1.3^), the worst time complexity is O(n²), the best time complexity is O(n), the space complexity is O(1).

The basic principle is to group records in increments of subscripts, perform a direct insertion sort for each group, and reduce the increments after each sort. When the increments are reduced to 1, the sort is finished. Suitable for medium sized data volumes, not the best choice for very large data volumes.


`public` `void` `shellSort(``int``[] nums) {`

`for` `(``int` `d = nums.length /` `2``; d >` `0` `; d /=` `2``) {`

`for` `(``int` `i = d; i < nums.length; i++) {` `int` `insertNum = nums[i]; ` `int` `insertIndex; ` `for` `(insertIndex = i - d; insertIndex >=` `0` `&& nums[insertIndex] > insertNum; insertIndex -= d) {` `nums[insertIndex + d] = nums[insertIndex]; ` `}` `nums[insertIndex + d] = insertNum; ` `} ` `} ` ` `}Copy the code

P4: Select sort directly

Direct selection sort is selective sort, which is an unstable sort with O(n²) time complexity and O(1) space complexity in any case. The basic principle is that each time you find the smallest element in an unsorted sequence, you swap places with the first element in the unsorted sequence, and repeat the process for the remaining unsorted sequences until all the elements are sorted. Suitable for small amount of data, slightly faster than direct insert sort.

`public` `void` `selectSort(``int``[] nums) {` `int` `minIndex; ` `for` `(``int` `index =` `0``; index < nums.length -` `1``; index++){` `minIndex = index; ` `for` `(``int` `i = index +` `1``; i < nums.length; i++){` `if``(nums[i] < nums[minIndex])` `minIndex = i; ` `} ` `if` `(index ! = minIndex){` `swap(nums, index, minIndex); ` `} ` `} ` ` `}Copy the code

P5: Heap sort

Heap sort belongs to selective sort, which is an improvement on direct selective sort, and it is an unstable sort, in any case the time complexity is O(nlogn), the space complexity is O(1).

The basic principle is to treat the records to be sorted as a complete binary tree, and you can build large or small root heaps, where each node in the large root heap has a value no less than that of its children, and the value of each node in the small root heap is no greater than that of its children. This method is suitable for large amounts of data.

Take the large root heap as an example. When the heap is being built, the last node is first taken as the current node. If the current node has a parent node and its value is greater than that of the parent node, the current node and the parent node are swapped. The last node replaces the root node and serves as the current node. If the current node has a child node with a smaller value than the child node, it is swapped with the child node with a larger value. After adjusting the heap, the temporary value is returned.

`public` `void` `add(``int``[] nums,` `int` `i,` `int` `num){` `nums[i] = num; ` `int` `curIndex = i; ` `while` `(curIndex >` `0``) {` `int` `parentIndex = (curIndex -` `1``) /` `2``; ` `if` `(nums[parentIndex] < nums[curIndex])` `swap(nums, parentIndex, curIndex); ` `else` `break` `; ` `curIndex =parentIndex; ` `}` `}` `public` `int` `remove(``int``[] nums,` `int` `size){` `int` `result = nums[``0``]; ` `nums[``0``] = nums[size -` `1``]; ` `int` `curIndex =` `0``; ` `while` ` (` `true``) {` `int` `leftIndex = curIndex *` `2` `+` `1``; ` `int` `rightIndex = curIndex *` `2` `+` `2``; ` `if` `(leftIndex >= size)` `break` `; ` `int` `maxIndex = leftIndex; ` `if` `(rightIndex < size && nums[maxIndex] < nums[rightIndex])` `maxIndex = rightIndex; ` `if` `(nums[curIndex] < nums[maxIndex])` `swap(nums, curIndex, maxIndex); ` `else` `break` `; ` `curIndex = maxIndex; ` `} ` `return` `result; ` ` `}Copy the code

P6: Bubble sort

Bubble sort belongs to commutative sort, which is a stable sort. The average time complexity and the worst time complexity are O(n²), and the best time complexity is O(n) and space complexity is O(1) when the elements are basically ordered.

The basic principle is to compare adjacent elements, if the first is larger than the second, swap, do the same for each pair of adjacent elements, starting from the first pair to the last pair at the end, after each round of sorting, the last element is ordered, repeat the above steps for n elements n -1 times.


`public` `void` `bubbleSort(``int``[] nums) {`

`for` `(``int` `i =` `0``; i < nums.length -` `1``; i++) {`

`for` `(``int` `index =` `0``; index < nums.length -` `1` `- i; index++) {`

`if` `(nums[index] > nums[index +` `1``])`

`swap(nums, index, index +` `1``)`

`}`

`}`

`}`

Copy the code

** Optimization: ** Unnecessary comparisons will be made when the sequence is already in order. You can set a flag bit to record whether there is an element exchange, if there is no direct end to the comparison.

`public` `void` `betterBubbleSort(``int``[] nums) {` `boolean` `swap; ` `for` `(``int` `i =` `0``; i < nums.length -` `1``; i++) {`

`swap =` `true` `; ` `for` `(``int` `index =` `0``; index < nums.length -` `1` `- i; index++) {`

`if` `(nums[index] > nums[index +` `1``]) {` `swap(nums, index ,index +` `1``); ` `swap =` `false` `; ` `} ` `} ` `if` `(swap)` `break` `; ` ` `} ` `}Copy the code

P7: Quicksort

Quicksort belongs to swap sort, is an improvement of bubble sort, and is a kind of unstable sort, the average time complexity and the best time complexity are O(nlogn), when the elements are basically ordered, the worst time complexity is O(n²), space complexity is O(logn).

The basic principle is to first select a reference element, and then through a sort to split the sorted data into two independent parts, one part is less than or equal to the reference element, the other part is greater than or equal to the reference element, and then perform recursive quicksort on the two parts of data respectively. This method is applicable to a large amount of data and the elements are basically out of order.

A partition of quicksort searches alternately from both ends until the low and high Pointers overlap, so the time complexity is O(n), and the time complexity of the whole algorithm is related to the number of partitions. The best case is that the middle number selected for each partition exactly divides the current sequence into almost equal parts, and a subtable of length 1 can be obtained after logn partitions, so that the time complexity of the algorithm is O(nlogn). The worst case is that the middle number selected each time is the largest or smallest element in the current sequence, which makes one of the resulting subtables empty and the length of the other subtable -1 of the original table. In this way, the data table with length N needs to go through n partitions, and the time complexity of the whole sorting algorithm is O(n²).

Although quicksort requires only the auxiliary space of one element, it requires a stack space to implement recursion. At best, each run of quicksort splits the sequence of elements evenly into two subtables of similar size, with a maximum stack depth of log(n+1) and a maximum stack depth of n at worst.


`public` `void` `quickSort(``int``[] nums,` `int` `start,` `int` `end) {`

`if` `(start < end) {` `int` `pivotIndex = getPivotIndex(nums, start, end); ` `quickSort(nums, start, pivotIndex -` `1``); ` `quickSort(nums, pivotIndex +` `1``, end); ` `}` `}` `public` `int` `getPivotIndex(``int``[] nums,` `int` `start,` `int` `end) {` `int` `pivot = nums[start]; ` `int` `low = start; ` `int` `high = end; ` `while` `(low < high) {`

`while` `(low <= high && nums[low] <= pivot)` `low++; ` `while` `(low <= high && nums[high] > pivot)` `high--; ` `if` `(low < high)` `swap(nums, low, high); ` `}` `swap(nums, start, high); ` `return` `high; ` ` `}Copy the code

** Direct insertion sort is used when the size is small enough, such as end-start < 10.


P8: merge sort

Merge sort is a sort algorithm based on merge operation. It is a stable sorting algorithm with O(N logn) time complexity and O(n) space complexity in any case.

Basic principle is the application of partition method will stay collating sequence is divided into two parts, then the two parts respectively recursion sequence, finally merging, using an auxiliary space and set the two Pointers point to two orderly sequence starting elements respectively, the pointer to the corresponding small element is added to the auxiliary space, repeat the steps to a certain sequence to reach the end, The remaining elements of another sequence are then merged to the end of the auxiliary space. Suitable for large amount of data and stability requirements.

`int``[] help; ` `public` `void` `mergeSort(``int``[] arr) {` `int``[]help=` `new` `int``[arr.length]; ` `sort(arr,` `0``, arr.length -` `1``); ` `}` `public` `void` `sort(``int``[] arr,` `int` `start,` `int` `end) {` `if` `(start == end)` `return` `; ` `int` `mid = start + (end - start) /` `2``; ` `sort(arr, start, mid); ` `sort(arr, mid +` `1``, end); ` `merge(arr, start, mid, end); ` `}` `public` `void` `merge(``int``[] arr,` `int` `start,` `int` `mid,` `int` `end) {` `if` `(end +` `1` `- start >=` `0``) System.arraycopy(arr, start, help, start, end +` `1` `- start); ` `int` `p = start; ` `int` `q = mid +` `1``; ` `int` `index = start; ` `while` `(p <= mid && q <= end) {`

`if` ` (help[p] < help[q])`

`arr[index++] = help[p++]; ` `else`

`arr[index++] = help[q++]; ` `} ` `while` `(p <= mid) arr[index++] = help[p++]; ` `while` `(q <= end) arr[index++] = help[q++]; ` ` `}Copy the code

P9: Selection principle of sorting algorithm

When the data volume is small, direct insertion sort or direct selection sort can be considered. When the element distribution is orderly, direct insertion sort will greatly reduce The Times of comparison and moving records. If stability is not required, direct selection sort can be used, with slightly higher efficiency than direct insertion sort.

When the data size is medium, you can choose Hill sort.

When the data volume is large, heap sort, quicksort and merge sort can be considered. Merge sort can be used if stability is required, quick sort can be used if element distribution is random, heap sort can be used if element distribution is close to positive or reverse order.

Bubble sort is generally not used.


Design Pattern 7

P1: Principles of design patterns

Open closed Principle: The fundamental design principle in object-oriented design that a software entity (class, module, method, etc.) should be open for extension but closed for modification. It emphasizes building frameworks with abstractions, extending details with implementations, and improving the reusability and maintainability of code. For example, the source code should not be modified during a version update, but new features can be added.

** Single responsibility principle: ** A single responsibility for a class, interface, or method improves code readability and maintainability, and reduces code complexity and the risk of change.

Dependency inversion principle: ** Programs should rely on abstract classes or interfaces, not concrete implementation classes. It can reduce the coupling degree of code and improve the stability of the system.

** Interface isolation principle: ** implements interface isolation by defining different functions in different interfaces, avoiding class dependency on interfaces it does not need and reducing the redundancy and complexity of dependencies between interfaces.

** Reeves substitution principle: ** a supplement to the open and closed principle, which stipulates that any parent class can appear where the subclass must be able to appear, can restrict inheritance overflow, add key program robustness.

** Demeter principle: ** Also known as the least known principle, each module should understand and rely on other modules as little as possible to reduce code coupling.

** Synthesis/aggregation principle: ** Try to use combination (has A) or aggregation (contains A) rather than inheritance to achieve the purpose of software reuse, can make the system more flexible, reduce the degree of coupling.


P2: Classification of design patterns

** Creation mode: ** provides a way to hide creation logic while creating objects, rather than using the new operator to instantiate objects directly, giving programs more flexibility in determining which objects need to be created for a given instance. Including: factory pattern, abstract factory pattern, singleton pattern, Builder pattern, prototype pattern.

** Structural pattern: ** Implements the ability to create complex structural objects through inheritance and references between classes and interfaces. Includes: adapter mode, bridge mode, filter mode, composite mode, decorator mode, appearance mode, share mode, proxy mode.

** Behavioral pattern: ** Implements different behavior through different communication modes between classes. Including: responsibility chain pattern, naming pattern, interpreter pattern, iterator pattern, mediator pattern, memo pattern, observer pattern, state pattern, policy pattern, template pattern, visitor pattern.


P3: Factory mode

Factory pattern belongs to the creation pattern, which is divided into simple factory pattern, factory method pattern and abstract factory pattern.

The simple factory pattern means that the instance is created from a factory object, and the client does not need to care about the creation logic, just providing the parameters passed into the factory object.

The factory method pattern defines an interface for creating objects and lets the implementing class of the interface decide which object to create. The factory method pattern lets class instantiation be deferred to subclasses. In the factory method mode, the client only needs to care about the corresponding factory instead of creating details, which mainly solves the problem of product expansion. In the simple factory mode, if the product category increases, the factory will have more and more responsibilities, which is not easy to maintain.

The abstract factory pattern provides an interface for creating a series of related or interdependent objects without specifying their concrete classes. The client does not depend on the details of how product class instances are created and implemented, and is primarily used when the system has more than one product family of products, and the system only consumes products from one of those product families.

Conclusion:

** Simple factory: ** a factory, an abstract product. A McDonald’s, for example, can produce a variety of hamburgers.


`public` `class` `MacDonaldFactory {`

`public` `Hamburger eatHamburger(String name) {`

`if` ` (` `"beef"``.equals(name))`

`return` `new` `BeefHamburger(); ` `else` `if` ` (` `"pig"``.equals(name))`

`return` `new` `PigHamburger(); ` `return` `null``; ` `}` `}` `interface` `Hamburger {` `void` `eat(); ` `}` `class` `BeefHamburger` `implements` `Hamburger {` `@Override` `public` `void` `eat() {`

`System.out.println(``"Eat a beef burger."` `); ` `}` `}` `class` `PigHamburger` `implements` `Hamburger {` `@Override` `public` `void` `eat() {`

`System.out.println(``"Eat a pork burger."` `); ` ` `} ` `}Copy the code

** Factory method: ** multiple factories, an abstract product. For example, a McDonald’s can produce a variety of hamburgers, and a KFC can also produce a variety of hamburgers.

`public` `interface` `HamburgerFactory {` `Hamburger build(); ` `}` `class` `MCFactory` `implements` `HamburgerFactory {` `@Override` `public` `Hamburgerbuild() {`

`return` `new` `MCHamburger(); ` `}` `}` `class` `KFCFactory` `implements` `HamburgerFactory {` `@Override` `public` `Hamburgerbuild() {`

`return` `new` `KFCHamburger(); ` `}` `}` `interface` `Hamburger {` `void` `eat(); ` `}` `class` `MCHamburger` `implements` `Hamburger {` `@Override` `public` `void` `eat() {`

`System.out.println(``"Eat a McDonald's burger"` `); ` `}` `}` `class` `KFCHamburger` `implements` `Hamburger {` `@Override` `public` `void` `eat() {`

`System.out.println(``"Eat a KFC burger."` `); ` ` `} ` `}Copy the code

** Abstract factory: ** Multiple factories, a variety of abstract products. For example, a McDonald’s and a KFC can produce a variety of hamburgers and colas.

`public` `interface` `FoodFactory {` `Hamburger buildHamburger(); ` `Drink buildDrink(); ` `}` `class` `MCFactory` `implements` `FoodFactory {` `@Override` `public` `HamburgerbuildHamburger() {`

`return` `new` `MCHamburger(); ` `}` `@Override` `public` `DrinkbuildDrink() {`

`return` `new` `MCDrink(); ` `}` `}` `class` `KFCFactory` `implements` `FoodFactory {` `@Override` `public` `HamburgerbuildHamburger() {`

`return` `new` `KFCHamburger(); ` `}` `@Override` `public` `DrinkbuildDrink() {`

`return` `new` `KFCDrink(); ` `}` `}` `interface` `Hamburger {` `void` `eat(); ` `}` `class` `MCHamburger` `implements` `Hamburger {` `@Override` `public` `void` `eat() {`

`System.out.println(``"Eat a McDonald's burger"` `); ` `}` `}` `class` `KFCHamburger` `implements` `Hamburger {` `@Override` `public` `void` `eat() {`

`System.out.println(``"Eat a KFC burger."` `); ` `}` `}` `interface` `Drink {` `void` `drink(); ` `}` `class` `MCDrink` `implements` `Drink {` `@Override` `public` `void` `drink() {`

`System.out.println(``"Drink McDonald's beverages."` `); ` `}` `}` `class` `KFCDrink` `implements` `Drink {` `@Override` `public` `void` `drink() {`

`System.out.println(``"Drink KFC."` `); ` ` `} ` `}Copy the code

P4: Singleton mode

The singleton pattern is the creation pattern, which means that there is only one instance of a singleton class in any case. The constructor must be private and create a static instance object and provide a static public method to obtain the instance. The advantage is that there is only one instance in memory, which reduces memory overhead, especially when instances are frequently created and destroyed, and avoids multiple resource usage. The disadvantage is that there is no abstraction layer, which is difficult to extend and conflicts with the single responsibility principle.

Hungry type: * * * * in the class loader is initialized to create singletons, is thread-safe, but no matter whether to use will create the object, may waste memory.

`public` `class` `HungrySingleton {`

`private` `HungrySingleton(){}` `private` `static` `HungrySingleton instance =` `new` `HungrySingleton(); ` `public` `static` `HungrySingletongetInstance() {`

`return` `instance; ` ` `} ` `}Copy the code

** lazy: ** is loaded only when called externally. It is not thread safe and can be locked to keep the thread safe but inefficient.


`public` `class` `LazySingleton {`

`private` `LazySingleton(){}` `private` `static` `LazySingleton instance; ` `public` `static` `LazySingletongetInstance() {`

`if``(instance ==` `null``) {` `instance =` `new` `LazySingleton(); ` `} ` `return` `instance; ` ` `} ` `}Copy the code

** Double-checked locks: ** Use volatile and double-checked to reduce the range of synchronized locks and improve efficiency.


`public` `class` `DoubleCheckSingleton {`

`private` `DoubleCheckSingleton(){}` `private` `volatile` `static` `DoubleCheckSingleton instance; ` `public` `static` `DoubleCheckSingletongetInstance() {`

`if``(instance ==` `null``) {`

`synchronized` `(DoubleCheckSingleton.``class``) {`

`if` `(instance ==` `null``) {` `instance =` `new` `DoubleCheckSingleton(); ' '} ' '} ' ' '} ' 'return` `instance; ` ` `} ` `}Copy the code

Static inner classes: ** can solve both the hungry memory waste problem and the lazy thread safety problem.


`public` `class` `StaticSingleton {`

`private` `StaticSingleton(){}`

`public` `static` `StaticSingleton getInstance() {`

`return` `StaticClass.instance; ` `}` `private` `static` `class` `StaticClass {` `private` `static` `final` `StaticSingleton instance =` `new` `StaticSingleton(); ` ` `} ` `}Copy the code

** Enumeration: ** This approach, advocated by Effective Java authors, not only avoids multithreaded synchronization problems, but also prevents deserialization from recreating new objects, absolutely prevents multiple instantiations, and prevents reflection from breaking singletons.

`public` `enum` `EnumSingleton {` `INSTANCE; ` ` `}Copy the code

P5: Proxy mode

The proxy pattern is a structural pattern that provides a proxy for other objects to control access to the object, enhancing the function of the target object. The advantage is that it can enhance the function of the target object, reduce the coupling degree of the code to a certain extent, and have good scalability. The disadvantage is that adding proxy objects between the client and the target can slow down request processing and increase system complexity.

** Static proxy: ** Proxy objects hold references to real objects, and proxy object methods are also called when called, but some additional logic is added before and after the real object methods. The proxy operation needs to be done manually, the bytecode file of the proxy class exists before the program runs, and the relationship between the proxy class and the proxyed class is determined before the program runs. The disadvantage is that a proxy class can only serve one target class, which can add a lot of work to serving multiple types.

`public` `interface` `Company {` `void` `findWorker(); ` `}` `public` `class` `Hr` `implements` `Company {` `@Override` `public` `void` `findWorker() {`

`System.out.println(``"I need to find someone to hire."` `); ` `}` `}` `public` `class` `Proxy` `implements` `Company {` `private` `Hr hr; ` `public` `Proxy(){` `this``.hr =` `new` `Hr(); ` `}` `@Override` `public` `void` `findWorker() {` `hr.findWorker(); ` `System.out.println(``"Found the employee."` `); ` ` `} ` `}Copy the code

Dynamic proxies: Dynamic proxies create specific proxy classes at run time, and the relationship between proxy classes and proxied classes is undefined until run. Dynamic proxy has stronger applicability, mainly divided into JDK dynamic proxy and CGLib dynamic proxy.

  • **JDK dynamic proxy: ** passesProxyOf the classnewInstanceMethod takes three arguments, the proxied object’s classloader, the proxied object’s interface, and oneInvocationHandlerInvoking processor instances to specify specific logic, the biggest advantage over static proxies is that all methods declared in the interface are transferred toInvocationHandlerIn theinvokeMethods are processed centrally.
`public` `static` `void` `main(String[] args) {` `Hr hr =` `new` `Hr(); ` `Hr proxyHr = (Hr) Proxy.newProxyInstance(hr.getClass().getClassLoader(), hr.getClass().getInterfaces(), (proxy, method, args1) -> {` `System.out.println(``"Receive proxy request"` `); ` `Object obj = method.invoke(hr, args1); ` `System.out.println(``"Employee found, request completed."` `); ` `return` `obj; ` `}); ` `proxyHr.findWorker(); ` ` `}Copy the code
  • **CGLib dynamic proxies: ** Unlike JDK dynamic proxies, which require implementation of the proxied object’s interface, CGLib requires inheritance of the proxied object. CGLib dynamic proxies cannot be used if a class is final. Both agents generate bytecode at run time. The JDK dynamic agent writes section codes directly, while the CGLib dynamic agent writes section codes using the ASM framework, which works on compiled Class files to generate, transform, and analyze compiled Java classes represented as byte arrays. The JDK dynamic proxy calls the proxy method through reflection, while the GCLib dynamic proxy calls the method directly through the FastClass mechanism, generates a class for the proxy class and the propped class, which assigns an int to the proxy class and the propped class method. Methods are called more efficiently because they can be located directly without reflection.

P6: Decorator mode

It refers to the structural mode in which functions are attached to the object without changing the original object. Compared with inheritance, functions of the original object can be expanded more flexibly. This pattern creates a decorator class that wraps the original class and provides additional functionality while preserving the integrity of the class method signature. The decorator pattern is suitable for scenarios where you can extend the functionality of a class or add additional responsibilities to a class without adding many subclasses, or add functionality to a class dynamically, which can then be undone dynamically.

The difference between dynamic proxies and ** decorator patterns focuses on dynamically adding methods to objects, while dynamic proxies focus on controlling access to objects. Dynamic proxies typically create instances of proxyed objects in the proxy class, whereas the decorator pattern takes decorators as arguments to the constructor.


P7: Adapter mode

The adapter pattern is a structural pattern that acts as a bridge between two incompatible interfaces and combines the functions of two independent interfaces to transform the interface of one class into another. This pattern involves a single class that is responsible for adding independent or incompatible interface functions. The advantage is that classes that would otherwise not work together due to interface incompatibilities can work together. The disadvantage is that too much use of adapters will make the system very messy, not easy to grasp the whole.

The difference between the ** and decorator patterns: the ** adapter pattern converts one interface to another. The purpose of the adapter pattern is to change the interface to solve the problem of interface incompatibility. The decorator pattern does not change the interface of the decorator object, but enhances the function of the original object. For example, in the java.io package, the adapter mode converts the InputStream byte InputStream through the adapter InputStreamReader to a Reader character InputStream, The decorator mode augments InputStream with the decorator BufferedInputStream to a buffered byte InputStream.


Java based 17

P1: Basic concepts of the Java language

Advantages:

  • With platform-independent, get rid of the shackles of the hardware platform, to achieve the ideal of “write once, run everywhere”.

  • Provides a relatively safe memory management and access mechanism, avoiding most memory leaks and pointer out-of-bounds problems.

  • The implementation of hot code detection and runtime compilation and optimization, so that Java programs can get higher performance with the increase of runtime.

  • It has a complete set of application program interfaces and supports many third party class libraries.

Principles of Java platform independence:

Mainly through the JVM and The Java language specification.

  • The compiler generates an architecture-neutral object file format, which is compiled code that can run on many processors as long as a Java runtime system is available. Java compiler by generating unrelated to a specific computer architecture bytecode instruction to implement this feature, bytecode file not only can easily explain executed on any machine, can also dynamically switching costs to machine code, transformation is implemented by the JVM, the JVM is platform-dependent, shielding the differences between the operating system.
  • The size of the basic data types in Java and the behavior of the operations are clearly stated. For example, ints in Java are always 32-bit integers, whereas ints in C/C++ may be 16-bit integers, 32-bit integers, or any other size specified by the compiler developer. In Java, numeric types have a fixed number of bytes, binary data is stored and transmitted in a fixed format, and strings are stored in the standard Unicode format.

Technical terms:

  • JDK: Java Development Kit As the core of Java, it provides various tools and resources needed to compile and run Java programs, including the Java compiler, JRE and common Java basic class libraries. The JDK is software used by programmers who write Java programs.
  • JRE: Java Runtime Environment (JRE) is an indispensable Runtime Environment for running programs based on the Java language. JRE is software used by users running Java programs.
  • SE: Standard Edition, a Java platform for desktop or simple server applications.
  • EE: Enterprise Edition, Java platform for complex server applications.
  • ME: Micro Edition, the Java platform for small devices.

P2: Java basic data type

The data type Occupied Memory size Value range
byte 1 byte – 2 7 ^ ^ ^ ^ 7-1 ~ 2
short 2 – – 2 ^ ^ 15 15 ^ – ^ 1 ~ 2
int 4 bytes – 2 ^ 31 ^ ~ ^ 2 ^ 31-1
long 8 bytes – 2 63 ^ ^ ^ ^ 63-1 ~ 2
float 4 bytes ±3.4E+38F (effective digits 6~7)
double 8 bytes + 1.7E+308 (15 effective digits)
char English accounts for 1 byte in both UTF-8 and GBK, while Chinese accounts for 3 bytes in UTF-8 and 2 bytes in GBK. /
boolean Individual variables are replaced by ints, which are 4 bytes long, while arrays are encoded as byte arrays, which are 1 byte long. True, false,

Each basic datatype has its own wrapper class, and except for int and char, which correspond to Integer and Character, the wrapper classes for all basic datatype are capitalized. Automatic boxing refers to wrapping a base data type as a wrapper class object, such as adding an element of type int to a collection whose generic type is Integer. Automatic unboxing refers to converting a wrapper object to a primitive data type, such as assigning a wrapper object to a variable of a primitive data type. To compare the values of two wrapped classes, use the equals method instead of the == comparison operator.


P3: String

** immutable: **String is an immutable class, and the array of values that stores the data is also an immutable array of final modifications, so when you modify the value of a String variable, you’re not actually modifying the value in the array of strings that it references. Instead, it recreates a String object and assigns it to the String variable for reference.

** String concatenation: ** concatenates strings directly with +, and if literals are concatenated automatically to a new constant. To improve stitching efficiency, StringBuilder or StringBuffer mutable strings can be used. The difference is that StringBuffer uses synchronized to ensure thread-safety. However, string stitching is usually a single thread operation. So StringBuilder is used a lot. Concatenation of constants and constants, the result is also in the constant pool, and there are no two identical constants. As long as there are variables in the concatenated string, the result is in the heap.

Create: If it is a String constant assignment, such as String s = “s”, the String constant content is stored in the constant pool, and the variable is stored on the stack and refers directly to the String in the constant pool. If new is used, such as String s = new String(“s”), the instance object will be created in the heap first, and then the constant pool will look for the String constant. If it is found, it will be used directly. If it is not found, it will create a new space and store the content. Object to reference a string in the constant pool.


P4: Value calls and reference calls

Calling by value means that the method receives the value provided by the caller, while calling by reference means that the method receives the address of the variable provided by the caller. Java always calls by value, which means that a method gets a copy of the values of all its arguments. When an object is passed, the method actually receives a copy of the reference to that object. Method cannot modify parameters of the base datatype. You can change the state of an object parameter, but you cannot make an object parameter reference to a new object.

For example, if you pass a value of type int, changing the value does not affect the argument because you are changing a copy of the value. If you pass an array of type int[], changing the contents of the array affects the argument, and changing the reference to the argument does not make the argument refer to the new array object.


P5: Object oriented

Concept: Object orientation is a programming philosophy that is better suited to solving large scale problems than process orientation. Object-oriented development can abstract the real things, map the real things to the development object, close to people’s thinking. And the reuse of code can be realized through inheritance or combination, so the development efficiency is high. And the object-oriented development method improves the readability of the code, makes the code structure more clear, and facilitates the maintenance of the code.

Features:

  • ** Encapsulation: ** also known as data hiding, formally combines data and behavior in a package and hides the implementation from users of the object.
  • ** Inheritance: ** a class can be extended by inheritance. Extended subclasses can inherit the attributes and methods of their parent class and add their own unique attributes and methods. In Java, classes can only be single-inherited, and interfaces can be multi-inherited. Inheritance is an “IS-A” relationship that improves code reuse.
  • ** Polymorphisms: ** Variables of a parent class can refer to objects of a subclass, which are dynamically bound at run time to determine the method to call.
    • Overloaded methods: overloaded methods have multiple methods in the same class with the same name but different argument lists. The return type of overloaded methods is not required, but the argument lists of methods must be different. Overloading is a compile-time polymorphism.
    • ** Overrides: ** means that a subclass has a method with the same name and argument list as the parent method, that the return value is no greater than the parent method’s return value, that the exception type is no greater than the parent method’s exception type, and that the access modifier visibility is no less than the parent method’s access modifier visibility. Overwriting is a run-time polymorphism.

P6: Method modifier

Access modifier Visibility of this class Visibility of this package Subclass visibility Different package visibility
public Square root Square root Square root Square root
protected Square root Square root Square root x
The default Square root Square root x x
private Square root x x x

P7: Interfaces and abstract classes

** Member variables: Member variables in interfaces are public static final modified constants by default. There are no special requirements for member variables in abstract classes.

** constructors: ** Neither interfaces nor abstract classes can be instantiated directly, but interfaces do not have constructors; abstract classes do.

** methods: Methods in ** interfaces are public by default. Java 8 began to support default and static methods, and Java 9 began to support private methods. Methods in abstract classes are not required. Abstract classes may not contain abstract methods, but a class containing abstract methods must be an abstract class.

** inheritance: ** interfaces can have multiple inheritance and implementations, while abstract classes can only have single inheritance.

The ** selection rule: ** If you know a class should be a base class, the first choice should be to make it an interface. Choose an abstract class only when you must have method definitions and member variables. The choice of interfaces and abstract classes must be guided by the principle that behavior models should always be defined through interfaces, not abstract classes. Problems with modeling behavior from abstract classes: If there is A product class A, and there are two subclasses B and C with their own functionality, if there is A new product requirement that has both product B and product C functionality, there is A problem because Java does not allow multiple inheritance, whereas if it is an interface, you only need to implement both interfaces simultaneously.


P8: Object class

The Object class is the parent of all classes.

  • **equals: ** Used to check whether one object is equal to another= =To compare references to two objects, you can override the equals method to customize the comparison rules. The equals method needs to satisfy the following specifications: reflexivity, symmetry, transitivity, consistency, and for any non-empty reference to X,x.equals(null)Returns false.
  • **hashCode: **hashCode is an integer exported from an object. It is not regular. Each object has a default hashCode, which is derived from the object’s storage address. Strings may have the same hash code because the hash code for strings is derived from the content. In order to use objects correctly in a collection, you generally need to override both equals and hashCode methods. Equals must be the same, hashCode must be the same, but hashCode is not necessarily the same as equals. So hashCode is a necessary but not sufficient condition that two objects are the same.
  • toStringThe toString method is called by default when an object is printed. If this method is not overridden, it prints a string representing the value of the object. This can be used when printing arraysArrays.toString()Methods.
  • **clone: The **clone method is protected. A class can clone only its own objects using this method. If you want other classes to call this method, you must make it public. If an object’s class does not implement the Cloneable interface, calling the Clone method on that object raises a CloneNotSupport exception. The default clone method is a shallow-copy. Overwriting the Clone method requires implementing the Cloneable interface and specifying the access modifier public.
    • ** Shallow copy: ** If the object contains a reference to a child object, the copied field will get another reference to the same child object. It is safe if the shared child object is immutable. Usually child objects are mutable, so shallow copies are not safe.
    • ** Deep copy: ** copies the base data type and reference data type completely. Deep copy is safe.
  • ** Finalize: ** is called before the garbage collector cleans up the object, which is deprecated because it cannot be determined when the object will be executed.
  • **getClass: ** Returns the class object that contains the object information.
  • ** Wait/notify/notifyAll: ** Blocks or wakes up the thread holding the lock.

P9: Inner class

There are two main reasons for using inner classes: inner classes can be hidden from other classes in the same package. Inner class methods can access data in the scope that defines the inner class, including data that is otherwise private. Inner classes are a compiler phenomenon, independent of virtual machines. The compiler converts the inner class into a regular class file, separating the outer class name from the inner class name with the dollar sign $, and the virtual machine knows nothing about it.

** Static inner class: ** is a static modifier that belongs to the outer class itself and is loaded only once. Class can define components of the static inner class can be defined, can access the static variables and methods of the external class, through the new external class. Inner class constructor to create the object. Static inner classes should be used as long as the inner class does not need to access the outer class object.

** Member inner class: ** Each object that belongs to an external class is loaded with the object. Instead of defining static members and methods, you can access all the contents of an external class and create objects through the new external class constructor.

** Local inner classes: ** defined in methods, constructors, code blocks, loops. Access modifiers cannot be declared, only instance member variables and instance methods can be defined, scoped only in the block of code that declares the local class.

** Anonymous inner class: ** Local inner class with no name to simplify code, anonymous inner class will immediately create an anonymous inner class object return, the object type of the current new class subclass type. Anonymous inner classes are commonly used to implement event listeners and other callbacks.


`class` `OuterClass{`

`static` `class` `StaticInnerClass {}`

`class` `NormalInnerClass {}`

`public` `void` `test() {` ` class ` ` LocalClass {} ` ` / / create a static inner class object ` ` new ` ` OuterClass. StaticInnerClass (); ' 'new' 'OuterClass().' 'new' 'NormalInnerClass(); '// local inner class creates object' new ' 'LocalClass(); '// Anonymous inner class creates object Runnable Runnable = () -> {}; ` ` `} ` `}Copy the code

P10: static

The static keyword has two main functions :(1) allocate a single storage space for a specific data type or object, regardless of the number of objects created. (2) Associate a property or method with a class rather than an object, so that it can be accessed by the class name without creating the object.

Scope of action:

Static modified variables are called static variables, also known as class variables, and can be accessed directly by the class name. Static variables are stored in the METHODS section of the JVM.

Static methods can be accessed directly by the class name. Static methods can only access static variables or methods.

Static code blocks are called static code blocks and can only be defined under a class. They are executed only once when the class is loaded.

Classes that are static are called static inner classes and have access to the static variables and methods of the external class.

Static can also be used to import static variables from packages.

Class initialization order:

(1) Superclass static code blocks and static variables (2) Subclass static code blocks and static variables (3) superclass common code blocks and common variables (4) superclass constructor (5) Subclass common code blocks and common variables (6) Subclass constructor

Where code blocks and variables are initialized in the order declared in the class.


P11: serialization and deserialization

Java objects are created while the JVM is running, and live objects are destroyed when the JVM exits. If objects and their state need to be persisted, they need to be serialized, storing information about objects and their state in byte arrays, and deserializing those byte arrays as needed. Object serialization holds the state of the object, so static variables in a class are not serialized because they are class attributes.

To realize the serialization function, java.io.Serializabale flag interface is required. Serialization and deserialization must keep the same serialization ID, and private Static Final Long serialVersionUID is generally used to define serialization ID. If you need to serialize the state of the parent class, the parent class also needs to implement the interface.

There are many serialization frameworks, such as Fastjson and Thrift. You can also use the writeObject method of the ObjectOutputStream class in the JDK to stream objects to disk. The ObjectInputStream class’s readObject method implements deserialization, reading as a stream from disk.

In addition to static variables, transient variables are not serialized. The purpose of transient is to limit the life cycle of this field to memory instead of writing to disk for persistence. The variable modified by TRANSIENT will be set to the default initial value of the corresponding data type.

In addition to implementing the Serializabale interface, another approach is to implement the Exteranlizable interface. The writeExternal and readExternal methods need to be overridden, which are more efficient than Serializable and can determine which attributes need to be serialized (even transient modified variables), but less efficient for large or repetitive objects.


P12: reflection

In the running state, for any class, can know all the attributes and methods of the class, for any object, can call any of its methods and attributes; This ability to dynamically retrieve information and invoke methods on objects is called Java’s reflection mechanism. The advantage is that the runtime dynamically obtains all the information of the class, the disadvantage is that the encapsulation of the class is broken, the constraint of generics. Reflection is the core soul of the framework. Dynamic proxy design pattern uses reflection mechanism, and Spring, Hibernate and other frameworks also use reflection mechanism extensively.

During program execution, the Java runtime system always maintains a runtime type identifier for all objects. This information keeps track of the Class to which each object belongs. The virtual machine uses the runtime type information to select the correct method to execute.

There are three ways to obtain an instance of a Class :(1) directly from the Class name. Class. ② Through the object’s getClass() method. ③ Pass class.forname (the fully qualified name of the Class). The getFields, getMethods, and getConstructors methods in the Class Class return an array of public fields, methods, and constructors supported by the Class, including public members of the parent Class, respectively. The getDeclaredFields, getDeclaredMethods, and getDeclaredConstructors methods of the Class return an array of all fields, methods, and constructors declared by the Class, including private, package, and protected members, respectively. But not members of the parent class.

Field, Method, and Constructor describe the fields, methods, and constructors of a class, respectively. All three classes have a getName method that returns the name of a field, method, or constructor. The Field Class has a getType method that returns an object describing the type of the Field, which is also Class. The Method and Constructor classes have methods that report parameter types, and the Method class has a Method that reports return types. Each of these classes has a getModifiers method that returns an integer with different 0/1 bits describing the modifiers used.


P13: annotation

Annotations are tags that attach additional information to a class or interface to help compilers and JVMS perform certain functions. For example, the common @override annotation identifies a method as an Override method.

Meta-annotations are annotations of custom annotations, including:

  • @ Target: To constrain the position of the annotation, the value is an ElementType enumerated class instance, These include METHOD methods, VARIABLE variables, TYPE classes/interfaces, PARAMETER METHOD arguments, CONSTRUCTORS, and LOACL_VARIABLE local variables.

  • @Rentention: To harness the life cycle of the annotation, the value is RetentionPolicy enumerated CLASS instance, including SOURCE SOURCE, CLASS bytecode, and RUNTIME RUNTIME.

  • Documented: Indicates that this annotation should be Documented by the Javadoc tool.

  • Inherited: Indicates that an annotated type is Inherited.


Abnormal P14.

All exceptions are derived from a class instance of the Throwable class, divided into Error and Exception at the next level.

The Error class describes internal errors and resource exhaustion errors in the Java runtime system, and there is generally nothing you can do about them.

The Exception class is divided into RuntimeExceptions and other exceptions. The general rule is that exceptions caused by programming errors are runtimeExceptions if there is nothing wrong with the program, but exceptions caused by something like an IO error are other exceptions. Exceptions derived from Error and RuntimeException are non-checking exceptions, and the rest are checking exceptions.

Common RuntimeException exceptions:

  • ClassCastException, bad cast.
  • ArrayIndexOutOfBoundsException, access to an array of crossing the line.
  • NullPointerException, NullPointerException.

Common check type abnormalities:

  • FileNotFoundException, attempting to open a file that does not exist.
  • ClassNotFoundException, an attempt to find a Class object based on the specified string that does not exist.
  • IOException, which attempts to read data beyond the end of the file.

Exception handling:

** Throws an exception: ** Does not handle an exception. Instead, the exception is thrown to the caller, who will handle it as the case may be. Throws an exception in two forms: throws an exception declared with the keyword and applies to the method; throws an exception directly within the method using the throw statement.

** Catch exception: ** Use a try/catch to catch exceptions. Exceptions that occur ina try are caught by a catch block, and are handled as the case may be. If there is a finally block, it will be executed regardless of whether an exception occurs. Java 7 starts with the ability to automatically release resources by defining them in a try block.


P15: generics

The nature of generics is parameterized types. Generics provide a compile-time type security detection mechanism that allows a program to detect illegal types at compile time.

Type erasure:

Virtual machines have no objects of generic type. All objects belong to ordinary classes. Whenever a generic type is defined, it is automatically supplied with a corresponding primitive type whose name is the generic type name with the type parameters removed. Type variables are erased and replaced with Object if there is no qualified type, or with the first qualified type if there is, such as

replacing T with type A.

Generics are primarily used at compile time, and the type information in the generics is not contained in the Java bytecode files generated after compilation.

Generic specification:

The generic tag instructions
E (Element) Used in a collection to represent the elements stored in the collection.
T (Type) Represents classes, including basic classes and custom classes.
K (Key) Represents a Key, such as a Key in a Map collection.
V (Value) Represents a Value, such as Value in a Map collection.
N (Number) Represents a numeric type.
? Represents an indeterminate type.

Generic qualification:

Use <? Extends T>, which means that the type represented by the wildcard is a subtype of class T or a subinterface of the T interface.

Use <? Super T>, which indicates that the type represented by the wildcard is the parent type of class T or the parent interface of the T interface.


P16: New Java 8 feature

**lambda expressions: ** Lambda expressions allow functions to be passed to methods as arguments to methods, primarily to simplify code for anonymous inner classes.

** FunctionalInterface: ** identified with the @functionalinterface annotation, has one and only abstract method that can be implicitly converted to a lambda expression.

** Method references: ** You can refer directly to methods or constructors of existing classes or objects, further simplifying lambda expressions. Method references come in four forms: constructor references, static methods that reference a class, arbitrary object methods that reference a particular class, and methods that reference an object.

** Interface methods: ** interface can define the default method modified by default, reduce the complexity of interface upgrade, can also define static methods.

** Annotations: **Java 8 introduces repeated annotations, where the same annotations can be declared multiple times in the same place. The scope of annotations has also been expanded to include local variables, generics, method exceptions, and so on.

** Type speculation: ** enhances type speculation to make code more concise, such as omitting generic parameters from objects when defining generic collections.

The **Optional class is used to handle null pointer exceptions and improve code readability.

The **Stream class: ** introduces the functional programming style to the Java language, providing many features that make code more concise. The methods include forEach() traversal, count() statistics, filter() according to conditions, limit() takes the first n elements, skip() skips the first n elements, map() mapping processing, concat() merges stream, etc.

** Date: ** Enhancements to the date and time API, the new Java.time includes handling date, time, date/time, time zone, time, and clock operations.

**JavaScript: **Java 8 provides a new Nashorn JavaScript engine that allows us to run specific JavaScript applications on the JVM.


P17: Java IO

IO model The corresponding Java version
BIO (synchronous blocking IO) 1.4 before
NIO (Synchronous non-blocking IO) 1.4
AIO (Asynchronous non-blocking IO) 1.7

Synchronous and asynchronous are communication mechanisms, and blocking and non-blocking are invocation states.

  • Synchronous I/O means that the user thread initiates an I/O request and waits for or polls the kernel I/O operation to complete before continuing.

  • Asynchronous I/O means that the user thread can continue to execute the I/O request. When the kernel completes the I/O operation, the user thread is notified or the callback function registered by the user thread is invoked.

  • Blocking I/O indicates that the I/O operation can be returned to user space only after it is complete.

  • Non-blocking I/O returns a status value immediately after an I/O operation is invoked, without waiting for the I/O operation to complete.

BIO:

Synchronous blocking IO, the server implementation mode is a connection request for a thread, that is, when the client has a connection request, the server needs to start a thread to process, if the connection does not do anything, it will cause unnecessary thread overhead. This can be improved through the thread pool mechanism, which is called pseudo-asynchronous IO.

Character stream consists of character InputStream Reader and character OutputStream Writer. Character stream consists of byte InputStream InputStream and byte OutputStream OutputStream. Both character stream and character stream have corresponding buffering stream and filtering stream. It is also possible to wrap a byte stream as a stream of characters.

** Application scenarios: ** Less connections, more server resources, low development difficulty.


NIO:

Synchronous non-blocking IO, the server implementation mode is multiple connection requests corresponding to a thread, the connection requests sent by the client will be registered to a multiplexer Selector, multiplexer polling to connect to I/O requests will start a thread for processing, there is data will start thread processing, performance is good.

Synchronous means that the thread continues to receive client connections and process data, and non-blocking means that if a pipe has no data, it can poll the next pipe without waiting.

There are three core components:

  • Selector

    Selector or multiplexer, the main function is polling to check the state of multiple channels, determine whether the Channel registered events occur, that is, determine whether a Channel is in readable or writable state. The Channel is registered with the Selector before it is used. After the registration, a SelectionKey is obtained, through which information about the Channel and the Selector can be obtained.

  • Channel

    A two-way Channel, instead of a Stream in IO, cannot access data directly. Buffers are used to read and write data and to interact with other channels.

    **FileChannel handles files, DatagramChannel handles UDP data, SocketChannel handles TCP data, and ServerSocketChannel handles TCP data, and serves as the server.

  • Buffer

    A Buffer is essentially a block of memory that can read and write data. This memory is wrapped as a NIO Buffer object to simplify reading and writing data. The three important attributes of a Buffer are as follows: position indicates the position of the next read/write, limit indicates the limit of the current read/write, and Capacity indicates the maximum capacity.

    • flip()Position is set to 0 and limit is set to the current position value.
    • throughclear()Convert read to write mode (for reading all data, set position to 0 and limit to Capacity).
    • throughcompact()Convert read to write mode (used when not all data has been read and there is unread data, position points to the next unread data).
    • The direction of the channel is opposite to the direction of Buffer. Reading data is equivalent to writing to Buffer, and writing data is equivalent to reading from Buffer.

    ** Writes data to Buffer, calls flip to switch the Buffer from write mode to read mode, reads data from Buffer, and calls clear or Compact to clear the Buffer.

** Applicable scenarios: ** Large number of connections, short connection time, high development difficulty.


AIO:

Asynchronous non-blocking IO, the server implementation mode is that a valid request corresponds to a thread, and the client’s I/O request is first completed by the operating system and then notified the server application to start the thread to directly use data.

Asynchronous means that the server thread receives the client channel and sends it to the bottom layer to process the IO communication. It can do other things by itself. Non-blocking means that the client will process the data and notify the server after processing it.

AsynchronousServerSocketChannel asynchronous server-side channel, through static methods open (), for instance, through the accept method for client connection channel.

AsynchronousSocketChannel asynchronous client channel, through static methods open (), for instance, the connect method to link server channel.

AsynchronousChannelGroup Asynchronous channel packet manager that can implement resource sharing. Creation requires passing in an ExecutorService, which binds to a thread pool that handles two tasks: processing IO events and triggering the CompletionHandler callback interface.

Implementation method:

Blocking calls through the Future’s GET method.

By implementing the CompletionHandler interface, we override the callback method completed() for a successful request and the callback method failed() for a failed request.

** Applicable scenarios: ** Large number of connections, long connection time, high development difficulty.


Finally, all the above information has been sorted into relevant PDF documents. If you need to obtain it, please get it in the background private letter: PDF

If you have any other questions about this article, please leave them in the comments section at the bottom!