Java 代碼精簡之道 (中)

1.利用自身方法

1.1.利用構造方法編程

構造方法,能夠簡化對象的初始化和設置屬性操做。對於屬性字段較少的類,能夠自定義構造方法。json

普通:數組

@Getter
@Setter
@ToString
public class PageDataVO<T{
    private Long totalCount;
    private List<T> dataList;
}

PageDataVO<UserVO> pageData = new PageDataVO<>();
pageData.setTotalCount(totalCount);
pageData.setDataList(userList);
return pageData;

精簡:安全

@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class PageDataVO<T{
    private Long totalCount;
    private List<T> dataList;
}
return new PageDataVO<>(totalCount, userList);

注意:若是屬性字段被替換時,存在構造函數初始化賦值問題。好比把屬性字段title替換爲 nickname ,因爲構造函數的參數個數和類型不變,原有構造函數初始化語句不會報錯,致使把原title值賦值給 nickname 。若是採用 Setter 方法賦值,編譯器會提示錯誤並要求修復。數據結構

1.2.利用 Set 的 add 方法

利用 Set 的 add 方法的返回值,能夠直接知道該值是否已經存在,能夠避免調用 contains 方法判斷存在。app

普通:ide

如下案例是進行用戶去重轉化操做,須要先調用 contains 方法判斷存在,後調用add方法進行添加。函數


Set<Long> userIdSet = new HashSet<>();
List<UserVO> userVOList = new ArrayList<>();
for (UserDO userDO : userDOList) {
    if (!userIdSet.contains(userDO.getId())) {
        userIdSet.add(userDO.getId());
        userVOList.add(transUser(userDO));
    }
}

精簡:
工具

Set<Long> userIdSet = new HashSet<>();
List<UserVO> userVOList = new ArrayList<>();
for (UserDO userDO : userDOList) {
    if (userIdSet.add(userDO.getId())) {
        userVOList.add(transUser(userDO));
    }
}


1.3.利用 Map 的 computeIfAbsent 方法

利用 Map 的 computeIfAbsent 方法,能夠保證獲取到的對象非空,從而避免了沒必要要的空判斷和從新設置值。性能

普通:

Map<Long, List<UserDO>> roleUserMap = new HashMap<>();
for (UserDO userDO : userDOList) {
    Long roleId = userDO.getRoleId();
    List<UserDO> userList = roleUserMap.get(roleId);
    if (Objects.isNull(userList)) {
        userList = new ArrayList<>();
        roleUserMap.put(roleId, userList);
    }
    userList.add(userDO);
}

精簡:

Map<Long, List<UserDO>> roleUserMap = new HashMap<>();
for (UserDO userDO : userDOList) {
    roleUserMap.computeIfAbsent(userDO.getRoleId(), key -> new ArrayList<>())
        .add(userDO);
}


1.4.利用鏈式編程

鏈式編程,也叫級聯式編程,調用對象的函數時返回一個this對象指向對象自己,達到鏈式效果,能夠級聯調用。鏈式編程的優勢是:編程性強、可讀性強、代碼簡潔。

普通

StringBuilder builder = new StringBuilder(96);
builder.append("select id, name from ");
builder.append(T_USER);
builder.append(" where id = ");
builder.append(userId);
builder.append(";");

精簡:

StringBuilder builder = new StringBuilder(96);
builder.append("select id, name from ")
    .append(T_USER)
    .append(" where id = ")
    .append(userId)
    .append(";");



2.利用工具方法

2.1.避免空值判斷

普通:

if (userList != null && !userList.isEmpty()) {
    // TODO: 處理代碼
}

精簡:

if (CollectionUtils.isNotEmpty(userList)) {
    // TODO: 處理代碼
}


2.2.避免條件判斷

普通:

