You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Kotlin中data class与inline class的区别及inline class设计理念、独有能力解析

Great question! Let's unpack the differences between Kotlin's data class and inline class, dive into the core ideas behind inline classes, and explore their unique capabilities that data classes can't match.

Kotlin Data Class vs. Inline Class: Key Differences

First, let's clarify the fundamental distinctions between the two:

  • Core Purpose:
    • data class is built to simplify creating classes whose sole job is holding structured data. Kotlin automatically generates utility methods like equals(), hashCode(), toString(), and copy() for you—making it perfect for DTOs, domain models, and other data-focused classes.
    • inline class (especially with the value syntax introduced in Kotlin 1.5.0) exists for type-safe wrapping of a single value without runtime overhead. It's a way to add type safety without paying the cost of object allocation.
  • Property Limits:
    • An inline class can only declare exactly one property (marked with value in Kotlin 1.5+). This isn't a random restriction—it's critical to how inline classes achieve their zero-overhead guarantee.
    • A data class can have any number of properties, as long as its main role is storing data.
  • Runtime Behavior:
    • In most cases, inline classes are erased at compile time—they're replaced with their underlying value type in the generated bytecode. For example, inline class UserId(val value: Int) will act as an Int at runtime unless you need to treat it as a generic type or Any, which triggers temporary boxing.
    • Data classes are regular class instances; every time you create a data class object, you're allocating memory for a new instance on the heap.
  • Inheritance Rules:
    • Inline classes can't inherit from other classes (they can only implement interfaces). This restriction prevents breaking their inline semantics.
    • Data classes can inherit from non-data classes, but they can't inherit from other data classes (since that would conflict with the auto-generated utility methods).
The Design Philosophy Behind Inline Classes

The core idea of inline classes is solving a common developer dilemma: how to get type safety without sacrificing performance.

Before inline classes, we had two imperfect options for wrapping values:

  1. Use a regular/data class: This gives type safety but adds runtime overhead (object allocation, method call indirection).
  2. Use a type alias: This has no overhead but offers no real type safety—typealias UserId = Int and typealias OrderId = Int are still treated as identical Int types by the compiler, so you can accidentally pass an OrderId where a UserId is expected.

Inline classes strike the perfect balance:

  • They act as distinct types at compile time, catching type mismatches early (like passing an OrderId to a function expecting a UserId).
  • At runtime, they're replaced with their underlying value, so there's no extra memory usage or performance hit compared to using the raw type directly.

This makes them ideal for scenarios where you need to distinguish between semantically different values that share the same underlying type—like user IDs, order IDs, measurements, or status codes.

What Inline Classes Can Do That Data Classes Can't

Now, let's look at the unique superpowers of inline classes:

  • Zero-overhead type safety: This is the biggest advantage. For example, if you have a function fun fetchUser(id: UserId), using an inline class UserId ensures you can't pass a random Int or OrderId by mistake—yet the compiled code treats id as an Int, so there's no object allocation overhead. A data class data class UserId(val value: Int) would require creating an instance every time, adding unnecessary heap allocations.
  • Seamless interop with raw types: Inline classes can be automatically converted to their underlying value type when needed (and vice versa with explicit conversion). For example, you can pass a UserId directly to a function that expects an Int, and the compiler handles the unwrapping for you. With a data class, you'd have to explicitly call userId.value every time.
  • Efficient generic usage: Kotlin 1.5+ allows inline classes to be used as generic type arguments (like List<UserId>). Under the hood, this compiles to List<Int>, so you get the type safety of a list of UserIds without the overhead of storing a list of data class objects.
  • Extendable without overhead: You can add extension functions and properties to inline classes, just like regular classes. For example:
    inline class UserId(val value: Int)
    
    fun UserId.isValid(): Boolean = value > 0
    
    This extension function compiles to a static method that takes an Int, so there's no extra cost—but you get the clarity of calling userId.isValid() instead of isValidUserId(userIdValue).
  • Minimal boxing: Unlike data classes, inline classes only box when absolutely necessary (like when cast to Any or a generic type). In most cases, they stay as the raw value, which is critical for performance-sensitive code (like loops or high-throughput systems).

内容的提问来源于stack exchange,提问作者Youn Tivoli

火山引擎 最新活动