# 后端开发技巧、常用规范

后端开发技巧、常用规范

开发技巧

equals() 方法的使用
  • null.equals()会出报空指针,因该是非null的值.equals()
  • 可以使用Objectsequals()方法避免空值,完美
String strOne = null;
String strTwo = null;
boolean oneFlag = Objects.equals(strOne, strTwo);
  • 忽略大小写:equalsIgnoreCase()

创建 HashMap 指定初始化大小
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
  • initialCapacity = (需要存储的元素个数/负载因子)+1。负载因子默认为0.75

  • 当不指定HashMap的大小会发生多次扩容会影响效率。比如map中有1000个元素,至少要将容量设置为1000/0.75=1333+1=1334,如果不设置大小那么就会多次扩容,比较影响效率。

  • 当无法估计大小的时候,请设置为HashMap的默认大小16


Optional.ofNullable().orElse() 避免空指针

通过Optional.ofNullable().orElse()避免空指针,例如遍历从map中拿到的某个list,原始代码如下:

Map<String,Object> map = new HashMap<>(16);
map.put("list",null);

List<Map<String ,Object>> list = (List<Map<String, Object>>) map.get("list");
// list 会报控指针
for (Map<String, Object> item : list) {
    logger.info(String.valueOf(item));
}
  • 使用Optional 完美解决
Map<String,Object> map = new HashMap<>(16);
map.put("list",null);

List<Map<String ,Object>> list = Optional.ofNullable((List < Map < String, Object >> ) map.get("list")).orElse(new ArrayList<>());
for (Map<String, Object> item : list) {
    logger.info(String.valueOf(item));
}
  • 使用Optional选择默认数据
User user1 = new User(1, "张三", "西安");
User user2 = new User(2, "李四", "新疆");

user1 = null;
User user = Optional.ofNullable(user1).orElseGet(() -> user2);
logger.info(user.toString());

Stream 求和
  • 使用 Stream流遍历集合中的元素进行求和操作
List<Double> doubleList = Arrays.asList(12.2, 13.45, 12.4);
double sumOne = doubleList.stream().mapToDouble(x -> x).sum();
logger.info("sumOne:{}", sumOne);

List<BigDecimal> decimalList = new ArrayList<>();
decimalList.add(new BigDecimal("12.3"));
decimalList.add(new BigDecimal("12.3"));
decimalList.add(new BigDecimal("12.3"));
BigDecimal bigDecimal = decimalList.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
logger.info("bigDecimal:{}", bigDecimal.floatValue());
logger.info("bigDecimal:{}", bigDecimal.doubleValue());

List<Object> objectList = Arrays.asList(12, 13, 1);
int sum = objectList.stream().mapToInt(x -> (int) x).sum();
logger.info("sum:{}", sum);

List 切割工具
  • Lists.partition()
for (List<DataDto> dataDtos : Lists.partition(list, 1000)) {
    dataDtoMapper.insertBatch(dataDtos);
}
  • 依赖
<dependency>
    <groupId>com.google.guavagroupId>
    <artifactId>guavaartifactId>
    <version>31.0.1-jreversion>
    <exclusions>
        <exclusion>
            <groupId>log4jgroupId>
            <artifactId>log4jartifactId>
        exclusion>
    exclusions>
dependency>

单例类或者工具类 添加私有构造方法
  • 工具类或者单例类提供私有的构造函数,没有过多的功能,提供私有构造函数防止外部使用污染。

类变量(集合)应该在使用完成之后清空,避免内存泄漏
  • 应该及时销毁没用的对象,如果对象过大,虚拟机没办法垃圾回收的时候,有可能造成内存泄漏,所以使用完就清空.
private static final Map<String, Object> TABLE_COLUMN_MAP = new HashMap<>();

// 清空集合
TABLE_COLUMN_MAP.clear();

使用 ThreaadLocal 后需要释放资源防止内存泄漏
private ThreadLocal<Map<String, String>> threadLocalMap = new ThreadLocal<>();

@Test
public void test1() {
    Map<String, String> map = new HashMap<>();
    map.put("test1", "张三");
    map.put("test2", "李四");
    threadLocalMap.set(map);
    getThreadLocalMap();
    threadLocalMap.remove();
}

巧妙使用设计模式
  • 巧妙使用接口、抽象类

  • Java 设计模式请看:https://blog.csdn.net/qq_37248504/article/details/117753021


