一、Map使用entrySet迭代输出

当我们需要输出Map的键和值时,使用entrySet迭代输出才是更高效的用法,比迭代keySet后再去通过get取值性能更好。

package day02;

import java.util.HashMap;
import java.util.Map;

/**
 * @author qx
 * @date 2023/9/12
 * @des
 */
public class Test {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("name", "qx");
        map.put("address", "广州");
        // entrySet方式 推荐使用
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        // keySet方式
        for (String s : map.keySet()) {
            System.out.println(map.get(s));
        }
    }
}

二、MyBatis不要加上查询条件where 1=1

遇到多个查询条件时,使用where1=1可以方便的解决我们的问题,但是这样很可能会造成非常大的性能损失,因为添加了where 1=1的过滤条件之后,数据库系统就无法使用索引等查询优化策略,数据库系统将会被迫对每行数据进行全表扫描来比较数据是否满足过滤条件,当表中的数据量很大的时候查询速度就会非常慢,此外还会存在SQL注入的风险。

错误的写法:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springbootdemo.mybatis.DeviceDao">
    <select id="findAll" parameterType="com.example.springbootdemo.bean.Device"
            resultType="com.example.springbootdemo.bean.Device">
        select * from t_device where 1=1
        <if test="name!=null and name!=''">
            and name=#{name}
        </if>
    </select>
</mapper>

正确的写法:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springbootdemo.mybatis.DeviceDao">
    <select id="findAll" parameterType="com.example.springbootdemo.bean.Device"
            resultType="com.example.springbootdemo.bean.Device">
        select * from t_device
        <where>
            <if test="name!=null and name!=''">
                and name=#{name}
            </if>
        </where>

    </select>
</mapper>

三、使用CollectionUtils.isEmpty对集合判断

使用CollectionUtils.isEmpty实现对集合的判空,时间复杂度不高,不需要多次循环遍历。

@Test
    void contextLoads() {
        Device device = new Device();
        List<Device> deviceList = deviceDao.findAll(device);
        if (!CollectionUtils.isEmpty(deviceList)) {
            System.out.println(deviceList);
        }
    }

四、初始化集合指定长度

尽量在初始化集合时指定集合的长度,能有效减少集合的扩容次数,因为集合每次扩容的时间复杂度很可能是O(n),耗费时间和性能。

@Test
    void contextLoads() {
        int[] arr = {1, 2, 3, 4};
        // 指定集合的长度
        List<Integer> list = new ArrayList<>(arr.length);
        for (int temp : arr) {
            list.add(temp);
        }
        // 输出 [1, 2, 3, 4]
        System.out.println(list);
    }

五、使用StringBuilder或StringBuffer拼接字符串

一般的字符串拼接在编译期会对其进行优化,但是在循环中字符串的拼接Java编译期无法执行优化,所以需要使用StringBuilder或StringBuffer进行替换。

StringBuilder:线程不安全,效率高

StringBuffer:线程安全,效率低

所以我们平时在开发中,如果是单线程操作的话使用StringBuilder即可,反之使用StringBuffer。

@Test
    void contextLoads() {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 100; i++) {
            builder.append(i).append("、");
        }
        System.out.println(builder);
    }

六、频繁调用Collection.contains方法建议使用Set

在Java集合中List的contains方法普遍时间复杂度为O(n),如果代码中需要频繁调用contains方法查询数据,那么需要先将集合list转换成Set实现,时间复杂度变为O(1)。

List的contains用法:

@Test
    void contextLoads() {
        long start = System.currentTimeMillis();
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i <= 10000000; i++) {
            if (list.contains(i)) {
                System.out.println("contain");
            }
        }
        long end = System.currentTimeMillis();
        // 输出530
        System.out.println(end - start);
    }

Set的contains用法:

@Test
    void contextLoads() {
        long start = System.currentTimeMillis();
        //List<Integer> list = new ArrayList<>();
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i <= 10000000; i++) {
            if (set.contains(i)) {
                System.out.println("contain");
            }
        }
        long end = System.currentTimeMillis();
        // 输出205
        System.out.println(end - start);
    }

七、使用静态代码块实现静态成员变量的赋值

对于是静态的集合变量,应该使用静态代码块赋值,而不是使用集合实现赋值。

private static List<Integer> list = new ArrayList<>();

    static {
        list.add(1);
        list.add(2);
        list.add(3);
    }

八、工具类屏蔽构造函数

工具类是一堆静态字段和函数的集合,不应该被实例化,但是Java为每个没有明确定义构造函数的类添加了一个隐式公有构造函数,为了避免不必要的实例化,我们应该显式定义私有构造函数来屏蔽整个隐式公有构造函数。

