In Java I have following class
public class Instruction {
public static Instruction label(String name) {
return new Instruction(Kind.label, name, null, 0);
}
public static Instruction literal(int value) {
return new Instruction(Kind.intLiteral, null, null, value);
}
public static Instruction literal(boolean value) {
return new Instruction(Kind.boolLiteral, null, null, value ? 1 : 0);
}
public static Instruction command(String name) {
return new Instruction(Kind.command, name, null, 0);
}
public static Instruction jump(String target) {
return new Instruction(Kind.jump, target, null, 0);
}
public static Instruction branch(String ifTarget, String elseTarget) {
return new Instruction(Kind.branch, ifTarget, elseTarget, 0);
}
private final Kind kind;
private final String s1;
private final String s2;
private final int value;
private Instruction(Kind kind, String s1, String s2, int value) {
this.kind = kind;
this.s1 = s1;
this.s2 = s2;
this.value = value;
}
...
}
How this should be done with Kotlin?
We do this pretty much the same in Kotlin, but by using a companion object:
fun main() {
val instruction = Instruction.literal(42)
}
class Instruction private constructor(...) {
companion object {
fun label(name: String) = Instruction(Kind.label, name, null, 0)
fun literal(value: Int) = Instruction(Kind.intLiteral, null, null, value)
}
...
}
Please read companion objects for more info.
@broot's answer is the correct Kotlin equivalent of the Java code. However, in Kotlin we can do better.
You're implicitly thinking of different subtypes of Instruction
here (which is clearly shown by the Kind
enum) but without really defining those types. For each of those subtypes, the s1
, s2
, and value
properties may or may not mean something. Instead, use a sealed class or interface, and only define properties where they actually make sense:
sealed interface Instruction {
data class Label(val name: String) : Instruction
data class IntLiteral(val value: Int) : Instruction
data class BooleanLiteral(val value: Boolean) : Instruction
data class Command(val name: String) : Instruction
data class Jump(val target: String) : Instruction
data class Branch(val ifTarget: String, val elseTarget: String) : Instruction
}
Then, each time you would have used the kind
property, you would instead just use an is
check. For instance, if (thing is Interface.Command) { ... }
or most likely a when
expression with all possible subtypes, which the compiler will check for type safety.
Thanks to Kotlin's flow typing, each type-checked branch (in if
or when
) will make the relevant properties available (such as value
, ifTarget
, name
, etc.).
Note: alternatively, literals could even be generic:
data class Literal<T>(val value: T) : Instruction
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With