JavaRush /Java блог /Random UA /Equals в Java і String compare - Порівняння рядків

Equals в Java і String compare - Порівняння рядків

Стаття з групи Random UA
Вітання! Сьогодні ми поговоримо про дуже важливу та цікаву тему, а саме — порівняння об'єктів між собою equals() у Java. І справді, у яких випадках Java Об'єкт А дорівнюватиме Об'єкту Б ? Equals та порівняння рядків - 1Давай спробуємо написати приклад:
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car car1 = new Car();
       car1.model = "Ferrari";
       car1.maxSpeed = 300;

       Car car2 = new Car();
       car2.model = "Ferrari";
       car2.maxSpeed = 300;

       System.out.println(car1 == car2);
   }
}
Виведення в консоль:

false
Так, стоп. А чому, власне, ці дві машини не дорівнюють? Ми задали їм однакові властивості, але результат порівняння false. Відповідь проста. Оператор ==порівнює не властивості об'єктів, а посилання. Будь у двох об'єктів навіть 500 однакових властивостей, результат порівняння все одно буде false. Адже посилання car1і car2 вказують на два різні об'єкти , на дві різні адресаи. Уяви собі ситуацію з порівнянням людей. У світі напевно є людина, яка має однакові з тобою ім'я, колір очей, вік, зріст, колір волосся і т.д. Тобто ви багато в чому схожі, але все-таки ви не близнюки, і тим більше не одна і та сама людина. Equals та порівняння рядків - 2Приблизно таку логіку застосовує оператор==, коли з його допомогою ми намагаємось порівняти два об'єкти. Але що, якщо у твоїй програмі тобі потрібна інша логіка? Наприклад, якщо ваша програма симулює аналіз ДНК. Вона має порівняти код ДНК двох людей і визначити, що це близнюки.
public class Man {

   int dnaCode;

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.dnaCode = 1111222233;

       Man man2 = new Man();
       man2.dnaCode = 1111222233;

       System.out.println(man1 == man2);
   }
}
Виведення в консоль:

false
Логічно, що результат вийшов той самий (адже ми особливо нічого не змінювали), але тепер він нас не влаштовує! Адже в реальному житті аналіз ДНК — стовідсоткова гарантія того, що маємо близнюків. Але наша програма та оператор ==говорять нам про інше. Як нам змінити цю поведінку та зробити так, щоб у разі збігу аналізів ДНК програма видавала правильний результат? Для цього Java було створено спеціальний метод — equals() .

Метод Equals() в Java

Як і метод toString(), який ми розбирали раніше, equals() належить класу Objectнайголовнішого класу Java, від якого походять всі інші класи. Однак сам по собі equals() ніяк не змінить поведінку нашої програми:
public class Man {

   String dnaCode;

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.dnaCode = "111122223333";

       Man man2 = new Man();
       man2.dnaCode = "111122223333";

       System.out.println(man1.equals(man2));
   }
}
Виведення в консоль:

false
Такий самий результат, ну і навіщо тоді потрібен цей метод? :/ Все просто. Справа в тому, що зараз ми використовували цей метод так, як він реалізований у самому класі Object. І якщо ми зайдемо в код класу Objectі подивимося, як у ньому реалізовано цей метод і що він робить, то побачимо:
public boolean equals(Object obj) {
   return (this == obj);
}
Ось і причина, чому поведінка нашої програми не змінилася! Всередині методу equals() класу Objectлежить те саме порівняння посилань, ==. Але фішка цього в тому, що ми можемо його перевизначити. Перевизначити — означає написати свій метод equals() у нашому класі Manі зробити його поведінку такою, яка нам потрібна! Зараз нас не влаштовує, що перевірка man1.equals(man2), по суті, робить те саме, що і man1 == man2. Ось що ми зробимо у такій ситуації:
public class Man {

   int dnaCode;

   public boolean equals(Man man) {
       return this.dnaCode ==  man.dnaCode;
   }

   public static void main(String[] args) {

       Man man1 = new Man();
       man1.dnaCode = 1111222233;

       Man man2 = new Man();
       man2.dnaCode = 1111222233;

       System.out.println(man1.equals(man2));

   }
}
Виведення в консоль:

true
Зовсім інший результат! Написавши свій метод equals() замість стандартного, ми досягли правильної поведінки: тепер якщо у двох людей однаковий код ДНК, програма каже нам: "Аналіз ДНК показав, що це близнюки" і повертає true! Перевизначаючи метод equals() у класах, ти можеш легко створювати потрібну логіку порівняння об'єктів. Ми торкнулися порівняння об'єктів лише загалом. Попереду у нас ще буде окрема велика лекція на цю тему (можеш швидко прочитати її вже зараз, якщо цікаво).

String compare в Java - Порівняння рядків

Чому ми розглядаємо порівняння рядків окремо від решти? Ну, насправді рядки у програмуванні — взагалі окрема пісня. По-перше, якщо взяти всі написані людством програми на Java, близько 25% об'єктів у них становлять саме вони. Тому ця тема дуже важлива. По-друге, процес порівняння рядків дійсно дуже відрізняється від інших об'єктів. Розглянемо простий приклад:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - найкращий сайт для вивчення Java!";
       String s2 = new String("JavaRush - найкращий сайт для вивчення Java!");
       System.out.println(s1 == s2);
   }
}
Виведення в консоль:

false
Але чому false? Рядки ж абсолютно однакові, слово-в-слово :/ Ти можеш припустити: це тому що оператор == порівнює посилання! Адже й s1різні s2адресаи в пам'яті. Якщо тебе відвідала така думка, то давай переробимо наш приклад:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - найкращий сайт для вивчення Java!";
       String s2 = "JavaRush - найкращий сайт для вивчення Java!";
       System.out.println(s1 == s2);
   }
}
Зараз у нас теж два посилання, але результат змінився на протилежний: Висновок в консоль:

true
Остаточно заплутався? :) Давай розбиратися. Оператор ==справді порівнює адресаи у пам'яті. Це правило працює завжди і в ньому не слід сумніватися. Значить, якщо s1 == s2повертає true, у цих двох рядків однакова адресаа пам'яті. І це справді так! Настав час познайомитися зі спеціальною областю пам'яті для зберігання рядків – пулом рядків ( ) String poolПул Equals та порівняння рядків - 3рядків – область для зберігання всіх рядкових значень, які створюєш у своїй програмі. Навіщо він був створений? Як і говорилося раніше, рядки займають більшу частину всіх об'єктів. У будь-якій великій програмі створюється дуже багато рядків. З метою економії пам'яті та потрібенString Pool— туди міститься рядок з потрібним тобі текстом, і надалі новостворені посилання посилаються на ту саму область пам'яті, немає потреби кожного разу виділяти додаткову пам'ять. Щоразу, коли ти пишеш String = “........”, програма перевіряє, чи є рядок з таким текстом у пулі рядків. Якщо є, нова створена не буде. І нове посилання вказуватиме на ту ж адресау в пулі рядків, де цей рядок зберігається. Тому коли ми написали у програмі
String s1 = "JavaRush - найкращий сайт для вивчення Java!";
String s2 = "JavaRush - найкращий сайт для вивчення Java!";
посилання s2вказує рівно туди, куди і s1. Перша команда створила в пулі рядків новий рядок з потрібним нам текстом, а коли справа дійшла до другого - вона просто послалася на ту саму область пам'яті, що і s1. Можна зробити хоч ще 500 рядків із таким самим текстом, результат не зміниться. Стоп. Але чому тоді раніше ми не спрацювали цей приклад?
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - найкращий сайт для вивчення Java!";
       String s2 = new String("JavaRush - найкращий сайт для вивчення Java!");
       System.out.println(s1 == s2);
   }
}
Думаю, інтуїтивно ти вже здогадуєшся у чому причина:) Спробуй припустити, перш ніж читати далі. Ти бачиш, що ці два рядки було створено по-різному. Одна – за допомогою оператора new, а друга без нього. Саме в цьому є причина. Оператор new під час створення об'єкта примусово виділяє йому нову область у пам'яті . І рядок, створений за допомогою new, не потрапляє до String Pool: вона стає окремим об'єктом, навіть якщо її текст повністю збігається з таким самим рядком з ' String Poola. Тобто якщо ми напишемо такий код:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - найкращий сайт для вивчення Java!";
       String s2 = "JavaRush - найкращий сайт для вивчення Java!";
       String s3 = new String("JavaRush - найкращий сайт для вивчення Java!");
   }
}
У пам'яті це буде виглядати ось так: Equals та порівняння рядків - 4І щоразу при створенні нового об'єкта в newпам'яті буде виділятися нова область, навіть якщо текст всередині нових рядків буде однаковим! З оператором ==начебто розібралися, а що з нашим новим знайомим методом equals()?
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - найкращий сайт для вивчення Java!";
       String s2 = new String("JavaRush - найкращий сайт для вивчення Java!");
       System.out.println(s1.equals(s2));
   }
}
Виведення в консоль:

