Preface
An old saying goes:
Tao is the spirit of art, and art is the body of art; use Tao to achieve Tao with art.
Among them: "Tao" refers to "rules, principles, theory", and "skills" refers to "methods, techniques, and techniques". It means: "Tao" is the soul of "Shi", and "Shi" is the body of "Tao"; "Tao" can be used to manage "Shi", or "Tao" can be obtained from "Shi".
When reading the big guy's article "Code Review is a bitter but interesting practice", the sentence that I felt the deepest is: "High-quality code must be the principle of less and more", which is the "Tao" of the big guy's code simplification.
Craftsmen pursue "art" to the extreme, but in fact they are seeking "Tao", and they are not far from understanding "Tao", or they have already attained the Tao. This is the "craftsman spirit" - a spirit of pursuing "to attain the Tao with skills". If a craftsman is only satisfied with "art" and cannot pursue "art" to the extreme to understand "Tao", he is just a craftsman who relies on "art" to support his family. Based on years of practical exploration, the author summarized a large number of "arts" of Java code simplification, trying to explain the "way" of Java code simplification in his heart.
1. Utilizing grammar
1.1.Use ternary expressions
ordinary:
String title; if (isMember(phone)) { title = "member"; } else { title = "Tourist"; }
streamline:
String title = isMember(phone) ? "member" : "Tourist";
Note: For arithmetic calculations of packaging types, you need to pay attention to avoiding the problem of null pointers when unpacking.
1.2.Use for-each statements
Starting from Java 5, for-each loops are provided, simplifying loop traversal of arrays and collections. The for-each loop allows you to iterate through an array without keeping the index in a traditional for loop, or to iterate through a collection without calling the hasNext and next methods in the while loop when using iterators.
ordinary:
double[] values = ...; for(int i = 0; i < ; i++) { double value = values[i]; // TODO: Process value} List<Double> valueList = ...; Iterator<Double> iterator = (); while (()) { Double value = (); // TODO: Process value}
streamline:
double[] values = ...; for(double value : values) { // TODO: Process value} List<Double> valueList = ...; for(Double value : valueList) { // TODO: Process value}
1.3.Use try-with-resource statement
All "resources" that implement the Closeable interface can be simplified with try-with-resource.
ordinary:
BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("")); String line; while ((line = ()) != null) { // TODO: Processing line } } catch (IOException e) { ("Read file exception", e); } finally { if (reader != null) { try { (); } catch (IOException e) { ("Close file exception", e); } } }
streamline:
try (BufferedReader reader = new BufferedReader(new FileReader(""))) { String line; while ((line = ()) != null) { // TODO: Processing line } } catch (IOException e) { ("Read file exception", e); }
1.4.Use the return keyword
Using the return keyword, you can return the function in advance to avoid defining intermediate variables.
ordinary:
public static boolean hasSuper(@NonNull List<UserDO> userList) { boolean hasSuper = false; for (UserDO user : userList) { if ((())) { hasSuper = true; break; } } return hasSuper; }
streamline:
public static boolean hasSuper(@NonNull List<UserDO> userList) { for (UserDO user : userList) { if ((())) { return true; } } return false; }
1.5.Use the static keyword
Using the static keyword, you can turn fields into static fields or functions into static functions, so you don't need to initialize class objects when calling.
ordinary:
public final class GisHelper { public double distance(double lng1, double lat1, double lng2, double lat2) { // Method implementation code } } GisHelper gisHelper = new GisHelper(); double distance = (116.178692D, 39.967115D, 116.410778D, 39.899721D);
streamline:
public final class GisHelper { public static double distance(double lng1, double lat1, double lng2, double lat2) { // Method implementation code } } double distance = (116.178692D, 39.967115D, 116.410778D, 39.899721D);
1.6.Use lambda expressions
After the release of Java 8, lambda expressions replaced the use of anonymous internal classes in large numbers. While simplifying the code, they also highlighted the part of the code that was truly useful in the original anonymous internal classes.
ordinary:
new Thread(new Runnable() { public void run() { // Thread processing code } }).start();
streamline:
new Thread(() -> { // Thread processing code}).start();
1.7. Utilizing method reference
Method reference (::), which simplifies lambda expressions and omits variable declarations and function calls.
ordinary:
(nameArray, (a, b) -> (b)); List<Long> userIdList = () .map(user -> ()) .collect(());
streamline:
(nameArray, String::compareToIgnoreCase); List<Long> userIdList = () .map(UserDO::getId) .collect(());
1.8.Use static import
Import static, when the same static constants and functions are used extensively in the program, it can simplify the reference of static constants and functions.
ordinary:
List<Double> areaList = ().map(r -> * (r, 2)).collect(()); ...
streamline:
import static ; import static ; import static ; List<Double> areaList = ().map(r -> PI * pow(r, 2)).collect(toList()); ...
Note: Static introduction can easily cause code reading difficulties, so you should be used with caution in actual projects.
1.9.Use unchecked exception
Java exceptions are divided into two categories: Checked exception and Unchecked exception. Unchecked exceptions inherit RuntimeException, and their feature is that the code can be compiled without processing them, so they are called Unchecked exceptions. Unnecessary try-catch and throws exception handling can be avoided using the Unchecked exception.
ordinary:
@Service public class UserService { public void createUser(UserCreateVO create, OpUserVO user) throws BusinessException { checkOperatorUser(user); ... } private void checkOperatorUser(OpUserVO user) throws BusinessException { if (!hasPermission(user)) { throw new BusinessException("User has no operation permission"); } ... } ... } @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("/createUser") public Result<Void> createUser(@RequestBody @Valid UserCreateVO create, OpUserVO user) throws BusinessException { (create, user); return (); } ... }
streamline:
@Service public class UserService { public void createUser(UserCreateVO create, OpUserVO user) { checkOperatorUser(user); ... } private void checkOperatorUser(OpUserVO user) { if (!hasPermission(user)) { throw new BusinessRuntimeException("User has no operation permission"); } ... } ... } @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("/createUser") public Result<Void> createUser(@RequestBody @Valid UserCreateVO create, OpUserVO user) { (create, user); return (); } ... }
2.Use annotations
2.1.Use Lombok annotations
Lombok provides a useful set of annotations that can be used to eliminate a large amount of boilerplate code in Java classes.
ordinary:
public class UserVO { private Long id; private String name; public Long getId() { return ; } public void setId(Long id) { = id; } public String getName() { return ; } public void setName(String name) { = name; } ... }
streamline:
@Getter @Setter @ToString public class UserVO { private Long id; private String name; ... }
2.2.Use Validation annotations
ordinary:
@Getter@Setter@ToStringpublic class UserCreateVO { @NotBlank(message = "User name cannot be empty") private String name; @NotNull(message = "The company logo cannot be empty") private Long companyId; ...}@Service@Validatedpublic class UserService { public Long createUser(@Valid UserCreateVO create) { // TODO: Create user return null; }}
streamline:
@Getter @Setter @ToString public class UserCreateVO { @NotBlank(message = "User name cannot be empty") private String name; @NotNull(message = "The company logo cannot be empty") private Long companyId; ... } @Service @Validated public class UserService { public Long createUser(@Valid UserCreateVO create) { // TODO: Create a user return null; } }
2.3.Use @NonNull annotation
Spring's @NonNull annotation, used to annotate parameters or return values are non-empty, and is suitable for internal team collaboration in the project. As long as the implementer and the caller follow the specifications, unnecessary null value judgments can be avoided, which fully reflects the "simple because of trust" advocated by Alibaba's "New Six-Meridian Divine Sword".
ordinary:
public List<UserVO> queryCompanyUser(Long companyId) { // Check company logo if (companyId == null) { return null; } // Query returns to the user List<UserDO> userList = (companyId); return ().map(this::transUser).collect(()); } Long companyId = 1L; List<UserVO> userList = queryCompanyUser(companyId); if ((userList)) { for (UserVO user : userList) { // TODO: Processing company users } }
streamline:
public @NonNull List<UserVO> queryCompanyUser(@NonNull Long companyId) { List<UserDO> userList = (companyId); return ().map(this::transUser).collect(()); } Long companyId = 1L; List<UserVO> userList = queryCompanyUser(companyId); for (UserVO user : userList) { // TODO: Processing company users}
2.4. Utilize annotation features
Annotations have the following features that can be used to streamline annotation declarations:
1. When the annotation attribute value is consistent with the default value, the attribute assignment can be deleted;
2. When the annotation has only the value attribute, the value can be removed for abbreviation;
3. When the annotation attribute combination is equal to another specific annotation, the specific annotation is directly used.
ordinary:
@Lazy(true); @Service(value = "userService") @RequestMapping(path = "/getUser", method = )
streamline:
@Lazy @Service("userService") @GetMapping("/getUser")
3. Utilize generics
3.1. Generic interface
Before Java introduced generics, Object was used to represent general objects. The biggest problem was that types could not be strongly checked and required casting.
ordinary:
public interface Comparable { public int compareTo(Object other); } @Getter @Setter @ToString public class UserVO implements Comparable { private Long id; @Override public int compareTo(Object other) { UserVO user = (UserVO)other; return (, ); } }
streamline:
public interface Comparable<T> { public int compareTo(T other); } @Getter @Setter @ToString public class UserVO implements Comparable<UserVO> { private Long id; @Override public int compareTo(UserVO other) { return (, ); } }
3.2. Generic Classes
ordinary:
@Getter @Setter @ToString public class IntPoint { private Integer x; private Integer y; } @Getter @Setter @ToString public class DoublePoint { private Double x; private Double y; }
streamline:
@Getter @Setter @ToString public class Point<T extends Number> { private T x; private T y; }
3.3. Generic methods
ordinary:
public static Map<String, Integer> newHashMap(String[] keys, Integer[] values) { // Check that the parameters are not empty if ((keys) || (values)) { return (); } // Convert hash map Map<String, Integer> map = new HashMap<>(); int length = (, ); for (int i = 0; i < length; i++) { (keys[i], values[i]); } return map; } ...
streamline:
public static <K, V> Map<K, V> newHashMap(K[] keys, V[] values) { // Check that the parameters are not empty if ((keys) || (values)) { return (); } // Convert hash map Map<K, V> map = new HashMap<>(); int length = (, ); for (int i = 0; i < length; i++) { (keys[i], values[i]); } return map; }
4. Use your own methods
4.1.Use the construction method
Constructing method can simplify the initialization and setting properties of objects. For classes with fewer attribute fields, you can customize the constructor.
ordinary:
@Getter @Setter @ToString public class PageDataVO<T> { private Long totalCount; private List<T> dataList; } PageDataVO<UserVO> pageData = new PageDataVO<>(); (totalCount); (userList); return pageData;
streamline:
@Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor public class PageDataVO<T> { private Long totalCount; private List<T> dataList; } return new PageDataVO<>(totalCount, userList);
Note: If the attribute field is replaced, there is a problem with constructor initialization assignment. For example, replace the attribute field title with nickname. Since the number and type of the constructor parameters remain unchanged, the original constructor initialization statement will not report an error, resulting in the original title value being assigned to nickname. If the Setter method is assigned, the compiler will prompt an error and ask for a fix.
4.2.Use Set's add method
Using the return value of Set's add method, you can directly know whether the value already exists, and you can avoid calling the contains method to determine existence.
ordinary:
The following case is a user deduplication conversion operation. You need to call the contains method first to determine the existence, and then call the add method to add.
Set<Long> userIdSet = new HashSet<>(); List<UserVO> userVOList = new ArrayList<>(); for (UserDO userDO : userDOList) { if (!(())) { (()); (transUser(userDO)); } }
streamline:
SSet<Long> userIdSet = new HashSet<>(); List<UserVO> userVOList = new ArrayList<>(); for (UserDO userDO : userDOList) { if ((())) { (transUser(userDO)); } }
4.3.Use Map's computeIfAbsent method
Using Map's computeIfAbsent method, it can ensure that the obtained object is not empty, thereby avoiding unnecessary empty judgment and resetting values.
ordinary:
Map<Long, List<UserDO>> roleUserMap = new HashMap<>(); for (UserDO userDO : userDOList) { Long roleId = (); List<UserDO> userList = (roleId); if ((userList)) { userList = new ArrayList<>(); (roleId, userList); } (userDO); }
streamline:
Map<Long, List<UserDO>> roleUserMap = new HashMap<>(); for (UserDO userDO : userDOList) { ((), key -> new ArrayList<>()) .add(userDO); }
4.4.Use chain programming
Chain programming, also known as cascading programming, returns a this object to point to the object itself when calling the object function, achieving the chain effect, and can be called in cascadingly. The advantages of chain programming are: strong programming, strong readability, and concise code.
ordinary:
StringBuilder builder = new StringBuilder(96); ("select id, name from "); (T_USER); (" where id = "); (userId); (";");
streamline:
StringBuilder builder = new StringBuilder(96); ("select id, name from ") .append(T_USER) .append(" where id = ") .append(userId) .append(";");
5. Utilizing tools
5.1. Avoid null value judgment
ordinary:
if (userList != null && !()) { // TODO: Processing code}
streamline:
if ((userList)) { // TODO: Processing code}
5.2. Avoid conditional judgment
ordinary:
double result; if (value <= MIN_LIMIT) { result = MIN_LIMIT; } else { result = value; }
streamline:
double result = (MIN_LIMIT, value);
5.3. Simplify assignment statements
ordinary:
public static final List<String> ANIMAL_LIST; static { List<String> animalList = new ArrayList<>(); ("dog"); ("cat"); ("tiger"); ANIMAL_LIST = (animalList); }
streamline:
// JDK genrepublic static final List<String> ANIMAL_LIST = ("dog", "cat", "tiger"); // Guava genrepublic static final List<String> ANIMAL_LIST = ("dog", "cat", "tiger");
Note: The returned List is not an ArrayList and does not support change operations such as add.
5.4. Simplify data copying
ordinary:
UserVO userVO = new UserVO(); (()); (()); ... (()); (userVO);
streamline:
UserVO userVO = new UserVO(); (userDO, userVO); (userVO);
Counterexample:
List<UserVO> userVOList = ((userDOList), );
Simplify the code, but not at the cost of excessive performance losses. An example is a shallow copy, and there is no need for heavyweight weapons like JSON.
5.5. Simplify exception assertions
ordinary:
if ((userId)) { throw new IllegalArgumentException("User ID cannot be empty"); }
streamline:
(userId, "User ID cannot be empty");
Note: Some plug-ins may not agree with this judgment, resulting in a null pointer warning when using the object.
5.6. Simplify test cases
Save the test case data into a file in JSON format and parse it into an object through JSON's parseObject and parseArray methods. Although the execution efficiency has decreased, a large number of assignment statements can be reduced, thus streamlining the test code.
ordinary:
@Test public void testCreateUser() { UserCreateVO userCreate = new UserCreateVO(); ("Changyi"); ("Developer"); ("AMAP"); ... Long userId = (OPERATOR, userCreate); (userId, "Creating user failed"); }
streamline:
@Test public void testCreateUser() { String jsonText = (getClass(), ""); UserCreateVO userCreate = (jsonText, ); Long userId = (OPERATOR, userCreate); (userId, "Creating user failed"); }
Suggestion: The JSON file name is best named after the method being tested, and can be represented by a numeric suffix if there are multiple versions.
5.7. Simplify algorithm implementation
Some conventional algorithms already have ready-made tools and methods, so we don’t need to implement them ourselves.
ordinary:
int totalSize = (); List<List<Integer>> partitionList = new ArrayList<>(); for (int i = 0; i < totalSize; i += PARTITION_SIZE) { ((i, (i + PARTITION_SIZE, totalSize))); }
streamline:
List<List<Integer>> partitionList = (valueList, PARTITION_SIZE);
5.8. Packaging tool method
There are some special algorithms without ready-made tools and methods, so we have to implement them ourselves.
ordinary:
For example, the method of setting parameter values in SQL is more difficult to use, and the setLong method cannot set parameter values to null.
// Set parameter valuesif ((())) { (1, ()); } else { (1, ); } ...
streamline:
We can encapsulate it as a tool class SqlHelper to simplify the code for setting parameter values.
/** SQL auxiliary class */ public final class SqlHelper { /** Set long integer value */ public static void setLong(PreparedStatement statement, int index, Long value) throws SQLException { if ((value)) { (index, ()); } else { (index, ); } } ... } // Set parameter values(statement, 1, ());
6. Utilize data structures
6.1. Simplify using arrays
For if-else statements with fixed upper and lower limit ranges, they can be simplified by array + loop.
ordinary:
public static int getGrade(double score) { if (score >= 90.0D) { return 1; } if (score >= 80.0D) { return 2; } if (score >= 60.0D) { return 3; } if (score >= 30.0D) { return 4; } return 5; }
streamline:
private static final double[] SCORE_RANGES = new double[] {90.0D, 80.0D, 60.0D, 30.0D}; public static int getGrade(double score) { for (int i = 0; i < SCORE_RANGES.length; i++) { if (score >= SCORE_RANGES[i]) { return i + 1; } } return SCORE_RANGES.length + 1; }
Thinking: The return value of the above case is incremented, so there is no problem using arrays to simplify. However, if the return value is not incremental, can it be simplified with an array? The answer is yes, please think about it and solve it yourself.
6.2. Simplify using Map
For if-else statements that map relationships, they can be simplified by Map. In addition, this rule also applies to switch statements that simplify mapping relationships.
ordinary:
public static String getBiologyClass(String name) { switch (name) { case "dog" : return "animal"; case "cat" : return "animal"; case "lavender" : return "plant"; ... default : return null; } }
streamline:
private static final Map<String, String> BIOLOGY_CLASS_MAP = ImmutableMap.<String, String>builder() .put("dog", "animal") .put("cat", "animal") .put("lavender", "plant") ... .build(); public static String getBiologyClass(String name) { return BIOLOGY_CLASS_MAP.get(name); }
The method has been simplified into one line of code, but there is actually no need to encapsulate the method.
6.3. Simplify using container classes
Java is not like Python and Go, methods do not support returning multiple objects. If multiple objects need to be returned, you must customize the class or utilize the container class. Common container classes include Apache's Pair class and Triple class. The Pair class supports returning 2 objects, and the Triple class supports returning 3 objects.
ordinary:
@Setter @Getter @ToString @AllArgsConstructor public static class PointAndDistance { private Point point; private Double distance; } public static PointAndDistance getNearest(Point point, Point[] points) { // Calculate the nearest point and distance ... // Return to the nearest point and distance return new PointAndDistance(nearestPoint, nearestDistance); }
streamline:
public static Pair<Point, Double> getNearest(Point point, Point[] points) { // Calculate the nearest point and distance ... // Return to the nearest point and distance return (nearestPoint, nearestDistance); }
6.4. Simplify using ThreadLocal
ThreadLocal provides thread-specific objects that can be used at any time during the entire thread life cycle, greatly facilitating the implementation of some logic. Saving thread context objects with ThreadLocal can avoid unnecessary parameter passing.
ordinary:
Since the format method of DateFormat is not secure (an alternative method is recommended), frequent initialization of DateFormat in threads is too low. If you consider reuse, you can only pass into DateFormat with parameters. Examples are as follows:
public static String formatDate(Date date, DateFormat format) { return (date); } public static List<String> getDateList(Date minDate, Date maxDate, DateFormat format) { List<String> dateList = new ArrayList<>(); Calendar calendar = (); (minDate); String currDate = formatDate((), format); String maxsDate = formatDate(maxDate, format); while ((maxsDate) <= 0) { (currDate); (, 1); currDate = formatDate((), format); } return dateList; }
streamline:
You may think that the following code is more than the amount of code. If there are more places to call tool methods, you can save a lot of codes for initializing and passing parameters in DateFormat.
private static final ThreadLocal<DateFormat> LOCAL_DATE_FORMAT = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd"); } }; public static String formatDate(Date date) { return LOCAL_DATE_FORMAT.get().format(date); } public static List<String> getDateList(Date minDate, Date maxDate) { List<String> dateList = new ArrayList<>(); Calendar calendar = (); (minDate); String currDate = formatDate(()); String maxsDate = formatDate(maxDate); while ((maxsDate) <= 0) { (currDate); (, 1); currDate = formatDate(()); } return dateList; }
Note: ThreadLocal has a certain risk of memory leakage. Try to call the remove method before the business code is over to clear data.
7. Utilize Optional
In Java 8, an Optional class is introduced, which is a container object that can be null.
7.1. Guaranteed value exists
ordinary:
Integer thisValue; if ((value)) { thisValue = value; } else { thisValue = DEFAULT_VALUE; }
streamline:
Integer thisValue = (value).orElse(DEFAULT_VALUE);
7.2. The guaranteed value is legal
ordinary:
Integer thisValue; if ((value) && (MAX_VALUE) <= 0) { thisValue = value; } else { thisValue = MAX_VALUE; }
streamline:
Integer thisValue = (value) .filter(tempValue -> (MAX_VALUE) <= 0).orElse(MAX_VALUE);
7.3. Avoid empty judgment
ordinary:
String zipcode = null; if ((user)) { Address address = (); if ((address)) { Country country = (); if ((country)) { zipcode = (); } } }
streamline:
tring zipcode = (user).map(User::getAddress) .map(Address::getCountry).map(Country::getZipcode).orElse(null);
8.Use Stream
Stream is a new member of Java 8. It allows you to process data collections declaratively and can be seen as an advanced iterator that traverses datasets. The stream mainly consists of three parts: obtaining a data source → data conversion → performing operations to obtain the desired result. Each time the original Stream object is converted, it returns a new Stream object, which allows its operations to be arranged like a chain, forming a pipeline. The functions provided by Stream are very useful, mainly including matching, filtering, summary, conversion, grouping, grouping and other functions.
8.1. Match collection data
ordinary:
boolean isFound = false; for (UserDO user : userList) { if (((), userId)) { isFound = true; break; } }
streamline:
boolean isFound = () .anyMatch(user -> ((), userId));
8.2. Filter collection data
ordinary:
List<UserDO> resultList = new ArrayList<>(); for (UserDO user : userList) { if ((())) { (user); } }
streamline:
List<UserDO> resultList = () .filter(user -> (())) .collect(());
8.3. Summary of collection data
ordinary:
double total = 0.0D; for (Account account : accountList) { total += (); }
streamline:
double total = ().mapToDouble(Account::getBalance).sum();
8.4. Convert collection data
ordinary:
List<UserVO> userVOList = new ArrayList<>(); for (UserDO userDO : userDOList) { (transUser(userDO)); }
streamline:
List<UserVO> userVOList = () .map(this::transUser).collect(());
8.5. Grouped collection data
ordinary:
Map<Long, List<UserDO>> roleUserMap = new HashMap<>(); for (UserDO userDO : userDOList) { ((), key -> new ArrayList<>()) .add(userDO); }
streamline:
Map<Long, List<UserDO>> roleUserMap = () .collect((UserDO::getRoleId));
8.6. Group summary collection
ordinary:
Map<Long, Double> roleTotalMap = new HashMap<>(); for (Account account : accountList) { Long roleId = (); Double total = ((roleId)).orElse(0.0D); (roleId, total + ()); }
streamline:
roleTotalMap = ().collect((Account::getRoleId, (Account::getBalance)));
8.7. Generate range sets
Python's range is very convenient, and Stream provides a similar approach.
ordinary:
int[] array1 = new int[N]; for (int i = 0; i < N; i++) { array1[i] = i + 1; } int[] array2 = new int[N]; array2[0] = 1; for (int i = 1; i < N; i++) { array2[i] = array2[i - 1] * 2; }
streamline:
int[] array1 = (1, N).toArray(); int[] array2 = (1, n -> n * 2).limit(N).toArray();
9. Utilize program structure
9.1. Return conditional expression
Conditional expression judgment returns a Boolean value, and the conditional expression itself is the result.
ordinary:
public boolean isSuper(Long userId) UserDO user = (userId); if ((user) && (())) { return true; } return false; }
streamline:
public boolean isSuper(Long userId) UserDO user = (userId); return (user) && (()); }
9.2. Minimize conditional scope
Minimize the conditional scope and try to propose public processing code.
ordinary:
Result result = (workDaily); if (()) { String message = "Reporting the workday successfully"; ((), message); } else { String message = "Failed to report the working day report:" + (); (message); ((), message); }
streamline:
String message; Result result = (workDaily); if (()) { message = "Reporting the workday successfully"; } else { message = "Failed to report the working day report:" + (); (message); } ((), message);
9.3. Adjust the expression position
Adjust the expression position and make the code more concise while the logic remains unchanged.
Normal 1:
String line = readLine(); while ((line)) { ... // Process logical code line = readLine(); }
Normal 2:
for (String line = readLine(); (line); line = readLine()) { ... // Process logical code}
streamline:
String line; while ((line = readLine())) { ... // Process logical code}
Note: Some specifications may not recommend this kind of simplified writing.
9.4.Use non-empty objects
When comparing objects, exchange object positions and using non-empty objects can avoid null pointer judgments.
ordinary:
private static final int MAX_VALUE = 1000; boolean isMax = (value != null && (MAX_VALUE)); boolean isTrue = (result != null && ());
streamline:
private static final Integer MAX_VALUE = 1000; boolean isMax = MAX_VALUE.equals(value); boolean isTrue = (result);
10. Utilize design models
10.1. Template method mode
The Template Method Pattern defines a fixed algorithm framework, and puts some steps of the algorithm into a subclass, so that the subclass can redefine some steps of the algorithm without changing the algorithm framework.
ordinary:
@Repository public class UserValue { /** Value operation */ @Resource(name = "stringRedisTemplate") private ValueOperations<String, String> valueOperations; /** Value mode */ private static final String KEY_FORMAT = "Value:User:%s"; /** Set value */ public void set(Long id, UserDO value) { String key = (KEY_FORMAT, id); (key, (value)); } /** Get value */ public UserDO get(Long id) { String key = (KEY_FORMAT, id); String value = (key); return (value, ); } ... } @Repository public class RoleValue { /** Value operation */ @Resource(name = "stringRedisTemplate") private ValueOperations<String, String> valueOperations; /** Value mode */ private static final String KEY_FORMAT = "Value:Role:%s"; /** Set value */ public void set(Long id, RoleDO value) { String key = (KEY_FORMAT, id); (key, (value)); } /** Get value */ public RoleDO get(Long id) { String key = (KEY_FORMAT, id); String value = (key); return (value, ); } ... }
streamline:
public abstract class AbstractDynamicValue<I, V> { /** Value operation */ @Resource(name = "stringRedisTemplate") private ValueOperations<String, String> valueOperations; /** Set value */ public void set(I id, V value) { (getKey(id), (value)); } /** Get value */ public V get(I id) { return ((getKey(id)), getValueClass()); } ... /** Get the primary key */ protected abstract String getKey(I id); /** Get value class */ protected abstract Class<V> getValueClass(); } @Repository public class UserValue extends AbstractValue<Long, UserDO> { /** Get the primary key */ @Override protected String getKey(Long id) { return ("Value:User:%s", id); } /** Get value class */ @Override protected Class<UserDO> getValueClass() { return ; } } @Repository public class RoleValue extends AbstractValue<Long, RoleDO> { /** Get the primary key */ @Override protected String getKey(Long id) { return ("Value:Role:%s", id); } /** Get value class */ @Override protected Class<RoleDO> getValueClass() { return ; } }
10.2. Builder Mode
Builder Pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. Such a design pattern is called Builder Pattern.
ordinary:
public interface DataHandler<T> { /** Analyze data */ public T parseData(Record record); /** Store data */ public boolean storeData(List<T> dataList); } public <T> long executeFetch(String tableName, int batchSize, DataHandler<T> dataHandler) throws Exception { // Build a download session DownloadSession session = buildSession(tableName); // Get the number of data long recordCount = (); if (recordCount == 0) { return 0; } // Perform data reading long fetchCount = 0L; try (RecordReader reader = (0L, recordCount, true)) { // Read data in turn Record record; List<T> dataList = new ArrayList<>(batchSize); while ((record = ()) != null) { // parse and add data T data = (record); if ((data)) { (data); } // Bulk data storage if (() == batchSize) { boolean isContinue = (dataList); fetchCount += batchSize; (); if (!isContinue) { break; } } } //Storing remaining data if ((dataList)) { (dataList); fetchCount += (); (); } } // Return to get the number return fetchCount; } // Use caseslong fetchCount = ("user", 5000, new DataHandler() { /** Analyze data */ @Override public T parseData(Record record) { UserDO user = new UserDO(); (("id")); (("name")); return user; } /** Store data */ @Override public boolean storeData(List<T> dataList) { (dataList); return true; } });
streamline:
public <T> long executeFetch(String tableName, int batchSize, Function<Record, T> dataParser, Function<List<T>, Boolean> dataStorage) throws Exception { // Build a download session DownloadSession session = buildSession(tableName); // Get the number of data long recordCount = (); if (recordCount == 0) { return 0; } // Perform data reading long fetchCount = 0L; try (RecordReader reader = (0L, recordCount, true)) { // Read data in turn Record record; List<T> dataList = new ArrayList<>(batchSize); while ((record = ()) != null) { // parse and add data T data = (record); if ((data)) { (data); } // Bulk data storage if (() == batchSize) { Boolean isContinue = (dataList); fetchCount += batchSize; (); if (!(isContinue)) { break; } } } //Storing remaining data if ((dataList)) { (dataList); fetchCount += (); (); } } // Return to get the number return fetchCount; } // Use caseslong fetchCount = ("user", 5000, record -> { UserDO user = new UserDO(); (("id")); (("name")); return user; }, dataList -> { (dataList); return true; });
The ordinary builder model requires the definition of the DataHandler interface when implementing it, and the DataHandler anonymous internal class needs to be implemented when calling it, and the code is more complicated and complicated. The streamlined builder model makes full use of functional programming, without defining interfaces during implementation, and directly uses Function interfaces; without implementing anonymous internal classes during calling, and directly uses lambda expressions, with less code and simpler.
10.3. Agent Mode
The most important proxy model in Spring is AOP (Aspect-Oriented Programming, aspect-oriented programming), which is implemented using JDK dynamic proxy and CGLIB dynamic proxy technology.
ordinary:
@Slf4j @RestController @RequestMapping("/user") public class UserController { /** User Service */ @Autowired private UserService userService; /** Query user */ @PostMapping("/queryUser") public Result<?> queryUser(@RequestBody @Valid UserQueryVO query) { try { PageDataVO<UserVO> pageData = (query); return (pageData); } catch (Exception e) { ((), e); return (()); } } ... }
Lite 1:
Exception handling based on @ControllerAdvice:
@RestController @RequestMapping("/user") public class UserController { /** User Service */ @Autowired private UserService userService; /** Query user */ @PostMapping("/queryUser") public Result<PageDataVO<UserVO>> queryUser(@RequestBody @Valid UserQueryVO query) { PageDataVO<UserVO> pageData = (query); return (pageData); } ... } @Slf4j @ControllerAdvice public class GlobalControllerAdvice { /** Exception handling */ @ResponseBody @ExceptionHandler() public Result<Void> handleException(Exception e) { ((), e); return (()); } }
Lite 2:
Exception handling based on AOP:
// UserController code is the same as "lite 1" @Slf4j @Aspect public class WebExceptionAspect { /** Point section */ @Pointcut("@annotation()") private void webPointcut() {} /** Exception handling */ @AfterThrowing(pointcut = "webPointcut()", throwing = "e") public void handleException(Exception e) { Result<Void> result = (()); writeContent((result)); } ... }
11.Use the deletion code
"Less means more", "Less" is not blank but streamlined, and "more" is not crowded but perfect. Only by deleting unnecessary code can the code be more streamlined and perfect.
11.1. Delete the abandoned code
Delete abandoned packages, classes, fields, methods, variables, constants, imports, annotations, comments, commented codes, Maven package imports, MyBatis' SQL statements, attribute configuration fields, etc. in the project, which can simplify the project code for easy maintenance.
ordinary:
import .slf4j.Slf4j; @Slf4j @Service public class ProductService { @Value("discardRate") private double discardRate; ... private ProductVO transProductDO(ProductDO productDO) { ProductVO productVO = new ProductVO(); (productDO, productVO); // (getDiscardPrice(())); return productVO; } private BigDecimal getDiscardPrice(BigDecimal originalPrice) { ... } }
streamline:
@Service public class ProductService { ... private ProductVO transProductDO(ProductDO productDO) { ProductVO productVO = new ProductVO(); (productDO, productVO); return productVO; } }
11.2. Delete the public of the interface method. For interface (interface), all fields and methods are public and can be explicitly declared as public. ordinary:
public interface UserDAO { public Long countUser(@Param("query") UserQuery query); public List<UserDO> queryUser(@Param("query") UserQuery query); }
11.2. Delete the public of the interface method
For interfaces, all fields and methods are public and can be explicitly declared as public.
ordinary:
public interface UserDAO { public Long countUser(@Param("query") UserQuery query); public List<UserDO> queryUser(@Param("query") UserQuery query); }
streamline:
public interface UserDAO { Long countUser(@Param("query") UserQuery query); List<UserDO> queryUser(@Param("query") UserQuery query); }
11.3. Delete the private of the enumeration constructor
For enums (menu), the constructors are private and can be explicitly declared as private.
ordinary:
public enum UserStatus { DISABLED(0, "Disable"), ENABLED(1, "Enable"); private final Integer value; private final String desc; private UserStatus(Integer value, String desc) { = value; = desc; } ... }
streamline:
public enum UserStatus { DISABLED(0, "Disable"), ENABLED(1, "Enable"); private final Integer value; private final String desc; UserStatus(Integer value, String desc) { = value; = desc; } ... }
11.4. Delete the final method of the final class method
For final classes, they cannot be inherited by subclasses, so their methods will not be overwritten, so there is no need to add final modification.
ordinary:
public final Rectangle implements Shape { ... @Override public final double getArea() { return width * height; } }
streamline:
public final Rectangle implements Shape { ... @Override public double getArea() { return width * height; } }
11.5. Delete the interface of the base class implements
If the base class has implemented an interface, there is no need for the subclass to implement the interface again, and only needs to directly implement the interface method.
ordinary:
public interface Shape { ... double getArea(); } public abstract AbstractShape implements Shape { ... } public final Rectangle extends AbstractShape implements Shape { ... @Override public double getArea() { return width * height; } }
streamline:
... public final Rectangle extends AbstractShape { ... @Override public double getArea() { return width * height; } }
11.6. Delete unnecessary variables
Unnecessary variables will only make the code look more cumbersome.
ordinary:
public Boolean existsUser(Long userId) { Boolean exists = (userId); return exists; }
streamline:
public Boolean existsUser(Long userId) { return (userId); }
postscript
The old saying goes:
If there is Tao or no skills, skills can still be sought; if there is Tao or no skills, it will stop at the art.
It means: if there is "Tao" but no "art", "art" can be gradually obtained; if there is "art" but no "Tao", you may stop at "art". Therefore, we should not be satisfied with summarizing "art" from practice, because the manifestations of "Tao" are changeable; but should be raised to the height of "Tao", because the principles behind "art" are the same. When we encounter new things, we can find "Tao" from theory, find "art" from practice, and try to understand new things.
This is all about this article about Java code simplification. For more related Java code simplification content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!