KotlinCS 124 LogoJava
PrevIndexNext
Kotlin
Java
  • Implementing a Map : 04/26/2024

  • map-reduce-filter : 04/25/2024

  • Generics : 04/24/2024

  • Hashing : 04/23/2024

  • Binary Search : 04/22/2024

  • MP3: Course Ratings : 04/19/2024

  • Quicksort : 04/18/2024

  • Merge Sort : 04/17/2024

  • Sorting Algorithms : 04/16/2024

  • MP Debugging Part 1 : 04/15/2024

  • MP2: Course Activity : 04/12/2024

  • Practice with Recursion : 04/11/2024

  • MP Debugging Part 0 : 04/10/2024

  • MP2: API Client : 04/09/2024

  • MP2: API Server : 04/08/2024

  • Trees and Recursion : 04/05/2024

  • Trees : 04/04/2024

  • Recursion : 04/03/2024

  • MP1: Filtering and Search : 04/02/2024

  • MP1: Loading and Sorting : 04/01/2024

  • Lists Review and Performance : 03/29/2024

  • Linked Lists : 03/28/2024

  • Algorithms and Lists : 03/27/2024

  • Continuing MP0 : 03/26/2024

  • Getting Started with MP0 : 03/25/2024

  • Lambda Expressions : 03/22/2024

  • Anonymous Classes : 03/21/2024

  • Practice with Interfaces : 03/20/2024

  • Implementing Interfaces : 03/19/2024

  • Using Interfaces : 03/18/2024

  • Working with Exceptions : 03/08/2024

  • Throwing Exceptions : 03/07/2024

  • Catching Exceptions : 03/06/2024

  • References and Polymorphism : 03/05/2024

  • References : 03/04/2024

  • Data Modeling 2 : 03/01/2024

  • Equality and Object Copying : 02/29/2024

  • Polymorphism : 02/28/2024

  • Inheritance : 02/27/2024

  • Data Modeling 1 : 02/26/2024

  • Companion Objects : 02/23/2024

  • Encapsulation : 02/22/2024

  • Constructors : 02/21/2024

  • Objects, Continued : 02/20/2024

  • Introduction to Objects : 02/19/2024

  • Compilation and Immutability : 02/16/2024

  • Practice with Collections : 02/15/2024

  • Maps and Sets : 02/14/2024

  • Lists and Type Parameters : 02/13/2024

  • Imports and Libraries : 02/12/2024

  • Multidimensional Arrays : 02/09/2024

  • Practice with Strings : 02/08/2024

  • null : 02/07/2024

  • Algorithms and Strings : 02/06/2024

  • Strings : 02/05/2024

  • Functions and Algorithms : 02/02/2024

  • Practice with Functions : 02/01/2024

  • More About Functions : 01/31/2024

  • Errors and Debugging : 01/30/2024

  • Functions : 01/29/2024

  • Practice with Loops and Algorithms : 01/26/2024

  • Algorithms : 01/25/2024

  • Loops : 01/24/2024

  • Arrays : 01/23/2024

  • Compound Conditionals : 01/22/2024

  • Conditional Expressions and Statements : 01/19/2024

  • Operations on Variables : 01/18/2024

  • Variables and Types : 01/17/2024

  • Welcome to CS 124 : 01/16/2024

Compilation and Immutability

var i = 0
val j = 1
j = 2

Welcome back to CS 124! You are not alone.

This lesson is a lot of fun. We’ll spend some time on our first discussion of Kotlin internals. Specifically, we’ll explain more about what happens when your code is run. Then we’ll discuss immutability in Kotlin.

Warm Up Debugging Challenge
Warm Up Debugging Challenge

Let’s warm up with another graded debugging challenge!

Compilation and Execution
Compilation and Execution

Our focus in this class is on teaching you to think computationally and express those thoughts coherently in the Kotlin programming language. Once you do that, you’ll be able to learn other programming languages easily and accomplish other computational tasks.

