Question
I am new to Kotlin and converted a simple Java file using Android Studio. The Java class contains only a static method:
public class MyClass {
public static int generateChecksumCrc16(byte[] bytes) {
int crc = 0xFFFF;
int temp;
int crcByte;
for (byte aByte : bytes) {
crcByte = aByte;
for (int bitIndex = 0; bitIndex < 8; bitIndex++) {
temp = (crc >> 15) ^ (crcByte >> 7);
crc <<= 1;
crc &= 0xFFFF;
if (temp > 0) {
crc ^= 0x1021;
crc &= 0xFFFF;
}
crcByte <<= 1;
crcByte &= 0xFF;
}
}
return crc;
}
}
Android Studio converted it to a Kotlin object:
object MyClass {
fun generateChecksumCrc16(bytes: ByteArray): Int {
var crc = 0xFFFF
var temp: Int
var crcByte: Int
for (aByte in bytes) {
crcByte = aByte.toInt()
for (bitIndex in 0..7) {
temp = (crc shr 15) xor (crcByte shr 7)
crc = crc shl 1
crc = crc and 0xFFFF
if (temp > 0) {
crc = crc xor 0x1021
crc = crc and 0xFFFF
}
crcByte = crcByte shl 1
crcByte = crcByte and 0xFF
}
}
return crc
}
}
Why did the converter use object rather than a regular Kotlin class? What is the difference between a Kotlin class and an object in this situation?
Short Answer
A Kotlin class is a blueprint from which you can create many instances. A Kotlin object creates exactly one shared instance: a singleton. Android Studio converted the Java type to an object because the original class contained only a static utility method. Kotlin has no static keyword, and an object is one way to group functionality that does not need per-instance state.
You will also learn when a top-level function or a companion object is a better Kotlin design.
Concept
In Java, static members belong to the class itself, not to an instance:
MyClass.generateChecksumCrc16(data);
A Java class that contains only static methods is often used as a utility class. You do not create new MyClass() to call its methods.
Kotlin does not have the static keyword. Instead, it provides several choices depending on what the code represents:
class: a reusable blueprint. Each constructor call creates a separate object.objectdeclaration: one automatically created, globally accessible instance. This is the singleton pattern.companion object: an object associated with a class; useful for factory methods, constants, and APIs conceptually owned by that class.- Top-level declaration: a function or property directly in a Kotlin file. This is often the simplest choice for a standalone utility function.
The converted Java class had no instance fields, constructor use, or instance methods. Its only method was static, so it did not need multiple MyClass instances. The converter chose:
object MyClass
That lets callers use nearly the same style:
val checksum = MyClass.generateChecksumCrc16(bytes)
Unlike Java's static, an object is still a real object. It can implement interfaces, hold state, and be passed as a value. Because it has one shared instance, mutable state inside an object must be handled carefully, especially in concurrent code.
Mental Model
Think of a class as a cookie cutter. You can use one cutter to make many cookies, and each cookie is a separate object with its own data.
class User(val name: String)
val firstUser = User("Asha")
val secondUser = User("Mateo")
Think of an object as a single shared noticeboard. There is one noticeboard for everyone; you do not create another one each time you need it.
object AppSettings {
var darkMode = false
}
A CRC calculation utility does not need a separate identity or stored data for every caller. It is closer to a shared tool on the wall, which is why an object can fit it.
Syntax and Examples
A regular class defines properties and behavior for instances:
class Counter {
var value = 0
fun increment() {
value++
}
}
val first = Counter()
val second = Counter()
first.increment()
println(first.value) // 1
println(second.value) // 0
first and second are different objects, so their value properties are independent.
An object declaration defines one singleton instance:
object ChecksumUtils {
fun isValidByteCount(bytes: ByteArray): Boolean {
return bytes.isNotEmpty()
}
}
val valid = ChecksumUtils.isValidByteCount(byteArrayOf(1, 2, 3))
You do not write ChecksumUtils(). Kotlin creates the sole instance automatically.
For a standalone utility function, a top-level function is often more idiomatic:
fun generateChecksumCrc16(bytes: ByteArray): Int {
var crc = 0xFFFF
for (aByte in bytes) {
var crcByte = aByte.toInt()
repeat(8) {
val temp = (crc shr 15) xor (crcByte shr 7)
crc = (crc shl 1) and 0xFFFF
if (temp > 0) {
crc = (crc xor 0x1021) and 0xFFFF
}
crcByte = (crcByte shl 1) and 0xFF
}
}
return crc
}
val checksum = generateChecksumCrc16(byteArrayOf(0x01, 0x02))
Top-level functions can be called directly within Kotlin, without a utility class name.
Step by Step Execution
Consider this singleton:
object RequestIds {
private var nextId = 1
fun newId(): Int {
val id = nextId
nextId++
return id
}
}
val first = RequestIds.newId()
val second = RequestIds.newId()
Execution proceeds as follows:
object RequestIdsdeclares one singleton instance. No code callsRequestIds().nextIdstarts as1inside that one instance.RequestIds.newId()calls a function on the singleton.- On the first call,
idreceives1, thennextIdbecomes2; the function returns1. - On the second call, the same singleton is used.
idreceives2, thennextIdbecomes3; the function returns2.
By contrast, each Counter() call for a regular class would create a separate value property.
For the CRC code, the variables crc, temp, and crcByte are local variables inside the function. Every call gets fresh local values, even though the function belongs to an object.
Real World Use Cases
Use a class when each item needs its own state or identity:
- A
User,Order,SocketConnection, orGameCharacter. - A parser configured differently for different inputs.
- A service that receives dependencies through its constructor.
Use an object when exactly one shared instance makes sense:
- An application-wide configuration holder, preferably immutable after setup.
- A stateless utility group when a top-level function is not appropriate.
- An implementation of an interface that has no per-instance state.
- A logger or a sealed hierarchy member.
interface Formatter {
fun format(value: Int): String
}
object HexFormatter : Formatter {
override fun format(value: Int): String = value.toString(16)
}
Use a top-level function for simple operations such as checksum calculations, string transformations, or data conversions:
fun normalizeEmail(email: String): String = email.trim().lowercase()
Use a companion object when behavior belongs to a particular type:
class User private constructor(val name: String) {
companion object {
fun guest(): User = User("Guest")
}
}
val guest = User.guest()
Real Codebase Usage
In Kotlin projects, utility code is commonly kept as top-level functions rather than Java-style static utility classes:
// Crc16.kt
fun generateChecksumCrc16(bytes: ByteArray): Int {
// calculation
return 0
}
A class is usually chosen when it owns dependencies or configuration:
class Crc16Calculator(private val initialValue: Int = 0xFFFF) {
fun calculate(bytes: ByteArray): Int {
var crc = initialValue
// calculation
return crc
}
}
This design makes different configurations explicit and improves testability.
A companion object is common for factories, constants, and validation that is strongly associated with a type:
class ApiKey private constructor(val value: String) {
companion object {
fun from(value: String): ApiKey {
require(value.isNotBlank()) { "API key cannot be blank" }
return ApiKey(value)
}
}
}
Use singleton objects cautiously when they contain mutable state. Hidden global state can make tests influence one another and can cause thread-safety problems. Prefer passing dependencies into classes when the object represents a service with replaceable behavior.
For Java callers, Kotlin top-level functions compile to static methods in a generated file class. An object method is normally called from Java through MyObject.INSTANCE.method(). Add @JvmStatic when Java callers need static-style access from an object or companion object.
Common Mistakes
Trying to instantiate an object
This does not compile because an object already has its one instance:
object ChecksumUtils
// val utils = ChecksumUtils() // Error
Use the object's name directly:
val utils = ChecksumUtils
Usually, call its function directly instead:
ChecksumUtils.generateChecksumCrc16(bytes)
Using a class for static-style code without a companion object
A regular class method requires an instance:
class ChecksumUtils {
fun calculate(bytes: ByteArray): Int = bytes.size
}
val value = ChecksumUtils().calculate(bytes)
This is valid, but creating an instance solely to call a stateless helper is often unnecessary. Prefer a top-level function, an object, or a companion object based on ownership.
Treating an object as automatically thread-safe
Kotlin initializes an object safely, but mutable properties still need proper synchronization or a safer design when multiple threads update them:
object Statistics {
var requests = 0
fun recordRequest() {
requests++ // Not automatically safe for concurrent updates
}
}
Avoid mutable global state where possible.
Using object for data that should have multiple instances
object User {
var name = ""
}
This represents only one user for the whole program. Use a class instead:
class User(val name: String)
Forgetting Kotlin byte conversion details
Byte is signed in Kotlin, ranging from -128 to 127. The conversion aByte.toInt() preserves the signed value. CRC algorithms often mask shifted values with and 0xFF, as the converted code does, to keep the intended lower eight bits.
Comparisons
| Feature | class | object declaration | companion object | Top-level function |
|---|---|---|---|---|
| Number of instances | Any number | Exactly one | Exactly one per containing class | No instance exposed |
Create with () | Yes | No | No | Not applicable |
| Best for | Per-instance state and behavior | Singleton behavior or shared utility | Type-related factories and constants | Standalone utility behavior |
| Kotlin call style | User("Asha") | Settings.load() | User.guest() | generateChecksumCrc16(bytes) |
| Java static equivalent | No direct equivalent | Similar use case, but is an instance | Similar use case | Compiles to a static method in a file class |
A companion object is not the same as Java static, even though Kotlin lets you call it with the class name:
class Connection {
companion object {
const val DEFAULT_PORT = 443
}
}
println(Connection.DEFAULT_PORT)
Internally, the companion is still an object associated with Connection.
Cheat Sheet
// Many independent instances
class User(val name: String)
val user = User("Asha")
// Exactly one instance
object AppConfig {
const val API_VERSION = "v1"
}
println(AppConfig.API_VERSION)
// Behavior associated with a type
class UserId private constructor(val value: String) {
companion object {
fun create(value: String) = UserId(value)
}
}
val id = UserId.create("u-42")
// Standalone utility, often preferred
fun generateChecksumCrc16(bytes: ByteArray): Int = 0
- Kotlin has no
statickeyword. - Use
classwhen callers need multiple objects with separate state. - Use
objectfor one shared instance. - Do not call an
objectwith parentheses. - Prefer top-level functions for small, stateless utilities.
- Use a companion object for constants, factories, and type-related operations.
- Avoid mutable state in singleton objects unless its lifetime and concurrency are deliberately managed.
- For Java-friendly static access from an
objector companion, use@JvmStaticwhen needed.
FAQ
Why did Android Studio convert my Java class to a Kotlin object?
Your Java class had only a static method. Since Kotlin has no static keyword, the converter used a singleton object so the method can still be called as MyClass.generateChecksumCrc16(bytes).
Is Kotlin object the same as Java static?
Not exactly. Java static members belong to a class and do not require an object instance. A Kotlin object is a real singleton instance. It often serves a similar purpose for utility code.
Should I use an object for every utility method in Kotlin?
Usually no. A top-level function is often simpler for a standalone, stateless operation. Use an object when grouping related behavior or representing a genuine singleton improves clarity.
Can a Kotlin object have properties and functions?
Yes. It can contain properties, functions, implement interfaces, and have initialization code. Its properties are shared because there is only one instance.
Can I create multiple instances of a Kotlin object?
No. An object declaration defines exactly one instance. Use a class when you need multiple instances.
When should I use a companion object?
Use one for behavior strongly associated with a class, such as factory functions, constants, or parsing methods like UserId.from(value).
Can Java call functions in a Kotlin object?
Yes. Java normally accesses the singleton through MyObject.INSTANCE. You can annotate suitable functions with @JvmStatic to provide static-style Java access.
Does object make my code thread-safe?
Object initialization is safe, but mutable state inside the object is not automatically safe for concurrent updates.
Mini Project
Description
Build a small CRC-16 utility that demonstrates how a stateless calculation can be placed in a Kotlin object. The calculator keeps all calculation variables local, so each call is independent and the singleton does not retain request-specific state.
Goal
Create a reusable Crc16 singleton and calculate a CRC-16 checksum for one or more byte arrays.
Requirements
Requirement 1
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.