代码整洁之道
ctrl + shift + f 格式化代码
一、String优化:
1、字符串判空使用str.length == 0 length是string的属性,调用属性性能非常高
2、将整形装换为String不要使用 1 + "" 使用 String.valueOf(1);
3、使用字符串拼接不使用 “str” + “ing” 而是使用src.concat("").concat(""); 或者使用StringBuilder或者StringBuffer,如果字符串不是成员变量,是局部变量的话,使用StringBuilder,性能很高。
4、在使用StringBuilder、StringBuffer的时候最好能给一个容量初始值
5、在创建String的时候最好使用 String str = ""; 而不使用 String str = new String("");
6、字符串的分割,split性能最佳
二、数组优化
1、初始化数组最佳 String[] strs = new String[10]; 不要使用String strs[] = new String[10];
2、存放基本类型数据集,有限考虑数组,对于javaBean使用集合、队列、栈
/**
* 输出结果为 1
* @param args
*/
public static void main(String[] args) {
int[] as = new int[]{1,2,3,4,5};
List<int[]> ints = Arrays.asList(as);
//输出结果为1
System.out.println(ints.size());
}
/**
* 输出结果为 5
* @param args
*/
public static void main(String[] args) {
Integer[] as = new Integer[]{1,2,3,4,5};
List<Integer> ints = Arrays.asList(as);
System.out.println(ints.size());
}
3、数组的copy
package com.example.config;
import java.util.Arrays;
/**
* @className TestArray
* @program: spring-cloud
* @author: Zyred
* @create: 2019-10-24 17:15
**/
public class TestArray {
private static final byte[] buffer = new byte[1024 * 10];
static {
for (int i = 0, len = buffer.length; i < len; i++) {
buffer[i] = (byte) (i);
}
}
private static long startTime ;
public static void main(String[] args) {
startTime = System.nanoTime();
byte[] newBuffer = new byte[1024 * 10];
for (int i = 0, lens = newBuffer.length; i < lens; i++) {
newBuffer[i] = buffer[i];
}
calcTime("forCopy");
startTime = System.nanoTime();
byte[] clone = buffer.clone();
calcTime("cloneCopy");
startTime = System.nanoTime();
byte[] copyOf = Arrays.copyOf(buffer, buffer.length);
calcTime("Arrays.copyOf");
startTime = System.nanoTime();
byte[] newBuffer1 = new byte[buffer.length];
System.arraycopy(buffer, 0, newBuffer1,0, buffer.length);
calcTime("System.arraycopy");
}
private static void calcTime(String str){
System.out.println(str + "----->" + (System.nanoTime() - startTime) / 1000);
}
}
结果:
forCopy----->293
cloneCopy----->13
Arrays.copyOf----->28
System.arraycopy----->8
4、数组的深层拷贝SerializationUtils.clone();
三、集合
1、CollectionUtils集合判空
2、空集合的优雅获取方式
public List<String> getEmptyList(){
//SET Collections.EMPTY_SET
//MAP Collections.EMPTY_MAP
return Collections.EMPTY_LIST;
return Collections.EmptyList();
}
四、优雅的方法
1、方法的名字,和业务场景尽量相似 2、避免方法返回null值
public String getStr(int a){
if(a > 0){
return "yes";
}
//减少return null的方法
return null;
}
3、避免方法的层级太深,最多不超过三层,如果有三层以上的,尽量优化到三层以下
public void test(int a){
boolean flag = true;
if(flag){
if(flag){
if(flag){
if(flag){
System.out.println("---->" + "is empty");
}
}
}
}
}
4、如果方法的形参超过3个,可以考虑封装成实体类
5、子类继承父类,重写了父类的方法后,一定要加上@Override注解,避免下一个程序员操作的时候,改了重写后的方法名称,导致出错
6、在接口中最好不要采用方法的修饰符
public interface InterfaceTest(){
//错误写法
public void test();
public String getIndex();
//正确写法,因为在接口中定义的抽象方法,每一个方法都是public,
void test();
void getIndex();
}
7、final的使用,提升代码整体的性能。
public void test(final Mpa<String> map, final String id){
//这样会报错的,形参中使用了final有效的杜绝了代码中的参数被改变
id = "";
}
五、优雅的使用异常
1、禁止捕获非受检异常
public void getStr(){
try{
}catch(NullPointerException e){
}
}
2、对异常的分类进行处理
try{
}catch(Exception e){
//禁止不做任何操作
}
try{
}catch(FileNotFoundException e){
logger.warn("文件找不到,请检查")
e.printStackTrace();
}
3、根据业务场景,封装自己适合的异常 4、循环体中禁止放异常块
六、范型的使用
1、泛型的擦除
//书写的时候,泛型用于校验,规定传入的类型
public void test(){
List<String> list = new ArrayList<>();
list.add("Z");
list.add("y");
}
//反编译的结果,
public void test(){
List list = new ArrayList<>();
list.add("Z");
list.add("y");
}
2、使用原生集合(不添加泛型),泛型默认为Object,如果后续取值的会出现ClassCastException异常
3、利用有限通配符来提升API的灵活性
<? extends T> //类型必须继承T
<? super T> //超类限定,类型必须是此类的超类(父类)
public class TestClass{
class Fruit{}
class Apple extends Fruit{}
class RedApple extends Apple{}
public void test(){
List<Fruit> fruitList = new ArrayList<>();
List<? super Apple> appleList = fruitList;
//正确
appleList.add(new Apple());
appleList.add(new RedApple());
//无法添加
appleList.add(new Fruit());
appleList.add(new Object());
}
}
七、枚举
1、标准的枚举类
public enum ColorEnum{
BULE(1, "黄色"),
YELLOW(2, "黄色"),
BLACK(3, "黑色");
private final Integer code;
private final String name;
ColorEnum(Integer code, String name){
this.code = code;
this.name = name;
}
public String getName(){
return this.name;
}
@Override
public String toString(){
return this.code;
}
}
2、switch语句只支持int,char,改用枚举
enum Signal {
GREEN, YELLOW, RED
}
public class test {
public static void main(String[] args) {
Signal color = Signal.RED;
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
八、序列化和反序列化
1、及时关闭流、先开后关、后开先关
public void testStream(HttpServletResponse resp){
OutputStream out = resp.getOutputStream();
InputStream is = new InputStream();
//先关闭后开启的流
is.close();
//后关闭先开启的流
out.close();
}
2、文件中的分隔符使用类中常量
//windows中和linux系统中,文件的路径斜杠方向不同,可是可以使用File.pathSeparator或者File.separator
pubulic static void mian(String[] args){
//在windows系统中输出内容是\ 而在linux中输出的内容是/
System.out.println(File.pathSeparator);
}
3、尽量使用缓存流
在软件系统中,IO速度比内存速度慢,IO读写在很多情况下会是系统的瓶颈。在java标准IO操作中,InputStream和OutputStream提供基于流的IO操作,以字节为处理单位;Reader和Writer实现了Buffered缓存,以字符为处理单位。从Java1.4开始,增加NIO(New IO),增加缓存Buffer和通道Channel,以块为处理单位,是双向通道(可读可写,类似RandomAccessFile),支持锁和内存映射文件访问接口,大大提升了IO速度。
缓冲流分为字节和字符缓冲流
字节缓冲流为:
BufferedInputStream 字节输入缓冲流
BufferedOutputStream 字节输出缓冲流
字符缓冲流为:
BufferedReader 字符输入缓冲流
BufferedWriter 字符输出缓冲流
4、流的四种使用
a、BufferedOutputStream 定义:
BufferedOutputStream类实现缓冲的输出了,通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必每一个字节写入都调用底层系统
public static void main(String[] args) {
try {
//创建字节输出流实例
OutputStream out=new FileOutputStream("I:\\test.txt");
//根据字节输出流构建字节缓冲流
BufferedOutputStream buf=new BufferedOutputStream(out);
String data="好好学习,天天向上";
//写入缓冲区
buf.write(data.getBytes());
//写入缓冲区,刷新缓冲区,即把内容写入
buf.flush();
//关闭流 关闭缓冲流时,也会刷新一次缓冲区
buf.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
b、BufferedInputStream 定义:
BufferedInputStream为别的输入流添加缓冲功能,在创建BufferedInputStream时会创建一个内部缓冲数组,用于缓冲数据,提高性能。
public static void main(String[] args) {
try {
//创建字节输入流实例
InputStream in=new FileInputStream("L:\\test.txt");
//根据字节输入流构建字节缓冲流
BufferedInputStream buf=new BufferedInputStream(in);
byte[]bytes=new byte[1024];
//数据读取
int len=-1;
StringBuffer sb = new StringBuffer();
while((len = buf.read(bytes)) != -1){
sb.append(new String(bytes,0,len));
}
System.out.println("内容为:"+sb);
//关闭流
buf.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
c、BufferedWriter 定义:
将文本写入字符输出流,缓冲各个字符,从而提供高效的写入。可以指定缓冲区的大小,一般情况下,默认的缓冲区大小就足够了
public static void main(String[] args) {
try {
Writer w = new FileWriter("L:\\test.txt");
//根据字符输出流创建字符缓冲流
BufferedWriter buf = new BufferedWriter(w);
//写入数据
buf.write("只要功夫深铁杵磨成针");
//刷新流
buf.flush();
//关闭流
buf.close();
w.close();
} catch (IOException e) {
e.printStackTrace();
}
}
d、BufferedReader 定义:
从字符输入流中读取信息,缓冲各个字符,从而实现高效读取。可以指定缓冲区的大小,一般情况下,默认的缓冲区大小就足够了
public static void main(String[] args) {
try {
Reader r = new FileReader("L:\\test.txt");
//根据字符输入流创建字符缓冲流
BufferedReader buf = new BufferedReader(r);
char [] data = new char[512];
//数据读取
int len = -1;
StringBuilder sb = new StringBuilder();
while((len = buf.read(data)) != -1){
sb.append(new String(data,0,len));
}
System.out.println("内容是: "+sb);
//关闭流
buf.close();
r.close();
} catch (IOException e) {
e.printStackTrace();
}
}
九、多线程
参考:
1、变量的线程安全分为成员变量
和局部变量
,如果变量是成员变量,很有可能造成线程不安全的问题,如果是final修饰了的变量例外
2、方法锁,对象锁,类锁概念
方法锁--->synchronized 修饰方法时。 对象锁--->synchronized 修饰方法或代码块。 类锁------>synchronized 修饰静态的方法或代码块。
总结:
synchronized 关键字加到 static 方法上是给 Class 上锁,加到非 static 方法上是给对象上锁。class 锁可以对类的所有实例起作用,即就是在任何时候,只能有一个线程访问该类的 static 方法。因为 static 方法就是类方法。
3、锁优化的思路
a、减少锁持有时间(尽量缩小锁的范围)
b、降低锁粒度
c、锁分离
d、锁粗化
e、锁消除
a、减少锁持有时间
public synchronized void synchronizedTest(){ getName(); //该方法满足线程安全 syncMethod(); getAge(); } *******************优化******************** public void synchronizedTest(){ getName(); getAge(); //将需要线程安全的方法使用synchronized关键字,不需要的,就让其他线程执行,提升系统的性能 synchronized(this){ syncMethod(); } }
b、降低锁粒度:降低锁的粒度,是为了降低线程对锁的竞争。
// TODO ConCurretnHashMap看了后再写此部分
c、锁分离
//TODO LinkedBlockingQueue看完再写
d、锁粗化
定义:
通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早地获得资源执行任务。但是,如果对同一个锁不停地进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化。
为此,虚拟机在遇到一连串连读地对同一锁不断进行请求和释放的操作时,便会把所有的锁作整合成对锁的一次请求,从而减少对锁的请求同步次数,这个操作叫做锁是粗化。比如代码段:
public void demoMethod() { synchronized(lock) { //do sth1 } //做其他不需要的同步的工作,但能很快执行完毕 synchronized(lock) { //do sth2 } }
粗锁后的代码:
public void demoMethod() { synchronized(lock) { //do sth1 //do sth2 } }
e、锁消除 定义:
锁消除是一种更彻底的锁优化,Java虚拟机在JIT编译时,通过对上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。
//例如,在一个不可能存在并发竞争的场合使用Vector,而Vector内部使用了Synchronized请求锁 public String[] createStrings() { //属于线程私有数据,不可能被其它线程访问 //JVM检测到无用的锁,会清除无用的锁 Vector<String> v = new Vector<String>(); for(int i=0; i<100; i++) { v.add(Integer.toString(i)); } return v.toArray(new String[]{}); }
4、优雅休眠线程
public static void main(String[] args) throws InterruptedException {
int a = 1;
//总结, <<= 是给参与运算的变量附上值,并且存在返回值
int b = a <<= 2;
//不改变参与运算的变量,只又运算后的返回值
int c = a << 2;
//a = 4
System.out.println(a);
//b = 4
System.out.println(b);
//c = 16
System.out.println(c);
//启动线程休眠
TimeUnit.MINUTES.sleep(1);
System.out.println("over----->");
}
十、优雅关闭I/O流
public static void main(String[] args) {
byte[] buffer = new byte[1024];
//在try中声明需要关闭流的对象
try (FileInputStream fis = new FileInputStream(new File("E:\\test.md"))) {
while (fis.read(buffer) > 0) {
System.out.println(buffer.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
//如果是多个需要关闭的流,则可以这么写
public static void main(String[] args) {
byte[] buffer = new byte[1024];
try (FileInputStream fis = new FileInputStream(new File("E:\\input.txt"));
FileOutputStream fos = new FileOutputStream(new File("E:\\output.txt"));
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
fis.read(buffer);
char[] c = getChars(buffer);
osw.write(c);
} catch (Exception e) {
e.printStackTrace();
}
}
十一、stream如何优雅
1、理解终端操作和中间操作
定义:
终端操作:终端操作会关闭该流,因为Stream顶级接口继承了AutoCloseable类 中间操作:中间操作会创建一个新的流,即返回参数是一个流,例如:
//返回值类型则是一个Stream<T> Stream<T> filter(Predicate<? super T> predicate);测试:
实现:
public static void main(String[] srgs){ List<Integer> lists = new ArrayList<>(); lists.add(4); lists.add(3); lists.add(6); lists.add(1); lists.add(5); lists.add(2); lists.add(-2); //此时产生了一个流,可以被理解为中间操作 Stream<Integer> stream = lists.parallelStream(); //1 //调用了一个终端操作 Optional<Integer> min = stream.min(Integer::compareTo); //2 System.out.println("最小值:" +min.get()); //如果此时继续操作第1步则会出现关闭流无法操作的异常 //如果想正常操作,则将该步骤放入第一步和第二步中间即可 Optional<Integer> max = stream.max(Integer::compareTo); } //异常信息: Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
2、reduce()方法的执行逻辑
//a = 1 5 8 14 15 20 22
//b = 4 3 6 1 5 2 2
public static void main(String[] srgs){
List<Integer> lists = new ArrayList<>();
lists.add(4);
lists.add(3);
lists.add(6);
lists.add(1);
lists.add(5);
lists.add(2);
lists.add(2);
Integer integerReduce = lists.stream().reduce(0, (a, b) -> {
System.out.println("a = " + a);
System.out.println("b = " + b);
return a + b;
});
System.out.println(integerReduce);
}
//得到运行结果:
/*a = 0
b = 4
a = 0
b = 3
a = 7
b = 6
a = 13
b = 1
a = 14
b = 5
a = 19
b = 2
a = 21
b = 2
*/
//整理得到的结果:
//a = 0 4 7 13 14 19 21
//b = 4 3 6 1 5 2 2
notice:调用
T reduce(T identity, BinaryOperator<T> accumulator);
是这个方法;结论:当
identity = 0, accumulator
中a变量的初始值就是0, 此时b变量代表list中的每个值,第一个则是4,打印出结果a = 0 b = 4
,注意:执行到了return
语句了,return
语句并不是代表结束当前调用,如果结束了当前调用,那么返回的结果则是4,显然是错误的,那么这个return
起到什么作用呢?此时的return
是在BinaryOperator<T> accumulator
中进行return
的,所以,只是结束了这个表达式,然后将return
的值,重新赋值到了a变量,此时a = 4
,再重复一次则得到了a = 7, a = 13 ....., a = 21
,当a =21
的时候,list集合中还存在一个值,就是最后一个b = 2
,最后一次循环return 21 + 2; 结果自然就是 23啦总结:
identity
:只是一个a变量初始的大小,在第一次的时候这个初始值也会参与计算;a
变量:第一次加载identity的值,之后存储每次和b变量相加的值;b
变量:遍历得到list集合中每个元素;
十二、优雅如何落地
1、简单的if/esle判断
if("a".equals(str)){
return "success";
}
return "error";
if(1 == sex){
return "男";
}else if(0 == sex){
return "女";
}else{
return "其他";
}
//这么写 他不香嘛?
return "a".equals(str) ? "success" : "error";
return 1 == sex ? "男" : 0 == sex ? "女" : "其他";
2、大量的 || 和 && 条件判断
private final String name = "job";
if("what".equals(name)
|| "is".equals(name)
|| "the".equals(name)
|| "fuck".equals(name)
|| "code".equals(name)
|| ",".equals(name)
|| "stupid".equals(name)){
}
//创建方法
public static boolean paramCheck(String name){
//扩展性强,阅读性高
String[] paras = {"what", "is", "the", "fuck", "code", ",", "stupid"};
for(int i = 0, len = paras.length; i < len; i++){
//这么写,不香吗?
if(paras[i].equals(name)){
return true;
}
}
return false;
}
3、代码的冗余
Map<Integer, Object>> map = new HashMap<>();
if (1 == flag) {
map.put(i, value1);
} else {
map.remove(i);
}
if (1 == flag) {
map.put(i, value2);
} else {
map.remove(i);
}
if (1 == flag) {
map.put(i, value3);
} else {
map.remove(i);
}
private void addNode(Map<Integer, Object>> map, int flag, int key, Object... objs) {
//定义if条件为1,为满足XXX条件,目的是不要使用魔法变量
int myFlag = 1;
for(int i = 0, len = objs.length; i < len; i ++){
if (myFlag == insertFlag) {
retList.put(key, objs[i]);
} else {
retList.remove(key);
}
}
}
4、大佬曾经说过,一个方法操作30行代码,都是小白
public void xxx(Person p){
/**数据验证逻辑,此处省略业务20行代码**/
/**************分隔符**************/
/** 数据添加逻辑,此处省略业务20行代码**/
/**************分隔符**************/
/**数据删除逻辑,此处省略业务20行代码**/
}
public void personCheckInfo(){
/**数据验证逻辑,此处省略业务20行代码**/
}
public void addPersonInfo(){
/** 数据添加逻辑,此处省略业务20行代码**/
}
public void delPersonInfo(){
/**数据删除逻辑,此处省略业务20行代码**/
}
/**
* 主方法, 分三个方法出来只是我个人习惯,其实实际开发中,这种场景没必要全部逻辑提出来分为方法
* 这么做的目的是为了减少每一个栈帧中数据的大小
*/
public void xxx(Person p){
personCheckInfo();
addPersonInfo();
delPersonInfo();
}
5、方法中减少随意定义变量
public void XXX(Person p){
//实际上这儿就多了两个String对象
String name = p.getName();
Integer age = p.getAge();
this.personMap.add(name, age);
}
//改进
public void XXX(Person p){
this.personMap.add(p.getName(), p.getAge());
}
6、封装常用变量 Variable.java
public class Variable{
/**
* 例如微信OPEN_ID,一般是放入配置文件,这里只是做演示
**/
private final static String WECHAT_OPEN_ID = "";
private final static Integer SEX_GIRL = 0;
private final static Integer SEX_BOY = 1;
}
7、包装类型于包装类型做比较判断的时候使用equals()
if(Variable.SEX_BOY == User.getSex());
//建议改为equals(),阿里巴巴规范手册中明确指出
if(Variable.SEX_BOY.equels(User.getSex()));
8、Getter & Setter
//当属性过多的时候,添加get、set方法确实是不明智的选择
public class User{
private String name;
private Integer age;
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//改进如下,pom文件中添加 lambok依赖,在idea中下载lombok插件即可
@Data
public class User{
private String name;
private Integer age;
}
9、减少代码层级
/**
* 1、层级过深
* 2、接口不要返回null,因为别人调用你接口的时候,不知道是哪儿出了问题
* 3、调用接口的人,并不知道错误原因
**/
public List<Student> getStudentByTeacherId(Long teacherId){
if(Objects.nonNull(teacherId)){
List<Student> listStudent = this.userReadMapper.getStudentByTeacherId(teacherId);
if(CollectionUtils.isEmpty(listStudent)){
return null;
}
return listStudent;
}
return null;
}
/************************分隔符*************************/
public ResponseEntity<List<Student>> getStudentByTeacherId(Long teacherId){
if(Objects.nonNull(teacherId)){
//不一定非要返回500,自己定义即可,写清楚接口文档
return new ResponseEntity("主键不能为空", 500);
}
List<Student> listStudent = this.userReadMapper.getStudentByTeacherId(teacherId);
if(CollectionUtils.isEmpty(listStudent)){
return Collections.EMPTY_LIST;
}
return listStudent;
}
10、实体类中重写toString();
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//如果对象的属性过于多的情况下,可以考虑使用ablibab的fastJson提供帮助
@Override
public String toString() {
//这样看起来就相对清晰且不繁琐
return JSONObject.toJSONString(this);
}
11、异常除日志的打印和查询失败的返回
//为什么返回一个静态的user空对象,而不是new一个对象返回回去
//如果此方法被并发10000访问,那么堆中会new 10000个user对象,而静态的只存在一个,
//如果占用内存过高,那么启动一次GC需要消耗大量的资源。
public static final User EMPTY_USER = new User();
public User getUserInfoByToken(HttpServletRequest req){
try{
User user = (User)redisUtils.getUser(req);
}catch(ClassCastException e){
log.warn("当前登录的用户不是***类型用户");
e.printStackTrace();
//切记不能返回null
return EMPTY_USER;
}
}
12、整形如何优雅toString()
int a = 0;
String str = a + "";
//对比结果,很明显第二种优雅吧
String src = String.valueOf(a);
13、如何优雅获取时间
//yyyy-mm-dd
String now = new SimpleDateFormat("yyyy-mm-dd").format(new Date());//no
String now = LocalDate.now();//yes
LocalTime.MAX; //获取当天最后时间 23:59:59.999999999
LocalTime.now(); //获取 HH:MM:ss:SSS
//获取yyyy-mm-dd HH:MM:ss
new SimpleDateFormat("yyyy-mm-dd HH:MM:ss").format(new Date()); //no
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); //yes
//原则上,Date对象是可变对象,不推荐使用Date对象
//其次是并没有使用new关键字,如果new关键词创建对象诞生在Eden区,那么第二种则没有new对象,节约了系统资源