So we don’t spend a lot of time on exactly what is going on under the hood when Kotlin executes the code that you submit. On some level, it doesn’t matter. As long as the core language constructs have a stable meaning, the internals of how things work don’t matter. When I write:

for (i in 0 until 8) {
println("Happy birthday!")
}

I expect to see a message displayed 8 times. Kotlin could and might completely change exactly how that is accomplished, but as long as it is accomplished, I’m not going to notice. This is a powerful idea in computer science, even if at times the methods of implementation are somewhat less than practical.

But! In this case having a bit of an understanding of what is happening is actually useful. And, what is actually happening is quite interesting! So let’s peek under the hood…

Two Steps
Two Steps

When you run a Kotlin program there are actually two distinct steps that are taking place:

  1. Compilation: in this step your Kotlin source code is compiled or translated into a different set of simpler instructions. The compiler can and does catch certain kinds of errors at this stage.
  2. Execution: the Kotlin compiler produces a file containing bytecode, which can then be executed. This is when you program actually runs and does whatever you’ve instructed it to do. There are other errors that can occur at this stage.

Compilation
Compilation

Let’s talk a bit about when compilation happens, what is produced, and the kind of errors that the compiler can produce during this step.

Execution
Execution

Now let’s return to the same environment and discuss the execution step and the kind of errors that can occur at that point.

Development v. Production
Development v. Production

One of the reasons understanding these steps is so important is that they have a relationship to the difference between development and production:

Compiler errors generally only happen in development. That means that only developers see them! In contrast, runtime errors can happen in production! That means they might affect actual users.

Software developers acquire a high degree of patience with broken and crashy software. Users do not. That means that, if you can reduce runtime errors and catch them during development, you will produce better software.

Kotlin represents a new generation of compilers that harnesses the power of modern computers to help catch errors in your code before they affect users. So while Java allows you to do this—this code generates a runtime error:

// WHY DOES JAVA LET ME DO THIS!?!?!?!
String s = null;
System.out.println(s.length());

Kotlin does not—this code generates a compiler error:

// Kotlin's got your back...
var s: String? = null
println(s.length)

Practice: List to Set

Created By: Geoffrey Challen
/ Version: 2021.9.0

Given a nullable List of Strings, write a method toSet that converts it to a Set of Strings that contains all the same Strings in the original list. So given a List containing "test", "me", and "test", you would return a Set containing "test" and "me". require that the passed List is not null.

Immutability in Kotlin
Immutability in Kotlin

Before we conclude, we’ll introduce an important idea in Kotlin: immutability.

We’ve discussed previously how to declare and initialize variables in Kotlin:

var i = 8
println(i)

We’ve also pointed out that, due to Kotlin’s powerful type inference, even the Kotlin tracks and enforces variable types, we usually don’t need to explicitly provide types. In the example above, Kotlin will figure out what type i is from the type of the initial assignment. 8 is an Int literal, so i will be inferred to have type Int. We know this.

val and Immutable Variables
val and Immutable Variables

But what is this var keyword that we are using above? It seems somewhat unnecessary at this point. Using it does help us distinguish between variable declaration and reassignment:

var i = 8 // var -> Declaring and initializing i
i = 10 // no var -> reassigning i

However, var actually hints at a deeper and more fundamental feature of Kotlin: immutability. To see how this works, let’s examine var’s counterpart, val:

var i = 8 // var -> i can be reassigned
i = 10
val j = 10 // val -> i cannot be reassigned
j = 20 // fails during compilation

if Expressions
if Expressions

In addition to val to declare an immutable variable, Kotlin provides a number of other features to support immutability in our code.

Consider the following snippet:

val temperature = 100
var isHot = false
if (temperature > 90) {
isHot = true
}
println(isHot)

isHot is entirely dependent on the value of temperature, which in the code above is a val and so cannot change. But, because we need to set the value of isHot inside an if statement, we have to declare it as a var so that we can change it in the if branch.

Happily Kotlin provides a very elegant way around this problem: if expressions. Previously we’ve seen if statements. if expressions are very similar, except that they can be used on the right side of an assignment! Let’s explore this idea together:

