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

问题描述 投票:3回答:2

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

让我们考虑这段代码:

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

方法@Override上没有onScrolled注释。使用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,为什么将Throwable强制转换为IllegalStateException extends Throwable?目的是什么?


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

java kotlin decompiler javacompiler kotlinc
2个回答
4
投票

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

要回答您的特定问题:

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

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

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

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

  5. 可能是反编译器感到困惑,或者字节码太难将其反编译为有效的Java。请记住,Java字节码的类型检查要比Java宽松得多,并且编译后许多编译时间信息会消失,因此将字节码反编译为有效的Java并非易事。

  6. 因为没有编写反编译器来简化程序?您可以尝试让反编译器作者添加该代码,但是比您想象的要难得多。

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

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

如果您对此主题感兴趣,我强烈建议您学习Java字节码的工作原理,然后使用a Java bytecode disassembler来查看引擎盖下的实际情况。这将使您看到字节码中的内容以及反编译的内容。


2
投票

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

如果您运行它,您将看到一切都按Kotlin编写的方式工作。

现在让我阅读字节码并进行解释。

1。字节码不在乎Java要求。即使在Java中,也可以在super()调用之前执行某些操作。例如,在此之前执行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注释用@Retention(RetentionPolicy.SOURCE)注释,因此已从字节码中删除。


4。字节码:

ICONST_0
ISTORE 7

我在Java中的翻译:

int i7 = 0;

由于Java字节码中没有boolean局部变量,它们被int变量替换,因此Java反编译器无法正确地对其进行反编译。


5。 Kotlin在此处创建的字节码非常复杂。 Java反编译器必须像我一样无法正确反编译。


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


7。使用字节码进行此强制转换:

CHECKCAST java/lang/Throwable
© www.soinside.com 2019 - 2024. All rights reserved.