Kotlin 从 generateSequence() 到 flow() ,但是生成了错误的字节码
Kotlin from generateSequence() to flow() , but wrong bytecode generated
我有一个使用 http 请求检索远程页面的函数。该页面可能有一个或零个 next
页面的 link。如果我想生成所有页面的链,generateSequence
是一个理想的解决方案。这就是我所做的:
首先,有两个效用函数:
fun getBlockingDocument(url: String): Document?
顾名思义,就是一个阻塞函数。该实现只是发送 HTTP 请求并解析为 JSoup
文档。
fun getNextIndexPage(doc: Document, url: String): String?
,也是屏蔽功能,不过跟网络无关,只是解析获取下一页,所以这里屏蔽是可以的。
好的,这是序列代码:
val initUrl = // initial url
generateSequence(tools.getBlockingDocument(initUrl).let { initUrl to it }) { (url, doc) ->
doc?.let {
parser.getNextIndexPage(doc, url)
}?.let { nextUrl ->
nextIndexUrl to tools.getBlockingDocument(nextUrl)
}
}.forEachIndexed { index, urlAndDoc ->
val url = urlAndDoc.first
logger.info("[{}] : {}", index, url)
}
它运行良好,并正确链接了所有页面。
但是如果我将网络调用更改为挂起函数怎么办?这是我创建的:
suspend fun getSuspendingDocument(url: String): Document?
我没有发现与 flow
相似的 generateSequence
构建器样本,所以我这样实现:
@ExperimentalCoroutinesApi
@Test
fun testGetAllPagesByFlow() {
val flow = flow<Pair<String, Document?>> {
suspend fun generate(url: String) {
tools.getSuspendingDocument(url)?.let { url to it }?.also { (url, doc) ->
emit(url to doc)
parser.getNextIndexPage(doc, url)?.also { nextUrl ->
generate(nextUrl) // recursive
}
}
}
generate("http://...initial url here")
} // flow
runBlocking {
flow.collectIndexed { index, urlAndDoc ->
val url = urlAndDoc.first
logger.info("[{}] : {}", index, url)
}
}
}
我使用递归调用 (fun generate()
) 来 emit
在每个页面中找到的下一个 url。我不确定这是否是创建 Flow
的惯用方法,但我没有发现类似的代码。如果你有better/idiomatic方法,请告诉我,非常感谢!
无论如何,我认为它应该工作,但我的 IDE (IntelliJ) 抱怨 wrong bytecode generated
,我以前从未见过它。
Error:Kotlin: [Internal Error] org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: wrong bytecode generated
@Lorg/jetbrains/annotations/Nullable;() // invisible
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
L1
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.p$ : Lkotlinx/coroutines/flow/FlowCollector;
ASTORE 2
L2
L3
LINENUMBER 44 L3
NEW destiny/data/FlowTest$testGetAllPagesByFlow$flow
DUP
ALOAD 2
ALOAD 3
ACONST_NULL
INVOKESPECIAL destiny/data/FlowTest$testGetAllPagesByFlow$flow.<init> (Lkotlinx/coroutines/flow/FlowCollector;Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow;Lkotlin/coroutines/Continuation;)V
ASTORE 3
L4
L5
LINENUMBER 56 L5
ALOAD 3
CHECKCAST destiny/data/FlowTest$testGetAllPagesByFlow$flow
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.$initUrl : Ljava/lang/String;
ALOAD 0
ALOAD 0
ALOAD 2
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.L[=13=] : Ljava/lang/Object;
ALOAD 0
ALOAD 3
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.L : Ljava/lang/Object;
ALOAD 0
ICONST_1
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.label : I
INVOKEVIRTUAL destiny/data/FlowTest$testGetAllPagesByFlow$flow.invoke (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
L6
DUP
ALOAD 4
IF_ACMPNE L7
L8
LINENUMBER 42 L8
ALOAD 4
ARETURN
L9
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.L : Ljava/lang/Object;
CHECKCAST destiny/data/FlowTest$testGetAllPagesByFlow$flow
ASTORE 3
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.L[=13=] : Ljava/lang/Object;
CHECKCAST kotlinx/coroutines/flow/FlowCollector
ASTORE 2
L10
ALOAD 1
INVOKESTATIC kotlin/ResultKt.throwOnFailure (Ljava/lang/Object;)V
ALOAD 1
L7
LINENUMBER 58 L7
POP
L11
L12
GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
ARETURN
L13
L14
L15
NEW java/lang/IllegalStateException
DUP
LDC "call to 'resume' before 'invoke' with coroutine"
INVOKESPECIAL java/lang/IllegalStateException.<init> (Ljava/lang/String;)V
ATHROW
RETURN
L16
LOCALVARIABLE $this$flow Lkotlinx/coroutines/flow/FlowCollector; L2 L14 2
LOCALVARIABLE $fun$generate Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow; L4 L11 3
LOCALVARIABLE this Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow; L0 L13 0
LOCALVARIABLE $result Ljava/lang/Object; L0 L13 1
MAXSTACK = 5
MAXLOCALS = 4
File being compiled at position: (42,46) in /destiny/data/core/src/test/java/destiny/data/FlowTest.kt
The root cause org.jetbrains.kotlin.codegen.CompilationException was thrown at: org.jetbrains.kotlin.codegen.TransformationMethodVisitor.visitEnd(TransformationMethodVisitor.kt:92)
at org.jetbrains.kotlin.codegen.FunctionCodegen.endVisit(FunctionCodegen.java:990)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:487)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:260)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:176)
...
太长了,剩下的就省略了
这段代码有什么问题?
如果递归方式不理想,有没有更好的解决方案? (像generateSequence
,很漂亮)。谢谢。
环境:
<kotlin.version>1.3.50</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.3.2</version>
</dependency>
IntelliJ 2018.3.6
$ java -version
java version "11.0.3" 2019-04-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.3+12-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.3+12-LTS, mixed mode)
为什么要使用递归?下面的代码会做同样的事情
val f: Flow<Pair<String, Document?>> = flow {
var nextUrl: String? = url
while (nextUrl != null) {
val doc = tools.getSuspendingDocument(nextUrl)
emit(url to doc)
if (doc == null) break;
nextUrl = parser.getNextIndexPage(doc, url)
}
}
我有一个使用 http 请求检索远程页面的函数。该页面可能有一个或零个 next
页面的 link。如果我想生成所有页面的链,generateSequence
是一个理想的解决方案。这就是我所做的:
首先,有两个效用函数:
fun getBlockingDocument(url: String): Document?
顾名思义,就是一个阻塞函数。该实现只是发送 HTTP 请求并解析为 JSoup
文档。
fun getNextIndexPage(doc: Document, url: String): String?
,也是屏蔽功能,不过跟网络无关,只是解析获取下一页,所以这里屏蔽是可以的。
好的,这是序列代码:
val initUrl = // initial url
generateSequence(tools.getBlockingDocument(initUrl).let { initUrl to it }) { (url, doc) ->
doc?.let {
parser.getNextIndexPage(doc, url)
}?.let { nextUrl ->
nextIndexUrl to tools.getBlockingDocument(nextUrl)
}
}.forEachIndexed { index, urlAndDoc ->
val url = urlAndDoc.first
logger.info("[{}] : {}", index, url)
}
它运行良好,并正确链接了所有页面。
但是如果我将网络调用更改为挂起函数怎么办?这是我创建的:
suspend fun getSuspendingDocument(url: String): Document?
我没有发现与 flow
相似的 generateSequence
构建器样本,所以我这样实现:
@ExperimentalCoroutinesApi
@Test
fun testGetAllPagesByFlow() {
val flow = flow<Pair<String, Document?>> {
suspend fun generate(url: String) {
tools.getSuspendingDocument(url)?.let { url to it }?.also { (url, doc) ->
emit(url to doc)
parser.getNextIndexPage(doc, url)?.also { nextUrl ->
generate(nextUrl) // recursive
}
}
}
generate("http://...initial url here")
} // flow
runBlocking {
flow.collectIndexed { index, urlAndDoc ->
val url = urlAndDoc.first
logger.info("[{}] : {}", index, url)
}
}
}
我使用递归调用 (fun generate()
) 来 emit
在每个页面中找到的下一个 url。我不确定这是否是创建 Flow
的惯用方法,但我没有发现类似的代码。如果你有better/idiomatic方法,请告诉我,非常感谢!
无论如何,我认为它应该工作,但我的 IDE (IntelliJ) 抱怨 wrong bytecode generated
,我以前从未见过它。
Error:Kotlin: [Internal Error] org.jetbrains.kotlin.codegen.CompilationException: Back-end (JVM) Internal error: wrong bytecode generated
@Lorg/jetbrains/annotations/Nullable;() // invisible
// annotable parameter count: 1 (visible)
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
L1
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.p$ : Lkotlinx/coroutines/flow/FlowCollector;
ASTORE 2
L2
L3
LINENUMBER 44 L3
NEW destiny/data/FlowTest$testGetAllPagesByFlow$flow
DUP
ALOAD 2
ALOAD 3
ACONST_NULL
INVOKESPECIAL destiny/data/FlowTest$testGetAllPagesByFlow$flow.<init> (Lkotlinx/coroutines/flow/FlowCollector;Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow;Lkotlin/coroutines/Continuation;)V
ASTORE 3
L4
L5
LINENUMBER 56 L5
ALOAD 3
CHECKCAST destiny/data/FlowTest$testGetAllPagesByFlow$flow
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.$initUrl : Ljava/lang/String;
ALOAD 0
ALOAD 0
ALOAD 2
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.L[=13=] : Ljava/lang/Object;
ALOAD 0
ALOAD 3
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.L : Ljava/lang/Object;
ALOAD 0
ICONST_1
PUTFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.label : I
INVOKEVIRTUAL destiny/data/FlowTest$testGetAllPagesByFlow$flow.invoke (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
L6
DUP
ALOAD 4
IF_ACMPNE L7
L8
LINENUMBER 42 L8
ALOAD 4
ARETURN
L9
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.L : Ljava/lang/Object;
CHECKCAST destiny/data/FlowTest$testGetAllPagesByFlow$flow
ASTORE 3
ALOAD 0
GETFIELD destiny/data/FlowTest$testGetAllPagesByFlow$flow.L[=13=] : Ljava/lang/Object;
CHECKCAST kotlinx/coroutines/flow/FlowCollector
ASTORE 2
L10
ALOAD 1
INVOKESTATIC kotlin/ResultKt.throwOnFailure (Ljava/lang/Object;)V
ALOAD 1
L7
LINENUMBER 58 L7
POP
L11
L12
GETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;
ARETURN
L13
L14
L15
NEW java/lang/IllegalStateException
DUP
LDC "call to 'resume' before 'invoke' with coroutine"
INVOKESPECIAL java/lang/IllegalStateException.<init> (Ljava/lang/String;)V
ATHROW
RETURN
L16
LOCALVARIABLE $this$flow Lkotlinx/coroutines/flow/FlowCollector; L2 L14 2
LOCALVARIABLE $fun$generate Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow; L4 L11 3
LOCALVARIABLE this Ldestiny/data/FlowTest$testGetAllPagesByFlow$flow; L0 L13 0
LOCALVARIABLE $result Ljava/lang/Object; L0 L13 1
MAXSTACK = 5
MAXLOCALS = 4
File being compiled at position: (42,46) in /destiny/data/core/src/test/java/destiny/data/FlowTest.kt
The root cause org.jetbrains.kotlin.codegen.CompilationException was thrown at: org.jetbrains.kotlin.codegen.TransformationMethodVisitor.visitEnd(TransformationMethodVisitor.kt:92)
at org.jetbrains.kotlin.codegen.FunctionCodegen.endVisit(FunctionCodegen.java:990)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethodBody(FunctionCodegen.java:487)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:260)
at org.jetbrains.kotlin.codegen.FunctionCodegen.generateMethod(FunctionCodegen.java:176)
...
太长了,剩下的就省略了
这段代码有什么问题?
如果递归方式不理想,有没有更好的解决方案? (像generateSequence
,很漂亮)。谢谢。
环境:
<kotlin.version>1.3.50</kotlin.version>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.3.2</version>
</dependency>
IntelliJ 2018.3.6
$ java -version
java version "11.0.3" 2019-04-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.3+12-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.3+12-LTS, mixed mode)
为什么要使用递归?下面的代码会做同样的事情
val f: Flow<Pair<String, Document?>> = flow {
var nextUrl: String? = url
while (nextUrl != null) {
val doc = tools.getSuspendingDocument(nextUrl)
emit(url to doc)
if (doc == null) break;
nextUrl = parser.getNextIndexPage(doc, url)
}
}