后端开发技巧、常用规范
开发技巧
equals() 方法的使用
-
null.equals()
会出报空指针,因该是非null
的值.equals()
- 可以使用
Objects
的equals()
方法避免空值,完美
String strOne = null;
String strTwo = null;
boolean oneFlag = Objects.equals(strOne, strTwo);
创建 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));
}
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));
}
User user1 = new User(1, "张三", "西安");
User user2 = new User(2, "李四", "新疆");
user1 = null;
User user = Optional.ofNullable(user1).orElseGet(() -> user2);
logger.info(user.toString());
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 切割工具
for (List<DataDto> dataDtos : Lists.partition(list, 1000)) {
dataDtoMapper.insertBatch(dataDtos);
}
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</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();
}
巧妙使用设计模式
Spring 工厂模式的结合使用
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();
}
Spring、SpringBoot 经常使用技巧
Stream 流等操作时候使用方法的引用
List<String> collect = list.stream().filter("lisi"::equals).map(String::toUpperCase).collect(Collectors.toList());
collect.forEach(System.out::println);
list.removeIf("李四"::equals);
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
初始化 Map
Map<String, String> mapTwo = new HashMap<>();
mapTwo.put("a", "b");
mapTwo.put("c", "d");
Map<String, String> mapOne = new HashMap<String, String>() {
{
put("one", "testOne");
put("two", "testTwo");
put("three", "testThree");
}
};
ImmutableMap<String, Integer> map = ImmutableMap.of("a", 1, "b", 2, "c", 3);
logger.info(String.valueOf(map));
List 初始化
List<String> listOne = new ArrayList<>();
listOne.add("test");
listOne.add("test");
List<String> listTwo = new ArrayList<String>(){{
add("one");
add("one");
add("one");
}};
List<String> listThree = Arrays.asList("one", "two", "three");
List<String> listFour = Stream.of("testone", "testtwo", "testthree").collect(Collectors.toList());
ArrayList<String> listFive = Lists.newArrayList("one", "two", "three");
后端统一返回对象(泛型使用)
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 <T>
* @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() 防止强制转换报错
@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"));
}
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
- 使用
Idea
的undo commit
撤销上次的commit
重新来。
Git重新 commit(还没有push到远端)
-
reset Mixed
之前 Commit
修改的内容会保留。
-
reset Hard
之前Commit
修改的内容不会保留。
-
reset Soft
类似于 reset Mixed
,但是又区别。比如上次的commit
中有个新建的文件,当使用reset Mixed
命令时候,撤销commit
后这个新建的文件是没有进行add
操作的,文件的颜色是红色的。使用reset Soft
命令这个文件是进行了add
操作了的。
利用Set去重
@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);
}
@Override
public int hashCode() {
return Objects.isNull(shopCode) ? 0 : shopCode.hashCode();
}
异常处理
class Throwable;
class Exception;
class Error;
向上抛异常:最底层的代码例如工具类,直接向上抛原始异常就行了。
输出原始异常:如果是捕获到了异常,那么一定要输出原始异常的信息,如果只是输出e.getMessage
无法定位到代码出错的行数,如果是空指针错误信息都没有。
使用日志框架层:例如log4j
优雅的抛出异常
@Test
public void testFive() {
String str = null;
Objects.requireNonNull(str, "str is null!");
}
@Test
public void testSix() {
String str = null;
Assert.notNull(str, "str is null!");
}
Mybatis 使用技巧
Oracle Mybatis 常用语法、报错处理
Sql 常用语句
常用规范
其实使用Idea
开发的时候,开启Idea
自带的代码检测,处理掉所有的告警大部分代码问题会解决,代码命名规范等可以使用阿里巴巴开发规范插件,使用SonarLint
插件可以处理一些垃圾代码,可以扫描一些静态的错误,从而提高代码质量。
SonarLint 检查提示和解决方法
类的命名
- 使用驼峰式命名的规范。
DO、 DTO 、 VO
等除外
UserDO
UserDTO
UserVO
- 在模块或者接口,类,方法中使用了设计模式,那么请在命名的时候体现出来。
CompomentFactory
AbstractPrint
pojo 类中的属性使用包装类
抛出异常的时候确定异常的类型,可以定义自己的异常抛出
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
做常用对象的缓存
组合优先于继承
- 继承有利于代码复用,但是尽可能不要进行跨包的继承。
- 组合,即不扩展已有的类,而是在的类中新增一个现有类的。
用枚举代替常量值
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
风格,必须遵从
驼峰形式。
-
抽象类命名使用 Abstract
或 Base
开头;异常类命名使用 Exception
结尾;测试类
命名以它要测试的类的名称开始,以 Test
结尾。
-
中括号是数组类型的一部分,数组定义如下:String[] args
-
为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词
组合来表达其意。
-
对于 Service
和 DAO
类,基于 SOA
的理念,暴露出来的服务一定是接口,内部的实现类用 Impl
的后缀与接口区别。
-
类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter
方法
各层命名规范(阿里巴巴规范)
- 获取单个对象的方法用 get 做前缀。
- 获取多个对象的方法用 list 做前缀。
- 获取统计值的方法用 count 做前缀。
- 插入的方法用 save/insert 做前缀。
- 删除的方法用 remove/delete 做前缀。
- 修改的方法用 update 做前缀。
类成员与方法访问控制(阿里巴巴规范)
- 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是
private
。
- 工具类不允许有
public
或 default
构造方法
- 类非
static
成员变量并且与子类共享,必须是 protected
- 类非
static
成员变量并且仅在本类使用,必须是 private
- 类
static
成员变量如果仅在本类使用,必须是 private
。
- 若是
static
成员变量,必须考虑是否为 final
- 类成员方法只供类内部调用,必须是
private
- 类成员方法只对继承类公开,那么限制为
protected
工具类
镜像源、常用工具
Markdown
Linux、DOS常用命令
好用工具推荐