声明局部变量时便初始化。 同时偏向于使用for循环而不是while循环,使用while循环会造成一些复制粘贴错误,比如:
Iterator<Element> i = c.iterator(); while (i.hasNext()) { doSomething(i.next()); } ... Iterator<Element> i2 = c2.iterator(); while (i.hasNext()) { // BUG! doSomethingElse(i2.next()); }
上面的代码编译运行都不会有任何问题,就是因为变量i的作用域太广了,导致后面的while循环也可以使用,如果使用for循环:
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) { Element e = i.next(); ... // Do something with e and i } ... // Compile-time error - cannot find symbol i for (Iterator<Element> i2 = c2.iterator(); i.hasNext(); ) { Element e2 = i2.next(); ... // Do something with e2 and i2 }
代码在编译时就会出现问题,同时for循环代码也更简洁一点。
由于传统的for循环和迭代器会涉及到数组下标或者是是否还有元素的问题,在更关注元素本身的情况下使用for-each会让代码更加简洁,同时避免bug出现的几率。下面三种情况不适合使用 foreach
:
foreach
支持循环所有实现 Iterable
接口的对象,总的来说就是如果更加关注循环元素本身并且不需要操作集合元素(删除等等)的话使用 foreach
。
使用标准库可以充分利用开发者和其他人的经验,避免踩坑。java7之后的随机数生成器使用 ThreadLocalRandom
,性能会提升很多。而对fork join和并行流则可以使用 SplittableRandom
。
使用标准库的好处:
由于开发人员并不知道每次更新加入的新功能和特性,尽管有上面那么多的好处,但是却很少开发人员这样做。所以需要对版本的更迭和新特性保持持续关注,同时也需要对java标准类库的熟悉: java.utl java.lang java.io
及其子包。
通常开发中如果Java标准类库中没有可用功能可以寻找第三方的类库,如果还是没有则自己实现。不要重复造轮子!!!
避免在需要返回具体值的地方使用float和double
通常 float
和 double
用来做科学和工程计算的,都用于浮点计算,并且在货币计算上面很难使用。正确的货币计算应该使用 int
long
或者 BigDecimal
,但 BigDecimal
使用上比基本类型麻烦,或者在货币计算中统一使用最小的货币单位来进行计算。
总的来说就是在需要具体值的时候不使用 double
和 float
,根据实际的数据长度来选择使用 int
long
或者 BigDecimal
。
偏向于使用基本类型而不是其包装类
基本类型和其包装类型有几点不同:
// Broken comparator - can you spot the flaw? Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1); naturalOrder.compare(new Integer(42), new Integer(42));
上面的代码会返回1,因为第一次比较出现自动拆箱,42==42,第二次比较==但是却比较的是两个包装类返回false,所以包装类使用==比较永远是错误的。
在混用包装类和基本类型的时候总是会出现包装类的自动拆箱,所以要非常小心包装类是否为null。
// Hideously slow program! Can you spot the object creation? public static void main(String[] args) { Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); }
上面代码结果输出没什么问题,但是性能上却很差,因为sum是一个包装类,而循环中的i是基本类型,循环中反复拆装箱导致性能问题。
但也有必须使用包装类的时候:
虽然自动拆装箱减少了很多麻烦,但是并没有代表其是安全的,正确的选择基本类型和包装类型来避免空指针和性能问题。
在可选的情况下避免使用String
String本身的表达是不够强的,本身是设计用来表达字符本身的,对于其他类型的值就难以表达。枚举和其他聚合类都很难用String来代替,并且缺乏功能性。
总的来说避免使用String来代替其他类型,特别是误用在枚举、基本类型和聚合类中,这样使用不够灵活,慢,同时更容易出现问题。
留意字符连接的性能
+
拼接固定长度的字符尚可,但是如果出现在循环中就会造成严重的性能问题,这种情况可以使用 StringBuilder
,并且初始化长度。
如果有合适的接口存在,最好把参数、返回、属性、变量都声明成接口类型。比如:
// Good - uses interface as type Set<Son> sonSet = new LinkedHashSet<>();
而不是:
// Bad - uses class as type! LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
习惯上面的会让代码更加灵活,同时可以快速的切换不同的实现。 但如果没有合适的接口或者要使用实现类中特有的功能那么还是引用原类型,比如 String
, Integer
这种值对象是没有一个公共的接口的。
总的来说如果存在这么一个接口,同时又不依赖于某个实现特有的功能那么引用接口是更为合适的。
反射的不足:
适当的使用反射能够起到很好的效果,比如需要执行在编译期无法知道的类,就可以使用反射来创建对象,并且通过他的接口或者父类方法来操作。
在反射的代码中有很多检查异常,可以使用 ReflectiveOperationException
来捕获,它是反射异常的一个公共父类。
总的来说反射是个很强大的工具,但同时也有很多缺点,如果需要使用反射,最好只使用反射来初始化对象,对象的操作使用它的接口或者父类来处理。
正确的使用本地方法
Java本地方法(JNI)主要有3个用途:
通常不建议使用本地方法来提高程序性能,使用本地方法是不安全的,因为使用本地方法的代码不在对内存问题免疫,本地方法比java本身更加依赖于平台;同时本地方法很难调试,如果不小心很可能降低性能,因为gc无法去处理本地方法的内存。
总的来说谨慎使用本地方法,本地方法中一个小的bug可能导致程序的崩溃。
正确的优化代码
不要牺牲合理的设计和结构来交换性能,专注于编写好的程序而不是快的程序,但这并不是说就不需要考虑性能,实现可以修改和优化,但是架构本身的缺陷和漏洞除了重构无法优化。
努力避免限制性能的设计和代码,特别是API等等底层代码,这些代码后期的优化和修改十分困难,并且影响很大。设计时多考虑性能上的问题,比如使用接口而不是实现类,不然就会把自己绑定到实现类上,从而忽略了性能更好的实现。
不要为了性能而去封装性能差的API,API性能差可以随着版本迭代而修复,但是你封装的代码却永远是这样。
优化代码的前后都需要测试性能,往往你想要优化的代码不会有什么性能提升甚至性能会更差,所以每一个优化都需要进行前后对比。如果你的代码用了不同的实现或者需要跑在不同的硬件平台上,你需要更多的时间去优化程序性能。
总的来说就是专注于编写好的代码,同时注意性能问题,但是编写API或者其他底层公共代码则需要考虑性能问题;测试性能,如果代码性能不够好,定位耗时的代码并且优化它,每一个优化都一定要经过测试。
JAVA有两类命名规范:排版规范和语法规范, 打破命名规范会让代码变得难以阅读和掌握,同时可能会引起误解。
排版规范:
语法规范:
Thread
, PriorityQueue
,而不可实例化的类通常是由复数形式的名词构成,比如 Collectors
, Collections
; Runnable
, Iterable
; 总的来说,学习标准的命名并深入了解和使用,排版规范更直接和清楚,语法规范则更为复杂和宽松。
这一章很贴近平时编程从代码命名规范,接口、方法设计,API使用到后面的代码优化都提供了很多的建议,还是可以帮助我们规范不少平时的编码习惯。
BugHome版权所有丨转载请注明出处:https://minei.me/archives/502.html