开发技巧
equals() 方法的使用
null.equals()
会出报空指针,因该是非null
的值.equals()
- 可以使用
Objects
的equals()
方法避免空值,完美
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.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();
}
巧妙使用设计模式
-
巧妙使用接口、抽象类
-
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 经常使用技巧
-
获取容器中的
Bean
、Bean
的初始化 -
更多详情请看: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
社区版能满足常用的开发,支持maven
、gradle
项目,真的没有必要破解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 初始化
- 构造
List
,add()
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");
后端统一返回对象(泛型使用)
- 静态方法
success
、error
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() 防止强制转换报错
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
- 使用
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去重
-
注意重写
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
常用规范
其实使用
Idea
开发的时候,开启Idea
自带的代码检测,处理掉所有的告警大部分代码问题会解决,代码命名规范等可以使用阿里巴巴开发规范插件,使用SonarLint
插件可以处理一些垃圾代码,可以扫描一些静态的错误,从而提高代码质量。
SonarLint 检查提示和解决方法
- 详细请看:https://blog.csdn.net/qq_37248504/article/details/115266913
类的命名
- 使用驼峰式命名的规范。
DO、 DTO 、 VO
等除外
UserDO
UserDTO
UserVO
- 在模块或者接口,类,方法中使用了设计模式,那么请在命名的时候体现出来。
CompomentFactory
AbstractPrint
pojo 类中的属性使用包装类
-
基本类型会有默认值,使用包装内可以设置为
null
-
pojo
的getter
和setter
方法里面不要增加业务逻辑。
抛出异常的时候确定异常的类型,可以定义自己的异常抛出
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
风格,必须遵从驼峰形式。
-
抽象类命名使用
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
工具类
镜像源、常用工具
- 详见: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