Kotlin 编译器或 Java 反编译器的奇怪行为

GV_*_*Qst 6 java decompiler kotlin javacompiler kotlinc

这个问题只是出于我的好奇心,所以我想得到一个完整的答案,而不是简单的“是”或“否”。

让我们考虑这段代码:

// Is stored in util files and used to omit annoying (this as? Smth)?.doSmth()
inline fun <reified T> Any?.cast(): T? {
    return this as? T
}

class PagingOnScrollListener(var onLoadMore: (currentPage: Int, pageSize: Int) -> Unit) : RecyclerView.OnScrollListener() {

    constructor() : this({ _, _ -> Unit })

    private var loading = false
    private var currentPage = 0
    private var latestPageSize = -1

    var visibleThreshold = VISIBLE_THRESHOLD_DEFAULT
    var pageSize = PAGE_SIZE_DEFAULT

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        val linearLayoutManager = recyclerView.linearLayoutManager

        val totalItemCount = linearLayoutManager.itemCount
        val lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition()

        if (!loading && totalItemCount - lastVisibleItem <= visibleThreshold
                && latestPageSize !in 0 until pageSize) {
            currentPage++
            loading = true
            onLoadMore(currentPage, pageSize)
        }
    }

    private inline val RecyclerView.linearLayoutManager
        get() = layoutManager?.cast<LinearLayoutManager>()
                ?: throw IllegalStateException("PagingOnScrollListener requires LinearLayoutManager to be attached to RecyclerView!")

    companion object {
        private const val VISIBLE_THRESHOLD_DEFAULT = 4
        private const val PAGE_SIZE_DEFAULT = 10
    }
}
Run Code Online (Sandbox Code Playgroud)

当我在AndroidStudio中使用“Show Kotlin Bytecode”工具,然后点击“Decompile”按钮时,我看到了这个java代码(我删除了一些不相关的东西):

// Is stored in util files and used to omit annoying (this as? Smth)?.doSmth()
inline fun <reified T> Any?.cast(): T? {
    return this as? T
}

class PagingOnScrollListener(var onLoadMore: (currentPage: Int, pageSize: Int) -> Unit) : RecyclerView.OnScrollListener() {

    constructor() : this({ _, _ -> Unit })

    private var loading = false
    private var currentPage = 0
    private var latestPageSize = -1

    var visibleThreshold = VISIBLE_THRESHOLD_DEFAULT
    var pageSize = PAGE_SIZE_DEFAULT

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        val linearLayoutManager = recyclerView.linearLayoutManager

        val totalItemCount = linearLayoutManager.itemCount
        val lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition()

