← Назад к вопросам
Будет ли равны ссылки на объекты после процесса сериализации/десериализации?
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 === deserialized | false | Разные адреса в памяти |
original == deserialized | true (если переопределён 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 (это нормально)