The problem
I recently encountered a weird error when using kotlin lambda
java.lang.NoClassDefFoundError: Failed resolution of: Lcom/dependproject/AnonymousActivity$onCreate$sThe $1The $1;
Copy the code
Exception caused by class definition not found. That’s amazing. What a ghost this is. I really don’t have this class, so I’ll start by pasting the code
val s = inlineLambda {
object : AnonymousClass<String>("") {
override fun printName() {
val context = this@AnonymousActivity
}
}
}
abstract class AnonymousClass<T> {
constructor(t:T)
abstract fun printName()
}
inline fun <T, R> T.inlineLambda(block : (T) -> R):R {
return block(this)
}
Copy the code
summary
A look at the code shows lambda syntax, inline inline functions, and anonymous classes.
Ok, well observed, did not find the above strange class. This is where we need tools to see what we can’t see, which is bytecode. Use the Kotlin Tool in the Android Studio navigation bar to convert the source code to bytecode as follows:
L11
LINENUMBER 55 L11
NEW com/dependproject/AnonymousActivity$onCreate$$inlined$inlineLambda$lambdaThe $1
DUP
LDC ""
ALOAD 0
INVOKESPECIAL com/dependproject/AnonymousActivity$onCreate$$inlined$inlineLambda$lambdaThe $1.<init> (Ljava/lang/Object; Lcom/dependproject/AnonymousActivity;) V L12 LINENUMBER 59 L12 L13 NOP L14 LINENUMBER 54 L14 CHECKCAST com/dependproject/AnonymousActivity$onCreate$sThe $1The $1
ASTORE 2
L15
Copy the code
Oh, you see, there’s this weird class,
CHECKCAST com/dependproject/AnonymousActivity$onCreate$sThe $1The $1
Copy the code
The code above is one of the operations assigned to S, checking the type of the class before assigning. Isn’t this the type that inlineLambda returns? Let’s move on to the inlineLambda method, which simply returns the type of a lambda expression, which is actually an implementation of AnonymousClass above.
So why can’t we find the generated class? When we look at the bytecode above, we find a class with a strange name
com/dependproject/AnonymousActivity$onCreate$$inlined$inlineLambda$lambdaThe $1
Copy the code
A quick look at the code shows that this class is generated by anonymous classes.
It is strange that the generated anonymous class name is not the same as the checkCast name.
Analysis of the
The teacher used to teach us that we should think boldly and carefully. In fact, analysis is the same, bold imagination. Observation checkcast class name, that name has a track record, is the name of the class + name + $1 $1, namely, com/dependproject AnonymousActivity + onCreate + $1 $1. The new anonymous class name is longer, but the rules are the same. The com/dependproject AnonymousActivity + onCreate + inlineLambda inlined + +These are numbered and can be ignored, so the name composition of the anonymous class is traceable. Then why is the name different?
So here’s my big guess. I think the anonymous class names generated by lambda expressions passed in by inline functions consist of a rule = class + method + inlined + method. Class + method is inline, so the compiler is not perfect and the generated class name rules are not uniform.
Wow, that’s a hell of an explanation.
It all sounds right. So let’s verify that.
In fact, the above lambda expression has a little too many interference variables. If you remove the operation in the method, according to my bold explanation above, you should also report an error. However, slap slap face. Not only does it not report an error, but the name of the generated anonymous class is exactly that
com/dependproject/AnonymousActivity$onCreate$sThe $1The $1
Copy the code
This one. Compare the code and find the difference here at this@AnonymousActivity. So let’s go back to the bytecode we just generated.
com/dependproject/AnonymousActivity$onCreate$$inlined$inlineLambda$lambdaThe $1
Copy the code
Notice, there’s a putfield that’s essentially an assignment operation, AnonymousActivity object that assigns a value to a member variable of a class. In fact, I made a bold guess when I saw this. When variables that point outside of the class are added, the compiled naming rules for anonymous classes change, and the rules are the same as above. But when the external class executes checkcast, it still assembs the names according to the old rules. These are purely personal guesses, not tested.
nagging
For inline functions, the performance is better with fewer method calls, and bytecode shows that inline functions + anonymous functions + lambda actually generate a class with a specific rule name.
But for non-inline functions, the situation is different. Let’s start with a call to the lambda method
lambda {
object : AnonymousClass<String>("") {
override fun printName() {
it
}
}
}
Copy the code
Corresponding bytecode
ALOAD 0
ALOAD 0
GETSTATIC com/dependproject/AnonymousActivity$onCreateThe $1.INSTANCE : Lcom/dependproject/AnonymousActivity$onCreateThe $1;
CHECKCAST kotlin/jvm/functions/Function1 INVOKEVIRTUAL com/dependproject/AnonymousActivity.lambda (Ljava/lang/Object; Lkotlin/jvm/functions/Function1;) Ljava/lang/Object; POPCopy the code
The anonymous class returns the following bytecode:
LINENUMBER 20 L1
NEW com/dependproject/AnonymousActivity$onCreateThe $1The $1
DUP
LDC ""
INVOKESPECIAL com/dependproject/AnonymousActivity$onCreateThe $1The $1.<init> (Ljava/lang/Object;) VCopy the code
It turns out that calling lambda produces two classes, one of which is
AnonymousActivity$onCreateThe $1
Copy the code
The other one is
AnonymousActivity$onCreateThe $1The $1
Copy the code
The first one, for lambda expression conversion, looks like this:
Class inheritance in kotlin/JVM/internal/Lambda, one method, imagine everyone should know, that is to invoke. Lambda is normally called in one of two ways,
val lambdaFun = {i:Int -> i}
lambdaFun(1)
lambdaFun.invoke(1)
Copy the code
One of these is invoke, which is created during compilation. AnonymousActivity$onCreate$1$1 is the implementation class of the returned anonymous class.
conclusion
Guess big and think big.
The latter
Thanks to salmon Zhang for seeing the questions and discussion on the Kotlin forum, portal. Generate inlined named rules in this file, everyone interested can have a look. It is worth mentioning that this bug was put forward in 2014.