Вітання! Сьогодні ми поговоримо про дуже важливу та цікаву тему, а саме — порівняння об'єктів між собою equals() у Java. І справді, у яких випадках Java Об'єкт А дорівнюватиме Об'єкту Б ? Давай спробуємо написати приклад:
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
вказують на два різні об'єкти , на дві різні адресаи. Уяви собі ситуацію з порівнянням людей. У світі напевно є людина, яка має однакові з тобою ім'я, колір очей, вік, зріст, колір волосся і т.д. Тобто ви багато в чому схожі, але все-таки ви не близнюки, і тим більше не одна і та сама людина. Приблизно таку логіку застосовує оператор==
, коли з його допомогою ми намагаємось порівняти два об'єкти. Але що, якщо у твоїй програмі тобі потрібна інша логіка? Наприклад, якщо ваша програма симулює аналіз ДНК. Вона має порівняти код ДНК двох людей і визначити, що це близнюки.
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
Пул рядків – область для зберігання всіх рядкових значень, які створюєш у своїй програмі. Навіщо він був створений? Як і говорилося раніше, рядки займають більшу частину всіх об'єктів. У будь-якій великій програмі створюється дуже багато рядків. З метою економії пам'яті та потрібен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 Pool
a. Тобто якщо ми напишемо такий код:
public class Main {
public static void main(String[] args) {
String s1 = "JavaRush - найкращий сайт для вивчення Java!";
String s2 = "JavaRush - найкращий сайт для вивчення Java!";
String s3 = new String("JavaRush - найкращий сайт для вивчення Java!");
}
}
У пам'яті це буде виглядати ось так: І щоразу при створенні нового об'єкта в 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 Pool
a через оператор ==
.
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() робить саме те, що потрібно. Ось тобі кілька посилань для самостійного вивчення:
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