kotlin使用let报java.lang.NoClassDefFoundError

问题阐述

如下代码:

  private fun shareUrlToFriend(logoUrl: String) {
        activity?.let {
            Glide.with(this)
                    .asBitmap()
                    .load(logoUrl)
                    .into(object : CustomTarget() {
                        override fun onLoadCleared(placeholder: Drawable?) {

                        }

                        override fun onResourceReady(resource: Bitmap, transition: Transition?) {
                            print(logoUrl)//就是一个方法使用了logoUrl

                        }
                    })
        }
    }

运行这段代码报java.lang.NoClassDefFoundError错误(表示运行中找不到类的定义)。经过替换尝试,报错不是Glide的锅。根据kotlin默认最后一行是返回值的规则,这代码最后let下面最后一个返回对象是CustomTarget的匿名内部类对象。因为:

// Glide into()方法
@NonNull
  public > Y into(@NonNull Y target) {
    return into(target, /*targetListener=*/ null, Executors.mainThreadExecutor());
  }

所以,可以把上面代码替换成如下简单代码:

class T {
    var a: Any? = null
    fun f(u: String) {
        a?.let {
            object : Inter {
                override fun e() {
                    print(u)
                }
            }
        }
    }

}

fun main() {
    val t = T()
    t.a = Any()
    t.f("u")
}

interface Inter {
    fun e()
}

这段执行f()函数代码会报一样的错误。

Exception in thread "main" java.lang.NoClassDefFoundError: com/a/wzm/shere/ui/T$f$1$1
    at com.a.wzm.shere.ui.T.f(Test.kt:14)
    at com.a.wzm.shere.ui.TestKt.main(Test.kt:28)
    at com.a.wzm.shere.ui.TestKt.main(Test.kt)
Caused by: java.lang.ClassNotFoundException: com.a.wzm.shere.ui.T$f$1$1
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    ... 3 more

Process finished with exit code 1

T$f$1$1是个什么鬼?

问题追溯

查看翻译后的Java代码:

//NO.0   主要看f()函数。
public final void f(@NotNull String u) {
      Intrinsics.checkParameterIsNotNull(u, "u");
      if (this.a != null) {
         boolean var3 = false;
         boolean var4 = false;
         int var6 = false;
         1 var10000 = (1)(new T$f$$inlined$let$lambda$1(u));// 1是啥?
      }

   }

里面这个1就是不没定义的类型(也就是报错里面需要定义的T$f$1$1)。为什么有个1出来捣乱?

//生成的类。
@Metadata(
   mv = {1, 1, 15},
   bv = {1, 0, 3},
   k = 1,
   d1 = {"\u0000\u0011\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u0002\n\u0000*\u0001\u0000\b\n\u0018\u00002\u00020\u0001J\b\u0010\u0002\u001a\u00020\u0003H\u0016¨\u0006\u0004¸\u0006\u0000"},
   d2 = {"com/a/wzm/shere/ui/Te$f$1$1", "Lcom/a/wzm/share/ui/Inter;", "a", "", "app"}
)
public final class Te$f$$inlined$let$lambda$1 implements Inter {
   // $FF: synthetic field
   final String $u$inlined;

   Te$f$$inlined$let$lambda$1(String var1) {
      this.$u$inlined = var1;
   }

   public void a() {
      String var1 = this.$u$inlined;
      boolean var2 = false;
      System.out.print(var1);
   }
}

类名跟注解里的("com/a/wzm/share/ui/Te$f$1$1")不一致。

实验

对上诉代码简单修改测试。发现只需要简单修改就不报错。比如:

  1. 不用 let第一行用if(a==null)return。(规避了let的问题,不讨论)。
  2. a后面的?去掉。
  3. 方法e()里不使用u
  4. object: Inter前面加上val x=进行赋值掉。
  5. let最后一行写个1,true或其他明确类型的东西。

而这样做,报一样错误。

  1. a?.let前面加val c=进行赋值操作。此时就算?(如2所诉) 去掉也报错。结合3,4,5不报错。

把上诉实验通过转换,查看翻译后的Java代码:

// NO.2
 public final void f(@NotNull String u) {
      Intrinsics.checkParameterIsNotNull(u, "u");
      Object var2 = this.a;
      boolean var3 = false;
      boolean var4 = false;
      int var6 = false;
      new T$f$$inlined$let$lambda$1(u);
   }
//NO.3
public final void f(@NotNull String u) {
      Intrinsics.checkParameterIsNotNull(u, "u");
      if (this.a != null) {
         boolean var3 = false;
         boolean var4 = false;
         int var6 = false;
         new T$f$1$1();
      } else {
         Object var10000 = null;
      }
   }
//NO.4
public final void f(@NotNull String u) {
      Intrinsics.checkParameterIsNotNull(u, "u");
      if (this.a != null) {
         boolean var3 = false;
         boolean var4 = false;
         int var6 = false;
         new T$f$$inlined$let$lambda$1(u);
      }

   }
//NO.5,最后一行加了个true
public final void f(@NotNull String u) {
      Intrinsics.checkParameterIsNotNull(u, "u");
      if (this.a != null) {
         boolean var3 = false;
         boolean var4 = false;
         int var6 = false;
         new T$f$$inlined$let$lambda$1(u);
         boolean var10000 = true;
      }

   }