Spring 工厂模式的结合使用
  • Java 工厂模式请看:https://blog.csdn.net/qq_37248504/article/details/117753021
  • 使用Spring 收集Bean对象放到一个Map中,从这个Map中拿到相应的对象的实例,直接上代码
  • 接口 CustomerService.java
public interface CustomerService {

    /**
     * 获取用户姓名
     * @return
     */
    String getUserName();

    /**
     * 注册
     */
    void registered();

    /**
     * 登录
     */
    void login();
}

  • 接口的实现类:需要收集的对象实例可以有很多个实例
@Service
public class CustomerServiceOneImpl implements CustomerService {


    @Override
    public String getUserName() {
        return "CustomerOne";
    }

    @Override
    public void registered() {

    }

    @Override
    public void login() {

    }
}

@Service
public class CustomerServiceTwoImpl implements CustomerService {


    @Override
    public String getUserName() {
        return "CustomerTwo";
    }

    @Override
    public void registered() {

    }

    @Override
    public void login() {

    }
}
  • 工厂方法:可以使用set注入、或者构造函数方式、或者从当前上下文中拿到接口的实例对象
@Component
public class CustomerFactory {

    /**
     * 对象 Map
     */
    private static final Map<String, CustomerService> OBJECT_MAP = new HashMap<>();


    /**
     * set 方法注入
     *
     * @param customerService
     */
    @Autowired
    private void setObjectMap(List<CustomerService> customerService) {
        for (CustomerService service : customerService) {
            OBJECT_MAP.put(service.getUserName(), service);
        }
    }

    /**
     * 获取 CustomerService 的实例
     *
     * @param type
     * @return
     */
    public static CustomerService getInstance(String type) {
        return OBJECT_MAP.get(type);
    }

}

  • 测试
/**
  * Spring 工厂模式测试
  */
@Override
public void testFive() {
    CustomerFactory.getInstance("CustomerOne").registered();
    CustomerFactory.getInstance("CustomerTwo").registered();
}
  • 完整代码请看:https://gitee.com/Marlon_Brando/back/commit/d9ad24564dfe1459ec9e0728822b709f1ea6fa41

Spring、SpringBoot 经常使用技巧
  • 获取容器中的BeanBean的初始化

  • 更多详情请看:https://blog.csdn.net/qq_37248504/article/details/113896952


Stream 流等操作时候使用方法的引用
  • Stream
List<String> collect = list.stream().filter("lisi"::equals).map(String::toUpperCase).collect(Collectors.toList());
collect.forEach(System.out::println);

list.removeIf("李四"::equals);
  • HashMap
Map<String, String> map = new HashMap<String, String>() {{
    put("one", "one");
    put("two", "one");
    put("three", "one");
}};
Map<String, String> mapOne = new HashMap<>();
map.forEach(mapOne::put);
logger.info(String.valueOf(mapOne));

Java 创建线程池
  • 使用 ThreadPoolExecutor创建线程池,使用线程,到处 new Thread() 没有回收造成资源浪费,因该交给线程池去管理线程。
public class ThreadPooTest {

    private static final Logger logger = LoggerFactory.getLogger(ThreadPooTest.class);

    private static final long THREAD_TIME_OUT = 60;

    @Test
    public void test() {
        // Cpu 核数
        int cpuNum = Runtime.getRuntime().availableProcessors();
        // 最大数
        int maxSize = 2 * cpuNum + 1;

        /**
         * 拒绝策略:当阻塞队列和最大线程都用完之后
         *
         * AbortPolicy:ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy。直接抛出异常。
         * CallerRunsPolicy:在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。
         * DiscardPolicy:会让被线程池拒绝的任务直接抛弃,不会抛异常也不会执行。
         * DiscardOldestPolicy:当任务呗拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。
         */
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(cpuNum,
                maxSize,
                THREAD_TIME_OUT,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(20),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        poolExecutor.execute(new Thread(() -> {
            logger.info(Thread.currentThread().toString());
        }));

    }
}

Spring Boot 配置全局的线程池
@Configuration
@EnableAsync
public class ThreadPoolConfig {

    private static final Logger logger = LoggerFactory.getLogger(ThreadPoolConfig.class);

