The Kotlin you’ll write every day
If you’ve written Java, Kotlin will feel like Java with the boring parts removed: no semicolons, no new, no getters and setters you write by hand, and a compiler that refuses to let you forget about null. If you’re coming from Swift, it will feel familiar: val is let, ? means optional, and when is switch.
Kotlin’s value on Android is less about syntax and more about the type system: nullability in types, sealed hierarchies with exhaustive when, value semantics via data classes, and coroutines (covered in Coroutines and Flow). Everything compiles to JVM bytecode with predictable costs; the things to know are what each feature generates (data class methods, lambdas as objects or inlined, companion objects as static holders) and where the Java boundary weakens the guarantees.
data class Product(
val id: String,
val name: String,
val priceCents: Long,
val discountPercent: Int = 0, // default value: callers may leave it out
val imageUrl: String? = null, // ? means "may be null"
) {
val finalPriceCents: Long
get() = priceCents * (100 - discountPercent) / 100
fun label(): String = when {
discountPercent >= 50 -> "Big sale"
discountPercent > 0 -> "On sale"
else -> "Regular"
}
}
val mug = Product(id = "7", name = "Mug", priceCents = 950) // no "new"
val title = "${mug.name}: ${mug.finalPriceCents} cents" // string template
val copy = mug.copy(discountPercent = 20) // a changed copypublic final class Product {
private final String id;
private final String name;
private final long priceCents;
private final int discountPercent;
@Nullable private final String imageUrl;
public Product(String id, String name, long priceCents, int discountPercent, @Nullable String imageUrl) {
this.id = id; this.name = name; this.priceCents = priceCents;
this.discountPercent = discountPercent; this.imageUrl = imageUrl;
}
public String getName() { return name; }
public long getPriceCents() { return priceCents; }
// …getters for the rest, plus equals, hashCode, toString, and a copy method by hand
public long getFinalPriceCents() { return priceCents * (100 - discountPercent) / 100; }
public String label() {
if (discountPercent >= 50) return "Big sale";
if (discountPercent > 0) return "On sale";
return "Regular";
}
}What Kotlin gave you for free:
data classgeneratesequals,hashCode,toStringandcopyfrom the properties in parentheses.valproperties can’t be reassigned; Kotlin writes the getters for you (Java code still seesgetName()).- Default and named arguments replace a pile of overloaded constructors.
String?says, in the type, thatimageUrlmay be missing. A plainStringcan never benull.whenis aswitchthat can test conditions and return a value.
Null safety in practice
val url: String? = product.imageUrl
val length = url?.length // Int?: null if url is null
val shown = url ?: "placeholder.png" // a fallback when it's null
if (url != null) {
loadImage(url) // smart cast: here url is a String
}
url?.let { loadImage(it) } // run only when it's not null
val sure = url!! // "I know it's not null": crashes if it is@Nullable String url = product.getImageUrl();
Integer length = url != null ? url.length() : null;
String shown = url != null ? url : "placeholder.png";
if (url != null) {
loadImage(url);
}
// Java has no compile-time help: forgetting a check is a NullPointerException at runtime.The !! operator turns Kotlin’s compile-time safety back into a runtime crash. Every !! is a promise that something can’t be null; when that promise is wrong, the app crashes with a NullPointerException. The Null safety tutorial goes deeper.
Tap the line with the bug.
class ProductActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val productId = intent.getStringExtra("product_id")!!
setContent { ProductScreen(productId) }
}
}public class ProductActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String productId = getIntent().getStringExtra("product_id");
loadProduct(productId.trim());
}
}Hint: The app opens this screen from a list (which passes product_id) and from a marketing push notification (built by another team, which passes productId). What does getStringExtra return for a key that isn't there?
getStringExtra returns null when the key is missing (Kotlin sees it as String?). Here a push notification uses a slightly different key, and !! turns that into a crash on launch, before the user sees anything. In Java there’s no !!, but the result is the same NullPointerException, one line later.
Decide what “missing” means instead of crashing:
val productId = intent.getStringExtra("product_id")
if (productId == null) {
Log.w("ProductActivity", "Opened without product_id: ${intent.extras?.keySet()}")
finish() // or show a "not found" screen
return
}
setContent { ProductScreen(productId) } // smart cast: productId is a String hereString productId = getIntent().getStringExtra("product_id");
if (productId == null) {
Log.w("ProductActivity", "Opened without product_id");
finish();
return;
}Even better: define the key once (a constant or a type-safe navigation route, see Navigation) so the list and the notification can’t disagree about it.
Lambdas and trailing lambdas
Android code passes functions around constantly: click listeners, callbacks, list transformations. In Kotlin, a lambda is written in braces, and when it’s the last argument it goes after the parentheses:
button.setOnClickListener { view -> openDetails() } // a trailing lambda
button.setOnClickListener { openDetails() } // the parameter can be left out
val names = products
.filter { it.discountPercent > 0 } // it: the single parameter
.sortedBy { it.finalPriceCents }
.map { it.name }
Button(onClick = { cart.add(product) }) { // Compose: content as a trailing lambda
Text("Add to cart")
}button.setOnClickListener(view -> openDetails());
List<String> names = products.stream()
.filter(p -> p.getDiscountPercent() > 0)
.sorted(Comparator.comparingLong(Product::getFinalPriceCents))
.map(Product::getName)
.toList();Predict the output
What does this print?
val products = listOf(
Product("1", "Mug", 950, discountPercent = 15),
Product("2", "Lamp", 4900, discountPercent = 60),
Product("3", "Pen", 200),
)
println(products.sortedByDescending { it.discountPercent }.map { it.label() })List<Product> products = List.of(
new Product("1", "Mug", 950, 15, null),
new Product("2", "Lamp", 4900, 60, null),
new Product("3", "Pen", 200, 0, null));
System.out.println(products.stream()
.sorted(Comparator.comparingInt(Product::getDiscountPercent).reversed())
.map(Product::label).toList());sortedByDescending puts the biggest discount first: 60, 15, 0. map { it.label() } turns each product into its label: 60% is Big sale (≥ 50), 15% is On sale, 0% is Regular. Each lambda gets one product as it.
What the compiler generates
- data class:
equals/hashCode/toString/copy/componentNfrom primary-constructor properties only; properties declared in the body are ignored by all of them (a common bug when someone moves a property into the body). - Lambdas: non-inline lambdas that capture variables become objects (an allocation per call site evaluation);
inlinehigher-order functions (map,filter,let,apply) are inlined, soreturninside them is non-local. - Companion objects: a static nested class plus an instance; use
@JvmStatic/@JvmField/const valwhen Java callers need real statics. - Default arguments: a synthetic method with a bitmask; Java callers see only the full signature unless you add
@JvmOverloads.
- Data classes: generated members from primary-constructor properties only;
copyis shallow. - Nullability:
?.,?:, smart casts on stable values,letfor scoped non-null; every!!is a crash site to justify. - Inline higher-order functions avoid lambda allocations and allow non-local returns; capturing non-inline lambdas allocate.
- Java callers:
@JvmStatic,@JvmField,const val,@JvmOverloads,@file:JvmName.
Check this part
What does a Kotlin data class generate for you?
intent.getStringExtra("id")!!and the extra is missing. What happens?A property is declared in a data class's body, not its constructor. Is it part of equals and copy?