Share This
Связаться со мной
Крути в низ
Categories
//Java Challengers #2: Сравнение строк

Java Challengers #2: Сравнение строк

Сравнение строк – увлекательная тема. Мы не только рассмотрим строки в Java, но также проанализируем популярные приемы и решим задачу.

java challengers 2 sravnenie strok 2678cc6 - Java Challengers #2: Сравнение строк

У нас как всегда много опаздывающих к началу курса, так что только вчера провели второе занятие среди нового потока «Разработчик Java». Но это так, мелочи жизни, а пока что мы продолжаем публикацию серии статей Java Challengers, перевод которых подготовили для вас.

В Java класс String инкапсулирует массив char (прим. переводчика – с Java 9 это уже массив byte, см. Компактные строки в Java 9). Говоря просто, String – это массив символов, используемый для составления слов, предложений или других конструкций.

Инкапсуляция – это одна из самых мощных концепций объектно-ориентированного программирования. Благодаря инкапсуляции вам не нужно знать, как работает класс String. Вам достаточно знать методы его интерфейса.

Когда вы смотрите на класс String в Java, вы можете увидеть, как инкапсулирован массив char:

public String(char value[]) {    this(value, 0, value.length, null);  }

Чтобы лучше понять инкапсуляцию, представьте физический объект: машину. Нужно ли вам знать, как работает автомобиль под капотом, чтобы управлять им? Конечно, нет, но вы должны знать, что делают интерфейсы автомобиля: педаль газа, тормоза и рулевое колесо. Каждый из этих интерфейсов поддерживает определенные действия: ускорение, торможение, поворот налево, поворот направо. То же самое и в объектно-ориентированном программировании.

Первая статья в серии Java Challengers была про перегрузку методов, которая широко используется в классе String. Перегрузка может сделать ваши классы действительно гибкими:

public String(String original) {}  public String(char value[], int offset, int count) {}  public String(int[] codePoints, int offset, int count) {}  public String(byte bytes[], int offset, int length, String charsetName) {}  // И так далее ...

Вместо того, чтобы пытаться понять, как работает класс String, эта статья поможет вам понять что он делает, и как использовать его в вашем коде.

Что такое пул строк (String pool)

Класс String, возможно, наиболее часто используемый класс в Java. Если новый объект создавать в динамической памяти (memory heap) каждый раз, когда мы используем String, то мы потратим впустую много памяти. Пул строк (String pool) решает эту проблему, сохраняя только один объект для каждого значения строки.

java challengers 2 sravnenie strok 7f7ffea - Java Challengers #2: Сравнение строк

Строки в пуле строк

Хотя мы создали несколько переменных String со значениями Duke и Juggy, но в динамической памяти (куче) создаётся и хранится только два объекта. Для доказательства посмотрите следующий пример кода.

Напомним, что в Java оператор «==» используется для сравнения двух объектов и определения того, один и тот же это объект или нет.

String juggy = "Juggy";  String anotherJuggy = "Juggy";  System.out.println(juggy == anotherJuggy);

Этот код вернет true, потому что две переменные String указывают на один и тот же объект в пуле строк. Их значения одинаковые.

Исключение – оператор new

Теперь посмотрите на этот код – он выглядит похожим на предыдущий пример, но здесь есть отличие.

String duke = new String("duke");  String anotherDuke = new String("duke");    System.out.println(duke == anotherDuke);

На основе предыдущего примера можно подумать, что этот код вернёт true, но это не так. Добавление оператора new приводит к созданию нового объекта String в памяти. Таким образом, JVM создаст два разных объекта.

Native-методы

Native-методы в Java – это методы, которые будут компилироваться с использованием языка C, обычно с целью управления памятью и оптимизации производительности.

Пулы строк и метод intern()

Для хранения строк в пуле используется способ, называемый «интернирование строк» (String interning).

Вот, что Javadoc говорит нам о методе intern():

/**   * Возвращает каноническое представление для строкового объекта.   *   * Пул строк (первоначально пустой) управляется классом {@code String}.   *   * Когда вызывается метод intern, если пул уже содержит строку,    * равную этому объекту {@code String}, определяемому через   * метод  {@link #equals(Object)}, тогда возвращается строка из пула.    * Иначе, этот объект {@code String} добавляется к    * пулу и возвращается ссылка на этот объект {@code String}.    *   * Из этого следует, что для любых двух строк {@code s} и {@code t},   * {@code s.intern() == t.intern()} будет {@code true}   * тогда и только тогда, когда {@code s.equals(t)} равно {@code true}.   *    * Все литеральные строки и строковые константы интернируются.    * Строковые литералы определяются в разделе 3.10.5 The Java™ Language Specification.   *    * @returns строка, которая имеет то же самое содержание как эта строка,    * но, гарантируется, что она будет из пула уникальных строк.   *   * @jls 3.10.5 String Literals   */ public native String intern();

Метод intern() используется для хранения строк в пуле строк. Во-первых, он проверяет, существует ли уже созданная строка в пуле. Если нет, то создает новую строку в пуле. Логика пула строк основана на паттерне Flyweight.

Теперь обратите внимание, что происходит, когда мы используем new для создания двух строк:

String duke = new String("duke");  String duke2 = new String("duke");  System.out.println(duke == duke2); // Здесь результат будет false  System.out.println(duke.intern() == duke2.intern()); // Здесь результат будет true

В отличие от предыдущего примера с ключевым словом new, в данном случае сравнение вернёт true. Это потому, что использование метода intern() гарантирует, что строка будет в пуле.

Метод equals в классе String