    @Bean
    public Executor globalExecutor() {
        // 获取当前cpu核数
        int cpuNum = Runtime.getRuntime().availableProcessors();
        ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
        poolExecutor.setCorePoolSize(cpuNum);
        //配置最大线程数
        poolExecutor.setMaxPoolSize(cpuNum * 2);
        //配置队列大小
        poolExecutor.setQueueCapacity(300);
        //线程存活时间
        poolExecutor.setKeepAliveSeconds(60);
        //配置线程池中的线程的名称前缀
        poolExecutor.setThreadNamePrefix("globalExecutor");
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        poolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        poolExecutor.initialize();
        logger.info(LogConstant.FLAG);
        logger.info(LogConstant.LOG_SUCCESS_PREFIX + "Spring 全局线程池初始化完成......");
        logger.info(JSON.toJSONString(poolExecutor));
        logger.info(LogConstant.FLAG);
        return poolExecutor;
    }
}

  • 使用
/**
  * 全局线程池配置测试
  */
@Async("globalExecutor")
@Override
public void globalExecutorTest() {
    logger.info("test thread!");
}

Idea 热部署插件:Jrebel
  • Idea社区版能满足常用的开发,支持mavengradle项目,真的没有必要破解Idea,哈哈哈哈哈哈哈哈。

  • 使用特别Nice基本上不用重启服务,网上都有注册码。


初始化 Map
  • 惯例
Map<String, String> mapTwo = new HashMap<>();
mapTwo.put("a", "b");
mapTwo.put("c", "d");
  • java8新特性:双括号初始化
Map<String, String> mapOne = new HashMap<String, String>() {
    {
        put("one", "testOne");
        put("two", "testTwo");
        put("three", "testThree");
    }
};
  • com.google.guava中的方法
ImmutableMap<String, Integer> map = ImmutableMap.of("a", 1, "b", 2, "c", 3);
logger.info(String.valueOf(map));

List 初始化
  • 构造Listadd()
List<String> listOne = new ArrayList<>();
listOne.add("test");
listOne.add("test");
  • 双括号语法
List<String> listTwo = new ArrayList<String>(){{
    add("one");
    add("one");
    add("one");
}};
  • Arrays.asList()
List<String> listThree = Arrays.asList("one", "two", "three");
  • Stream.of()
List<String> listFour = Stream.of("testone", "testtwo", "testthree").collect(Collectors.toList());
  • com.google.guava中的方法
 ArrayList<String> listFive = Lists.newArrayList("one", "two", "three");

后端统一返回对象(泛型使用)
  • 静态方法 successerror
public class ResponseInfo<T> {

    /**
     * 状态码
     */
    private Integer code;
    /**
     * 信息
     */
    private String message;
    /**
     * 返回结果
     */
    private T data;

    public ResponseInfo() {
    }

