《100 Java Mistakes and How to Avoid Them》笔记 1
source link: https://yanbin.blog/100-java-mistakes-and-how-to-avoid-them-notes-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
《100 Java Mistakes and How to Avoid Them》笔记 1
这几日在阅读 Manning 出版社的 《100 Java Mistakes and How to Avoid Them》, 其中列举的确实是一些容易带入到代码中的错误,不少还是通过代码 Review 或单元测试很难发现的问题。也有些看似很弱智,却可能是隐匿许久的定时炸弹,只等某一特定条件出现时即爆。
阅读的同时简单的作了笔记及少许联想,所以内容有些杂乱无条理。最前面介绍了一些静态代码分析工具,也有两个动态分析工具。本书目前还是 Manning 的 MEAP 体验版,未正式发售。一共讲了 100 个常见错误如何避免(例如,怎么用最新 Java(Java 9 -- Java 21) 语法, API 来改进),以及用静态分析工具,单元测试及早发现。
这是读完了 1/4 数量的记录,笔记开始
-
SonarLint: https://www.sonarsource.com/products/sonarlint, 还能与 SonarQube 集成
-
Error Prone: https://errorprone.info, Google 开发的 Java 编译插件, 所以还能与 Maven 或 Gradle 等集成
-
PVS-Studio: https://www.viva64.com/en/pvs-studio, 付费项目
-
SpotBugs: https://spotbugs.github.io, 静态分析 Java 字节码
-
Coverity: https://www.synopsys.com/software-integrity/security-testing/static-analysis-sast.html, Synopsis 出品
-
Klocwork: https://www.perforce.com/products/klocwork, 热加载不重启调试的工具 JRebel 就是他们家的
-
CodeQA: https://codeql.github.com, 如果项目存储在 GitHub 上,可用该工具分析
-
Error Prone: com.google.errorprone.annotations
-
Checker: org.checkerframework
-
JetBrains: org.jetbrains.annotations 和 org.intellij.lang.annotations
-
Android: androidx.annotation
-
JDT: org.eclipse.jdt.annotation
-
JCIP(book: Java Concurrency In Practise): net.jcip
-
JSR 305: javax.annotation 最终未没采用
-
FindBugs/SpotBugs: edu.umd.cs.findbugs.annotations, 它沿袭了一些 JSR 305 的注解
if (monthYear == null) { throw new NullPointerException("monthYear is marked non-null but is null"); |
-
NASA 开源的 Java Pathfinder, 它可用来检测数据竞争,未处理的异常,可能的失败断言,它配置起来很复杂
-
Dl-Check,它使用 Java agent, 主要用来发现潜在线程死锁,使用很简单,比如配置一个 maven 插件就行
assert condition; assert condition: explanation; |
>>> "a" + 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can only concatenate str (not "int") to str |
++
操作jshell> "User not found: " + ...> + '"' + "Tiger" + '"'; $1 ==> "User not found: 34Tiger\"" |
x+=1
-> x = x+1
, x =+1
=> x = (+1)
, 这里的加号 + 其实是个正的符号,减号也一样要注意jshell> x=1 x ==> 1 jshell> x += 1 $6 ==> 2 jshell> x =+1 x ==> 1 |
Double valueOrZero(boolean condition, Double value) { return condition ? value: 0.0; |
condition ? value: 0.0
不受方法返回值 Double 类型的影响Double valueOrZero(boolean condition, Double value) { return Double.valueOf( condition ? value.doubleValue() : 0.0); |
jshell> Double valueOrZero(boolean condition, Double value) { ...> return condition ? value : 0.0; ...> } | modified method valueOrZero(boolean,Double) jshell> valueOrZero(true, null) | Exception java.lang.NullPointerException: Cannot invoke "java.lang.Double.doubleValue()" because "<parameter2>" is null | at valueOrZero (#12:2) | at (#13:1) jshell> valueOrZero(false, null) $14 ==> 0.0 |
javap -c
反编译看字节码,比如上面的 valueOrZero 方法反编译出来是 java.lang.Double valueOrZero(boolean, java.lang.Double); Code: 0: iload_1 1: ifeq 11 4: aload_2 5: invokevirtual #7 // Method java/lang/Double.doubleValue:()D 8: goto 12 11: dconst_0 12: invokestatic #13 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double; 15: areturn |
Integer mapValue(int input) { return input > 20 ? 2 : input > 10 ? 1 : null; |
java.lang.Integer mapValue(int); Code: 0: iload_1 1: bipush 20 3: if_icmple 10 6: iconst_2 7: goto 27 10: iload_1 11: bipush 10 13: if_icmple 23 16: iconst_1 17: invokestatic #17 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 20: goto 24 23: aconst_null 24: invokevirtual #22 // Method java/lang/Integer.intValue:()I 27: invokestatic #17 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 30: areturn |
return Integer.valueOf(input > 20 ? 2: (input > 10 ? Integer.valueOf(1) : null).intValue()); |
return input > 20 ? 2: input > 10 ? 1 : null; |
&&
或 ||
, 小心把它们误写成了非短路的 &
和 |
操作check1() || check2() || check3() // 不要写成了 check1() | check2() || check3() |
&
和 |
.if(updateA() && updateB())){ ... } |
if(updateA() & updateB()){ .... } |
!(a || b)
相当于 !a && !b
, 所以对于已有 ||
操作,如if(line.startsWith("#") || line.startsWith("//")) {...} |
!
, 而写成if(!(line.startsWith("#") || line.startsWith("//")) {...} |
if( !line.startsWith("#") || !line.startsWith("//")) {...} // 这是不对的 |
if( !line.startsWith("#") && !line.startWith("//")) {...} // 正确写法 |
!( a && b)
相当于 !a || !b
void printAll(Object... data) { for (Object d: data) { System.out.println(d); |
printAll("Hello", "World"); printAll(new Object[] {"Hello", "World"}); |
Object[]
的时候是内联的方式传参的,如果是先在某处声明了,过后再传入的Object[] obj = new Object[]{"Hello", "World"}; ....... // 中间一段其他代码 printAll(obj); |
printAll(new Object[]{"Hello", "World"})
是一样的Object obj = new Object[]{"Hello", "World"}; printAll(obj); |
[Ljava.lang.Object;@548ad73b |
new Object[]{"Hello", "World"}
整体作为一个参数传给了 printAll() 方法,调用上相当于printAll(new Object[]{new Object[]{"Hello", "World"}}); |
printAll((Object)null); // data 为 [null] printAll((Object[])null); // data 为 null |
*
打散,如 foo(*["Hello", "World"])String[] obj = new String[]{"Hello", "World"}; printAll(obj); |
printAll("Hello", "World");
的效果,但会出现警告Test.java:4: warning: non-varargs call of varargs method with inexact argument type for last parameter; printAll(obj); cast to Object for a varargs call cast to Object[] for a non-varargs call and to suppress this warning 1 warning Hello World |
Arrays.asList(new int[]{1,2,3}); // 输出 [[I@2d127a61] Arrays.asList(new Integer[]{1,2,3}); // 输出 [1, 2, 3] |
default void replaceAll(UnaryOperator<E> operator) { Objects.requireNonNull(operator); final ListIterator<E> li = this.listIterator(); while (li.hasNext()) { li.set(operator.apply(li.next())); |
list.replaceAll(String::trim); |
Data data; if (condition1) { data = getData1(); } else if( condition2) { data = getData2(); } if (condition3) { // 这样写没有语法错误,但会造成上面的 data 赋值被忽略,这里的 if 与上面的 if/else 不成一块,在新的一行里写就容易看出问题来 data = getData3(); } else if (condition4) { data = getData4(); } else { data = getDefaultData(); process(data); |
final Data data
, 上面的代码就不能通过编译,因为 data 会被赋值多次if (obj instanceof Number) { if (object instanceof BigInteger) { ...... // 总是被 obj instanceof Number 阻挡住了 |
return switch(obj) { case Number number -> BigInteger.valueOf(number.longValue()); // 这里会有编译错误,this case label is dominated by a preceding case label case BigInteger bigInteger -> bigInteger case null, default -> BigInteger.ZERO |
if (age >= 6 ) return CHILD; if (age >= 18) return FULL; |
if (age >=6 && age <=17 ) return CHILD; if (age >= 18) return FULL throw new IllegalArgumentException("Wrong age: " + age); // 测试中未考虑的情况会抛出异常,然后回来补充更多的区间 |
switch(button) { case YES -> actOnYes(); case No -> actOnNo(); case CANCEl -> actOnCancel(); |
->
用法的 switch 相当于每个 case 后都会自动加上 break
.final Boolean answer; switch(ch) { case 'T': case 't': answer = true; case 'F': case 'f': anser = false; // complilation error: answer is reassigned break; default: anser = null; |
->
的 switch 语法. 现代 IDE 能自动完成到 switch ->
的转换final Boolean anser = switch(ch) { case 'T', 't' -> true; case 'F', 'f' -> false; default -> null; |
for (int i=lo; i<=hi; i++)
, 如 hi 是 Integer.MAX_VALUE 或更大的值(Long), 则 i<=hi 将永远是 true, i++ 溢出后变成负数class MyHandler { public static final MyHandler INSTANCE = new MyHandler(); // 1 private static final Logger logger = Logger.getLogger(MyHandler.class); // 2 private MyHandler() { } catch (Exception ex) { logger.error("initialization error", ex); // 这里会出现 NullPointerException |
abstract class ParentClass { private int id = generatedId(); abstract int generatedId(); class SubClass extends ParentClass { Random random = new Random(); @Override int generatedId() { return random.nextInt(); new SubClass(); // NullPointerException, random is null |
class A { public static final A INSTANCE = new B(); class B extends A{ |
values()
方法或 switch
作用在当前枚举类型,枚举实际的初始化过程是enum DayOfWeek { SUN, MON, TUE, WED, THU, FRI, SAT; |
public static final DayOfWeek SUN = new DayOfWeek(0); // 0 是 original, 枚举中的其他构造参数会放到 int original 参数之前 |
super.onKeyDown(event)
调用父类相同方法的设计是很差劲的设计,更应该用抽象方法。new ArrayList<>(existingList.size())
, 预先指定 List 的大小很多时候是没有帮助的,因为在往其中添加元素时接近(不是到达) List 容量时就会扩容。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK