← Назад к вопросам

Будет ли равны ссылки на объекты после процесса сериализации/десериализации?

1.8 Middle🔥 111 комментариев
#JVM и память#Работа с данными

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Будет ли равны ссылки на объекты после сериализации/десериализации?

Короткий ответ: НЕТ, это будут разные объекты в памяти.

1. Основная проблема

data class User(val id: Int, val name: String)

val original = User(1, "John")

// Сериализация
val json = Gson().toJson(original)  // Превращаем в JSON

// Десериализация
val deserialized = Gson().fromJson(json, User::class.java)  // Создаём новый объект

// Сравнение
println(original === deserialized)  // false (разные объекты в памяти)
println(original == deserialized)   // true (одинаковое содержимое)
println(original.hashCode() == deserialized.hashCode())  // true

2. Почему это происходит?

Сериализация преобразует объект в строку/байты (теряется информация о памяти):

Оригинальный объект:              JSON:                  Новый объект:
┌──────────────────┐              ┌──────────────────┐   ┌──────────────────┐
│ User @4a574795   │  serialize   │ {"id":1,          │   │ User @74ce3018   │
│ id=1             │  --------->  │  "name":"John"}  │   │ id=1             │
│ name=John        │              └──────────────────┘   │ name=John        │
└──────────────────┘              deserialize            └──────────────────┘
                                  <---------

@4a574795 (старый адрес)  != @74ce3018 (новый адрес)

3. Таблица сравнения

СравнениеРезультатПочему
original === deserializedfalseРазные адреса в памяти
original == deserializedtrue (если переопределён equals)Одинаковое содержимое
original.hashCode() == deserialized.hashCode()true (обычно)Хэш от содержимого

4. Пример с разными случаями

data class User(val id: Int, val name: String) : Serializable

fun main() {
    val original = User(1, "John")
    
    // JSON сериализация
    val json = Gson().toJson(original)
    val deserialized = Gson().fromJson(json, User::class.java)
    
    // Результаты
    println("original === deserialized: ${original === deserialized}")  // false
    println("original == deserialized: ${original == deserialized}")   // true
    println("original: $original, hash: ${original.hashCode()}")
    println("deserialized: $deserialized, hash: ${deserialized.hashCode()}")
    
    // Проверка памяти
    println("System.identityHashCode(original): ${System.identityHashCode(original)}")
    println("System.identityHashCode(deserialized): ${System.identityHashCode(deserialized)}")
}

Output:
original === deserialized: false
original == deserialized: true
original: User(id=1, name=John), hash: 2123828
deserialized: User(id=1, name=John), hash: 2123828
System.identityHashCode(original): 2094113178
System.identityHashCode(deserialized): 1892627923

5. Это проблема в коллекциях

val original = User(1, "John")
val set = setOf(original)

val json = Gson().toJson(original)
val deserialized = Gson().fromJson(json, User::class.java)

println(deserialized in set)  // true (equals работает)
val map = mapOf(original to "data")
println(map[deserialized])    // data (hashCode и equals совпадают)

Важно: HashMap, HashSet и другие используют hashCode() и equals(), поэтому десериализованный объект найдётся в коллекции.

6. Когда === важен?

class User(val id: Int, val name: String) {
    val id = id
    var listener: (() -> Unit)? = null  // Слушатель
}

val original = User(1, "John")
original.listener = { println("Changed") }

// После сериализации/десериализации слушатель потеряется!
val json = Gson().toJson(original)
val deserialized = Gson().fromJson(json, User::class.java)

println(deserialized.listener)  // null! (функции не сериализуются)
println(original.listener != null)  // true

7. Как решить эту проблему?

Вариант 1: Кэширование

class ObjectCache<T> {
    private val cache = mutableMapOf<String, T>()
    
    fun serialize(key: String, obj: T) {
        val json = Gson().toJson(obj)
        cache[key] = obj  // Кэшируем оригинальный объект
    }
    
    fun deserialize(key: String): T? = cache[key]  // Возвращаем оригинальный
}

Вариант 2: Singleton pattern (для переиспользуемых объектов)

object UserRegistry {
    private val users = mutableMapOf<Int, User>()
    
    fun getUser(id: Int): User {
        return users.getOrPut(id) { User(id, "Default") }
    }
    
    fun updateUser(user: User) {
        users[user.id] = user
    }
}

// Всегда одна ссылка на пользователя
val user1 = UserRegistry.getUser(1)
val user2 = UserRegistry.getUser(1)
println(user1 === user2)  // true!

Вариант 3: Repository с кэшем

class UserRepository {
    private val cache = mutableMapOf<Int, User>()
    
    fun getUser(id: Int): User {
        // Если в кэше, вернём оригинальный объект
        if (id in cache) return cache[id]!!
        
        // Иначе загружаем
        val user = api.fetchUser(id)
        cache[id] = user
        return user
    }
}

8. Практический пример: LiveData

class UserViewModel : ViewModel() {
    private val _user = MutableLiveData<User?>()
    val user: LiveData<User?> = _user
    
    fun loadUser(id: Int) {
        viewModelScope.launch {
            val user = repository.getUser(id)  // Одна ссылка через репозиторий
            _user.value = user
        }
    }
}

// Благодаря репозиторию, всегда одна ссылка на User

9. Сравнение: === vs ==

data class User(val id: Int, val name: String)

val original = User(1, "John")
val copy = User(1, "John")

println(original === copy)  // false (разные объекты)
println(original == copy)   // true (data class переопределяет equals)
println(original === original)  // true (одна ссылка)

10. В контексте Android

Проблема: Bundle сериализует объекты при передаче между Activity

// Activity 1
val user = User(1, "John")
val bundle = Bundle().apply {
    putParcelable("user", user)  // Сериализация
}
intent.putExtras(bundle)
startActivity(intent)

// Activity 2
val user2 = intent.getParcelableExtra<User>("user")  // Десериализация
println(user === user2)  // false (новый объект)

Решение: использовать ViewModel для общего состояния

class SharedViewModel : ViewModel() {
    val user = MutableLiveData<User>()
}

// Activity 1
viewModel.user.value = User(1, "John")  // Одна ссылка

// Activity 2
viewModel.user.observe(this) { user ->
    // Это ОДНА ссылка, что установили в Activity 1
}

Итог

  • После сериализации/десериализации === вернёт false
  • == вернёт true если equals переопределён (data class это делает)
  • Это нормально для большинства случаев (работаем с содержимым, не с ссылками)
  • Если нужна одна ссылка, используй Repository pattern или кэширование
  • ViewModel помогает избежать лишней сериализации при передаче данных
  • Bundle сериализует при передаче между Activity (это нормально)