double result;
if (value <= MIN_LIMIT) {
    result = MIN_LIMIT;
else {
    result = value;
}

精簡:

double result = Math.max(MIN_LIMIT, value);


3.3.簡化賦值語句

普通:

public static final List<String> ANIMAL_LIST;
static {
    List<String> animalList = new ArrayList<>();
    animalList.add("dog");
    animalList.add("cat");
    animalList.add("tiger");
    ANIMAL_LIST = Collections.unmodifiableList(animalList);
}

精簡:

// JDK流派
public static final List<String> ANIMAL_LIST = Arrays.asList("dog""cat""tiger");
// Guava流派
public static final List<String> ANIMAL_LIST = ImmutableList.of("dog""cat""tiger");

注意:Arrays.asList 返回的 List 並非 ArrayList ,不支持 add 等變動操做。



2.4.簡化數據拷貝

普通:

UserVO userVO = new UserVO();
userVO.setId(userDO.getId());
userVO.setName(userDO.getName());
...
userVO.setDescription(userDO.getDescription());
userVOList.add(userVO);

精簡:

UserVO userVO = new UserVO();
BeanUtils.copyProperties(userDO, userVO);
userVOList.add(userVO);


2.5.簡化異常斷言

普通:

if (Objects.isNull(userId)) {
    throw new IllegalArgumentException("用戶標識不能爲空");
}

精簡:

Assert.notNull(userId, "用戶標識不能爲空");

注意:可能有些插件不認同這種判斷,致使使用該對象時會有空指針警告。


2.6.簡化測試用例

把測試用例數據以 JSON 格式存入文件中,經過 JSON 的 parseObject 和 parseArray 方法解析成對象。雖然執行效率上有所降低,但能夠減小大量的賦值語句,從而精簡了測試代碼。

普通:

@Test
public void testCreateUser() {
    UserCreateVO userCreate = new UserCreateVO();
    userCreate.setName("Changyi");
    userCreate.setTitle("Developer");
    userCreate.setCompany("AMAP");
    ...
    Long userId  = userService.createUser(OPERATOR, userCreate);
    Assert.assertNotNull(userId, "建立用戶失敗");
}

精簡:

@Test
public void testCreateUser() {
    String jsonText = ResourceHelper.getResourceAsString(getClass(), "createUser.json");
    UserCreateVO userCreate = JSON.parseObject(jsonText, UserCreateVO.class);
    Long userId  = userService.createUser(OPERATOR, userCreate);
    Assert.assertNotNull(userId, "建立用戶失敗");
}

建議:JSON 文件名最好以被測試的方法命名,若是有多個版本能夠用數字後綴表示。


3.利用數據結構

3.1.利用數組簡化

對於固定上下限範圍的 if-else 語句,能夠用數組+循環來簡化。

普通:

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;
}

精簡:

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;
}

思考:上面的案例返回值是遞增的,因此用數組簡化是沒有問題的。可是,若是返回值不是遞增的,可否用數組進行簡化呢?答案是能夠的,請自行思考解決。

3.2.利用 Map 簡化

對於映射關係的 if-else 語句,能夠用Map來簡化。此外,此規則一樣適用於簡化映射關係的 switch 語句。

普通:

public static String getBiologyClass(String name) {
    switch (name) {
        case "dog" :
            return "animal";
        case "cat" :
            return "animal";
        case "lavender" :
            return "plant";
        ...
        default :
            return null;
    }
}

精簡:

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);
}

已經把方法簡化爲一行代碼,其實都沒有封裝方法的必要了。


3.3.利用容器類簡化

Java 不像 Python 和 Go ,方法不支持返回多個對象。若是須要返回多個對象,就必須自定義類,或者利用容器類。常見的容器類有 Apache 的 Pair 類和 Triple 類, Pair 類支持返回 2 個對象, Triple 類支持返回 3 個對象

普通:

@Setter
@Getter
@ToString
@AllArgsConstructor
public static class PointAndDistance {
    private Point point;
    private Double distance;
}

public static PointAndDistance getNearest(Point point, Point[] points) {
    // 計算最近點和距離
    ...

    // 返回最近點和距離
    return new PointAndDistance(nearestPoint, nearestDistance);
}

精簡:

public static Pair<Point, Double> getNearest(Point point, Point[] points) {
    // 計算最近點和距離
    ...

    // 返回最近點和距離
    return ImmutablePair.of(nearestPoint, nearestDistance);
}


3.4.利用 ThreadLocal 簡化

ThreadLocal 提供了線程專有對象,能夠在整個線程生命週期中隨時取用,極大地方便了一些邏輯的實現。用 ThreadLocal 保存線程上下文對象,能夠避免沒必要要的參數傳遞。

普通:

因爲 DateFormat 的 format 方法線程非安全(建議使用替代方法),在線程中頻繁初始化 DateFormat 性能過低,若是考慮重用只能用參數傳入 DateFormat 。例子以下:

public static String formatDate(Date date, DateFormat format) {
    return format.format(date);
}

public static List<String> getDateList(Date minDate, Date maxDate, DateFormat format) {
    List<String> dateList = new ArrayList<>();
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(minDate);
    String currDate = formatDate(calendar.getTime(), format);
    String maxsDate = formatDate(maxDate, format);
    while (currDate.compareTo(maxsDate) <= 0) {
        dateList.add(currDate);
        calendar.add(Calendar.DATE, 1);
        currDate = formatDate(calendar.getTime(), format);
    }
    return dateList;
}
精簡:

可能你會以爲如下的代碼量反而多了,若是調用工具方法的地方比較多,就能夠省下一大堆 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 = Calendar.getInstance();
    calendar.setTime(minDate);
    String currDate = formatDate(calendar.getTime());
    String maxsDate = formatDate(maxDate);
    while (currDate.compareTo(maxsDate) <= 0) {
        dateList.add(currDate);
        calendar.add(Calendar.DATE, 1);
        currDate = formatDate(calendar.getTime());
    }
    return dateList;
}

注意:ThreadLocal 有必定的內存泄露的風險,儘可能在業務代碼結束前調用 remove 方法進行數據清除。

相關文章
相關標籤/搜索