//NO.6 又出现未知类型:1
public final void f(@NotNull String u) {
      Intrinsics.checkParameterIsNotNull(u, "u");
      Object var3 = this.a;
      boolean var4 = false;
      boolean var5 = false;
      int var7 = false;
      1 x = (1)(new T$f$$inlined$let$lambda$1(u));
   }
区别 匿名类 是否出现类型(1)
NO.0(原始) T$f$$inlined$let$lambda$1
NO.2 T$f$$inlined$let$lambda$1
NO.3 T$f$1$1
NO.4 T$f$$inlined$let$lambda$1
NO.5 T$f$$inlined$let$lambda$1
NO.6 T$f$$inlined$let$lambda$1

首先分析第4点和第5点。第4点把匿名类对象赋值给了x,这意味这let只能取下一行做返回值(没有下行,就是Unit)。所以也就是说let有明确返回值就不报错。
没有明确返回值类型且“不关心”返回值(如NO.2),也不会错。NO.0也不“关心”返回值啊?可是NO.0又对a的空判断,对let返回又两种结果,要么有返回,要么没返回,将也它归纳为“关心”结果。
至于NO.3的情况(就算结合NO.6也不报错),生成的类就是NO.0中报错中没定义的类(为什么会这样,稍后讨论)。所以也就没问题了。

问题1

为什么let“关心”返回值会有区别?let的返回值究竟是个啥?

分析

首先,查看let方法的定义。

@kotlin.internal.InlineOnly
public inline fun  T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

let 是泛型的扩展方法,参数是个高阶函数。作用是将一种类型T,通过block方法变换变成类型R

所以let返回值是个R。那R是什么呢?

当然是自己定喽。我们一直都是直接用let,实际上严格用法应该这样:

    val a = 1
    val s = a.let {
        return@let it.toString()
    }

只不过,kotlin的自动类型识别帮我们做了类型区分。

问题中的类型1估计就是不明确的R。所以我们手动指定累行试试。

fun f(u: String) {
       a?.let {
            object : Inter {
                override fun a() {
                    print(u)
                }
            }
        }
    }

对应的Java代码。

public final void f(@NotNull String u) {
     Intrinsics.checkParameterIsNotNull(u, "u");
     if (this.a != null) {
        boolean var3 = false;
        boolean var4 = false;
        int var6 = false;
        Inter var10000 = (Inter)(new T$f$$inlined$let$lambda$1(u));
     }
  }

原本是1的地方变成了我们指定的类型Inter。代码跑起来也不在跌跟斗。

那为什么“不关心”结果的时候翻译过来就不需要转 R 的类型呢?(目前找到的解释是编译器优化掉明确不需要的过程)

问题2

看这个问题之前,我们应该发现了,let里的代码直接被拷到f()函数里面,而不是生成高阶函数block: (T) -> R表示的接口的实现类。而且Inter本该内部类的实现,也变成了定义了外部独立的类。为什么会这样?

解释

这是因为inline关键字的作用:inline 的工作原理就是将内联函数的函数体复制到调用处实现内联。详情见参考资料。

实验

写一个没有inline的仿let方法,再替换原let

fun  T.mylet(block: (T) -> R): R {
    return block(this)
}
//No.7
fun f(u: String) {
        a?.mylet {
            object : Inter {
                override fun a() {
                    print(u)
                }
            }
        }
    }

运行不报错,看法Java代码。

//NO.7
public final void f(@NotNull final String u) {
      Intrinsics.checkParameterIsNotNull(u, "u");
      Object var10000 = this.a;
      if (var10000 != null) {
          var2 = ()TestKt.mylet(var10000, (Function1)(new Function1() {
            // $FF: synthetic method
            // $FF: bridge method
            public Object invoke(Object var1) {
               return this.invoke(var1);
            }

            @NotNull
            public final  invoke(@NotNull Object it) {
               Intrinsics.checkParameterIsNotNull(it, "it");
               return new Inter() {
                  public void a() {
                     String var1 = u;
                     boolean var2 = false;
                     System.out.print(var1);
                  }
               };
            }
         }));
      }

   }

内部用Function1 代表block: (T) -> R表示的接口的实现类。Inter依旧是内部类的方式实现。

可见,inline关键字在处理方法体中的内部类,做了明显的优化处理(即生成一个独立的外部类)。

问题3

结合问题1中的NO.3和问题2以及实验,发现Inter中用到let外部信息时,生成的类是T$f$$inlined$let$lambda$1(但后面还会强转为T$f$1$1,也就是那个1,然后报找不到类的定义的错误),而不使用外部的信息时,生成的类是T$f$1$1。区分度是什么?

猜想

通过inline内联函数传入的lambda表达式生成的匿名类,如果有指向外部的变量,那么命名为:class + method + inlined + method + lambda + number。如果没有,命名为: class + method + number + number(第一个number表示let同层级编号,第二个number表示内部类的编号。而然运行中外部类在执行checkcast的时候,还是按照旧的规则去组装命名。(仅为猜想,未得原因)

结论

其实还没有具体结论!遇到此类问题,大胆猜想,动手实践,总结规律。

kotlin使用let报java.lang.NoClassDefFoundError_第1张图片
配一张图

你可能感兴趣的:(kotlin使用let报java.lang.NoClassDefFoundError)