代码优化:
- 奇偶判断
- 不要在常量与变量中出现易混淆字符
- 不要让常量变成了变量
- 三元表达式的类型务必一致
- 建议显式的声明UID
- switch使用注意
- 用整数类型处理货币
- 注意边界问题
- 基本类型转换时尽量避免隐式的类型转换
- 警惕包装类型的NULL值
- 警惕包装类型的比较
- 建议工具类不可实例化
- 警惕对象的潜拷贝
- 建议覆写实体类的toString方法
- 合理使用String、StringBuffer、StringBuilder
- 注意字符串的位置
- 明确场景下为集合指定初始容量
- 使用Arrays.asList方法时要注意List对象不可改变
- 注意子列表只是元列表的一个视图
- 提倡异常封装
- 建议采用异常链传递异常
- 不要在finally中处理返回值
- 不要在循环条件中计算
- 推荐使用guava扩展工具包
- 代码注释应该正确、清晰、简洁
- 避免重复代码
- 避免过长方法
- 定义的变量名称,方法名称,类名称等应该具有一定的意义
- 避免过大的类
- 避免过长的参数列表
设计优化:
- 接口职责要保持单一
- 增强类的可替换性
- 模块之间的依赖通过抽象发生
- 一个类对另一个类的依赖应该建立在最小的接口上
- 尽量降低类与类之间的耦合
一、代码优化:
(1)奇偶判断
说明:
不要使用 i % 2 == 1 来判断是否是奇数,因为i为负奇数时不成立,请使用 i % 2 != 0 来判断是否是奇数,或使用高效式 (i & 1) != 0来判断。
(2)不要在常量与变量中出现易混淆字符
示例:
long i= 1l; // 不推荐 long i= 1L; // 推荐 |
说明:
如果字母和数字必须混合使用,字母”l”务必大写,字母”o”则增加注释
(3)不要让常量变成了变量
实例:
public static final int RAND_CONST = new Random().nextInt(); // 不推荐 |
说明:
务必让常量的值在运行期保持不变,不要使用常量会变的这个功能来实现序列号算法、随机种子生成
(4)三元表达式的类型务必一致
示例:
public static void test() { int i = 80 ; String str = String.valueOf(i < 100 ? 90 : 100 ); // 推荐 String str1 = String.valueOf(i < 100 ? 90 : 100.0 ); // 不推荐 System.out.println( "两者是否相等:" + str.equals(str1)); } |
说明:
1.若两个操作数不可转换,则不作转换,返回值是Object类型;
2.若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字转换,int转为long,long转为float等;
3.若两个操作数中有一个是数字S,另外一个是表达式,且其类型标志位T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T的范围,则T转换为S;
4.若两个操作数都是直接量数字,则返回值类型范围较大者。
(5)建议显式的声明UID
示例:
import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; public String getName() { return name; } public void setName(String name) { this .name = name; } } |
说明:
1.SerializableUID,也叫做流标识符(Stream Unique Identifier),即类的版本定义的,它可以显示声明也可以隐式声明,显式声明如上例对于serialVersionUID 的定义,隐式声明则是不声明,编译器在遍译的时候帮我生成。生成的依据是通过包名、类名、继承关系、非私有的方法和属性,以及参数、返回值等诸多因子算出来的,极度复杂,基本上计算出来的这个值是唯一的。
2.JVM在反序列化时,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,如果相同,则认为类没有改变;如果不相同,抛出异常InviladClassException。
3.显式声明UID可以在序列化对象变化的时候我们反序列化实现版本兼容
(6)switch使用注意
示例:
switch (date) { case YEAR: // do something break ; case MONTH: // do something break ; default : // do something } |
说明:
1.switch语句注意空指针异常
2.default不能少
3.case语句上记得加上break
(7)用整数类型处理货币
示例:
public static BigDecimal divide(Long amount, Long divisor) { return new BigDecimal(amount).divide( new BigDecimal(divisor), 10 , RoundingMode.HALF_DOWN); } |
说明:
1.BigDecimal是专门为弥补浮点数无法精确计算的缺憾而设计的类,并且它本身也提供了加减乘除的常用数学算法
2.把参与运算的值扩大100倍,并转为整型,然后在展现时再缩小100倍,这样处理的好处是计算简单,准确
(8)注意边界问题
示例:
public class Test { // 光速是30万公里/秒,常量 public static final int LIGHT_SPEED = 30 * 10000 * 1000 ; public static void main(String[] args) { System.out.println( "题目:太阳光照射到地球需要8分钟,计算太阳到地球的距离." ); // int dis2 = LIGHT_SPEED * 60 * 8; // 溢出 long dis2 = 1L * LIGHT_SPEED * 60 * 8 ; // 推荐 System.out.println( "太阳与地球之间的距离是:" + dis2 + " 米" ); } } |
说明:
无
(9)基本类型转换时尽量避免隐式的类型转换
示例:
public class Test { // 光速是30万公里/秒,常量 public static final int LIGHT_SPEED = 30 * 10000 * 1000 ; public static void main(String[] args) { System.out.println( "题目:太阳光照射到地球需要8分钟,计算太阳到地球的距离." ); // 可能要超出整数范围,使用long型 long dis2 = LIGHT_SPEED * 60 * 8 ; // 不推荐,未考虑类型转换 long dis3 = 1L * LIGHT_SPEED * 60 * 8 ; // 推荐 System.out.println( "太阳与地球之间的距离是:" + dis2 + " 米" ); System.out.println( "太阳与地球之间的距离是:" + dis3 + " 米" ); } } |
说明:
因为Java是先运算然后进行类型转换的,涉及到基本类型转换的时候使用主动的方式减少不必要的BUG
(10)警惕包装类型的NULL值
示例:
public static int testMethod(List<Integer> list) { int count = 0 ; for (Integer i : list) { count += (i != null ) ? i : 0 ; // 推荐 // count += i; // 不推荐 } return count; } |
说明:
基本类型和包装类型都是可以通过自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)自由转换,但是NULL值并不能转换为基本类型,所以包装类型参与运算时,要做NULL值校验
(11)警惕包装类型的比较
示例:
public static void main(String[] args) { System.out.println( "测试包装类型相等------>" ); Integer i1 = new Integer( 100 ); Integer j1 = new Integer( 100 ); isEqual(i1, j1); System.out.println( "测试包装类型相等------>" ); Integer i11 = new Integer( 100 ); Integer j11 = 100 ; isEqual(i11, j11); System.out.println( "测试包装类型相等------>" ); Integer i12 = 100 ; Integer j12 = 100 ; isEqual(i12, j12); System.out.println( "测试包装类型相等------>" ); Integer i13 = new Integer( 100 ); int j13 = 100 ; isEqual(i13, j13); System.out.println( "测试包装类型相等------>" ); Integer i14 = 100 ; int j14 = 100 ; isEqual(i14, j14); } public static void isEqual(Integer i, Integer j) { System.out.println(i == j); // 不推荐 System.out.println(i.equals(j)); // 推荐 System.out.println(i.compareTo(j)== 0 ); // 推荐 } |
说明:
1.在java中"=="是用来判断两个操作数是否有相等关系的,如果是基本类型则判断值是否相等,如果是对象则判断是否是一个对象的两个引用,也就是地址是否相等
2.在Java中,">" 和 "<" 用来判断两个数字类型的大小关系,注意只能是数字类型的判断,对于Integer包装类型,是根据其intValue()方法的返回值(也就是其相应的基本类型)进行比较的
(12)建议工具类不可实例化
示例:
class UtilsClazz{ private UtilsClazz(){ throw new Error( "Don't instantiate " +getClass()); } } |
说明:
保证了所有的访问都是通过类名来进行的,并且通过反射也不能修改构造函数
(13)警惕对象的潜拷贝
示例:
public class Person implements Cloneable { public static void main(String[] args) { // 定义父亲 Person f = new Person( "父亲" ); // 定义大儿子 Person s1 = new Person( "大儿子" , f); // 小儿子的信息时通过大儿子拷贝过来的 Person s2 = s1.clone(); s2.setName( "小儿子" ); // 大儿子认干爹 s1.getFather().setName( "干爹" ); System.out.println(s1.getName() + " 的父亲是 " + s1.getFather().getName()); System.out.println(s2.getName() + " 的父亲是 " + s2.getFather().getName()); } // 姓名 private String name; // 父亲 private Person father; public Person(String _name) { name = _name; } public Person(String _name, Person _parent) { name = _name; father = _parent; } @Override public Person clone() { Person p = null ; try { p = (Person) super .clone(); // 潜拷贝 if ( null != p.getFather()) { p.setFather((Person)(p.getFather().clone())); // 将引用对象也clone一下,间接弥补了潜拷贝的缺陷 } } catch (CloneNotSupportedException e) { e.printStackTrace(); } return p; } public String getName() { return name; } public void setName(String name) { this .name = name; } public Person getFather() { return father; } public void setFather(Person father) { this .father = father; } } |
说明:
1.基本类型:如果变量是基本类型,则拷贝其值。比如int、float等。
2.对象:如果变量是一个实例对象,则拷贝其地址引用,也就是说此时拷贝出的对象与原有对象共享该实例变量,不受访问权限的控制。
3.String字符串:这个比较特殊,拷贝的也是一个地址,是个引用,但是在修改时,它会从字符串池(String pool)中重新生成新的字符串,原有的字符串对象保持不变,在此处我们可以认为String是一个基本类型。
(14)建议覆写实体类的toString方法
示例:
/** * 使用apache包实现toString方法 */ public class BaseModel implements Serializable { private static final long serialVersionUID = 1L; @Override public String toString() { return org.apache.commons.lang.builder.ReflectionToStringBuilder.toString( this , ToStringStyle.MULTI_LINE_STYLE); } } /** * 使用反射实现toString方法 */ public class BaseDto implements Serializable { private static final long serialVersionUID = 1L; @Override public String toString() { Field[] field = this .getClass().getDeclaredFields(); if ( null != field && field.length > 0 ) { List<Field> fields = Arrays.asList(field); if ( null != fields && fields.size() > 0 ) { String result = fields.stream() .filter(f -> null != f && ! "serialVersionUID" .equalsIgnoreCase(f.getName())) .map(f -> this .getFiledVal(f, this )).collect(Collectors.joining( "," )); if ( null != result && result.length() > 0 ) { result = Optional.ofNullable( this .getClass().getSimpleName()).orElse( "" ) + " [" + result + "]" ; return result; } } } return super .toString(); } private String getFiledVal(Field field, Object obj) { String key = "" ; String value = "" ; try { if ( null != field) { field.setAccessible( true ); key = Optional.ofNullable(field.getName()).orElse( "" ); Object valObj = field.get(obj); if ( null != valObj) { value = Optional.ofNullable(valObj.toString()).orElse( "" ); } } } catch (Exception e) { e.printStackTrace(); } return key + "=" + value; } } |
说明:
可以在需要的时候输出调试信息
(15)合理使用String、StringBuffer、StringBuilder
说明:
1.String:字符串不经常变化的场景,例如常量的声明、少量变量的计算。
2.StringBuffer:频繁的字符串运算,并且运行在多线程环境中,例如HTTP参数解析和封装。
3.StringBuilder:频繁的字符串运算,并且运行在单线程环境中,例如JSON封装。
(16)注意字符串的位置
示例:
public class Test{ public static void main(String[] args) { String str1 = 1 + 2 + "apples" ; String str2 = "apples" + 1 + 2 ; System.out.println(str1); // 输出3apples System.out.println(str2); // 输出apples12 } } |
说明:
在“+” 表达式中,String字符串具有最高优先级。
(17)明确场景下为集合指定初始容量
示例:
public static void main(String[] args) { StudentVO student = null ; long begin1 = System.currentTimeMillis(); List<StudentVO> list1 = new ArrayList<>(); for ( int i = 0 ; i < 1000000 ; i++){ student = new StudentVO(i, "chenssy_" +i,i); list1.add(student); } long end1 = System.currentTimeMillis(); System.out.println( "list1 time:" + (end1 - begin1)); long begin2 = System.currentTimeMillis(); List<StudentVO> list2 = new ArrayList<>( 1000000 ); for ( int i = 0 ; i < 1000000 ; i++){ student = new StudentVO(i, "chenssy_" +i,i); list2.add(student); } long end2 = System.currentTimeMillis(); System.out.println( "list2 time:" + (end2 - begin2)); } |
说明:
避免集合扩容造成的额外性能开销
(18)使用Arrays.asList方法时要注意List对象不可改变
示例:
public class MainFacade { public static void main(String[] args) { List<Integer> list = Arrays.asList( 1 , 2 , 3 ); list.add( 5 ); // 抛出UnsupportedOperationException异常 System.out.print(list.toString()); } } |
说明:
Arrays.asList得到的ArrayList是一个继承自AbstractList同时实现了RandomAccess和Serializable接口的静态私有内部类,它的add、set、remove方法都是父类AbstractList提供,AbstractList中并没相关的具体实现而是直接抛出UnsupportedOperationException异常。
注意子列表只是元列表的一个视图
示例:
public static void main(String[] args) { // 定义一个包含两个字符串的列表 List<String> c = new ArrayList<String>(); c.add( "A" ); c.add( "B" ); // 构造一个包含c列表的字符串列表 List<String> c1 = new ArrayList<String>(c); // subList生成与c相同的列表 List<String> c2 = c.subList( 0 , c.size()); // c2增加一个元素 c2.add( "C" ); System.out.println( "c==c1? " + c.equals(c1)); System.out.println( "c==c2? " + c.equals(c2)); } |
说明:
subList产生的列表只是一个视图,所有的修改动作直接作用于原列表。
(19)提倡异常封装
示例:
/** * 运行时异常基类 */ public class BaseRuntimeException extends RuntimeException { private static final long serialVersionUID = 1L; private int code; public int getCode() { return code; } public BaseRuntimeException( int code, String message) { super (message); this .code = code; } public BaseRuntimeException(String message) { super (message); this .code = SuperErrorCodeConstants.UNKNOW_ERROR_CODE; } public BaseRuntimeException(String message, Throwable cause) { super (message, cause); this .code = SuperErrorCodeConstants.UNKNOW_ERROR_CODE; } } |
说明:
1.提高系统友好性
2.提高系统可维护性
(20)建议采用异常链传递异常
示例:
try { //doSomething } catch (Exception e){ throw new BusinessException(e); } |
说明:
异常需要封装和传递,底层不要吞噬异常(特别是不能根据异常做业务处理),在上层通过切面统一处理异常
(21)不要在finally中处理返回值
示例:
/** * 不会修改返回值,返回1 */ public static int doStuff() { int a = 1 ; try { return a; } catch (Exception e) { throw new RuntimeException(e); } finally { // 重新修改一下返回值 a = - 1 ; } return 0 ; } /** * 异常被屏蔽 */ public static void doSomeThing(){ try { //正常抛出异常 throw new RuntimeException(); } finally { //告诉JVM:该方法正常返回 return ; } } |
说明:
在finally中处理return返回值会产生一些隐蔽性的错误
(22)不要在循环条件中计算
示例:
// 不推荐 public void test1(vector vector) { for ( int i= 0 ; i < vector.size (); i++) { // do something } } // 推荐 public void test2(vector vector) { int size = vector.size () for ( int i= 0 ; i < size; i++) { // do something } } |
推荐使用guava扩展工具包
(23)代码注释应该正确、清晰、简洁
说明:
1.冗余注释、故事式注释、过时注释、大块无用的注释代码、流水账注释要去除
2.javaDoc注释不要引入过多的HTML标签影响可读性
3.未完成的代码要加上TODO注释
4.根据具体场景要加上警示性注释
(24)避免重复代码
说明:
如果在一个以上的地方看到相同的程序结构,那么设法将它们合而为一
(25)避免过长方法
说明:
如果一个方法内的大部分操作需要调用其他类的操作,应该将这个方法移至其他类中
(26)定义的变量名称,方法名称,类名称等应该具有一定的意义
说明:
不要定义类似a1,a2,abc…这类没有意义或意义不明显的变量。
(27)避免过大的类
说明:
应该按职能的不同将其细化为多个小类或子类。
如果每遇到某种变化,你都必须在许多不同的类内做许多小修改,应该把所有需要修改的代码放进同一个类中,使得“外界变化”与“需要修改的类”趋于一一对应
如果常常可以在很多地方看到相同的三四项数据(如:两类中相同的字段、许多方法签名中相同的参数),应该将这些数据项提炼到一个独立对象中
(28)避免过长的参数列表
说明:
太长的参数列表难以理解,而且容易造成前后不一致,不易使用,应该将对象传递给方法。
二、设计优化:
(1)接口职责要保持单一
说明:
这里的职责指的是一个接口(类)要承担的业务含义
(2)增强类的可替换性
说明:
只要父类可以出现的地方子类型就可以出现,并且将父类类型替换为子类型不会产生任何错误与异常,使用者不需要知道是父类型还是子类型
(3)模块之间的依赖通过抽象发生
说明:
实现类之间不要直接发生依赖关系
(4)一个类对另一个类的依赖应该建立在最小的接口上
说明:
例如,类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类C必须去实现他们不需要的方法。将臃肿的接口I拆分为独立的几个接口,类A和类C分别与它需要的接口简历依赖关系
(5)尽量降低类与类之间的耦合
说明:
陌生的类最好不要作为局部变量的形式出现在类的内部
下一篇:个人日常编码规范二