你知道吗? ide 补全时有陷阱!!!

69 阅读4分钟

问题引入:

编码过程:

我们写了以下代码 ,IDE 提示我们添加 == true

kim image

于是我们按提示加了 == true 有了以下代码

class TestKotlin {
    var list:ArrayList<Any>? = null
}

fun main() {
    var test :TestKotlin? = TestKotlin()
    if(test?.list?.isEmpty() == true){
        //test的list属性是空的则输出 11111111
        System.out.println("11111111")
        return
    }else{
        System.out.println("22222222")
    }
    //其他逻辑....
    System.out.println("33333333")
}

问题现象:

输出了 2222222,33333333 ,如下图所示 ,逻辑并没有按我们预想的逻辑执行

kim image

问题分析:

猜想:

IDE提示添加 == true 是说明 test?.list?.isEmpty() 返回值是  Boolean?,而非 Boolean ,故需要添加 == true, 当返回值是 Boolean? 时,  == true 条件返回了 false 导致走了下面的逻辑

验证:

字节码层面 :list 为null 时不再执行 isEmpty(),会将 null 和 true 进行对比,返回 false

@Lkotlin/Metadata;(mv={1, 5, 1}, k=2, xi=48, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u00a8\u0006\u0002"}, d2={"main", "", "test"})

  // access flags 0x19
  public final static main()V
   L0  // new TestKotlin
    LINENUMBER 2 L0
    NEW TestKotlin
    DUP
    INVOKESPECIAL TestKotlin.<init> ()V
    ASTORE 0
   L1   // 获取 list 判断是 null 执行 L3,不是 null 执行 L2
    LINENUMBER 3 L1
    ALOAD 0
    ASTORE 1
    ALOAD 1
    INVOKEVIRTUAL TestKotlin.getList ()Ljava/util/ArrayList;
    ASTORE 2
    ALOAD 2
    IFNONNULL L2
    ACONST_NULL
    GOTO L3
   L2 // 执行 isEmpty 
   FRAME APPEND [TestKotlin TestKotlin java/util/ArrayList]
    ALOAD 2
    INVOKEVIRTUAL java/util/ArrayList.isEmpty ()Z
    INVOKESTATIC java/lang/Boolean.valueOf (Z)Ljava/lang/Boolean;
   L3  // 执行 equal  null == true 或者 isEmpty的返回值 == true
   FRAME SAME1 java/lang/Boolean
    ICONST_1
    INVOKESTATIC java/lang/Boolean.valueOf (Z)Ljava/lang/Boolean;
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.areEqual (Ljava/lang/Object;Ljava/lang/Object;)Z
    IFEQ L4  //当前栈顶等于 0 时跳转 L4 
   L5  // 
    LINENUMBER 4 L5
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "11111111"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GOTO L6
   L4  //输出 22222222
    LINENUMBER 6 L4
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "22222222"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L6  //输出 33333333
    LINENUMBER 8 L6
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "33333333"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L7
    LINENUMBER 9 L7
    RETURN
   L8
    LOCALVARIABLE test LTestKotlin; L1 L8 0
    MAXSTACK = 2
    MAXLOCALS = 3

  // access flags 0x1009
  public static synthetic main([Ljava/lang/String;)V
   L0
    INVOKESTATIC KtTestKt.main ()V
    RETURN
   L1
    LOCALVARIABLE args [Ljava/lang/String; L0 L1 0
    MAXSTACK = 0
    MAXLOCALS = 1
}

修正方案:

fun main() {
    var test :TestKotlin? = TestKotlin()
    if(test?.list.isNullOrEmpty()){
        System.out.println("11111111")
    }else{
        System.out.println("22222222")
    }
    System.out.println("33333333")
}

输出结果:

kim image

字节码验证:

// class version 52.0 (52)
// access flags 0x31
public final class KtTestKt {

  // compiled from: KtTest.kt

  @Lkotlin/Metadata;(mv={1, 5, 1}, k=2, xi=48, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0002\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001\u00a8\u0006\u0002"}, d2={"main", "", "test"})

  // access flags 0x19
  public final static main()V
   L0  //new TestKoltlin
    LINENUMBER 2 L0
    NEW TestKotlin
    DUP
    INVOKESPECIAL TestKotlin.<init> ()V
    ASTORE 0
   L1  //取 getList 如果是null 执行 L2,否则执行isEmpty,如果true 执行 L2 否则执行L3
    LINENUMBER 3 L1
    ALOAD 0
    ASTORE 1
    ALOAD 1
    INVOKEVIRTUAL TestKotlin.getList ()Ljava/util/ArrayList;
    CHECKCAST java/util/Collection
    ASTORE 1
    ICONST_0
    ISTORE 2
    ICONST_0
    ISTORE 3
    ALOAD 1
    IFNULL L2  //list 为null 执行 L2
    ALOAD 1  
    INVOKEINTERFACE java/util/Collection.isEmpty ()Z (itf)  //执行 isEmpty
    IFEQ L3  //栈顶是 0 执行 L3
   L2   //
   FRAME FULL [TestKotlin java/util/Collection I I] []
    ICONST_1  //将1推到栈顶 执行 L4
    GOTO L4
   L3
   FRAME SAME
    ICONST_0  //将 0 推送至栈顶 
   L4
    LINENUMBER 3 L4
   FRAME SAME1 I
    IFEQ L5  //如果栈顶是0 执行L5 否则执行L6
   L6   // 输出 1111111111
    LINENUMBER 4 L6
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "11111111"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
    GOTO L7
   L5  //输出 2222222
    LINENUMBER 6 L5
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "22222222"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L7  //执行 33333333
    LINENUMBER 8 L7
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "33333333"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L8
    LINENUMBER 9 L8
    RETURN
   L9
    LOCALVARIABLE test LTestKotlin; L1 L9 0
    MAXSTACK = 2
    MAXLOCALS = 4

  // access flags 0x1009
  public static synthetic main([Ljava/lang/String;)V
   L0
    INVOKESTATIC KtTestKt.main ()V
    RETURN
   L1
    LOCALVARIABLE args [Ljava/lang/String; L0 L1 0
    MAXSTACK = 0
    MAXLOCALS = 1
}

可以看到不管是 null 或者 empty 都会进入 L6 进行正常逻辑

总结:

Koltin 对于IDE 的 "欺骗性" 补全 要慎重,特别是 == true 的补全,要关注下 else 的异常逻辑。