Метод equals() используется для того, чтобы проверить одинаковое или нет состояние двух классов. Поскольку equals() находится к классе Object, то каждый Java-класс наследует его. Но метод equals() должен быть переопределен, чтобы он работал правильно. Конечно, String переопределяет equals().

Взгляните:

public boolean equals(Object anObject) {    if (this == anObject) {      return true;    }      if (anObject instanceof String) {      String aString = (String)anObject;      if (coder() == aString.coder()) {         return isLatin1() ? StringLatin1.equals(value, aString.value)            : StringUTF16.equals(value, aString.value);      }    }      return false;  }

Как вы видите, значение класса String сравнивается через equals(), а не через ссылку на объект. Не имеет значения, если ссылки на объекты разные: будут сравниваться состояния.

Наиболее распространенные методы String

Есть ещё одна вещь, которую вам нужно знать, прежде чем решить задачку на сравнение строк.

Рассмотрим наиболее распространённые методы класса String:

// Удаляет пробелы в начале и в конце строки  trim()   // Получает подстроку по индексам  substring(int beginIndex, int endIndex)  // Возвращает длину строки  length()   //  Заменяет строку, можно использовать регулярное выражение  replaceAll(String regex, String replacement)  // Проверяет, есть ли указанная последовательность CharSequence  в строке  contains(CharSequences)

Решите задачку на сравнение строк

Давайте проверим, что вы узнали о классе String, решив небольшую задачку.

В этой задаче вы сравните несколько строк, используя изученные концепции. Глядя на код ниже, можете ли вы определить значение каждой переменной result?

public class ComparisonStringChallenge {    public static void main(String... doYourBest) {      String result = "";      result += " powerfulCode ".trim() == "powerfulCode" ? "0" : "1";        result += "flexibleCode" == "flexibleCode" ? "2" : "3";        result += new String("doYourBest")           == new String("doYourBest") ? "4" : "5";        result += new String("noBugsProject")          .equals("noBugsProject") ? "6" : "7";        result += new String("breakYourLimits").intern()          == new String("breakYourLimits").intern() ? "8" : "9";        System.out.println(result);    }  }  

Каков будет вывод?

  • A: 02468
  • B: 12469
  • C: 12579
  • D: 12568

Правильный ответ приведён в конце статьи.

Что сейчас произошло? Понимание поведения String

В первой строке мы видим:

result += " powerfulCode ".trim() == "powerfulCode" ? "0" : "1";

В этом случае результат false, ведь когда метод trim() удаляет пробелы, он создаёт новый String с помощью оператора new.

Далее мы видим:

result += "flexibleCode" == "flexibleCode" ? "2" : "3";

Здесь нет никакой тайны, строки одинаковы в пуле строк. Это сравнение возвращает true.

Затем имеем:

result += new String("doYourBest")       == new String("doYourBest") ? "4" : "5";

Использование new приводит к созданию двух новых строк, и не важно, равны их значения или нет. В этом случае сравнение будет false даже если значения одинаковые.

Далее:

result += new String("noBugsProject")      .equals("noBugsProject") ? "6" : "7";

Поскольку мы использовали метод equals(), будет сравниваться значение строки, а не экземпляр объекта.

В этом случае не имеет значения, разные объекты или нет, поскольку сравнивается значение. Результат true.

В итоге:

result += new String("breakYourLimits").intern()      == new String("breakYourLimits").intern() ? "8" : "9";

Как вы видели ранее, метод intern() помещает строку в пул строк. Обе строки указывают на один и тот же объект, поэтому в данном случае true.

Распространенные ошибки со строками

Бывает трудно определить, указывают ли две строки на один и тот же объект или нет, особенно когда строки содержат одно и то же значение. Полезно помнить, что использование new всегда приводит к созданию нового объекта в памяти, даже если значения строк одинаковые.

Использование методов класса String для сравнения ссылок на объекты также может быть сложным. Особенность в том, что если метод изменяет что-то в строке, то будут разные ссылки на объекты.

Несколько примеров, которые помогут прояснить:

System.out.println("duke".trim() == "duke".trim());

Это сравнение будет истинным, потому что метод trim() не создает новую строку.

System.out.println(" duke".trim() == "duke".trim());

В этом случае первый метод trim() генерирует новую строку, так как метод будет выполнять свою работу, и поэтому ссылки разные.

Наконец, когда trim() выполнит свою работу, он создает новую строку:

// Реализация метода trim в классе String  new String(Arrays.copyOfRange(val, index, index + len), LATIN1);

Что нужно помнить о строках

  • Строки неизменяемые, поэтому состояние строки изменить нельзя.
  • Для экономии памяти JVM хранит строки в пуле строк. При создании новой строки JVM проверяет ее значение и указывает на существующий объект. Если в пуле нет строки с этим значением, JVM создаёт новую строку.
  • Оператор «==» сравнивает ссылки на объект. Метод equals() сравнивает значения строк. То же правило будет применяться ко всем объектам.
  • При использовании оператора new будет создана новая строка в хипе (Прим. переводчика – в оригинале написано, что в пуле, но это не так, спасибо zagayevskiy), даже если есть строка с тем же значением.

Ответ

Ответ на эту задачу – D. Вывод будет 12568.

Продолжение следует…

Библиотека рекомендует
Профессиональный 6 мес. онлайн-курс «Разработчик Java», который погрузит вас особенности разработки серверных приложений на Java, проектирование, тестирование и особенности платформы.
Для зачисления в группу пройдите вступительный тест.

Автор статьи

Еще больше полезных материалов:

  • Spring для начинающих: наиболее полный видеокурс
  • Большая подборка книг, видео и статей для Java Junior
  • 8 крутых Youtube-каналов, которые помогут изучить Java
  • 5 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Связаться со мной
Close