6

这个问题是由我的好奇心驱使的,所以我希望得到一个完整的答案,而不是简单的“是”或“否”。

让我们考虑这段代码:

// 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 要求supercall 是构造函数主体中的第一条语句。


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.

方法上没有@Override注释onScrolledoverride用修饰符为方法添加注释是否太难了?但是存在@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转换?目的是什么?ThrowableIllegalStateException extends Throwable


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

4

2 回答 2

6

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

要回答您的具体问题:

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

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

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

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

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

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

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

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

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

于 2019-10-05T19:55:58.947 回答
3

有 2 种行之有效的方法可以找出字节码的作用:运行它并读取它。

如果你运行它,你会看到一切都像用 Kotlin 编写的那样工作。

现在让我阅读字节码并解释一下。

1. 字节码不关心java需求。super()即使在 java 中,也可以在调用之前执行一些操作。例如,这里super(string + string);的加法是在它之前执行的。


2.字节码:

GETSTATIC me/stackoverflow/a10/PagingOnScrollListener$1.INSTANCE : Lme/stackoverflow/a10/PagingOnScrollListener$1;
CHECKCAST kotlin/jvm/functions/Function2
INVOKESPECIAL me/stackoverflow/a10/PagingOnScrollListener.<init> (Lkotlin/jvm/functions/Function2;)V

我在 Java 中的翻译:

this((Function2)PagingOnScrollListener$1.INSTANCE);

我认为 java 反编译器由于类名怪异而无法正确反编译它1。Java 使用数字作为匿名类名,但这些类不能有静态声明。

这里没有创建新的函数实例,因为 Kotlin 足够聪明,可以看到每次都可以使用同一个实例。


3. @Override annotation 是用 注释的@Retention(RetentionPolicy.SOURCE),所以从字节码中去掉。


4.字节码:

ICONST_0
ISTORE 7

我在 Java 中的翻译:

int i7 = 0;

Java反编译器未能正确反编译它,因为Java字节码中没有boolean局部变量,它们被替换为int变量。


5. Kotlin 这里创建的字节码非常复杂。Java 反编译器一定无法像我一样正确反编译它。


6.反编译器目前不支持这种简化。


7.字节码中有这样的转换:

CHECKCAST java/lang/Throwable
于 2019-10-05T21:09:12.820 回答