Посмотрите на код и попытайтесь ответить, чему равны значения этих выражений:
// Какой результат будет в этих примерах — `true` или `false`?
"a" == "a";
"a".toUpperCase() == "a".toUpperCase();
Правильный ответ: в первом случае true
, во втором — false
. Почему? Для ответа на этот вопрос нужно немного погрузиться в то, как работают компьютеры.
В наших программах мы оперируем данными — числами, строками, булевыми значениями. Мы выполняем разнообразные операции — записываем их в переменные, умножаем, делим, конкатенируем.
Так свою работу видит программист. Но внутри компьютера все немного по-другому. Во время работы программа получает доступ и манипулирует данными через их адреса в памяти:
// Под хранение переменной выделяется область памяти
// Программа запоминает адрес этой области и работает с ней внутри себя
var name = "CodeBasics";
// Программа считала значение переменной по адресу, где хранится значение
System.out.println(name);
Память — это большая область для хранения данных, которая очень похожа на склад. В памяти любое значение получает номер, по которому его можно извлечь и заменить. Этот номер и есть адрес.
Из-за этих технических особенностей на сравнение данных между собой можно смотреть двумя способами:
Пример из реальной жизни: два одинаковых стакана из одного набора. Несмотря на свою одинаковость, все же разные стаканы.
Языки программирования по-разному работают с этими понятиями. Как и во многих других языках, в Java все данные делятся на два больших типа:
Так работают примитивные данные:
// Сравнение идет по значению, а не адресам
4 == 4; // true
true == true; // true
10.0 == 10.0; // true
Из ссылочных данных мы пока знакомы только со строками, но они работают хитро, поэтому в качестве примера посмотрим на массивы. Не обращайте внимание на незнакомый синтаксис. Просто обратите внимание, что в этом коде вроде бы одинаковые штуки не равны друг другу:
// Создание массивов
int[] a = {1, 2}
int[] b = {1, 2}
// Значения одинаковые, но ссылки разные
a == b; // false
Строки относятся к ссылочным типам данных, но ведут себя странно:
// Сравнение, как у примитивных типов данных
"hm" == "hm"; // true
// Сравнение, как у ссылочных типов данных
"hexlet".toUpperCase() == "hexlet".toUpperCase(); // false
Программы постоянно оперируют строками, поэтому эффективность работы с ними выходит на первое место. Если бы строка всегда вела себя как ссылочный тип, то на каждое значение в коде выделялась дополнительная память:
// Без оптимизаций это выражение привело бы к двойному выделению памяти
// По одной единице памяти на каждый "hm"
"hm" == "hm";
Но этого не происходит. Когда Java встречает явно создаваемую строку, выполняется проверка, а есть ли уже в памяти такая строка.
Если есть, то она переиспользуется, если нет — создается:
// Выделяется память
var name1 = "Java";
// Такая строка уже есть, поэтому подставляется ссылка на уже созданную строку
// В результате экономится память
var name2 = "Java";
// Сравнение по ссылке
// Обе переменные указывают на один участок памяти
name1 == name2; // true
Но если строка возвращается из метода, то она помещается в свою область памяти со своим уникальным адресом:
// Выделяется новая память в любом случае
var name1 = "java".toUpperCase(); // "JAVA"
// Выделяется новая память в любом случае
var name2 = "java".toUpperCase(); // "JAVA"
name1 == name2; // false
Может показаться, что ссылочные данные приносят сплошные проблемы. На самом деле они нужны. Это станет понятно, когда мы столкнемся с изменяемостью в будущем.
В прикладном программировании мы чаще сравниваем строки по значению, чем по ссылке. Для этого в строки встроен метод equals()
:
var name1 = "java".toUpperCase(); // "JAVA"
var name2 = "java".toUpperCase(); // "JAVA"
name1.equals(name2); // true
https://replit.com/@hexlet/java-basics-logical-operations
Помимо equals()
, в строки встроен метод equalsIgnoreCase()
, который выполняет проверку по значению без учета регистра:
var name1 = "java".toUpperCase(); // "JAVA"
var name2 = "java".toLowerCase(); // "java"
name1.equalsIgnoreCase(name2); // true
Иногда сравнение строк в Java ведет себя как сравнение значений, но никогда не делайте ставку на это. При изменении кода легко забыть поправить проверку и получить ошибку. Всегда используйте методы, когда нужно сравнивать по значению.
Реализуйте метод isPalindrome()
, который определяет, является ли слово палиндромом или нет. Палиндром это слово, которое читается одинаково в обоих направлениях.
App.isPalindrome("шалаш"); // true
App.isPalindrome("ага"); // true
App.isPalindrome("хекслет"); // false
// Слова в метод могут быть переданы в любом регистре
App.isPalindrome("Ага"); // true
Для определения палиндрома, необходимо перевернуть строку и сравнить ее с исходной. Для этого воспользуйтесь методом StringUtils.reverse()
StringUtils.reverse("мама"); // "амам"
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1import static org.assertj.core.api.Assertions.assertThat;
2
3class Test {
4 public static void main(String[] args) {
5 assertThat(App.isPalindrome("wow")).isTrue();
6 assertThat(App.isPalindrome("hexlet")).isFalse();
7 assertThat(App.isPalindrome("asdffdsa")).isTrue();
8 assertThat(App.isPalindrome("Wow")).isTrue();
9 }
10}
11
Решение учителя откроется через: