does Kotlin compiler allow extending classes with inner and/or nested classes and if so what's the correct syntax?
in EnclosingClass.java
class EnclosingJavaClass {
class NestedJavaClass1 {}
class NestedJavaClass2 {}
// ...
}
this is what i'm unsure of below,
in EnclosingClassExtensions.kt
class EnclosingJavaClass.NestedKotlinClass {
// ...
}
assuming that EnclosingClass.java cannot be modified (eg: library code), can i declare somehow a EnclosingClass.SomeNewKotlinClass ?
Related
i have a polymorphic type that is implemented by objects and classes.
sealed interface Base
#Serializable
#SerialName("Sub")
class Sub(...) : Base
#Serializable
#SerialName("Obj")
object Obj : Base
i use this type with kotlinx.serialization
polymorphic(Base::class) {
subclass(Sub::class)
subclass(Obj::class)
}
this runs when there is no obfuscation, but when obfuscation is turned on, i get:
Serializer for class 'Obj' is not found. Mark the class as #Serializable or provide the serializer explicitly.
my proguard configuration regarding kotlinx.serialization is
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keepclasseswithmembers class .** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class my.package.**$$serializer { *; }
The problem is the generated serializer is different for objects and classes.
while in classes it is Sub$serializer in objects it is Obj$defaultSerializer$delegate, which slips through the Proguard rules.
While a good solution would be changing the Proguard rules to catch this case,
a usefull hack is to pass the serializer directly, instead of letting the polymorphic builder find it by the class.
polymorphic(Base::class) {
..
subclass(Obj.serializer())
}
When writing a junit test:
internal class MyTest {
#org.junit.jupiter.api.Nested
class MyInnerClass {
}
}
A warning is displayed by my code editor (IntelliJ)
Only non-static nested classes can serve as #Nested test classes.
How to write a nested classes in junit tests in kotlin?
When using #Nested classes in kotlin they need the prefix inner as only inner classes can be used as nested classes.
Only non-static nested classes (i.e. inner classes) can serve as
#Nested test classes.
https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested
Your code should look somewhat like this:
internal class MyTest {
#Nested
inner class MyInnerClass {
#Test
fun customTest() {
//TODO: do something
}
}
}
The Code A is from the project android/architecture-components-samples.
The author place the code of instance a class DefaultServiceLocator in the interface ServiceLocator.
In my mind , normally a interface should not include any implement code.
Is it a good idea to place the code of instance a class in a interface in Kotlin?
Code A
interface ServiceLocator {
companion object {
private val LOCK = Any()
private var instance: ServiceLocator? = null
fun instance(context: Context): ServiceLocator {
synchronized(LOCK) {
if (instance == null) {
instance = DefaultServiceLocator(
app = context.applicationContext as Application,
useInMemoryDb = false)
}
return instance!!
}
}
/**
* Allows tests to replace the default implementations.
*/
#VisibleForTesting
fun swap(locator: ServiceLocator) {
instance = locator
}
}
...
}
open class DefaultServiceLocator(val app: Application, val useInMemoryDb: Boolean) : ServiceLocator {
...
}
In my mind , normally a interface should not include any implement code.
Welcome back from hibernation ;) Yes, you could achieve the same with interface + abstract class but you can have default implementation also as part of the interface for some time now in many languages. Which way you go is up to you, but if you have only one abstract class implementing your interface then it is often handy to be able to merge this into one file for sake of ease of future maintenance.
As per kotlin interfaces documentation:
Interfaces in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state. They can have properties but these need to be abstract or to provide accessor implementations.
So... there's no problem in using method implementations on the interfaces. That feature might offer you extra power (if you like and need to use it).
I have an annotation that requires defaultValue to be compile-time constant. I take defaultValue from enum below:
enum class RaceType {
MARATHON,
SPRINT;
companion object {
fun apply(type: RaceType): RaceDto {
return when (type) {
MARATHON -> MarathonDto()
SPRINT -> SprintDto()
}
}
}
}
My dtos are the following:
interface RaceDto {
}
data class MarathonDto: RaceDto
data class SprintDto: RaceDto
when I use annotation #QraphQLArgument(defaultValue = RaceType.SPRINT.name) Kotlin requires RaceType.SPRINT.name to be compile-time constant.
Annotation implementation itself:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface GraphQLArgument {
String NONE = "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
String NULL = "\n\t\t\n\t\t\n\ue000\ue001\ue002\ue003\n\t\t\t\t\n";
String name();
String description() default "";
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
Class<? extends DefaultValueProvider> defaultValueProvider() default JsonDefaultValueProvider.class;
}
I looked through similar questions but don't see a way how it can be resolved. I also found article related to the topic but nothing worked so far.
Side note: I cannot change annotation since it is from the library and I cannot change the library as well.
To summarize, is there a way to make from enum compile-time constant in Kotlin to use in an annotation?
is there a way to make from enum compile-time constant in Kotlin to use in an annotation?
No, because formally enums aren't compile-time constants in Java.
However please consider the sealed classes:
sealed class RaceType {
object MARATHON: RaceType() {
const val name = "MARATHON" // copy-paste is required here until https://youtrack.jetbrains.com/issue/KT-16304
}
object SPRINT: RaceType()
companion object {
fun apply(type: RaceType): RaceDto {
return when (type) { // the check is in compile time, because of sealed class
MARATHON -> MarathonDto()
SPRINT -> SprintDto()
}
}
}
}
A little part of copy-paste is still required. Please vote on kotlin compiler bug or follow this thread.
However, as I understand, this doesn't solve your issue with #QraphQLArgument(defaultValue = RaceType.SPRINT.name) unfortunately, because the name of class is not the same with value. In the other words, with sealed classes you need to write code to convert input strings to them.
Sealed class in Kotlin can have private constructor only. That means we can call the constructor only in itself:
Sealed classes are not allowed to have non-private constructors (their constructors are private by default).
// `private` and `constructor()` are redundant.
sealed class Expr private constructor()
But, when we utilize sealed class, a sub class have to inherit seald class:
// Above Kotlin 1.1
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
As you can see the code above, sealed class's private constructor is called outside of sealed class itself. When sub class is instantiated, the constructor of parent(sealed class) will be called before sub class's own constructor is called. Is it just exception to visibility modifiers?
https://kotlinlang.org/docs/reference/visibility-modifiers.html#classes-and-interfaces
For members declared inside a class: private means visible inside this class only (including all its members);
Consider the following code:
open class A private constructor(var name: String){
class B : A("B")
class C : A("C")
}
The above code compiles fine, as the constructor is called inside the class A.
If a class D tries to inherit outside A, it won't compile.
class D : A("D") // Error: Cannot access '<init>': it is private in 'A'
As mentioned on the page Sealed class in Kotlin,
A sealed class can have subclasses, but all of them must be declared in the same file as the sealed class itself. (Before Kotlin 1.1, the rules were even more strict: classes had to be nested inside the declaration of the sealed class).
It seems that kotlin relaxed the requirement of nested classes only.
So, the following code works fine in 1.1+ but would fail in earlier versions:
sealed class A(var name: String)
class B : A("B")
class C : A("C")
whereas the following code would have been required in versions before 1.1, which respects the private constructor.
sealed class A (var name: String){
class B : A("B")
class C : A("C")
}
So, allowing private constructors of sealed classes outside the class (but within the same file) can be considered an enhancement to make the code cleaner.
You can figure out what's happening by taking a look at the generated bytecode (you can do this by going to Tools -> Kotlin -> Show Kotlin Bytecode and then choosing Decompile in the pane that appears.). Decompiling it to Java shows this code for the Expr class:
public abstract class Expr {
private Expr() {
}
// $FF: synthetic method
public Expr(DefaultConstructorMarker $constructor_marker) {
this();
}
}
So there is a non-private constructor for the Expr class generated, with a special parameter. Then, as you'd expect, if you look at the decompiled bytecode of Const for example, you'll see that it calls into this constructor:
public final class Const extends Expr {
public Const(double number) {
super((DefaultConstructorMarker)null);
this.number = number;
}
// other fields and methods ...
}
You still can't subclass Expr from Kotlin, because the Kotlin compiler knows that it's a sealed class from the metadata in the file, and will respect that.
As for Java client code, there you can't access this same constructor yourself because the DefaultConstructorMarker is package-private in the kotlin.jvm.internal package that it's in, so even if you write out the import statement for it manually, the compiler won't allow it.
My guess is that the package-private visibility might only be enforced at compile time, and that's why the Kotlin compiler is able to output the bytecode corresponding to the snippet above (not completely sure though).