val temperature = 100
val isHot = if (temperature > 90) {
true
} else {
false
}
println(isHot)

if expressions turn out to be incredibly useful and powerful, and we’ll use them whenever we need to initialize a variable based on a condition. Not only are they clear and expressive, but they frequently allow us to mark the initialized variable as val if it does not need to change after the assignment.

Why Immutability
Why Immutability

Kotlin’s use of var and val promote immutability to a first-class citizen in the language. (Simliar behavior is possible in Java, which Kotlin interoperates with, through the final keyword. But it is less central to the design of the language.) Each time you create a variable you must decide if it can be reassigned (var) or not (val).

You may think that this is a bit strange. After all, it’s a variable! Shouldn’t its value be able to change? Sometimes change is necessary. But often it turns out not to be. And, by reducing the number of variables that can change in our programs, we end up writing code that is easier to understand.

Put it this way. When I create and initialize a val, I know immediately what its value is and always will be. In contrast, when I create and initialize a var, I have to keep an eye out for any places that its value could change unexpectedly in the code that follows. That makes my code harder to understand.

Immutability is an important software design principle. So while it may not seem completely clear why this is so important now, starting now we’ll try and take advantages of val and Kotlin’s other immutability features to reduce the amount of mutability in our code.

Practice: String Duplicate Words Ignore Case

Created By: Geoffrey Challen
/ Version: 2021.9.0

Given a String containing words separated by the " " (space) character, write a method hasDuplicateWords that returns true if any word appears twice in the String. Also, you should ignore case: so "Chuchu" and "chuchu" are considered the same word. require that the passed String is not null.

So, for example, given the String "Wow that is amazing", you would return false, but given the String "Chuchu chuchu xyz" you would return true.

Our intention is for you to solve this problem using a Set, but there are also solutions that use a Map. You should not use a nested loop to solve this problem. You may want to use the lowercase method of Kotlin Strings.

Script Parser Warm-Up
Script Parser Warm-Up

Next let’s discuss how to approach our next practice homework problem. This problem is a bit trickier, since we need to determine when to properly insert entries into our Map, and do some String parsing. So let’s discuss how to get started.

Please don’t attempt this practice problem until you are done with yesterday’s homework problem!

// Warm-up for Script Parser
Created By: Geoffrey Challen
/ Version: 2022.2.0

Write a method called parseScript that accepts a single String and returns a Map<String, MutableList<String>>. The passed String contains a script consisting of lines separated by newlines, each with the following format:

Name: Line

For example, here's a simple script:

Geoffrey: What do you think of this homework problem?
Ahmed: it's a bit sus
Geoffrey: I bet they'll be able to figure it out!
Maaheen: We'll be here to help if they need it.

parseScript parses the script and returns a map mapping each character's name to their lines in order. So, for the script above, the map would contain three keys: "Geoffrey", "Ahmed", and "Maaheen". The List<String> for the key "Geoffrey" would contain the Strings "What do you think of this homework problem!" and "I bet they'll be able to figure it out!" The List<String> for the key "Amhed" would contain the String "it's a bit sus".

A few hints for approaching this problem.

You'll want to use .split to parse the passed String into individual lines. All lines will have the format shown above.

You'll also need to use .split to split each line into the name and their line of dialog. You can assume that the character ":" only appears to delimit the name of the rest of the line.

The first time you encounter a character, there will not be an entry in your map for them. So you should check for this, and create the list when appropriate.

There may be extra whitespace around the name or the line of dialogue, so use .trim appropriately.

You should not need to use any import statements to solve this problem. Instead, use mutableMapOf() and mutableListOf() to create mutable maps and lists when needed.

No New Homework Today
No New Homework Today

Please continue to work on yesterday’s more challenging problem. Good luck! Jump on the tutoring site or find us on the forum when you need some assistance. And keep in mind that this is just one problem…

More Practice

Need more practice? Head over to the practice page.