Object Oriented Programming Intro
The purpose of this workshop is to understand Kotlin OOP part from Java Developer Perspective. We will see many examples on how easy it is to call java code from Kotlin thus knowledge about java libraries is still valuable and you don't need to re-learn everything from scratch.
Repo with exercises : https://github.com/PawelWlodarski/kotlin-workshops
Declaring Classes
In this part we will see :
- How to declare a class in Kotlin
- How to use java classes in Kotlin
- Java/Kotlin Byte Code Comparison
- Constructors in Kotlin
- Final values and variables
- Private state in classes
FILE : ClassesInKotlin.kt
First of all notice that you don't have class named ClassesInKotlin because in Kotlin you can declare functions as top level constructs.
fun main(args: Array<String>) {
...
}
Java classes can be used directly in Kotlin code. In this first example we will Explain each part of Kotlin declaration syntax.
val userJava: UserJava = UserJava("George", 20)
Main differences from java
- type is on the right side of a variable name
- type can be interferred by compiler
- no semicolons
- val is like final in java
Compilation - Simple Classes Comparison
Because in Kotlin you can put many public classes into one file so our single demo file generate multiple classes. Let's compare interface generated from java code with the Kotlin counterpart.
ls -al target/classes/lodz/jug/kotlin/starter/oop/
(***)
UserJava.class
UserKotlin.class
Compare amount of code between Java and Kotlin.
Kotlin Code :
class UserKotlin(val name: String, val age: Int) {
override fun toString() = "KotlineUser($name, $age)"
}
Java Code :
public class UserJava {
private String name;
private int age;
public UserJava(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "UserJava{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Both generate similar byte code:
javap target/classes/lodz/jug/kotlin/starter/oop/UserKotlin.class
Compiled from "ClassesiNKotlin.kt"
public final class lodz.jug.kotlin.starter.oop.UserKotlin {
public java.lang.String toString();
public final java.lang.String getName();
public final int getAge();
public lodz.jug.kotlin.starter.oop.UserKotlin(java.lang.String, int);
}
and Java
javap target/classes/lodz/jug/kotlin/starter/oop/UserJava.class
Compiled from "UserJava.java"
public class lodz.jug.kotlin.starter.oop.UserJava {
public lodz.jug.kotlin.starter.oop.UserJava(java.lang.String, int);
public java.lang.String getName();
public int getAge();
public java.lang.String toString();
}
Constructors
Lets look at class definition once again
class UserKotlin(val name: String, val age: Int) {
override fun toString() = "KotlineUser($name, $age)"
}
Primary constructor is defined next to the class name. If you want to define another constructor (although because of default parameters it may not be necessary that often) then you need to define another function marked as constructor
class UserTwoConstructors(val name: String, val city: String) {
constructor(name: String) : this(name, "LODZ") {
Displayer.section("Use with two cons created with ($name,Lodz)") //no access to city here
}
}
But what if you want to perform some operations in constructor ? For now it looks like you can only declare fields there however you have another construction to achieve this - init
class UserWithInit(val name: String, val city: String) {
val other: String
constructor(name: String) : this(name, "LODZ") {
println("in constructor")
}
init {
other = "otherValue"
println("in init")
}
}
standard javap will not tell us much here
javap target/classes/lodz/jug/kotlin/starter/oop/UserWithInit.class
Compiled from "ClassesiNKotlin.kt"
public final class lodz.jug.kotlin.starter.oop.UserWithInit {
public final java.lang.String getOther();
public final java.lang.String getName();
public final java.lang.String getCity();
public lodz.jug.kotlin.starter.oop.UserWithInit(java.lang.String, java.lang.String);
public lodz.jug.kotlin.starter.oop.UserWithInit(java.lang.String);
}
but if we go deeper
pawel@maszyna:~/projects/workshops/kotlin$ javap -c target/classes/lodz/jug/kotlin/starter/oop/UserWithInit.class
Compiled from "ClassesiNKotlin.kt"
(...)
public lodz.jug.kotlin.starter.oop.UserWithInit(java.lang.String, java.lang.String);
Code:
0: aload_1
1: ldc #24 // String name
3: invokestatic #30 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_2
7: ldc #31 // String city
9: invokestatic #30 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
12: aload_0
13: invokespecial #34 // Method java/lang/Object."<init>":()V
16: aload_0
17: aload_1
18: putfield #17 // Field name:Ljava/lang/String;
21: aload_0
22: aload_2
23: putfield #21 // Field city:Ljava/lang/String;
26: aload_0
27: ldc #36 // String otherValue
29: putfield #11 // Field other:Ljava/lang/String;
32: ldc #38 // String in init
34: astore_3
35: getstatic #44 // Field java/lang/System.out:Ljava/io/PrintStream;
38: aload_3
39: invokevirtual #50 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
42: return
(...)
Now we can see that everything from init is part of primary constructor.
Adding setters
You can choose accessibility level by manipulating val and var .
- val - only getter
- var - getter and setter
- nothing - no getter or setter, just a parameter in the constructor
- private val, private var - no getter neither setter but here we have field generated in the class body
so from class
class SocialMediaAccount(val name: String, var wall: String, friends: List<String>) {
private var messages: List<String> = java.util.LinkedList()
//property example - feature unique for Kotlin
// show what happen when messages.size is asigned directly to field
val numberOfMessage:Int
get() = messages.size
//can not use '=' syntax - not expression
fun addMessage(m: String) {
messages += m
}
//remove type and show warning
fun getMessages(): List<String> = Collections.unmodifiableList(messages)
}
We have generated bytecode.
javap target/classes/lodz/jug/kotlin/starter/oop/SocialMediaAccount.class
Compiled from "ClassesInKotlin.kt"
public final class lodz.jug.kotlin.starter.oop.SocialMediaAccount {
public final int getNumberOfMessage();
public final void addMessage(java.lang.String);
public final java.util.List<java.lang.String> getMessages();
public final java.lang.String getName();
public final java.lang.String getWall();
public final void setWall(java.lang.String);
public lodz.jug.kotlin.starter.oop.SocialMediaAccount(java.lang.String, java.lang.String, java.util.List<java.lang.String>);
State encapsulation
Just put private in front of val/var/def.
class SocialMediaAccount(val name: String, var wall: String, friends: List<String>) {
private var messages: List<String> = java.util.LinkedList()
//can not use '=' syntax - not expression
fun addMessage(m: String) {
messages += m
}
//remove type and show warning
fun getMessages(): List<String> = Collections.unmodifiableList(messages)
}
Messages field is not accessible from outside the class . We have public function for adding messages and also public function for retrieving messages. This is standard approach known from java.
Property fields
Concept of Property fields may be not quite intuitive for java programmers at the beginning (but maybe it is - you never know).
class SocialMediaAccount(val name: String, var wall: String, friends: List<String>) {
private var messages: List<String> = java.util.LinkedList()
//property example - feature unique for Kotlin
// show what happen when messages.size is assigned directly to field
val numberOfMessage:Int
get() = messages.size
(...)
Displayer.section("Stevens account size property", account.numberOfMessage)
Notice that get method is connected with property by just being declared next to it. What is gain of property declaration ?
Now you can use syntax which looks like usage of standard field
Displayer.section("Stevens account size property", account.numberOfMessage)
In bytecode we see that in result we have a standard method which uses private field messages
javap -c target/classes/lodz/jug/kotlin/starter/oop/SocialMediaAccount.class
Compiled from "ClassesInKotlin.kt"
public final class lodz.jug.kotlin.starter.oop.SocialMediaAccount {
public final int getNumberOfMessage();
Code:
0: aload_0
1: getfield #11 // Field messages:Ljava/util/List;
4: invokeinterface #16, 1 // InterfaceMethod java/util/List.size:()I
9: ireturn
Objects and Interfaces
In this part we will see:
- Kotlin classes implementing Java interfaces
- Calling java static methods from Kotlin
- Kotlin Object concept
FILE : ObjectsAndInterfaces.kt
When switching to another language you may be concerned that your previous experience gained with Java will be wasted. This is not the case with Kotlin because Kotlin and Java has very nice interoperability. First of all you can easily add Kotlin implementation of a Java interface.
So if we have good old Java interface.
public interface JavaInterface {
String toJson();
}
Kotlin implementation is really straightforward
class KotlinImplementation(private val value:String) : JavaInterface{
override fun toJson(): String =
"""{
| "value" : '$value'
|}"""
}
And usage :
val kotlinImplementation:JavaInterface=KotlinImplementation("This is Kotlin")
Displayer.section("kotlin class with java interface",kotlinImplementation.toJson())
Java Static Methods
Also there is no problem with calling java static methods. When we have classic utility class :
public class StaticMethods {
public final static String prefix(String prefix, String base){
return prefix+"_"+base;
}
public final static Integer multiply(Integer i1, Integer i2){
return i1*i2;
}
}
then using it in the code is really straighforward
Displayer.title("calling java static methods from Kotlin")
Displayer.section("STATIC FROM JAVA 1 : "+StaticMethods.prefix("scala","printing"))
Displayer.section("STATIC FROM JAVA 2 (2*3): "+StaticMethods.multiply(2,3))
Interfaces in Kotlin
If we want simple interface without default methods like in Java 7 then we just need to declare empty methods with fun . Kotlin doesn't have distinction between extending classes and implementing interfaces you just need to put colon and type interface name
interface KotlinInterface {
fun toJson():String
}
class SecondImplementation(val value:String) : KotlinInterface{
override fun toJson(): String = """{"secondValue" : '$value'}"""
}
No surprises in byte code here. Kotlin interface was converted into standard Java interface and Kotlin implementation become standard Java class.
javap -c target/classes/lodz/jug/kotlin/starter/oop/KotlinInterface.class
Compiled from "ObjectsAndInterfaces.kt"
public interface lodz.jug.kotlin.starter.oop.KotlinInterface {
public abstract java.lang.String toJson();
}
javap target/classes/lodz/jug/kotlin/starter/oop/KotlinInterface.class
Compiled from "ObjectsAndInterfaces.kt"
public interface lodz.jug.kotlin.starter.oop.KotlinInterface {
public abstract java.lang.String toJson();
}
Objects
Time for a construction which you will not find in Java. You can easily call static java methods from Kotlin code yet Kotlin doesn't have the static keyword itself. To achieve similar behaviour we can create singleton object.
object FactoryExample{
fun factoryMethod(v:String):KotlinInterface = SecondImplementation(v)
infix fun infixFactory(v:String):KotlinInterface = SecondImplementation(v)
}
There is always only one instance of this object and it is a very good place to define factory methods for general class instances.
javap target/classes/lodz/jug/kotlin/starter/oop/FactoryExample.class
Compiled from "ObjectsAndInterfaces.kt"
public final class lodz.jug.kotlin.starter.oop.FactoryExample {
public static final lodz.jug.kotlin.starter.oop.FactoryExample INSTANCE;
public final lodz.jug.kotlin.starter.oop.KotlinInterface factoryMethod(java.lang.String);
public final lodz.jug.kotlin.starter.oop.KotlinInterface infixFactory(java.lang.String);
static {};
}
infix - in short - if you want to write method invocation without dot and parentheses then this is for you.
infix fun infixFactory(v:String):KotlinInterface = SecondImplementation(v)
(...)
val factoredInfix: KotlinInterface = FactoryExample infixFactory "infixExample"
invoke - if you want to call methods without actually using their names (very useful for functions to not have f.apply(1) like in java)
operator fun invoke(v:String):KotlinInterface = SecondImplementation(v)
(...)
val factoredInvoke=FactoryExample("invoke Example")
Displayer.section("invoke",factoredInvoke.toJson())
Properties in Interfaces
You can declare properties also in interfaces. They can use other abstract methods so this will work similarly to template method
interface KotlinInterfaceWithPreoperty {
fun toJson():String
val trimmedJson:String
get() = toJson().trim()
}
and usage
Displayer.title("Properties in interfaces")
val fourthImplementation=FourthImplementation("4th value")
Displayer.section("4th toJson",fourthImplementation.toJson())
Displayer.section("4th toJsonTrim",fourthImplementation.trimmedJson)
Kotlin Test
FILE : StaticMethodsTest
Kotlin has very nice testing framework inspired by scala test.
<dependency>
<groupId>io.kotlintest</groupId>
<artifactId>kotlintest</artifactId>
<version>{version}</version>
</dependency>
It is a great ilustration of many concepts trained during those workshops.
You declare a test by extending one of couple "Specs" - let's start with StringSpec.
class StaticMethodsTest : StringSpec() {
init {
"Static Methods should add prefix " {
val result = StaticMethods.prefix("prefix", "test")
result shouldBe "prefix_test"
}
"Static Methods should add prefix " {
val result = StaticMethods.multiply(4, 5)
result shouldBe 20 //change to 19 and check result
}
}
}
First of all you need to put tests in init - so technically this is primary constructor.
No why following part even compiles ?
"Static Methods should add prefix " {
This is topic for the future but generally Kotlin allows you to override limited set of operators. Here we are overriding invoke operator and also we are using some syntax sugar from kotlin which allows you to pass lambdas with curly braces
operator fun String.invoke(test: () -> Unit): TestCase {
and this :
result shouldBe "prefix_test"
here we see another two Kotlin features
- extension method
- infix operator which we saw already
infix fun String.shouldBe(other: String) {
if (this != other) {
throw ComparisonFailure("", other, this)
}
}
So as you see everything here is a Kotlin native code.
Exercises
1) Test Java class
FILE : TestingJavaClassesExercise
replace ??? with valid Kotlin instructions to make code compile and test passing
when first test is ready uncomment second one
2) Statefull Instance in Kotlin
FILE : StatefulInstanceInKotlinExercise
Implement similar logic in Kotlin as it was in previous exercise in Java - class with general state. Implement trait HasState which works like a standard interface.
In the second part try to save changes history - this one is HARD when you are beginner but still worth to try. History will be kept in standard java.util.Collection. Here you will have to declare java List with Kotlin alias type MutableList
3) Factory Method
FILE : FactoryMethodExercise
You have provided an interface for processing strings:
interface Processor{
fun process(input:String):String
}
implement two processors : one changes string to upper and seconds trim whitespaces
implement object factory which returns processor according to string id -declare id's according to tests