fbpx

Blog

Разбираемся со строками в Java и Kotlin

Цель сегодняшнего поста раз и навсегда разобраться с таким типом данных как String. Вы все еще конкатенируете строки и не знаете зачем нужен StringBuilder? Тогда не проходите мимо – полученные знания помогут вам при собеседовании – такие вопросы часто любят задавать.

String Pool

Класс String представляет собой массив char. Каждый char занимает 2 байта. Кроме этого класс String имеет кэшируемый хэш, который занимает еще 4 байта. Кроме того, каждый объект имеет служебную информацию которая занимает 8 байт. И, если мы говорим о Java Development Kit 7 или ниже, то у класса String есть еще поля offset и length. Так класс String наиболее используемый типа данных, то многие инстансы таких объектов могут занимать значительную часть памяти. Поэтому, для сокращения потребляемой памяти JVM имеет, так называемый String pool, который представляет собой имплементацию паттерна проектирования Flyweightшаблон проектирования, при котором объект, представляющий себя как уникальный экземпляр в разных местах программы, по факту не является таковым, так как размер потребляемой памяти может быть критичным для девайсов с маленьким размером свободной памяти, таких как мобильные устройства.

Создавая новую строку (используя двойные кавычки “”), JVM сначала ищет инстанс такой же строки (у которой такое же значение) в String pool. Если такое же значение найдено, то возвращается ссылка на это значение. В противном случае – создается новый инстанс в String pool и возвращается ссылка на него. Используя же конструктор, мы создаем новый инстанс в heap, а не в String pool:

При создании строки с таким же значением, возвращается ссылка на уже существующий объект

Эта техника называется copy-on-write (COW). Идея в том, что когда мы вызываем необходимый объект, то возвращается ссылка на уже существующий вместо создания нового. Пример:

fun main(vars: Array<String>) { 
   val cat1 = "Cat" 
   val cat2 = "Cat" 
   val cat3 = String("Cat".toCharArray()) 
   println(cat1 === cat2) 
   println(cat1 === cat3) 
}

Результат такого кода:

true 
false

При COW, когда мы пытаемся модифицировать объект через ссылку, то создается копия объекта, она изменяется и после этого возвращается ссылка на новый созданный объект.

При модификации строки – создается новый объект
fun main(vars: Array) {
    val cat1 = "Cat"
    val cat2 = cat1.plus("Dog")
    println(cat1)
    println(cat2)
    println(cat1 === cat2)
 }

Результат

Cat
CatDog
false

Теперь, зная как работает JVM при изменении строки, рассмотрим такой код:

class User(val id: Int = 0, val firstName: String = "", val lastName: String = "")
 fun main(vars: Array) {
 val user = User()
 val building = "304a"
 val query = "SELECT id, firstName, lastName FROM Building " + building + " WHERE firstName = " + user.firstName
 }

При каждой конкатенации будет создаваться объект, поэтому лучше всего использовать StringBuilder или String Templates в Kotlin

val query = "SELECT id, firstName, lastName FROM Building $building WHERE firstName = ${user.firstName}"

А теперь, фишка, о которой мало кто знает, но вы можете блеснуть на собеседовании. У класса String есть метод intern(). Метод intern() перед созданием объекта String смотрит есть ли этот объект в String Pool и возвращает его. Иначе создается новый объект в String Pool. Таким образом вы можете сохранить строку, созданную в куче в пуле строк. Пример:

fun main(args: Array) {
     val cat1 = String("Cat".toCharArray())
     val cat2 = String("Cat".toCharArray())
     println(cat1 === cat2) 
 }

Данный код вернет false

А код ниже уже вернет true

fun main(args: Array) {
     val cat1 = String("Cat".toCharArray())
     val cat2 = String("Cat".toCharArray())
     println(cat1.intern() === cat2.intern()) 
 }

Вот так! Надеюсь эта статья была для вас полезной. Пишите комментарии в ВК и подписывайтесь на telegram-канал.