true
Цікаво. Ми точно знаємо, що s1і s2вказують на різні області пам'яті. Проте метод equals() говорить, що вони рівні. Чому? Пам'ятаєш, ми говорабо вище про те, що метод equals() можна перевизначити у своєму класі, щоб він порівнював об'єкти так, як тобі потрібно? Із класом Stringтак і вчинабо. Він має перевизначений метод equals(). І порівнює він не посилання, саме послідовність символів у рядках. І якщо текст у рядках однаковий, неважливо, як вони були створені і де зберігаються: в пулі рядків, або окремій області пам'яті. Результатом порівняння буде вірно. До речі, Java дозволяє коректно порівнювати рядки без урахування регістру. У звичайній ситуації, якщо написати один із рядків, наприклад, капсом, то результатом порівняння буде false:
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - найкращий сайт для вивчення Java!";
       String s2 = new String("CODEGYM - ЛУЧШИЙ САЙТ ДЛЯ ИЗУЧЕНИЯ JAVA!");
       System.out.println(s1.equals(s2));
   }
}
Виведення в консоль:

false
Для цього випадку в класі Stringє метод equalsIgnoreCase(). Якщо у твоєму порівнянні головним є саме послідовність конкретних символів, а не їхній регістр, можна застосувати його. Наприклад, це буде корисно при порівнянні двох поштових адреса:
public class Main {

   public static void main(String[] args) {

       String address1 = "м. Москва, вул. Академіка Корольова, будинок 12";
       String address2 = new String("Г. МОСКВА, УЛ. АКАДЕМИКА КОРОЛЕВА, ДОМ 12");
       System.out.println(address1.equalsIgnoreCase(address2));
   }
}
В даному випадку очевидно, що йдеться про одну і ту ж адресау, тому використання методу equalsIgnoreCase()буде вірним рішенням.

Метод String.intern()

У класу Stringє ще один хитрий метод - intern(); Метод intern()безпосередньо працює зі String Pool'ом. Якщо ти викликаєш метод intern()у якогось рядка, він:
  • Дивиться, чи є рядок із таким текстом у пулі рядків
  • Якщо є, повертає посилання на неї в пулі
  • Якщо ж ні — поміщає рядок із цим текстом у пул рядків та повертає посилання на нього.
Застосувавши метод intern()посилання на рядок, що створювалася через new, ми можемо порівнювати її з посиланням на рядок з ' String Poola через оператор ==.
public class Main {

   public static void main(String[] args) {

       String s1 = "JavaRush - найкращий сайт для вивчення Java!";
       String s2 = new String("JavaRush - найкращий сайт для вивчення Java!");
       System.out.println(s1 == s2.intern());
   }
}
Виведення в консоль:

true
Раніше, коли ми порівнювали їх без intern(), результат дорівнював false. Тепер метод intern()перевірив, чи є рядок з текстом "JavaRush - найкращий сайт для вивчення Java!" у пулі рядків. Зрозуміло, вона там є: ми її створабо, коли написали
String s1 = "JavaRush - найкращий сайт для вивчення Java!";
Була проведена перевірка, що посилання s1та посилання, повернута методом s2.intern()вказують на одну область у пам'яті, і, звичайно, так воно і є:) Підбиваючи підсумки, запам'ятай та використовуй головне правило: Для порівняння рядків ЗАВЖДИ використовуй метод equals()! Порівнюючи рядки, ти майже завжди маєш на увазі порівняння їхнього тексту, а не посилань, областей пам'яті та іншого. Метод equals() робить саме те, що потрібно. Ось тобі кілька посилань для самостійного вивчення:
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