SoFunction
Updated on 2025-03-08

Java code simplification (recommended)

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(() -&gt; {
 // 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&lt;Void&gt; 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&lt;Void&gt; 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&lt;UserVO&gt; queryCompanyUser(Long companyId) {
 // Check company logo if (companyId == null) {
 return null;
 }

 // Query returns to the user List&lt;UserDO&gt; userList = (companyId);
 return ().map(this::transUser).collect(());
}

Long companyId = 1L;
List&lt;UserVO&gt; userList = queryCompanyUser(companyId);
if ((userList)) {
 for (UserVO user : userList) {
 // TODO: Processing company users }
}

streamline:

public @NonNull List&lt;UserVO&gt; queryCompanyUser(@NonNull Long companyId) {
 List&lt;UserDO&gt; userList = (companyId);
 return ().map(this::transUser).collect(());
}

Long companyId = 1L;
List&lt;UserVO&gt; 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&lt;String, Integer&gt; newHashMap(String[] keys, Integer[] values) {
 // Check that the parameters are not empty if ((keys) || (values)) {
 return ();
 }

 // Convert hash map Map&lt;String, Integer&gt; map = new HashMap&lt;&gt;();
 int length = (, );
 for (int i = 0; i &lt; length; i++) {
 (keys[i], values[i]);
 }
 return map;
}
...

streamline:

public static &lt;K, V&gt; Map&lt;K, V&gt; newHashMap(K[] keys, V[] values) {
 // Check that the parameters are not empty if ((keys) || (values)) {
 return ();
 }

 // Convert hash map Map&lt;K, V&gt; map = new HashMap&lt;&gt;();
 int length = (, );
 for (int i = 0; i &lt; 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 &amp;&amp; !()) {
 // 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&lt;String&gt; ANIMAL_LIST = ("dog", "cat", "tiger");
// Guava genrepublic static final List&lt;String&gt; 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&lt;Point, Double&gt; 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&lt;String, String&gt; 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&lt;String, String&gt; 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&lt;I, V&gt; {
 /** Value operation */
 @Resource(name = "stringRedisTemplate")
 private ValueOperations&lt;String, String&gt; 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&lt;V&gt; getValueClass();
}

@Repository
public class UserValue extends AbstractValue&lt;Long, UserDO&gt; {
 /** Get the primary key */
 @Override
 protected String getKey(Long id) {
 return ("Value:User:%s", id);
 }

 /** Get value class */
 @Override
 protected Class&lt;UserDO&gt; getValueClass() {
 return ;
 }
}

@Repository
public class RoleValue extends AbstractValue&lt;Long, RoleDO&gt; {
 /** Get the primary key */
 @Override
 protected String getKey(Long id) {
 return ("Value:Role:%s", id);
 }

 /** Get value class */
 @Override
 protected Class&lt;RoleDO&gt; 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&lt;T&gt; {
 /** Analyze data */
public T parseData(Record record);

 /** Store data */
public boolean storeData(List&lt;T&gt; dataList);
}

public &lt;T&gt; long executeFetch(String tableName, int batchSize, DataHandler&lt;T&gt; 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&lt;T&gt; dataList = new ArrayList&lt;&gt;(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&lt;T&gt; dataList) {
 (dataList);
 return true;
 }
});

streamline:

public &lt;T&gt; long executeFetch(String tableName, int batchSize, Function&lt;Record, T&gt; dataParser, Function&lt;List&lt;T&gt;, Boolean&gt; 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&lt;T&gt; dataList = new ArrayList&lt;&gt;(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 -&gt; {
 UserDO user = new UserDO();
 (("id"));
 (("name"));
 return user;
 }, dataList -&gt; {
 (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&lt;?&gt; queryUser(@RequestBody @Valid UserQueryVO query) {
 try {
  PageDataVO&lt;UserVO&gt; 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&lt;PageDataVO&lt;UserVO&gt;&gt; queryUser(@RequestBody @Valid UserQueryVO query) {
 PageDataVO&lt;UserVO&gt; pageData = (query);
 return (pageData);
 }
 ...
}

@Slf4j
@ControllerAdvice
public class GlobalControllerAdvice {
 /** Exception handling */
 @ResponseBody
 @ExceptionHandler()
 public Result&lt;Void&gt; 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&lt;Void&gt; 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!