    public ResponseInfo(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public ResponseInfo(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }


    /**
     * 失败
     *
     * @param code
     * @param message
     * @param 
     * @return
     */
    public static <T> ResponseInfo<T> error(Integer code, String message) {
        return new ResponseInfo<>(code, message);
    }

    /**
     * 成功
     *
     * @param code
     * @param message
     * @return
     */
    public static <T> ResponseInfo<T> success(Integer code, String message) {
        return new ResponseInfo<>(code, message);
    }

    /**
     * 成功标志
     *
     * @param code
     * @param message
     * @param object
     * @return
     */
    public static <T> ResponseInfo<T> success(Integer code, String message, T object) {
        return new ResponseInfo<>(code, message, object);
    }
}

return 空的集合代替 return null
  • 避免空指针问题
return Collections.emptyList();
return Collections.emptyMap();
return Collections.emptySet();

使用 String.valueof() 防止强制转换报错
  • valueOf()方式可以避免类型转换异常
@Test
public void testOne() {
    Integer one = 24;
    BigDecimal two = new BigDecimal(23);

    Map<String, Object> map = new HashMap<>();
    map.put("one", one);
    map.put("two", two);
    // 报错
    String strOne = (String) map.get("one");
    String strTwo = (String) map.get("two");
	// 正常使用
    String strThree = String.valueOf(map.get("one"));
    String strFour = String.valueOf(map.get("two"));
}
  • 尽量使用valueof()方法避免转换异常

String.join()拼接集合中的元素
// 拼接迭代器中的元素
List<String> list = Arrays.asList("zhangsan", "lisi", "wangwu");
String joinStr = String.join("','", list);
logger.info(joinStr);
// zhangsan','lisi','wangwu

TreeSet 排序
@Test
public void treeSetTest() {
    TreeSet<String> set = new TreeSet<String>((Comparator) (o1, o2) -> {
        int length1 = String.valueOf(o1).length();
        int length2 = String.valueOf(o2).length();
        if (length1 == length2) return 1;
        return Integer.compare(length1, length2);
    });
    set.add("zhangsan");
    set.add("test");
    set.add("lisi");
    set.add("1234");
    set.add("wangwu");
    logger.info(String.valueOf(set));
}

Map 做缓存
  • 使用Map可以做本地的部分缓存,将常用的对象例如工具类等可以放在缓存当中,从缓存中取值,
  • 注意Map作为缓存的时候,确定使用恰当。

final 的使用场景
  • 不允许被继承的类,如:String 类
  • 不允许修改引用的域对象,如:POJO 类的域变量
  • 不允许被重写的方法
  • 不允许运行过程中重新赋值的局部变量

for 循环移除元素
  • 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator

方式,如果并发操作,需要对 Iterator 对象加锁。

Iterator<String> iterator = list.iterator(); 
while (iterator.hasNext()) { 
	String item = iterator.next(); 
	if (删除元素的条件) { 
		iterator.remove(); 
	} 
}

遍历Map
  • 使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
for (Map.Entry<String, String> entry : map.entrySet()) {
    logger.info("键:{}", entry.getKey());
    logger.info("值:{}", entry.getValue());
}

集合空值说明
集合类 key value super 说明
HashMap 允许为null 允许为null AbstractMap 线程不安全
TreeMap 不允许为null 允许为null AbstractMap 线程不安全
ConcurrentHashMap 不允许为null 不允许为null AbstractMap 线程安全
Hashtable 不允许为null 不允许为null Dictionary 锁分段技术(JDK8:CAS)

Git删除add的文件
git rm --cached 文件名

Git重新 commit
  • 使用Ideaundo commit撤销上次的commit重新来。

Git重新 commit(还没有push到远端)
  • reset Mixed 之前 Commit 修改的内容会保留。

  • reset Hard 之前Commit修改的内容不会保留。

  • reset Soft 类似于 reset Mixed,但是又区别。比如上次的commit中有个新建的文件,当使用reset Mixed命令时候,撤销commit后这个新建的文件是没有进行add操作的,文件的颜色是红色的。使用reset Soft命令这个文件是进行了add操作了的。


利用Set去重
  • 注意重写equals()hash(),可以根据实际业务场景进行去重操作,例如如果商品编号相等那么断定这个商品是同一件商品。

  • 重写对象的equals()

@Override
public boolean equals(Object object) {
    if (this == object) return true;
    if (Objects.isNull(object) || getClass() != object.getClass()) return false;
    Shop shop = (Shop) object;
    return (Objects.nonNull(shop) ? shopCode.equals(shop.getShopCode()) : shop == null);
}
  • 重写对象的hash()
@Override
public int hashCode() {
    return Objects.isNull(shopCode) ? 0 : shopCode.hashCode();
}

异常处理
  • java异常
class Throwable;
class Exception;
class Error;
  • 处理方法:

​ 向上抛异常:最底层的代码例如工具类,直接向上抛原始异常就行了。

​ 输出原始异常:如果是捕获到了异常,那么一定要输出原始异常的信息,如果只是输出e.getMessage无法定位到代码出错的行数,如果是空指针错误信息都没有。

​ 使用日志框架层:例如log4j


优雅的抛出异常
  • 使用Objects
@Test
public void testFive() {
    String str = null;
    Objects.requireNonNull(str, "str is null!");
}
  • 使用Assets
@Test
public void testSix() {
    String str = null;
    Assert.notNull(str, "str is null!");
}

Mybatis 使用技巧
  • 详见:https://blog.csdn.net/qq_37248504/article/details/106932085

Oracle Mybatis 常用语法、报错处理
  • 详见:https://blog.csdn.net/qq_37248504/article/details/118367963

Sql 常用语句
  • 详见:https://blog.csdn.net/qq_37248504/article/details/116171452

常用规范

其实使用Idea开发的时候,开启Idea自带的代码检测,处理掉所有的告警大部分代码问题会解决,代码命名规范等可以使用阿里巴巴开发规范插件,使用SonarLint插件可以处理一些垃圾代码,可以扫描一些静态的错误,从而提高代码质量。


SonarLint 检查提示和解决方法
  • 详细请看:https://blog.csdn.net/qq_37248504/article/details/115266913

类的命名
  • 使用驼峰式命名的规范。DO、 DTO 、 VO等除外
UserDO
UserDTO
UserVO
  • 在模块或者接口,类,方法中使用了设计模式,那么请在命名的时候体现出来。
CompomentFactory
AbstractPrint

pojo 类中的属性使用包装类
  • 基本类型会有默认值,使用包装内可以设置为null

