There are only two qualities in the world: efficiency and inefficiency, and only two sorts of people: the efficient and the inefficient. -- Bernard Shaw
I would like to rephrase this quote of Bernard Shaw as follows: There are only two kinds of code: efficient code and inefficient code, and there are only two kinds of programmers in the world: programmers who write efficient code and programmers who write inefficient code. Every R&D team has to find a way to write efficient code. This article provides 50 efficient code samples based on the author's actual experience and extensive reading of reference materials. I hope this will enable all Java programmers to write efficient code.
When you directly assign a constant value, you only create an object reference that points to the constant value.
Bad code:
Long i = new Long(1L);
String s = new String("abc");
Good code:
Long i = 1L;
String s = "abc";
In each object instance of a class, each member variable has a copy, and each member static constant has only one instance.
Bad code:
public class HttpConnection {
private final long timeout = 5L;
...
}
Good code:
public class HttpConnection {
private static final long TIMEOUT = 5L;
...
}
The elementary data types in Java are double, float, long, int, short, char, and boolean, which correspond to the packing classes Double, Float, Long, Integer, Short, Character, and Boolean, respectively. Java virtual machines (JVMs) support automatic conversion between elementary data types and packing classes, which is called automatic packing and unpacking. Packing and unpacking consume CPU and memory resources, so you should avoid automatic packing and unpacking as much as possible.
Bad code:
Integer sum = 0;
int[] values = ...;
for (int value : values) {
sum += value; // Equivalent to result = Integer.valueOf(result.intValue() + value);
}
Good code:
int sum = 0;
int[] values = ...;
for (int value : values) {
sum += value;
}
Bad code:
List<UserDO> userList = new ArrayList<>();
if (isAll) {
userList = userDAO.queryAll();
} else {
userList = userDAO.queryActive();
}
Good code:
List<UserDO> userList;
if (isAll) {
userList = userDAO.queryAll();
} else {
userList = userDAO.queryActive();
}
Parameters and temporary variables of the elementary type in a function are stored in stacks and easy to access. For parameters and temporary variables of the object type, related references are stored in stacks, but content is stored in heaps. This lowers the access speed. All types of member variables in a class are stored in heaps and access is slow.
Bad code:
public final class Accumulator {
private double result = 0.0D;
public void addAll(@NonNull double[] values) {
for(double value : values) {
result += value;
}
}
...
}
Good code:
public final class Accumulator {
private double result = 0.0D;
public void addAll(@NonNull double[] values) {
double sum = 0.0D;
for(double value : values) {
sum += value;
}
result += sum;
}
...
}
Earlier versions of the Java Development Kit (JDK) do not provide good support for defining variables outside a loop. However, later JDK versions have improved in this respect. An analysis of compiled bytecode indicates that code with intra-loop defined variables and code with extra-loop defined variables have the same runtime efficiency. According to the principle of minimizing the scope of local variables, intra-loop defined variables can avoid delayed recycling due to the prolonged lifecycle of large objects.
Bad code:
UserVO userVO;
List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
userVO = new UserVO();
userVO.setId(userDO.getId());
...
userVOList.add(userVO);
}
Good code:
List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
UserVO userVO = new UserVO();
userVO.setId(userDO.getId());
...
userVOList.add(userVO);
}
Non-thread security classes are applicable to unchanged static constants with support for multithread access.
Bad code:
public static final Map<String, Class> CLASS_MAP;
static {
Map<String, Class> classMap = new ConcurrentHashMap<>(16);
classMap.put("VARCHAR", java.lang.String.class);
...
CLASS_MAP = Collections.unmodifiableMap(classMap);
}
Good code:
public static final Map<String, Class> CLASS_MAP;
static {
Map<String, Class> classMap = new HashMap<>(16);
classMap.put("VARCHAR", java.lang.String.class);
...
CLASS_MAP = Collections.unmodifiableMap(classMap);
}
Non-thread security classes are applicable to unchanged member variables with support for multithread access.
Bad code:
@Service
public class StrategyFactory implements InitializingBean {
@Autowired
private List<Strategy> strategyList;
private Map<String, Strategy> strategyMap;
@Override
public void afterPropertiesSet() {
if (CollectionUtils.isNotEmpty(strategyList)) {
int size = (int) Math.ceil(strategyList.size() * 4.0 / 3);
Map<String, Strategy> map = new ConcurrentHashMap<>(size);
for (Strategy strategy : strategyList) {
map.put(strategy.getType(), strategy);
}
strategyMap = Collections.unmodifiableMap(map);
}
}
...
}
Good code:
@Service
public class StrategyFactory implements InitializingBean {
@Autowired
private List<Strategy> strategyList;
private Map<String, Strategy> strategyMap;
@Override
public void afterPropertiesSet() {
if (CollectionUtils.isNotEmpty(strategyList)) {
int size = (int) Math.ceil(strategyList.size() * 4.0 / 3);
Map<String, Strategy> map = new HashMap<>(size);
for (Strategy strategy : strategyList) {
map.put(strategy.getType(), strategy);
}
strategyMap = Collections.unmodifiableMap(map);
}
}
...
}
JSON can convert between objects and JSON strings, so it may be used by some programmers to convert objects. Such object conversion works, but results in low performance.
Bad code:
List<UserDO> userDOList = ...;
List<UserVO> userVOList = JSON.parseArray(JSON.toJSONString(userDOList), UserVO.class);
Good code:
List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
UserVO userVO = new UserVO();
userVO.setId(userDO.getId());
...
userVOList.add(userVO);
}
You may write less code when using reflection to assign values to objects, but this lowers the code performance.
Bad code:
List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
UserVO userVO = new UserVO();
BeanUtils.copyProperties(userDO, userVO);
userVOList.add(userVO);
}
Good code:
List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
UserVO userVO = new UserVO();
userVO.setId(userDO.getId());
...
userVOList.add(userVO);
}
Many novice users of JDK 8 may consider lambda expressions to be the syntax of internal anonymous classes. In fact, lambda expressions are implemented by the invokeDynamic
instruction on many virtual machines, so they are more efficient than internal anonymous classes.
Bad code:
List<User> userList = ...;
Collections.sort(userList, new Comparator<User>() {
@Override
public int compare(User user1, User user2) {
Long userId1 = user1.getId();
Long userId2 = user2.getId();
...
return userId1.compareTo(userId2);
}
});
Good code:
List<User> userList = ...;
Collections.sort(userList, (user1, user2) -> {
Long userId1 = user1.getId();
Long userId2 = user2.getId();
...
return userId1.compareTo(userId2);
});
Additional class loading is required once a class is added.
Bad code:
public static final Map<String, Class> CLASS_MAP =
Collections.unmodifiableMap(new HashMap<String, Class>(16) {
private static final long serialVersionUID = 1L;
{
put("VARCHAR", java.lang.String.class);
}
});
Good code:
public static final Map<String, Class> CLASS_MAP;
static {
Map<String, Class> classMap = new HashMap<>(16);
classMap.put("VARCHAR", java.lang.String.class);
...
CLASS_MAP = Collections.unmodifiableMap(classMap);
}
You can specify the final modifier for a class so that the class cannot be inherited. If a class is specified to be final, all methods of the class are final methods, and the Java compiler can inline all final methods. Inlining greatly improves Java runtime efficiency. For more information, see Java runtime optimization. On average, this can improve performance by 50%.
Bad code:
public class DateHelper {
...
}
Good code:
public final class DateHelper {
...
}
Note: When you add a dynamic proxy to beans in Spring aspect-oriented programming (AOP), an exception may occur if the final modifier is specified for the bean class.
A static method can be directly called without creating a class instance. A static method does not belong to an object, but to the class where it is located. A static method can be accessed by using its class name, avoiding resource consumption due to repeated object creation. Declare a private method in a class as a static method even if the method does not use any class member variables.
Bad code:
public int getMonth(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar.get(Calendar.MONTH) + 1;
}
Good code:
public static int getMonth(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
return calendar.get(Calendar.MONTH) + 1;
}
Using the Elementary data type as the method parameter type can help you avoid unnecessary packing, unpacking, and null pointer judgment.
Bad code:
public static double sum(Double value1, Double value2) {
double double1 = Objects.isNull(value1) ? 0.0D : value1;
double double2 = Objects.isNull(value2) ? 0.0D : value2;
return double1 + double2;
}
double result = sum(1.0D, 2.0D);
Good code:
public static double sum(double value1, double value2) {
return value1 + value2;
}
double result = sum(1.0D, 2.0D);
Using the Elementary data type as the method return value type can help you avoid unnecessary packing, unpacking, and null pointer judgment. Many of the JDK class library methods use the elementary data type for return values. This avoids unnecessary packing and unpacking as well as null pointer judgment for return values. Examples: Collection.isEmpty()
and Map.size()
.
Bad code:
public static Boolean isValid(UserDO user) {
if (Objects.isNull(user)) {
return false;
}
return Boolean.TRUE.equals(user.getIsValid());
}
// Calls code.
UserDO user = ...;
Boolean isValid = isValid(user);
if (Objects.nonNull(isValid) && isValid.booleanValue()) {
...
}
Good code:
public static boolean isValid(UserDO user) {
if (Objects.isNull(user)) {
return false;
}
return Boolean.TRUE.equals(user.getIsValid());
}
// Calls code.
UserDO user = ...;
if (isValid(user)) {
...
}
Setting non-null parameter values for protocol methods can help you avoid unnecessary null pointer judgment. In protocol programming, @NonNull
and @Nullable
can be used to tag parameters. It is up to the caller to follow the tagging.
Bad code:
public static boolean isValid(UserDO user) {
if (Objects.isNull(user)) {
return false;
}
return Boolean.TRUE.equals(user.getIsValid());
}
Good code:
public static boolean isValid(@NonNull UserDO user) {
return Boolean.TRUE.equals(user.getIsValid());
}
Setting non-null return values for protocol methods can help you avoid unnecessary null pointer judgment. In protocol programming, @NonNull
and @Nullable
can be used to tag parameters. It is up to the implementer to follow the tagging.
Bad code:
// Defines an interface.
public interface OrderService {
public List<OrderVO> queryUserOrder(Long userId);
}
// Calls code.
List<OrderVO> orderList = orderService.queryUserOrder(userId);
if (CollectionUtils.isNotEmpty(orderList)) {
for (OrderVO order : orderList) {
...
}
}
Good code:
// Defines an interface.
public interface OrderService {
@NonNull
public List<OrderVO> queryUserOrder(Long userId);
}
// Calls code.
List<OrderVO> orderList = orderService.queryUserOrder(userId);
for (OrderVO order : orderList) {
...
}
Bad code:
UserDO user = null;
if (StringUtils.isNotBlank(value)) {
user = JSON.parseObject(value, UserDO.class);
}
Good code:
UserDO user = JSON.parseObject(value, UserDO.class);
Method calls may cause new data items to be pushed into or pulled from a stack, which consumes CPU and memory resources. Therefore, avoid unnecessary function encapsulation. However, the performance loss is worthwhile if method calls are added to make code more concise and clearer.
Bad code:
// Encapsulates the function.
public static boolean isVip(Boolean isVip) {
return Boolean.TRUE.equals(isVip);
}
// Uses code.
boolean isVip = isVip(user.getVip());
Good code:
boolean isVip = Boolean.TRUE.equals(user.getVip());
You can prevent a method from being overwritten by specifying the final modifier for the method. The Java compiler may inline all final methods. Inlining greatly improves Java runtime efficiency. On average, this can improve performance by 50%.
Note: All private methods are implicitly specified with the final modifier, so you do not need to specify the final modifier for them.
Bad code:
public class Rectangle {
...
public double area() {
...
}
}
Good code:
public class Rectangle {
...
public final double area() {
...
}
}
Note: When you add a dynamic proxy to beans in Spring AOP, methods with the final modifier are not proxied.
Bad code:
List<UserDO> userList = ...;
for (int i = 0; i < userList.size(); i++) {
...
}
Good code:
List<UserDO> userList = ...;
int userLength = userList.size();
for (int i = 0; i < userLength; i++) {
...
}
Bad code:
List<UserDO> userList = userDAO.queryActive();
if (isAll) {
userList = userDAO.queryAll();
}
Good code:
List<UserDO> userList;
if (isAll) {
userList = userDAO.queryAll();
} else {
userList = userDAO.queryActive();
}
The shift operation can greatly improve performance. You can use the shift operation to calculate a positive integer multiplied or divided by 2^n
(n
is a positive integer).
Bad code:
int num1 = a * 4;
int num2 = a / 4;
Good code:
int num1 = a << 2;
int num2 = a >> 2;
Only one value is calculated and used repeatedly when a public expression is fetched.
Bad code:
double distance = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
Good code:
double dx = x2 - x1;
double dy = y2 - y1;
double distance = Math.sqrt(dx * dx + dy * dy);
Or
double distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
If !
is used in the non-operator position, one more calculation is performed. Do not use !
unless it is necessary.
Bad code:
if (!(a >= 10)) {
... // Conditional processing 1
} else {
... // Conditional processing 2
}
Good code:
if (a < 10) {
... // Conditional processing 1
} else {
... // Conditional processing 2
}
In an if-else statement, each if conditional statement performs calculation until the true if statement appears. A switch statement optimizes jumps and is executed by the tableswitch or lookupswitch instruction in Java. It can process multi-constant selection branches more efficiently than an if-else statement. According to experiments, an if-else statement is more efficient than a switch statement when processing less than five branches, whereas a switch statement is more efficient than an if-else statement when processing more than five branches, assuming that the occurrence probability of each branch is the same.
Bad code:
if (i == 1) {
... ; // Branch 1
} else if (i == 2) {
... ; // Branch 2
} else if (i == ...) {
... ; // Branch n
} else {
... ; // Branch n + 1
}
Good code:
switch (i) {
case 1 :
... // Branch 1
break;
case 2 :
... // Branch 2
break;
case ... :
... // Branch n
break;
default :
... // Branch n + 1
break;
}
Note: You can use Map to implement the strategy pattern for complex services.
Regular expression matching is inefficient. Use string matching instead.
Bad code:
String source = "a::1,b::2,c::3,d::4";
String target = source.replaceAll("::", "=");
Stringp[] targets = source.spit("::");
Good code:
String source = "a::1,b::2,c::3,d::4";
String target = source.replace("::", "=");
Stringp[] targets = StringUtils.split(source, "::");
Note: The StringUtils.split
function does not retain empty strings.
The string length is variable, whereas the character length is fixed to 1
, so it is more efficient to query and match using characters.
Bad code:
String source = "a:1,b:2,c:3,d:4";
int index = source.indexOf(":");
String target = source.replace(":", "=");
Good code:
String source = "a:1,b:2,c:3,d:4";
int index = source.indexOf(':');
String target = source.replace(':', '=');
Strings belong to the final class and their content cannot be modified. Therefore, an object is created once strings are concatenated. During initialization, StringBuilder applies for a memory where subsequent strings are concatenated. No additional memory is applied for and no objects are created during the concatenation process.
Bad code:
String s = "";
for (int i = 0; i < 10; i++) {
if (i != 0) {
s += ',';
}
s += i;
}
Good code:
StringBuilder sb = new StringBuilder(128);
for (int i = 0; i < 10; i++) {
if (i != 0) {
sb.append(',');
}
sb.append(i);
}
Using +
to convert strings is convenient but inefficient. Use String.valueOf
instead, which is much more efficient.
Bad code:
int i = 12345;
String s = "" + i;
Good code:
int i = 12345;
String s = String.valueOf(i);
The Pattern.compile
method has a high performance overhead and may be hidden in methods considered convenient, such as the String.matches
, String.replaceAll
, and String.split functions
. Example:
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
If you call these methods multiple times, you can precompile a regular expression to improve execution efficiency. You can also compile the optimized code based on the original code.
Bad code:
// The following code will be called N times.
String source = "a:1|b:2|c:3|d:4";
boolean isMatched = source.matches("^(\\w+:\\d+\\|)*(\\w+:\\d+)$"); // true
String target = source.replaceAll("(\\w+):(\\d+)", "$1=$2"); // a=1|b=2|c=3|d=4
String[] targets = source.split("\\|"); // ["a:1","b:2","c:3","d:4"]
Good code:
// Precompiles a regular expression.
final Pattern PATTERN1 = Pattern.compile("^(\\w+:\\d+\\|)*(\\w+:\\d+)$");
final Pattern PATTERN2 = Pattern.compile("(\\w+):(\\d+)");
final Pattern PATTERN3 = Pattern.compile("\\|");
// The following code will be called N times.
String source = "a:1|b:2|c:3|d:4";
boolean isMatched = PATTERN1.matcher(source).matches();
String target = PATTERN2.matcher(source).replaceAll("$1=$2");
String[] targets = PATTERN3.split(source);
System.arraycopy
Instead of Loops to Copy ArraysUse System.arraycopy
or Arrays.copyOf
to copy arrays.
Bad code:
int[] sources = new int[] {1, 2, 3, 4, 5};
int[] targets = new int[sources.length];
for (int i = 0; i < targets.length; i++) {
targets[i] = sources[i];
}
Good code:
int[] sources = new int[] {1, 2, 3, 4, 5};
int[] targets = new int[sources.length];
System.arraycopy(sources, 0, targets, 0, targets.length);
T[0]
You can convert a collection to an Array
by using toArray(new T[n])
or toArray(new T[0])
. In earlier Java versions, use toArray(new T[n])
because the reflection required to create an array is slow. In OpenJDK 6 and later versions with internal reflection, toArray(new T[0])
is more efficient and performs better than toArray(new T[n])
. toArray(new T[n])
gets the list size one more time than toArray(new T[0])
. toArray(new T[n])
is less efficient if it takes much time to calculate the list size.
Bad code:
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, ...);
Integer[] integers = integerList.toArray(new Integer[integerList.size()]);
Good code:
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, ...);
Integer[] integers = integerList.toArray(new Integer[0]); // Do not use new Integer[]{}.
Note: A toArray(Class\<T\> clazz)
method must be provided for a collection to avoid useless empty array initialization (new T[0])
.
toArray()
to Convert a Collection to an Object ArrayUse toArray()
instead of toArray[new Object[0]]
to convert a collection to an object array. toArray()
is more efficient because it avoids type judgment and empty array application.
Bad code:
List<Object> objectList = Arrays.asList(1, "2", 3, "4", 5, ...);
Object[] objects = objectList.toArray(new Object[0]);
Good code:
List<Object> objectList = Arrays.asList(1, "2", 3, "4", 5, ...);
Object[] objects = objectList.toArray();
When a Java collection is initialized, a default size is specified for this collection. Resizing is performed when the default size does not meet data requirements. The time complexity for the resize operation may be O(n)
. Specify the collection size if it is predictable to avoid resizing or reduce the number of resizes.
Bad code:
List<UserDO> userDOList = ...;
Set<Long> userSet = new HashSet<>();
Map<Long, UserDO> userMap = new HashMap<>();
List<UserVO> userList = new ArrayList<>();
for (UserDO userDO : userDOList) {
userSet.add(userDO.getId());
userMap.put(userDO.getId(), userDO);
userList.add(transUser(userDO));
}
Good code:
List<UserDO> userDOList = ...;
int userSize = userDOList.size();
Set<Long> userSet = new HashSet<>(userSize);
Map<Long, UserDO> userMap = new HashMap<>((int) Math.ceil(userSize * 4.0 / 3));
List<UserVO> userList = new ArrayList<>(userSize);
for (UserDO userDO : userDOList) {
userSet.add(userDO.getId());
userMap.put(userDO.getId(), userDO);
userList.add(transUser(userDO));
}
JDK provides methods to specify the collection capacity in one step, which saves time and space by avoiding repeated resizing. These methods are executed by calling System.arraycopy
and therefore support efficient batch data copy.
Bad code:
List<UserDO> user1List = ...;
List<UserDO> user2List = ...;
List<UserDO> userList = new ArrayList<>(user1List.size() + user2List.size());
for (UserDO user1 : user1List) {
userList.add(user1);
}
for (UserDO user2 : user2List) {
userList.add(user2);
}
Good code:
List<UserDO> user1List = ...;
List<UserDO> user2List = ...;
List<UserDO> userList = new ArrayList<>(user1List.size() + user2List.size());
userList.addAll(user1List);
userList.addAll(user2List);
Arrays.asList
to Convert an Array
to a List
For detail about why you should use Arrays.asList
to convert an Array
to a List
, see section 7.2 of this post. Consider the example code as well.
Bad code:
List<String> typeList = new ArrayList<>(8);
typeList.add("Short");
typeList.add("Integer");
typeList.add("Long");
String[] names = ...;
List<String> nameList = ...;
for (String name : names) {
nameList.add(name);
}
Good code:
List<String> typeList = Arrays.asList("Short", "Integer", "Long");
String[] names = ...;
List<String> nameList = ...;
nameList.addAll(Arrays.asList(names));
This operation allows you to get data directly. No other operations are required.
Bad code:
Map<Long, UserDO> userMap = ...;
for (Long userId : userMap.keySet()) {
UserDO user = userMap.get(userId);
...
}
Good code:
Map<Long, UserDO> userMap = ...;
for (Map.Entry<Long, UserDO> userEntry : userMap.entrySet()) {
Long userId = userEntry.getKey();
UserDO user = userEntry.getValue();
...
}
isEmpty
Instead of a Size Method to Detect Empty ValuesLogically, it is reasonable to detect empty values by using a size method, but you can also use an isEmpty
method to make your code more readable and perform better. The time complexity for implementing any isEmpty
method is invariably O(1)
, whereas the time complexity for implementing some size methods may be O(n)
.
Bad code:
List<UserDO> userList = ...;
if (userList.size() == 0) {
...
}
Map<Long, UserDO> userMap = ...;
if (userMap.size() == 0) {
...
}
Good code:
List<UserDO> userList = ...;
if (userList.isEmpty()) {
...
}
Map<Long, UserDO> userMap = ...;
if (userMap.isEmpty()) {
...
}
Lists can be classified into random-access lists and non-random-access lists. You can determine the type of a list based on whether the RandomAccess
interface is implemented. It is efficient to retrieve random-access lists by using the GET
method. However, it is inefficient to retrieve non-random-access lists by using the GET
method.
Bad code:
LinkedList<UserDO> userDOList = ...;
int size = userDOList.size();
for (int i = 0; i < size; i++) {
UserDO userDO = userDOList.get(i);
...
}
Good code:
LinkedList<UserDO> userDOList = ...;
for (UserDO userDO : userDOList) {
...
}
Use iteration to traverse random-access and non-random-access lists.
In Java collection libraries, the time complexity of the contains method for List
is typically O(n)
, whereas the time complexity of HashSet
is O(1)
. Convert List
to HashSet
when you need to frequently call the contains method to search data.
Bad code:
List<Long> adminIdList = ...;
List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
if (adminIdList.contains(userDO.getId())) {
userVOList.add(transUser(userDO));
}
}
Good code:
Set<Long> adminIdSet = ...;
List<UserDO> userDOList = ...;
List<UserVO> userVOList = new ArrayList<>(userDOList.size());
for (UserDO userDO : userDOList) {
if (adminIdSet.contains(userDO.getId())) {
userVOList.add(transUser(userDO));
}
}
You can directly retrieve data and determine whether the data is empty rather than determine whether the data exists before retrieving it. This avoids repeated searching.
Bad code:
public static UserVO transUser(UserDO user, Map<Long, RoleDO> roleMap) {
UserVO userVO = new UserVO();
userVO.setId(user.getId());
...
if (roleMap.contains(user.getRoleId())) {
RoleDO role = roleMap.get(user.getRoleId());
userVO.setRole(transRole(role));
}
}
Good code:
public static UserVO transUser(UserDO user, Map<Long, RoleDO> roleMap) {
UserVO userVO = new UserVO();
userVO.setId(user.getId());
...
RoleDO role = roleMap.get(user.getRoleId());
if (Objects.nonNull(role)) {
userVO.setRole(transRole(role));
}
}
Capture exceptions directly rather than using instanceof for judgment. This makes your code more concise and improves its runtime efficiency.
Bad code:
try {
saveData();
} catch (Exception e) {
if (e instanceof IOException) {
log.error("An I/O exception occurred while saving data.", e);
} else {
log.error("Other exceptions occurred while saving data.", e);
}
}
Good code:
try {
saveData();
} catch (IOException e) {
log.error("An I/O exception occurred while saving data.", e);
} catch (Exception e) {
log.error("Other exceptions occurred while saving data.", e);
}
If the loop body throws an exception and you do not need to repeat the loop, you do not need to capture the exception in the loop body. This is because too many exceptions may reduce the program execution efficiency.
Bad code:
public Double sum(List<String> valueList) {
double sum = 0.0D;
for (String value : valueList) {
try {
sum += Double.parseDouble(value);
} catch (NumberFormatException e) {
return null;
}
}
return sum;
}
Good code:
public Double sum(List<String> valueList) {
double sum = 0.0D;
try {
for (String value : valueList) {
sum += Double.parseDouble(value);
}
} catch (NumberFormatException e) {
return null;
}
return sum;
}
Compared with conditional expressions, exceptions are less efficient in business process control.
Bad code:
public static boolean isValid(UserDO user) {
try {
return Boolean.TRUE.equals(user.getIsValid());
} catch(NullPointerException e) {
return false;
}
}
Good code:
public static boolean isValid(UserDO user) {
if (Objects.isNull(user)) {
return false;
}
return Boolean.TRUE.equals(user.getIsValid());
}
During initialization, specify the expected buffer capacity to avoid wasting time and space due to repeated resizing.
Bad code:
StringBuffer buffer = new StringBuffer();
StringBuilder builder = new StringBuilder();
Good code:
StringBuffer buffer = new StringBuffer(1024);
StringBuilder builder = new StringBuilder(1024);
To maintain buffers, JVMs need to spend time on object creation and garbage collection. Therefore, reuse the same buffer if possible.
Bad code:
StringBuilder builder1 = new StringBuilder(128);
builder1.append("update t_user set name = '").append(userName).append("' where id = ").append(userId);
statement.executeUpdate(builder1.toString());
StringBuilder builder2 = new StringBuilder(128);
builder2.append("select id, name from t_user where id = ").append(userId);
ResultSet resultSet = statement.executeQuery(builder2.toString());
...
Good code:
StringBuilder builder = new StringBuilder(128);
builder.append("update t_user set name = '").append(userName).append("' where id = ").append(userId);
statement.executeUpdate(builder.toString());
builder.setLength(0);
builder.append("select id, name from t_user where id = ").append(userId);
ResultSet resultSet = statement.executeQuery(builder.toString());
...
You can use the setLength method to reset the buffer size to 0
.
Design buffer reuse to improve program runtime efficiency.
Bad code:
// Converts XML (UserDO).
public static String toXml(UserDO user) {
StringBuilder builder = new StringBuilder(128);
builder.append("<UserDO>");
builder.append(toXml(user.getId()));
builder.append(toXml(user.getName()));
builder.append(toXml(user.getRole()));
builder.append("</UserDO>");
return builder.toString();
}
// Converts XML (Long).
public static String toXml(Long value) {
StringBuilder builder = new StringBuilder(128);
builder.append("<Long>");
builder.append(value);
builder.append("</Long>");
return builder.toString();
}
...
// Uses code.
UserDO user = ...;
String xml = toXml(user);
Good code:
// Converts XML (UserDO).
public static void toXml(StringBuilder builder, UserDO user) {
builder.append("<UserDO>");
toXml(builder, user.getId());
toXml(builder, user.getName());
toXml(builder, user.getRole());
builder.append("</UserDO>");
}
// Converts XML (Long).
public static void toXml(StringBuilder builder, Long value) {
builder.append("<Long>");
builder.append(value);
builder.append("</Long>");
}
...
// Uses code.
UserDO user = ...;
StringBuilder builder = new StringBuilder(1024);
toXml(builder, user);
String xml = builder.toString();
Remove the buffer application in each conversion method and apply for a buffer for each conversion method. This reduces the time spent releasing buffer applications and saves temporary storage space in buffers.
Buffered streams such as BufferedReader
, BufferedWriter
, BufferedInputStream
, and BufferedOutputStream
can greatly reduce I/O occurrences and improve the I/O speed.
Bad code:
try (FileInputStream input = new FileInputStream("a");
FileOutputStream output = new FileOutputStream("b")) {
int size = 0;
byte[] temp = new byte[1024];
while ((size = input.read(temp)) != -1) {
output.write(temp, 0, size);
}
} catch (IOException e) {
log.error("An exception occurred while copying the file.", e);
}
Good code:
try (BufferedInputStream input = new BufferedInputStream(new FileInputStream("a"));
BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream("b"))) {
int size = 0;
byte[] temp = new byte[1024];
while ((size = input.read(temp)) != -1) {
output.write(temp, 0, size);
}
} catch (IOException e) {
log.error("An exception occurred while copying the file.", e);
}
You can manually specify the buffered stream size as needed to maximize the buffering effect of buffered streams.
Non-thread-safe classes can avoid unnecessary synchronization overhead.
Bad code:
StringBuffer buffer = new StringBuffer(128);
buffer.append("select * from ").append(T_USER).append(" where id = ?");
Good code:
StringBuilder buffer = new StringBuilder(128);
buffer.append("select * from ").append(T_USER).append(" where id = ?");
Use thread-safe classes rather than synchronize code to make your code more concise and efficient.
Bad code:
private volatile int counter = 0;
public void access(Long userId) {
synchronized (this) {
counter++;
}
...
}
Good code:
private final AtomicInteger counter = new AtomicInteger(0);
public void access(Long userId) {
counter.incrementAndGet();
...
}
In a method, only a small portion of the logic may require synchronous control. The code efficiency may be affected if the entire method is subject to synchronous control. Therefore, minimize the scope of synchronized blocks and only synchronize the target code.
Bad code:
private volatile int counter = 0;
public synchronized void access(Long userId) {
counter++;
... // Non-synchronous operation
}
Good code:
private volatile int counter = 0;
public void access(Long userId) {
synchronized (this) {
counter++;
}
... // Non-synchronous operation
}
Synchronized blocks incur performance overhead. Merge multiple synchronized blocks into one if possible.
Bad code:
// Processes a single order.
public synchronized handleOrder(OrderDO order) {
...
}
// Processes all orders.
public void handleOrder(List<OrderDO> orderList) {
for (OrderDO order : orderList) {
handleOrder(order);
}
}
Good code:
// Processes a single order.
public handleOrder(OrderDO order) {
...
}
// Processes all orders.
public synchronized void handleOrder(List<OrderDO> orderList) {
for (OrderDO order : orderList) {
handleOrder(order);
}
}
Overhead is incurred in multithreading when a thread is created and when the context is switched. Such overhead can be avoided by using a thread pool.
Bad code:
public void executeTask(Runnable runnable) {
new Thread(runnable).start();
}
Good code:
private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(10);
public void executeTask(Runnable runnable) {
executorService.execute(runnable);
}
The author is an IT engineer who has been working on the frontlines of programming for many years at Alibaba. He has an interest in programming techniques that solve practical problems.
For more information about Java programming, see Java Programming Techniques - Data Structures.
Are you eager to know the latest tech trends in Alibaba Cloud? Hear it from our top experts in our newly launched series, Tech Show!
Alibaba Clouder - May 10, 2021
Alibaba Cloud Community - September 13, 2021
JacksonTian - November 27, 2020
OpenAnolis - July 8, 2022
OpenAnolis - April 11, 2022
Lee Li - January 19, 2021
A low-code development platform to make work easier
Learn MoreA Big Data service that uses Apache Hadoop and Spark to process and analyze data
Learn MoreElastic and secure virtual cloud servers to cater all your cloud hosting needs.
Learn MoreAn elastic and horizontally scalable high-performance computing service providing the same computing performance as traditional physical servers including physical isolation.
Learn More