From 80c14c76ba53a49a4941800970b93a5db5afa221 Mon Sep 17 00:00:00 2001 From: Martin Berg Alstad Date: Wed, 30 Jul 2025 14:34:03 +0200 Subject: [PATCH] Completed Coursera: Kotlin for developers course --- .gitignore | 32 ++++++++++++++++++ .idea/.gitignore | 10 ++++++ .idea/codeStyles/Project.xml | 13 ++++++++ .idea/codeStyles/codeStyleConfig.xml | 5 +++ .idea/inspectionProfiles/Project_Default.xml | 12 +++++++ .idea/kotlinc.xml | 10 ++++++ .idea/libraries/KotlinJavaRuntime.xml | 17 ++++++++++ .idea/misc.xml | 6 ++++ .idea/modules.xml | 8 +++++ .idea/vcs.xml | 6 ++++ kotlin-for-java-developers.iml | 15 +++++++++ src/Also.kt | 4 +++ src/Apply.kt | 4 +++ src/Classes.kt | 27 +++++++++++++++ src/Conditionals.kt | 30 +++++++++++++++++ src/Constants.kt | 8 +++++ src/DataClass.kt | 2 ++ src/Exceptions.kt | 17 ++++++++++ src/Extensions.kt | 20 +++++++++++ src/FunctionTypes.kt | 11 ++++++ src/Functions.kt | 34 +++++++++++++++++++ src/Generics.kt | 30 +++++++++++++++++ src/HelloWorld.kt | 1 + src/In.kt | 24 ++++++++++++++ src/Inline.kt | 7 ++++ src/Lambdas.kt | 35 ++++++++++++++++++++ src/Lateinit.kt | 14 ++++++++ src/Lazy.kt | 8 +++++ src/Let.kt | 11 ++++++ src/Loops.kt | 21 ++++++++++++ src/Main.kt | 14 ++++++++ src/Nothing.kt | 8 +++++ src/Nullable.kt | 10 ++++++ src/Object.kt | 20 +++++++++++ src/OperatorOverloading.kt | 7 ++++ src/Properties.kt | 24 ++++++++++++++ src/Run.kt | 6 ++++ src/SafeCast.kt | 11 ++++++ src/SealedClass.kt | 11 ++++++ src/Sequences.kt | 22 ++++++++++++ src/With.kt | 33 ++++++++++++++++++ 41 files changed, 608 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/libraries/KotlinJavaRuntime.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 kotlin-for-java-developers.iml create mode 100644 src/Also.kt create mode 100644 src/Apply.kt create mode 100644 src/Classes.kt create mode 100644 src/Conditionals.kt create mode 100644 src/Constants.kt create mode 100644 src/DataClass.kt create mode 100644 src/Exceptions.kt create mode 100644 src/Extensions.kt create mode 100644 src/FunctionTypes.kt create mode 100644 src/Functions.kt create mode 100644 src/Generics.kt create mode 100644 src/HelloWorld.kt create mode 100644 src/In.kt create mode 100644 src/Inline.kt create mode 100644 src/Lambdas.kt create mode 100644 src/Lateinit.kt create mode 100644 src/Lazy.kt create mode 100644 src/Let.kt create mode 100644 src/Loops.kt create mode 100644 src/Main.kt create mode 100644 src/Nothing.kt create mode 100644 src/Nullable.kt create mode 100644 src/Object.kt create mode 100644 src/OperatorOverloading.kt create mode 100644 src/Properties.kt create mode 100644 src/Run.kt create mode 100644 src/SafeCast.kt create mode 100644 src/SealedClass.kt create mode 100644 src/Sequences.kt create mode 100644 src/With.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ddbf4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Kotlin ### +.kotlin + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..7bc07ec --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Environment-dependent path to Maven home directory +/mavenHomeManager.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..d2720f3 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..5535e8f --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..cba7a76 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/KotlinJavaRuntime.xml b/.idea/libraries/KotlinJavaRuntime.xml new file mode 100644 index 0000000..403bcec --- /dev/null +++ b/.idea/libraries/KotlinJavaRuntime.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a9182a4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..544ed1f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/kotlin-for-java-developers.iml b/kotlin-for-java-developers.iml new file mode 100644 index 0000000..43dd653 --- /dev/null +++ b/kotlin-for-java-developers.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Also.kt b/src/Also.kt new file mode 100644 index 0000000..0ba756b --- /dev/null +++ b/src/Also.kt @@ -0,0 +1,4 @@ +fun main() { + // Takes a regular lambda without a receiver + val uppercase = "Hello".also { it.uppercase() } +} \ No newline at end of file diff --git a/src/Apply.kt b/src/Apply.kt new file mode 100644 index 0000000..7e848a8 --- /dev/null +++ b/src/Apply.kt @@ -0,0 +1,4 @@ +fun main() { + // Returns the receiver as the result + val apply = "Hello".apply { print(this) } +} \ No newline at end of file diff --git a/src/Classes.kt b/src/Classes.kt new file mode 100644 index 0000000..a470395 --- /dev/null +++ b/src/Classes.kt @@ -0,0 +1,27 @@ +// Cannot initialize class, must be extended +abstract class Classes(val name: String) { + + // Makes it possible to override method + open fun finalFunction() = 1 + + // Must be overridden when extending + abstract fun overrideMe() +} + +// Primary constructor +class ExtendedClass(name: String, val age: Int) : Classes(name) { + + // Called after primary constructor + init { + println(age) + } + + // secondary constructor + constructor(name: String) : this(name, age = 0) + + // Overrides a method + override fun overrideMe() { + TODO("Not yet implemented") + } + +} \ No newline at end of file diff --git a/src/Conditionals.kt b/src/Conditionals.kt new file mode 100644 index 0000000..c09ba9e --- /dev/null +++ b/src/Conditionals.kt @@ -0,0 +1,30 @@ +import Color.*; + +fun main() { + when (RED) { + RED -> println("red") + BLUE -> println("blue") + GREEN -> println("green") + } + + when ("y") { + "yes", "y" -> println("Yes") + else -> println("No") + } + + val b: A = B() + + when (b) { + // Smart cast b to the type B + is B -> println("B") + else -> println("Unknown") + } +} + +enum class Color { + BLUE, GREEN, RED +} + +open class A + +class B : A() diff --git a/src/Constants.kt b/src/Constants.kt new file mode 100644 index 0000000..1d0f0e2 --- /dev/null +++ b/src/Constants.kt @@ -0,0 +1,8 @@ +// Compile time constant. Is inlined by the compiler. Only works for primitives and Strings. static method in Java +const val MAGIC_NUMBER = 42 + +// Is converted into a static final in Java. Supports all types +@JvmField +val JAVA_CONSTANT = MyClass() + +class MyClass \ No newline at end of file diff --git a/src/DataClass.kt b/src/DataClass.kt new file mode 100644 index 0000000..6c09c9c --- /dev/null +++ b/src/DataClass.kt @@ -0,0 +1,2 @@ +// data adds extra methods like toString, equals and hashcode, only uses data from primary constructor +data class DataClass(val name: String, val age: Int) diff --git a/src/Exceptions.kt b/src/Exceptions.kt new file mode 100644 index 0000000..ff9fd88 --- /dev/null +++ b/src/Exceptions.kt @@ -0,0 +1,17 @@ +import java.io.IOException + +fun main() { + val value = try { + throw NullPointerException("Whoops") + } catch (e: NullPointerException) { + println(e.message) + "Caught NullPointerException" + } + println(value) +} + +// Like the throws keyword in Java. Only needed when calling from Java +@Throws(IOException::class) +fun catchedException() { + throw IOException("Whoops") +} \ No newline at end of file diff --git a/src/Extensions.kt b/src/Extensions.kt new file mode 100644 index 0000000..92f2de1 --- /dev/null +++ b/src/Extensions.kt @@ -0,0 +1,20 @@ +import kotlin.random.Random + +fun main() { + println(9.random()) + listOf(1, 2, 3).sum() +} + +// Defines an extension function on the Int type, must be imported outside the file +// This refers to the value we extend +// Static function under the hood +fun Int.random(): Int = Random.nextInt(this) + +// Add extension to List class of Int +fun List.sum(): Int { + var result = 0 + for (i in this) { + result += i + } + return result +} diff --git a/src/FunctionTypes.kt b/src/FunctionTypes.kt new file mode 100644 index 0000000..5181ce8 --- /dev/null +++ b/src/FunctionTypes.kt @@ -0,0 +1,11 @@ +fun main() { + // Defines a function type with two int args and one int result + val sum: (Int, Int) -> Int = { a, b -> a + b } + println(run { "This is a function which is called immediately" }) + val nullableFunctionType: (() -> Unit)? = null + nullableFunctionType?.invoke() // Safe call a nullable function type + + val isEvenVariable = ::isEven // Store a ref to a function +} + +fun isEven(x: Int) = x % 2 == 0 diff --git a/src/Functions.kt b/src/Functions.kt new file mode 100644 index 0000000..b85f412 --- /dev/null +++ b/src/Functions.kt @@ -0,0 +1,34 @@ +// Specify the name to use when calling from another language +@file:JvmName("fun") + +fun main() { + print(max(2, 3)) + + println( + // Named arguments are optional + listOf("a", "b", "c").joinToString( + separator = "", + prefix = "(", + postfix = ")" + ) + ) + + // Using default value + println(repeatString("*")) + println(repeatString("*", 2)) + // Flip the arguments by using named arguments + println(repeatString(times = 5, s = "*")) +} + +// Top-level function +fun max(a: Int, b: Int): Int = if (a > b) a else b + +// Unit return type +fun print(value: Int) = println("Max is $value") + +// Times has a default value +fun repeatString(s: String, times: Int = 10): String = s.repeat(times) + +// Generates 4 overloaded functions for the JVM. Not needed when only using Kotlin +@JvmOverloads +fun sum(a: Int = 0, b: Int = 0, c: Int = 0) = a + b + c diff --git a/src/Generics.kt b/src/Generics.kt new file mode 100644 index 0000000..93fa3a1 --- /dev/null +++ b/src/Generics.kt @@ -0,0 +1,30 @@ +// T? marks type T as nullable +fun List.filterNullable(predicate: (T) -> Boolean): T? { + TODO() +} + +fun List.filterCanBeNull(predicate: (T) -> Boolean) { + TODO() +} + +// T cannot be nullable +fun List.filterNotNull(predicate: (T) -> Boolean) { + TODO() +} + +// Define bounds using where clause +fun ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable { + if (seq.endsWith(".")) { + seq.append('.') + } +} + +fun List.avg(): Double { + TODO() +} + +// Java doesn't allow overloaded methods that differ only by generic types. We add @JvmName to create a new name when used from Java +@JvmName(name = "avgDouble") +fun List.avg(): Double { + TODO() +} diff --git a/src/HelloWorld.kt b/src/HelloWorld.kt new file mode 100644 index 0000000..00b7141 --- /dev/null +++ b/src/HelloWorld.kt @@ -0,0 +1 @@ +fun main() = println("Hello World") \ No newline at end of file diff --git a/src/In.kt b/src/In.kt new file mode 100644 index 0000000..4c277a5 --- /dev/null +++ b/src/In.kt @@ -0,0 +1,24 @@ +import java.time.Instant + +fun main() { + // Check if a is in abc + println('a' in "abc") + // Check if a is not in abc + println('a' !in "abc") + // Check if now is within the bounds, using comparable under the hood + println(Instant.now() in Instant.MIN..Instant.MAX) +} + +/// Playground +fun isValidIdentifier(s: String): Boolean { + fun isValidCharacter(ch: Char): Boolean = ch == '_' || ch.isLetterOrDigit() + if (s.isEmpty() || s[0].isDigit()) { + return false + } + for (ch in s) { + if (!isValidCharacter(ch)) { + return false + } + } + return true +} diff --git a/src/Inline.kt b/src/Inline.kt new file mode 100644 index 0000000..43a1eec --- /dev/null +++ b/src/Inline.kt @@ -0,0 +1,7 @@ +fun main() { + thisIsInlined { true } +} + +// The function is replaced with the contects of the function +// No performance overhead +inline fun thisIsInlined(predicate: (Int) -> Boolean): Int? = 42.takeIf(predicate) diff --git a/src/Lambdas.kt b/src/Lambdas.kt new file mode 100644 index 0000000..669f3aa --- /dev/null +++ b/src/Lambdas.kt @@ -0,0 +1,35 @@ +fun main() { + listOf(1, 2, 3) + // Lambda, it is inferred argument, only used for single argument lambdas + .filter { it % 2 == 0 } + // Explicitly define args + .map { value -> value * value } + + mapOf(1 to 2) + // Automatic destructuring of mep entries + .mapValues { (key, value) -> "$key $value" } +} + +fun returnFromLambda(): List { + return listOf(3, 0, 5) + .flatMap { + if (it == 0) return emptyList() // Returns from the function, will always return an empty list when 0 + listOf(it, it) + } +} + +fun returnFromLambda2(): List { + return listOf(3, 0, 5) + .flatMap { + if (it == 0) return@flatMap emptyList() // Returns from the lambda + listOf(it, it) + } +} + +fun returnFromLambda3(): List { + return listOf(3, 0, 5) + .flatMap l@{ // Define custom label + if (it == 0) return@l emptyList() // Returns from the lambda + listOf(it, it) + } +} diff --git a/src/Lateinit.kt b/src/Lateinit.kt new file mode 100644 index 0000000..3adf7ae --- /dev/null +++ b/src/Lateinit.kt @@ -0,0 +1,14 @@ +fun main() { + val init = Init() + init.name = "" +} + +class Init { + // Allows setting the value at a later time without making it nullable. We can't use primitives + lateinit var name: String + + /// Checks if name is initialized + fun isInitialized(): Boolean { + return ::name.isInitialized + } +} diff --git a/src/Lazy.kt b/src/Lazy.kt new file mode 100644 index 0000000..d34c713 --- /dev/null +++ b/src/Lazy.kt @@ -0,0 +1,8 @@ +fun main() { + + // Computes only when called and only once + val lazyValue: String by lazy { + println("computed!") + "Hello" + } +} \ No newline at end of file diff --git a/src/Let.kt b/src/Let.kt new file mode 100644 index 0000000..d598805 --- /dev/null +++ b/src/Let.kt @@ -0,0 +1,11 @@ +fun main() { + val string = getString() + // Lambda is only called if string is not null + string?.let { println(it) } + val uppercaseMaybe = string + // Return value if predicate is true, otherwise null + ?.takeIf { it.length > 4 } + ?.let { it.uppercase() } +} + +fun getString(): String? = "Hello" diff --git a/src/Loops.kt b/src/Loops.kt new file mode 100644 index 0000000..d0233a6 --- /dev/null +++ b/src/Loops.kt @@ -0,0 +1,21 @@ +fun main() { + while (true) { + println("While") + break + } + + // Including upper bound + for (x in 1..10) { + print(x) + } + + // Excluding upper bound + for (x in 1 until 10) { + print(x) + } + + // Unpack the map to key and value + for ((key, value) in mapOf("key" to 1, "value" to 2, "key2" to 3)) { + println("$key = $value") + } +} diff --git a/src/Main.kt b/src/Main.kt new file mode 100644 index 0000000..eabd0b9 --- /dev/null +++ b/src/Main.kt @@ -0,0 +1,14 @@ +//TIP To Run code, press or +// click the icon in the gutter. +fun main() { + val name = "Kotlin" + //TIP Press with your caret at the highlighted text + // to see how IntelliJ IDEA suggests fixing it. + println("Hello, $name!") + + for (i in 1..5) { + //TIP Press to start debugging your code. We have set one breakpoint + // for you, but you can always add more by pressing . + println("i = $i") + } +} diff --git a/src/Nothing.kt b/src/Nothing.kt new file mode 100644 index 0000000..7cc2a25 --- /dev/null +++ b/src/Nothing.kt @@ -0,0 +1,8 @@ +fun main() { + doesNotReturn() + println("This is unreachable") +} + +fun doesNotReturn(): Nothing { + throw RuntimeException("This function should always fail") +} diff --git a/src/Nullable.kt b/src/Nullable.kt new file mode 100644 index 0000000..99772ee --- /dev/null +++ b/src/Nullable.kt @@ -0,0 +1,10 @@ +fun main() { + var nullableString: String? = null + println(nullableString?.length ?: 0) // Null safe method call with elvis operator for fallback + nullableString = "Not Null" + println(nullableString) +} + +fun nullableFun(value: Any?) { + value!! // Explicitly tell the compilar that it's not null +} diff --git a/src/Object.kt b/src/Object.kt new file mode 100644 index 0000000..8065774 --- /dev/null +++ b/src/Object.kt @@ -0,0 +1,20 @@ +// A singleton object +object Object { + val name: String + get() { + TODO() + } +} + +class Static { + + + companion object { + // Makes it possible to call it from Java as a static method on the class + // Default is a static method on the companion object + @JvmStatic + fun create(): Companion /* Companion is default name of companion object */ { + return Static + } + } +} diff --git a/src/OperatorOverloading.kt b/src/OperatorOverloading.kt new file mode 100644 index 0000000..1d0289f --- /dev/null +++ b/src/OperatorOverloading.kt @@ -0,0 +1,7 @@ +// Implement the plus method on custom class +// Same with other operators +operator fun MyNewOperator.plus(other: MyNewOperator): MyNewOperator { + return TODO() +} + +class MyNewOperator diff --git a/src/Properties.kt b/src/Properties.kt new file mode 100644 index 0000000..4074618 --- /dev/null +++ b/src/Properties.kt @@ -0,0 +1,24 @@ +fun main() { + val person = Person("name", age = 5) + println(person.name) // Calling getter + person.age = 6 // Calling setter +} + +// Defines a class with 2 properties. +// val generates only getter, var generates both getters and setters +class Person(val name: String, var age: Int) + +class Address { + + // Defines get and set methods with custom logic + var place: String = "" + get() = field.uppercase() + set(value) { + field = value.lowercase() + } + +} + +// Extension property on String +val String.name: String + get() = this.lowercase() diff --git a/src/Run.kt b/src/Run.kt new file mode 100644 index 0000000..3eddde6 --- /dev/null +++ b/src/Run.kt @@ -0,0 +1,6 @@ +fun main() { + // Similar to with but can be used with nullable values + val uppercase = nullableString()?.run { uppercase() } +} + +fun nullableString(): String? = "Hello" diff --git a/src/SafeCast.kt b/src/SafeCast.kt new file mode 100644 index 0000000..3e39bb1 --- /dev/null +++ b/src/SafeCast.kt @@ -0,0 +1,11 @@ +fun main() { + any("Whoops") +} + +fun any(any: Any) { + if (any is String) { + println(any.length) + } + // Safe case + println((any as? String)?.uppercase()) +} \ No newline at end of file diff --git a/src/SealedClass.kt b/src/SealedClass.kt new file mode 100644 index 0000000..6a37bfc --- /dev/null +++ b/src/SealedClass.kt @@ -0,0 +1,11 @@ +sealed class SealedClass +class OK : SealedClass() +class NotFound : SealedClass() + +fun main() { + // Is exhaustive since we know only Ok and NotFound extends sealed class + when (OK() as SealedClass) { + is OK -> println("OK") + is NotFound -> println("Not found") + } +} diff --git a/src/Sequences.kt b/src/Sequences.kt new file mode 100644 index 0000000..bdd1d8f --- /dev/null +++ b/src/Sequences.kt @@ -0,0 +1,22 @@ +fun main() { + // Lazy list, like Java Streams + sequenceOf(1, 2, 3) + .map { it * it } + // Will only enumerate the first 2 elements + .first { it > 2 } +} + +fun yieldSequence(): Sequence = sequence { + // yield returns a value to generate a sequence + while (true) yield(1) +} + +fun fibonacci(): Sequence = sequence { + yieldAll(0..1) + var values = Pair(0, 1) + while (true) { + val nextValue = values.first + values.second + yield(nextValue) + values = Pair(values.second, nextValue) + } +} diff --git a/src/With.kt b/src/With.kt new file mode 100644 index 0000000..b2c562a --- /dev/null +++ b/src/With.kt @@ -0,0 +1,33 @@ +fun main() { + val sb = StringBuilder() + // sb becomes this within the lambda scope + val string = with(sb) /* Is an extention function on type T */ { + appendLine("Something") + append('a') + toString() + } + + val buildString = buildString { + append(1) + append(2) + toString() + } + + val words = Words() + with(words) { + // The following two lines should compile: + "one".record() + +"two" + } + words.toString() == "[one, two]" +} + +class Words { + private val list = mutableListOf() + + fun String.record() = list.add(this) + + operator fun String.unaryPlus() = list.add(this) + + override fun toString() = list.toString() +}