  • pojogettersetter方法里面不要增加业务逻辑。


抛出异常的时候确定异常的类型,可以定义自己的异常抛出
public class MyException extends RuntimeException {

    public MyException() {
    }

    public MyException(String message) {
        super(message);
    }

    public MyException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyException(Throwable cause) {
        super(cause);
    }

    public MyException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
  • 使用
/**
  * 抛出自定义异常,测试自定义异常处理器
  */
@GetMapping("/myexception")
public String createMyException() throws MyException {
    throw new MyException("MyException 自定义异常信息");
}

在 for 循环中拼接字符串使用 StringBuilder
StringBuilder strOne = new StringBuilder("test");
for (int i = 0; i < 10000000; i++) {
    strOne.append(strOne);
}

不要使用魔法值(常量)
  • 使用接口在接口中定义常量
  • 维护常量类
  • 使用枚举类

final 的使用
  • 类、方法、变量能用就可以加上final防止重写、继承。

避免创建不必要的对象
  • 不要随便创建对象
  • 例如在for循环中尝试使用同一对象处理等。
  • 可以使用Map做常用对象的缓存

组合优先于继承
  • 继承有利于代码复用,但是尽可能不要进行跨包的继承。
  • 组合,即不扩展已有的类,而是在的类中新增一个现有类的。

用枚举代替常量值
  • Color.java
public enum Color {


    RED("red", "红色"), GREEN("green", "绿色");

    private String name;

    private String title;


    Color(String name, String title) {
        this.name = name;
        this.title = title;
    }

    public String getName() {
        return name;
    }

    public String getTitle() {
        return title;
    }
}

接口优先于反射机制

使用反射机制会带来以下的问题:

  • 丧失了编译期类型检查
  • 代码笨拙冗长
  • 性能损失

反射基本上只适合用在编写组件时、代码分析器、RPC等场景下使用。在使用反射机制时,如果可能,尽可能只通过反射机制实例化对象,而访问方法时,使用已知的接口或者超类。


命名风格(阿里巴巴规范)
  • 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束

  • 类名使用驼峰规则,DO、BO、DTO、VO等除外

  • 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从

    驼峰形式。

  • 抽象类命名使用 AbstractBase 开头;异常类命名使用 Exception 结尾;测试类

    命名以它要测试的类的名称开始,以 Test 结尾。

  • 中括号是数组类型的一部分,数组定义如下:String[] args

  • 为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词

    组合来表达其意。

  • 对于 ServiceDAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。

  • 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法


各层命名规范(阿里巴巴规范)
  • 获取单个对象的方法用 get 做前缀。
  • 获取多个对象的方法用 list 做前缀。
  • 获取统计值的方法用 count 做前缀。
  • 插入的方法用 save/insert 做前缀。
  • 删除的方法用 remove/delete 做前缀。
  • 修改的方法用 update 做前缀。

类成员与方法访问控制(阿里巴巴规范)
  • 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private
  • 工具类不允许有 publicdefault 构造方法
  • 类非 static 成员变量并且与子类共享,必须是 protected
  • 类非 static 成员变量并且仅在本类使用,必须是 private
  • static 成员变量如果仅在本类使用,必须是 private
  • 若是 static 成员变量,必须考虑是否为 final
  • 类成员方法只供类内部调用,必须是 private
  • 类成员方法只对继承类公开,那么限制为 protected

工具类

镜像源、常用工具
  • 详见:https://blog.csdn.net/qq_37248504/article/details/118605663

Markdown
  • 详见:https://blog.csdn.net/qq_37248504/article/details/109085445

Linux、DOS常用命令
  • 详见:https://blog.csdn.net/qq_37248504/article/details/106866545

好用工具推荐
  • 数据库连接工具:免费的Dbeaver(支持各种数据库):https://dbeaver.io/
  • Markdown语法工具:Typora:https://typora.io/
  • Redis客户端: https://gitee.com/qishibo/AnotherRedisDesktopManager

你可能感兴趣的:(后端开发,后端开发技巧,常用规范,工具类)