Effective Java is a classic Java learning guide that every Java developer should read. The author made a partial summary of the knowledge points in the book and daily work.
The author | appropriate source of autumn | ali technology to the public
Effective Java is a classic Java learning guide that every Java developer should read. The author made a partial summary of the knowledge points in the book and daily work.
Create and destroy object sections
1 If there are multiple constructor parameters, the constructor is preferred
When class constructs contain multiple parameters, students choose the JavaBeans pattern. In this mode, you can call a no-parameter constructor to create the object and then call setter methods to set the necessary and optional parameters. One of the more popular methods is to add Lombok’s @data annotation to a class to automatically generate getters/setters, equals, and so on. But the JavaBeans pattern cannot make classes immutable (see the section on minimizing mutability). This requires the developer to be in control of how values are updated, to ensure thread-safety, etc.
Recommended: Builder mode
The Builder mode sets parameters (similar to Proto Buffers) by calling setter-like methods on the Builder object. Finally, immutable objects are generated by calling the Build method. One way to use the Builder pattern involves adding the @Builder annotation provided by Lombok to your classes.
Application: API Request & Response
In the microservice architecture, the request and response of a service often contain many parameters. In the process of processing a request, I often worry that the content of the request has been modified by mistake. Therefore, the author tends to use the Builder model.
We can use the Builder pattern to build objects of this type. If additional logic (e.g. if-else) needs to be introduced during the build process, return the Builder object first and call the build method last.
import lombok.Builder; @builder public class SampleRequest {private String paramOne; private int paramTwo; private boolean paramThree; @builder public class SampleResponse {private Boolean success; } /** public interface SampleFacade {Result< SampleResponse> rpcOne(RequestParam< SampleRequest>); Public void testRpcOne() {SampleRequest request = SampleRequest.builder().paramOne("one").paramTwo(2).paramThree(true).build(); Result< SampleResponse> response = sampleFacade.rpcOne(request); }Copy the code
Enhance non-instantiation capabilities with private constructors
Some classes, such as utility classes, contain only static fields and static methods. These classes should be as uninstantiated as possible to prevent misuse by users.
Recommended: Private class constructor
To avoid misleading users that the class is designed specifically for inheritance, we can privatize the constructor.
public class SampleUtility { public static String getXXX() { return "test"; Public static void main(String[] args) {public static void main(String[] args) {public static void main(String[] args) { System.out.println(SampleUtility.getXXX()); }Copy the code
Two classes and interfaces
Minimize the accessibility of classes and members
Make every class or member as inaccessible as possible.
Recommendation: Sometimes we have to make some private classes, interfaces, or members package-private for testing purposes. Here, I recommend using the @VisiableForTesting annotation provided by Guava to indicate that this loosens the restrictions by making the accessibility level package-private for testing purposes.
import com.google.common.annotations.VisibleForTesting;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
String getXXX() {
return "test";
}
Copy the code
The PowerMock unit testing framework is also recommended. PowerMock is an enhanced version of Mockito that enables you to Mock private/static/final methods. Do this by adding the @prepareForTest annotation.
public class Utility { private static boolean isGreaterThan(int a, int b) { return a > b; } private Utility() {}} /** Test */ import org.junit.test; import org.junit.jupiter.api.Assertions; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; @RunWith(PowerMockRunner.class) @PrepareForTest({Utility.class}) public class UtilityTest { @Test public void Test_privateIsGreaterThan_success () throws Exception {/** Tests the private isGreaterThan method */ Boolean result = Whitebox.invokeMethod(Utility.class, "isGreaterThan", 3, 2); Assertions.assertTrue(result); }}Copy the code
2. Minimize deformability
Immutable class means that it is impossible to change the value of a member variable after an instance of a class is created. That is, all information contained in an instance must be provided when the instance is created and fixed for the life of the object.
Immutable classes typically use functional mode, where the corresponding method returns the result of a function that operates on its operands but does not modify them. A more common counterpart is the procedure or imperative approach. When using these methods, applying a procedure to its operands causes its state to change.
As mentioned in the section “Constructors are preferred if multiple constructor arguments”, immutable objects are simple, thread-safe, and have only one state. Developers using this class do not need to do any additional work to maintain constraints. In addition, mutable objects can have arbitrarily complex states. If mutator methods (e.g. update) are not described in detail, developers need to read the methods themselves. I often spend a lot of time trying to figure out which fields of a mutable object have been changed in a method and whether it will affect subsequent object operations after the method ends. I recommend passing in immutable objects and creating new immutable object returns based on this with updated parameters. Although more objects are created, they are immutable and more readable.
Guava Collection is Immutable
In daily development, ImmutableList (ImmutableSet, ImmuableMap) and the set of function patterns mentioned above are used to implement mutator methods.
import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; Private static final ImmutableMap< String, Integer> SAMPLE_MAP = ImmutableMap. Of ("One", 1, "Two", 2); private static final ImmutableMap< String, Integer> SAMPLE_MAP = ImmutableMap. / * * is recommended: */ public ImmutableList< TestObj> updateXXX(ImmutableList< TestObj> input) {return input.stream() .map(obj -> obj.setXXX(true)) .collect(toImmutableList()); } public void filterXXX(List< TestObj> input) {input.foreach (obj -> obj.setxxx (true));} public void filterXXX(List< TestObj> input) {input.foreach (obj -> obj. }Copy the code
Three generic
1 Lists take precedence over arrays
The array is covariant, i.e. Sub is a subtype of Super, so Sub[] is a subtype of Super[]. Arrays are materialized, and their element type constraints are known and checked at run time. Generics, on the other hand, are immutable and erasable (that is, their type information is enhanced at compile time and discarded at run time).
Be wary of public Static final arrays. It’s probably a security breach!
Four methods article
1 Verify the validity of parameters
If you pass invalid parameter values to a method that validates the parameters before executing complex, time-consuming logic, it will fail quickly and will clearly throw the appropriate exception. Without parameters to validate it, all sorts of strange exceptions can occur later on, and the cause is sometimes difficult to locate.
In my opinion, the API request provided by microservices should also follow this idea. Parameter verification is performed before the API request is processed by the service. Each request should be bound to a corresponding Request Validator. If the argument value is invalid, a specific ClientException (e.g. IllegalArgumentException) is thrown.
2 Carefully design method signatures
-
Choose method names carefully: Methods that perform an action are usually named with a verb or phrasal verb: createXXX, updateXXX, removeXXX, convertXXX, generateXXX For methods that return Boolean values, they usually start with is: IsValid, isLive, isEnabled
-
Avoid long parameter lists: Aim for four parameters, or fewer. When there are too many arguments, I use Pair, Triple, or helper classes (e.g Static member class)
public class SampleListener {
public ConsumeConcurrentlyStatus consumeMessage(String input) { SampleResult result = generateResult(input); . } private static SampleResult generateResult(String input) { ... } /** private static class SampleResult {private Boolean success; private List< String> xxxList; private int count; }Copy the code
}
3 returns a zero-length array or collection instead of NULL
If a method returns null instead of a zero-length array or collection, the developer needs to add it! NullpointerException = null;
Speaking of which, I would like to mention Optional. There is a lot of discussion on the Internet about the use of Optional and NULL. Optional allows the caller to continue a series of smooth method calls (e.g. stream.getfirst ().orelsethrow (() -> new MyFancyException())). The following is the author’s opinion.
/** Recommendation: The return value may be null. */ public Optional< Foo> findFoo(String id); /** * neutral: slightly clunky * consider doSomething("bar", null); * Or reload doSomething("bar"); And doSomething (" bar ", "baz"); **/ public Foo doSomething(String id, Optional< Bar> barOptional); /** * Not recommended: Violates the purpose of Optional's design. * When the Optional value defaults, there are generally three ways to handle it: 1) provide an alternative value; 2) The calling method provides a substitute value; These can be handled when a field is initialized or assigned. **/ public class Book { private List< Pages> pages; private Optional< Index> index; } /** * Not recommended: violates the purpose of Optional's design. * If the value is the default value, you can not add it to the list. **/ List< Optional< Foo>>Copy the code
General program design
1 Avoid float and double if precise answers are required
The float and double types are primarily used for scientific and engineering calculations. They perform binary floating-point arithmetic in order to provide fast approximations with relative accuracy over numerical ranges. However, they do not provide completely accurate results and are particularly unsuitable for monetary calculations. Float or double representing 0.1 exactly is not feasible.
If you need a system to record decimal points, use BigDecimal.
2 Basic type takes precedence over packing basic type
Primitive types (primitive) such as int, double, long and Boolean. Each primitive type has a corresponding reference type, called boxed Primitive, which corresponds to Integer, Double, Long, and Boolean. As mentioned in the book, the differences are as follows:
Public int sum(int a, int b) {return a + b; } public Integer sum(Integer a, Integer b) {return a + b; }Copy the code
If there is no specific application scenario, it is recommended to use the basic type. If you have to use boxing primitives, note the == operation and NullPointerException exception. Usage scenarios of basic types of packing:
- As an element in a collection (e.g. Set< Long>)
- Parameterized types (e.g. ThreadLocal< Long>)
- Reflected method calls
Six abnormal
Every exception thrown by a method should be documented
Always declare checked exceptions separately, and use Javadoc’s @throws tag to accurately document the conditions under which each exception is thrown.
In my daily work, I sometimes find unexpected exceptions when calling apis from other groups. Good documentation helps API callers handle related exceptions better. Documentation can include the type of exception, error code, and description of the exception.
Two other
Some companies divide exceptions generated by the API into ClientExceptions and ServerExceptions. General ClientException (e.g. invalid service request) is an exception handling caused by an unusual call to the API by the caller and is not in the scope of the main exception monitoring on the server. ServerException (e.g., database query timeout) is caused by the server itself and needs to be monitored.
Seven references
Bloch, Joshua. 2018. Effective Java, 3rd Edition
The original link
This article is the original content of Aliyun and shall not be reproduced without permission.