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

标签 java kotlin decompiler javacompiler kotlinc

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

让我们考虑这段代码:

// 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
    }
}

当我在 AndroidStudio 中使用“显示 Kotlin 字节码”工具,然后单击“反编译”按钮时,我看到了这段 java 代码(我删除了一些不相关的东西):

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!"));
   }
}

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

1.

// in constructor:
Intrinsics.checkParameterIsNotNull(onLoadMore, "onLoadMore");
super();

Java 要求 super 调用成为构造函数主体中的第一条语句。


2.

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

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

3.

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


4.

int $i$f$getLinearLayoutManager = false;

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


5.

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.
  ...

6.

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

为什么不让它更简单呢?

if (!this.loading && totalItemCount - lastVisibleItem <= this.visibleThreshold && (0 > this.latestPageSize || this.pageSize < this.latestPageSize)) 

7.

// Unhandled exception: java.lang.Throwable.
throw (Throwable)(new IllegalStateException("EndlessOnScrollListener requires LinearLayoutManager to be attached to RecyclerView!"));

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


这真的是在生产环境中执行的代码,还是只是 Java 反编译器无法找出所有这些东西?

最佳答案

您的大部分问题都可以用 Java != Java 字节码来回答。编译从 Java 中移除了大量仅在编译时需要的信息,字节码格式还支持许多在 Java 级别无效的内容。

回答您的具体问题:

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

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

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

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

  5. 大概是反编译器弄糊涂了,或者字节码很难反编译成有效的 Java。请记住,Java 字节码的类型检查比 Java 宽松得多,而且编译后很多编译时信息都会消失,因此将字节码反编译为有效的 Java 并不简单。

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

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

Is that really the code that is being performed in production or just Java Decompiler can't figure out all that stuff?

如果您对此主题感兴趣,我强烈建议您学习 Java 字节码的工作原理,然后使用 a Java bytecode disassembler看看引擎盖下到底发生了什么。这将允许您查看字节码中的内容以及可能是反编译的工件。

关于java - Kotlin 编译器或 Java 反编译器的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58251249/

相关文章:

Java正则表达式替换基于操作系统的文件路径

java - 将 .jar 文件转换为 JAVA DOC

c# - 如何反编译pdb得到C#源码?

Java,反编译器出现问题

Java 中 C# XmlSerializer 的 XmlIninclude 等效项

java - 可以在 Eclipse 命令行应用程序中隐藏 JVM 终止向导

kotlin - 抛弃Kotlin箭头中的嵌套选项

android - 更新到 Android Studio 北极狐,现在我得到 "Kotlin not configured"

java - 是否可以从Spring应用程序上下文中获取basePackages值?

Java反编译器给出奇怪的符号