        if (!loading && totalItemCount - lastVisibleItem <= visibleThreshold
                && latestPageSize !in 0 until pageSize) {
            currentPage++
            loading = true
            onLoadMore(currentPage, pageSize)
        }
    }

    private inline val RecyclerView.linearLayoutManager
        get() = layoutManager?.cast<LinearLayoutManager>()
                ?: throw IllegalStateException("PagingOnScrollListener requires LinearLayoutManager to be attached to RecyclerView!")

    companion object {
        private const val VISIBLE_THRESHOLD_DEFAULT = 4
        private const val PAGE_SIZE_DEFAULT = 10
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里我们可以看到一些奇怪的代码:

1.

public final class PagingOnScrollListener extends RecyclerView.OnScrollListener {
   private boolean loading;
   private int currentPage;
   private int latestPageSize;
   private int visibleThreshold;
   private int pageSize;
   @NotNull
   private Function2 onLoadMore;
   private static final int VISIBLE_THRESHOLD_DEFAULT = 4;
   private static final int PAGE_SIZE_DEFAULT = 10;

   public PagingOnScrollListener(@NotNull Function2 onLoadMore) {
      Intrinsics.checkParameterIsNotNull(onLoadMore, "onLoadMore");
      super();
      this.onLoadMore = onLoadMore;
      this.latestPageSize = -1;
      this.visibleThreshold = 4;
      this.pageSize = 10;
   }

   public PagingOnScrollListener() {
      this((Function2)null.INSTANCE);
   }

   public void onScrolled(@NotNull RecyclerView recyclerView, int dx, int dy) {
      Intrinsics.checkParameterIsNotNull(recyclerView, "recyclerView");
      super.onScrolled(recyclerView, dx, dy);
      int $i$f$getLinearLayoutManager = false;
      RecyclerView.LayoutManager var10000 = recyclerView.getLayoutManager();
      if (var10000 != null) {
         Object $this$cast$iv$iv = var10000;
         int $i$f$cast = false;
         var10000 = $this$cast$iv$iv;
         if (!($this$cast$iv$iv instanceof LinearLayoutManager)) {
            var10000 = null;
         }

         LinearLayoutManager var10 = (LinearLayoutManager)var10000;
         if (var10 != null) {
            LinearLayoutManager linearLayoutManager = var10;
            int totalItemCount = linearLayoutManager.getItemCount();
            int lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
            if (!this.loading && totalItemCount - lastVisibleItem <= this.visibleThreshold) {
               int var11 = this.pageSize;
               int var12 = this.latestPageSize;
               if (0 <= var12) {
                  if (var11 > var12) {
                     return;
                  }
               }

               int var10001 = this.currentPage++;
               this.loading = true;
               this.onLoadMore.invoke(this.currentPage, this.pageSize);
            }

            return;
         }
      }

      throw (Throwable)(new IllegalStateException("EndlessOnScrollListener requires LinearLayoutManager to be attached to RecyclerView!"));
   }
}
Run Code Online (Sandbox Code Playgroud)

Java 要求supercall 是构造函数体中的第一条语句。


2.

this((Function2)null.INSTANCE);对应constructor() : this({ _, _ -> Unit }) 什么null.INSTANCE意思?为什么没有预期的匿名对象?

// in constructor:
Intrinsics.checkParameterIsNotNull(onLoadMore, "onLoadMore");
super();
Run Code Online (Sandbox Code Playgroud)

3.

@Override方法上没有注释onScrolled。使用override修饰符向方法添加注释是否太难了?但是@NonNull@Nullable注释存在。


4.

int $i$f$getLinearLayoutManager = false;
Run Code Online (Sandbox Code Playgroud)

Boolean值被分配给int变量?为什么这条线出现在这里?这个变量没有用。为什么它声明了一个不会被使用的变量?


5.

this(new Function2() {
  @Override
  public Object invoke(Object o1, Object o2) {
    return kotlin.Unit.INSTANCE;
  }
});
Run Code Online (Sandbox Code Playgroud)

6.

int $i$f$getLinearLayoutManager = false;
Run Code Online (Sandbox Code Playgroud)

为什么不让它更简单呢?

RecyclerView.LayoutManager var10000 = recyclerView.getLayoutManager();
if (var10000 != null) {
  Object $this$cast$iv$iv = var10000; // what's the purpose of this assignment?
  int $i$f$cast = false;
  var10000 = $this$cast$iv$iv; // Incompatible types. RecyclerView.LayoutManager was expected but got Object.
  ...
Run Code Online (Sandbox Code Playgroud)

7.

if (!this.loading && totalItemCount - lastVisibleItem <= this.visibleThreshold) {
  int var11 = this.pageSize;
  int var12 = this.latestPageSize;
  if (0 <= var12) {
    if (var11 > var12) {
      return;
    }
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

如果我们知道,为什么它会转换IllegalStateException为?目的是什么?ThrowableIllegalStateException extends Throwable


那真的是在生产中执行的代码还是只是 Java Decompiler 无法弄清楚所有这些东西?

Ant*_*ony 6

您的大多数问题都可以用 Java != Java 字节码来回答。编译从 Java 中删除了很多只在编译时才需要的信息,字节码格式也支持很多在 Java 级别无效的东西。

要回答您的具体问题:

  1. Java 需要这个,但 Java 字节码没有这样的限制。据推测,Kotlin 知道参数不应该为 null 导致编译器插入代码在运行时进行检查。由于字节码在超级构造函数调用之前自由允许代码(有一些关于访问未初始化对象的警告),因此在您尝试反编译之前没有问题。

  2. 这看起来像是 Kotlin 特定的功能,所以我不确定。

  3. 有些注释保留在字节码中,有些则没有。@Override没有运行时行为,仅用作编译时检查,因此将其设置为仅编译时是有意义的。

  4. 在字节码级别,没有布尔值这样的东西(除了方法签名)。所有布尔(以及 char 和 short 和 byte)局部变量都被编译为整数,false = 0 和 true = 1。这意味着反编译器必须猜测任何给定的变量是整数还是布尔值,即一项非常艰巨的任务,不可能总是正确的。

  5. 大概是反编译器混淆了,或者字节码很难反编译成有效的 Java。请记住,Java 字节码的类型检查比 Java 松散得多,并且编译后很多编译时信息都消失了,因此将字节码反编译为有效的 Java 并不容易。

  6. 因为反编译器没有被编程来做这种简化?您可以尝试要求反编译器作者添加它,但这比您想象的要困难得多。

  7. 不查看字节码就不可能确定,但​​ Throwable 转换可能是由反编译器添加的。请记住,字节码和 Java 源代码是不兼容的格式,反编译不是一种精确的转换。

那真的是在生产中执行的代码还是只是 Java Decompiler 无法弄清楚所有这些东西?

如果您对这个主题感兴趣,我强烈建议您学习 Java 字节码的工作原理,然后使用Java 字节码反汇编器查看幕后实际发生的情况。这将允许您查看字节码中的内容以及可能是反编译的工件。