package com.example.springbootdemo.utils;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author qx
 * @date 2023/9/12
 * @des 日期工具类
 */
public class DateUtil {
    private DateUtil() {
    }

    public static String formatDate(Date date, String pattern) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
        return simpleDateFormat.format(date);
    }

}

九、删除多余的异常捕获并抛出

用catch语句捕获异常后,如果不进行处理,就只能让异常重新抛出,这跟不捕获异常的效果一样,可以删除这块代码或在方法上抛出异常。

错误写法:

private static String fileWrite(String content) {
        File file = new File("D:" + File.separator + "demo.txt");
        try {
            OutputStream outputStream = new FileOutputStream(file);
            outputStream.write(content.getBytes());
        } catch (FileNotFoundException e) {
            // 不处理 再次抛出
            throw new RuntimeException(e);
        } catch (IOException e) {
            // 不处理 再次抛出
            throw new RuntimeException(e);
        }
        return null;
    }

正确的写法:

private static String fileWrite(String content) throws IOException {
        File file = new File("D:" + File.separator + "demo.txt");
        OutputStream outputStream = new FileOutputStream(file);
        outputStream.write(content.getBytes());
        return null;
    }

十、转换成字符串使用String.valueOf(value)代替其他方法

把其它对象或类型转化为字符串时,使用String.valueOf(value) 比 ""+value 的效率更高。

public static void main(String[] args) throws IOException {
        int num = 100;
        String str1 = "" + num;

        // String.valueof效率更高
        String str2 = String.valueOf(num);

    }

十一、BigDecimal的正确初始化

BigDecimal(double)存在精度损失的风险,在精确计算的场景中需要使用BigDecimal.valueOf(double)的方法初始化BigDecimal。

package day02;

import java.math.BigDecimal;

/**
 * @author qx
 * @date 2023/9/12
 * @des
 */
public class Test {
    public static void main(String[] args) {
        BigDecimal bigDecimal1 = new BigDecimal(0.11);
        // 输出 0.11000000000000000055511151231257827021181583404541015625
        System.out.println(bigDecimal1);
        BigDecimal bigDecimal2 = BigDecimal.valueOf(0.11);
        // 输出 0.11
        System.out.println(bigDecimal2);
    }
}

十二、返回空数组和集合而非null

如果程序运行返回null,需要调用方强制检测null,否则就会抛出空指针异常,我们我们在程序返回数组或集合的时候,返回空数组或空集合,避免调用方因为没有判断null而抛出空指针异常的情况。

/**
     * 空数组形式
     *
     * @return
     */
    private static Integer[] getResults() {
        return new Integer[0];
    }

    /**
     * 空List形式
     *
     * @return
     */
    private static List<Integer> getResultList() {
        return Collections.emptyList();
    }

    /**
     * 空Map形式
     *
     * @return
     */
    private static Map<String, String> getResultMap() {
        return Collections.emptyMap();
    }

十三、优先使用常量或确定值调用equals 方法

对象的equals方法容易抛出空指针异常,应该使用常量或确定有值的对象来调用equals方法。

public static void main(String[] args) {
        String str = null;
        // 空对象调用会报错
        System.out.println(str.equals("aa"));

        // 常量调用不会报错
        System.out.println("aa".equals(str));
    }

十四、枚举的属性字段必须是私有且不可变

枚举通常被当作常量使用,如果枚举中存在公共属性字段或设置字段方法,那么这些枚举常量的属性很容易被修改,理想情况下,枚举中的属性字段都是私有的,并在私有构造函数中赋值,没有对应的Setter方法,最好用final修饰属性字段。

package com.example.springbootdemo.enums;

/**
 * @author qx
 * @date 2023/9/12
 * @des 
 */
public enum SportEnum {

    FOOTBALL("足球", 1),
    BASKETBALL("篮球", 2);

    // final修饰属性
    public final String name;

    private final int code;

    // 私有构造函数
    private SportEnum(String name, int code) {
        this.name = name;
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public int getCode() {
        return code;
    }
}

十五、String.split(regex)部分关键字需要转义

使用字符串split方法时,传入的分隔符字符串是正则表达式,则部分关键字比如(.[]()\|)需要转义。

public static void main(String[] args) {
        String str = "a.ab.abc";
        String[] arr = str.split(".");
        // 输出[]
        System.out.println(Arrays.toString(arr));

        //需要转义
        String[] arr1 = str.split("\\.");
        // 输出[a, ab, abc]
        System.out.println(Arrays.toString(arr1));
    }