Чому порівняння значень рядка == оператора не зробило його Java?


50

Кожен компетентний програміст Java знає, що вам потрібно використовувати String.equals () для порівняння рядка, а не ==, тому що == перевіряє рівність еталону.

Коли я маю справу з рядками, більшу частину часу я перевіряю на рівність значення, а не на рівність еталон. Мені здається, було б інтуїтивніше, якби мова дозволила порівнювати рядкові значення, використовуючи ==.

Для порівняння, оператор C # 's == перевіряє рівність значення для рядка s. І якщо вам дійсно потрібно було перевірити рівність еталону, ви можете використовувати String.ReferenceEquals.

Ще одним важливим моментом є те, що рядки незмінні, тому шкоди не можна зробити, якщо дозволити цю функцію.

Чи є якась конкретна причина, чому це не реалізовано на Java?


12
Ви можете подивитися на Scala, де ==є об'єктна рівність і eqє референтна рівність ( ofps.oreilly.com/titles/9780596155957/… ).
Джорджіо

Як зауваження, і це може вам не допомогти, але, наскільки я пам’ятаю, ви можете порівняти рядкові букви з '=='
Kgrover

10
@Kgrover: ви можете, але це просто зручний побічний продукт еталонної рівності і те, як Java агресивно оптимізує літеральні відповідники рядків у посиланнях на той самий об’єкт. Іншими словами, це працює, але з неправильних причин.
tdammers

1
@aviv ==оператор відображає лише те, Equalsякщо ==оператор був реалізований таким чином. Поведінка за замовчуванням для ==таких же, як ReferenceEquals(насправді, ReferenceEqualsвизначено як об’єктна версія ==)
Патрік Хузінга

3
Це наслідок дизайнерських рішень, які мають багато сенсу у багатьох інших сценаріях. Але оскільки ви, здається, це знаєте, і все-таки задаєте це питання, я відчуваю вимушеність протиставити ваше запитання: Чому для порівняння посилань String повинен бути випадок використання?
Яків Райхле

Відповіді:


90

Я здогадуюсь, що це просто послідовність, або "принцип найменшого здивування". Рядок - це об'єкт, тому було б дивно, якби трактували його інакше, ніж інші об'єкти.

У той час, коли з'явилася Java (~ 1995), просто мати щось подібне Stringбуло цілковитою розкішшю для більшості програмістів, які звикли представляти рядки як нульові завершені масиви. Stringповедінка тепер є те, що було тоді, і це добре; тонка зміна поведінки згодом може мати дивовижні, небажані наслідки в робочих програмах.

Як бічну примітку, ви можете використовувати String.intern()для отримання канонічного (інтернованого) представлення рядка, після чого можна проводити порівняння ==. Стажування займає деякий час, але після цього порівняння будуть дійсно швидкими.

Доповнення: на відміну від деяких відповідей, це не про підтримку перевантаження оператора . +Оператор (конкатенація) працює на Stringз , навіть якщо Java не підтримує оператор перевантаження; це просто обробляється як окремий випадок у компіляторі, який вирішує StringBuilder.append(). Так само ==можна було б розглядати як окрему справу.

Тоді чому дивуватися особливим випадком, +але не цим ==? Тому що, +просто не компілюється при застосуванні до не- Stringоб’єктів, так що це швидко видно. Різна поведінка в ==буде набагато менш очевидно і , таким чином , набагато більш дивно , коли він вдаряє вас.


8
Особливі випадки додають подив.
Blrfl

17
Струни були розкішшю в 1995 році? Дійсно ?? Подивіться на історію комп’ютерних мов. Кількість мов, які мали певний тип рядків у той час, значно перевищила б кількість тих, що не мають. Скільки мов, окрім С та його нащадків, використовували нульові завершені масиви?
WarrenT

14
@WarrenT: Безумовно, деякі (якщо не більшість) мов мали певний тип рядків, але рядки, зібрані сміттям, здатні Unicode, були новиною в 1995 році. Наприклад, Python представив рядки Unicode з версією 2.0, 2000 рік. Вибір незмінності також був спірним вибором у той час.
Joonas Pulakaka

3
@JoonasPulakka Тоді, можливо, ви повинні відредагувати свою відповідь, щоб сказати це. Тому що, як це нині, «загальна розкіш» вашої відповіді абсолютно неправильна.
svick

1
Стажування коштує: ви отримуєте рядок, який ніколи не буде розміщений. (Ну, хіба що, якщо ви не використовуєте власний двигун для стажування, який ви можете викинути.)
Донецькі стипендії

32

Джеймс Гослінг , творець Java, пояснив це так ще в липні 2000 року :

Я залишив перевантаження оператора як досить особистий вибір, тому що я бачив, як занадто багато людей зловживають цим на C ++. Я провів багато часу за останні п’ять-шість років, опитуючи людей про перевантаження операторів, і це дійсно захоплююче, тому що ви розбиваєте громаду на три частини: Напевно, приблизно 20-30 відсотків населення вважають перевантаження оператора як нерест чорта; хтось щось зробив із перевантаженням оператора, що просто дійсно позначив їх, тому що вони використовували як + для вставки списку, і це робить життя справді дуже заплутаною. Багато з цієї проблеми випливає з того, що є лише близько півдесятка операторів, яких ти можеш відчутно перевантажувати, і все ж є тисячі чи мільйони операторів, які люди хотіли б визначити - тому ти повинен вибрати,


50
Ага, так, старий "дозволяє" затупити точковий інструмент, щоб духи не поранили себе "виправдання.
Blrfl

22
@Blrfl: Якщо інструмент створює більше проблем, ніж вирішує, це не гарний інструмент. Звичайно, рішення про те, чи так це з перевантаженням оператора, може обернутися дуже довгою дискусією.
Джорджіо

15
-1. Це зовсім не відповідає на питання. Java робить має перевантаження операторів. ==Оператор перевантажений для об'єктів і примітивів. +Оператор перевантажений byte, short, int, long, float, double, Stringі , ймовірно , кілька інших , я забув. Було б цілком можливо перевантажити ==для Stringа.
Йорг W Міттаг

10
@Jorg - ні, ні. Перевантаження оператора неможливо визначити на рівні користувача. У компіляторі дійсно є деякі особливі випадки, але це навряд чи підходить
AZ01,

9
@Blrfl: Я не проти, щоб дуфи самі собі шкодили. Це коли вони підступно вискакують мені на очі, я дратуюся.
Йонас

9

Послідовність всередині мови. Наявність оператора, який діє по-різному, може здивувати програміста. Java не дозволяє користувачам перевантажувати операторів - тому рівність посилань є єдиним розумним значенням ==між об'єктами.

В межах Java:

  • Між числовими типами ==порівнює числову рівність
  • Між булевими типами ==порівнює булева рівність
  • Між об'єктами ==порівнює посилання на ідентичність
    • Використовуйте .equals(Object o)для порівняння значень

Це воно. Просте правило і просте визначення того, що ви хочете. Це все висвітлено у розділі 15.21 JLS . Він складається з трьох підрозділів, які легко зрозуміти, впровадити та обґрунтувати.

Як тільки ви дозволите перевантаження== , точна поведінка не є чимось, на що ви можете звернути увагу на JLS і покласти пальцем на певний предмет і сказати "ось як це працює", про це може стати важко обґрунтувати. Точна поведінка користувача ==може здивувати користувача. Кожного разу, коли ви його бачите, вам доведеться повертатися назад і перевіряти, що це насправді означає.

Оскільки Java не дозволяє перевантажувати операторів, потрібен спосіб провести тест рівності значень, на який можна переосмислити базове визначення. Таким чином, це було доручено цим вибором дизайну. ==в Java тестує числові типи для числових типів, булева рівність для булевих типів і опорна рівність для всього іншого (що може замінити .equals(Object o)виконання всього, що вони хочуть, за рівності значення).

Це не питання "чи є випадок використання певного наслідку цього дизайнерського рішення", а, скоріше, "це проектне рішення для полегшення цих інших речей, це є наслідком цього".

Струнне інтернування - один із таких прикладів. Згідно з JLS 3.10.5 , всі рядкові літерали інтерновані. Інші рядки інтерновані, якщо один викликає .intern()їх. Це "foo" == "foo"правда, це наслідок дизайнерських рішень, прийнятих для мінімізації сліду пам’яті, прийнятого літералами String. Крім того, струнне інтернування - це те, що знаходиться на рівні JVM, що має незначну експозицію користувача, але в переважній більшості випадків не повинно бути те, що стосується програміста (а випадки використання для програмістів не були те, що було високо в списку для дизайнерів при розгляді цієї функції).

Люди вкажуть на це +і +=перевантажені для String. Однак цього немає ні тут, ні там. Залишається випадок, що якщо ==має значення рівності значення для String (і лише String), для еталонної рівності знадобиться інший метод (який існує лише у String). Крім того, це зайво ускладнить методи, які сприймають Object і очікують, що ==вони поводяться в один бік і .equals()поводяться по-іншому, вимагаючи від користувачів спеціального випадку всіх цих методів для String.

Послідовний договір ==на об’єкти полягає в тому, що це лише еталонна рівність і .equals(Object o)існує для всіх об'єктів, які повинні перевірити ціннісну рівність. Ускладнення цього ускладнює занадто багато речей.


дякую, що знайшли час для відповіді. Це було б чудовою відповіддю на інші питання, з якими я пов’язаний. На жаль, це питання не підходить для цього питання. Я оновлю ОП роз'ясненнями на основі коментарів. Я більше шукаю випадки використання, коли користувач мови хотів би мати помилкові негативи при порівнянні рядків. Мова надає цю особливість як послідовність, тепер я хотів би, щоб ми пішли на крок далі. Можливо, думаючи про це від нового мовного дизайнера, чи потрібно це? (на жаль, немає lang-design.SE)
Anonsage

3
@Anonsage це не хибний негатив. Вони не той самий об’єкт. Це все, що це говорить. Треба також зазначити, що в Java 8 це new String("foo") == new String("foo")може бути істинним (див. Складання рядка ).

1
Щодо мовного дизайну, CS.SE рекламує, що там може бути тема.

оо, дякую! Я опублікую там свої майбутні запитання щодо дизайну. :) І так, на жаль, "хибнонегативний" - це не найточніший спосіб описувати моє запитання і те, що я шукаю. Мені потрібно написати більше слів, щоб люди не повинні здогадуватися, що я намагаючись сказати.
Anonsage

2
"Послідовність всередині мови" також допомагає генерикам
Брендан

2

Java не підтримує перевантаження оператора, а це ==стосується лише примітивних типів або посилань. Все інше вимагає виклику методу. Чому дизайнери це зробили, це питання, на яке вони можуть відповісти. Якби мені довелося здогадуватися, це, мабуть, тому, що перевантаження оператора приносить складність, вони не зацікавлені в додаванні.

Я не знаю C #, але, здається, дизайнери цієї мови встановили це таким чином, що кожен примітив є a, structа кожен struct- об'єктом. Оскільки C # дозволяє перевантажувати оператора, таке розташування дозволяє дуже легко будь-якому класу, а не тільки String, змусити себе працювати "очікуваним" способом з будь-яким оператором. C ++ дозволяє те саме.


1
"Java не підтримує перевантаження оператора, це означає, що == застосовується лише до примітивних типів або посилань. Все інше вимагає виклику методу.": Можна було б додати, що якщо ==мається на увазі рівність рядків, нам знадобиться інше позначення для рівності еталонів.
Джорджіо

@Giorgio: Так. Дивіться мій коментар до відповіді Гілада Неймана.
Blrfl

Хоча це можна вирішити статичним методом, який порівнює посилання двох об'єктів (або оператора). Як, наприклад, у C #.
Гілад Неаман

@GiladNaaman: Це була б гра з нульовою сумою, оскільки це викликає протилежну проблему, ніж у Java зараз: рівність буде в оператора, і вам доведеться використовувати метод порівняння посилань. Далі, вам доведеться встановити вимогу, щоб усі класи реалізували щось, до чого можна зв'язатись ==. Це ефективно додає перевантаження оператора, що може мати величезний вплив на спосіб реалізації Java.
Blrfl

1
@Blrfl: Не дуже. Завжди буде визначений спосіб порівняння reference ( ClassName.ReferenceEquals(a,b)), ==оператора та Equalsметоду за замовчуванням, на який вказують обидва ReferenceEquals.
Гілад Неаман

2

На інших мовах це було різним.

У Object Pascal (Delphi / Free Pascal) та C # оператор рівності визначений для порівняння значень, а не посилань при роботі над рядками.

Особливо в Pascal, рядок є примітивним типом (одна з речей, яку мені дуже подобається в Pascal, отримання NullreferenceException саме через неініціалізовану рядок просто дратує) і має семантику копіювання на запис, що робить (більшість часу) рядкові операції дуже дешево (іншими словами, помітно лише після того, як ви почнете об'єднувати багатомігабайтні рядки).

Отже, це рішення мовного дизайну для Java. Коли вони розробляли мову, вони слідували способу C ++ (наприклад, Std :: String), тому рядки - це об'єкти, що IMHO хак компенсувати, що C не має реального типу рядка, а не робить рядки примітивними (якими вони є).

Тож з причини, чому я можу лише припускати, що вони зробили це легко на своїй стороні і не кодуючи оператора, роблять виняток для компілятора до рядків.


як це відповідає на поставлене запитання?
гнат

Дивіться останнє речення (яке я розділив у відповідному абзаці в редакції).
Fabricio Araujo

1
IMHO, Stringповинен був бути примітивним типом на Java. На відміну від інших типів, компілятору потрібно знати про це String; крім того, операції над ним будуть досить поширеними, що для багатьох видів застосувань вони можуть створювати вузьке місце продуктивності (яке може бути полегшене за допомогою нашої підтримки). У типовому string[малому регістрі] об’єкт буде виділятися на купі для зберігання його вмісту, але жодного "нормального" посилання на цей об'єкт не буде ніде; Таким чином , це може бути один-indirected Char[]або Byte[]замість того , щоб бути Char[]indirected через інший об'єкт.
supercat

1

У Java немає оператора, який би перевантажував, і тому оператори порівняння перевантажуються лише для примітивних типів.

Клас 'String' не є примітивом, тому він не має перевантаження для '==' і використовує за замовчуванням порівняння адреси об'єкта в пам'яті комп'ютера.

Я не впевнений, але думаю, що в Java 7 або 8 oracle зробив виняток у компіляторі, щоб визнати str1 == str2якstr1.equals(str2)


"Я не впевнений, але я думаю, що в Java 7 або 8 oracle зробив виняток у компіляторі, щоб розпізнати str1 == str2 як str1.equals (str2)": Я не здивуюся: Oracle, здається, менш стурбований з мінімалізмом, ніж було Сонце.
Джорджіо

2
Якщо це правда, це дуже некрасивий злом, оскільки це означає, що зараз один клас мова трактує інакше, ніж усі інші, і порушує код, який порівнює посилання. : - @
Blrfl

1
@WillihamTotland: Розглянемо протилежний випадок. В даний час, якщо я створюю два рядки, s1і s2та дати їм такий же зміст, вони проходять рівність ( s1.equals(s2)порівняння) , але не таке ж посилання ( ==) для порівняння , тому що вони два різних об'єкта. Зміна семантики ==середньої рівності призведе до того, s1 == s2щоб оцінити, trueде вона використовується для оцінки false.
Blrfl

2
@Brlfl: Хоча це правда, але це здається вкрай поганою справою, на яку покладатися, в першу чергу, оскільки рядки - це незмінні, інтернабельні об'єкти.
Вілліхам Тотланд

2
@Giorgio: "Java 7 або 8 oracle зробили виняток у компіляторі, щоб розпізнати str1 == str2 як str1.equals (str2)" Ні, специфікація мови Java говорить: Оператори рівняння рівності : "Якщо операнди оператора рівності обидва або опорного типу, або нульового типу, тоді операція є рівністю об'єкта. " Це все, шановні. Я не знайшов нічого нового з цього приводу на початковому чернеті Java 8 .
Девід Тонхофер

0

Здається, Java була створена для того, щоб дотримуватися принципового правила, згідно з яким ==оператор повинен бути легальним у будь-який час, коли один операнд може бути перетворений на тип іншого, і повинен порівнювати результат такого перетворення з неперетвореним операндом.

Це правило навряд чи є унікальним для Java, але воно має певний далекосяжний (і IMHO прикро) вплив на дизайн інших аспектів мови, що стосуються типу. Було б чіткіше уточнити поведінку ==щодо конкретних комбінацій типів операндів, а також заборонити комбінації типів X і Y там, де x1==y1і як x2==y1це не випливає x1==x2, але мови, які рідко роблять це [згідно з цією філософією, double1 == long1повинні були або вказувати, чи double1не є точним поданням long1, а то інакше відмовлятися від компіляції;int1==Integer1слід забороняти, але повинні бути зручні та ефективні засоби, що не підкидають, для перевірки того, чи є об'єкт цілим кодом з певним значенням (порівняння з тим, що не є цілим коробкою, просто повертається false)].

Що стосується застосування ==оператора до рядків, якби Java забороняла прямі порівняння між операндами типу Stringі Object, це могло б досить добре уникнути сюрпризів у поведінці ==, але немає такої поведінки, яку він міг би реалізувати для таких порівнянь, які б не дивували. Наявність двох посилань на рядки, що зберігаються в типі, Objectповодяться по-різному від посилань, що зберігаються у типі, Stringбуло б набагато менш дивовижним, ніж будь-яке з цих типів поведінки відрізняється від правового порівняльного змішаного типу. Якщо String1==Object1це законно, це означатиме, що єдиним способом поведінки String1==String2та Object1==Object2відповідності String1==Object1є те, щоб вони відповідали один одному.


Мені щось не повинно бути пропущено, але IMHO ==на об'єктах повинен просто називати (null-safe) рівним, а для порівняння ідентичності слід використовувати щось інше (наприклад, ===або System.identityEqual). Змішування примітивів та об’єктів спочатку було б заборонено (раніше не було автобоксування до 1,5), а потім можна було знайти якесь просте правило (наприклад, unll-safe unbox, тоді викинути, а потім порівняти).
maaartinus

@maaartinus: Хороший дизайн мови повинен використовувати окремі оператори рівності для значення та рівності еталонів. Хоча я погоджуюся, що концептуально можна було б отримати int==Integerповернення оператора, falseякщо значення Integerє нульовим, і порівнюватимуть значення в іншому випадку, такий підхід був би на відміну від поведінки ==в усіх інших обставинах, коли він безумовно примушує обидва операнди до одного типу раніше порівнюючи їх. Особисто мені цікаво, чи було встановлено автоматичне розпакування, щоб дозволити int==Integerсобі поведінку, яка не була безглуздою ...
supercat

... оскільки автобокс intі порівняння порівняння було б нерозумним [але не завжди виходило з ладу]. В іншому випадку я не бачу причин дозволити неявну конверсію, яка може вийти з ладу з NPE.
supercat

Я думаю, що моя ідея узгоджується. Просто майте на увазі, що в кращому світі ==не має нічого спільного identityEquals. +++ "окремі оператори рівності для цінової та опорної рівності" - але які з них? Я б розглядав як примітивне, так ==і equalsяк порівняльне значення у тому сенсі, який equalsдивиться на значення посилання. +++ Коли ==мається на увазі equals, тоді int==IntegerДОЛЖНЕ зробити автоматичну коробку та порівняти посилання, використовуючи безпечні для нуля рівні. +++ Боюся, моя ідея насправді не моя, а лише те, що робить Котлін.
maaartinus

@maaartinus: Якщо ==ніколи не перевіряли еталонну рівність, то вона могла б розумно виконати тест рівності цінності, що не враховує нуль. Той факт , що вона робить тест рівності посилань, однак, сильно обмежує , як він може обробляти порівняння змішаних посилань / значення без непослідовності. Зауважте також, що Java фіксується на уявленні, що оператори просувають обидва операнди одного типу, а не створюють особливі поведінки, засновані на комбінаціях типів. Наприклад, 16777217==16777216.0fповертається, trueтому що він виконує збиткові перетворення першого операнда до float, тоді як ...
supercat

0

Загалом, є дуже вагома причина, щоб хотіти тестувати, чи дві посилання на об'єкт вказують на один і той же об'єкт. Я багато разів писав, що писав

Address oldAddress;
Address newAddress;
... populate values ...
if (oldAddress==newAddress)
... etc ...

Я можу або не матиму функцію рівності в таких випадках. Якщо я це зробити, функція рівня може порівнювати весь вміст обох об'єктів. Часто він просто порівнює якийсь ідентифікатор. "A і B - це посилання на один і той же об'єкт", а "A і B - це два різних об'єкти з однаковим змістом", звичайно, дві дуже різні ідеї.

Напевно, правда, що для незмінних об'єктів, таких як Strings, це менше питання. З незмінними предметами ми схильні думати про предмет і значення як про одне і те ж. Що ж, коли я кажу "ми", я маю на увазі "я", принаймні.

Integer three=new Integer(3);
Integer triangle=new Integer(3);
if (three==triangle) ...

Звичайно, це повертає помилкове значення, але я бачу, що хтось думає, що це має бути правдою.

Але як тільки ви скажете, що == порівнює контрольні ручки, а не вміст для Об'єктів взагалі, створення спеціального випадку для Strings було б потенційно заплутаним. Як тут говорив хтось інший, що робити, якщо ви хочете порівняти ручки двох об'єктів String? Чи буде якась особлива функція робити це лише для Strings?

А як же ...

Object x=new String("foo");
Object y=new String("foo");
if (x==y) ...

Це помилково, тому що вони є двома різними об'єктами, чи правда, тому що вони є рядками, зміст яких рівний?

Так, так, я розумію, як програмісти плутають це. Я це зробив сам, я маю на увазі написати if myString == "foo", коли я мав на увазі, якщо myString.equals ("foo"). Але, якщо не змінити значення оператора == для всіх об'єктів, я не знаю, як це вирішити.


Зауважте, що інші сучасні мови JVM, наприклад Scala, використовують ==для позначення "рівних рядків".
Андрес Ф.

@AndresF. (знизує плечима) У Java "<" означає "менше", тоді як у XML це "відкриває тег". У VB "=" може означати "дорівнює", тоді як у Java використовується лише для призначення. І т. Д. Навряд чи дивно, що різні мови використовують різні символи для того, щоб означати одне і те ж, або той самий символ, щоб означати різні речі.
Джей

На вашу відповідь це було не копати. Це був просто коментар. Я тільки вказував, що більш сучасна мова, ніж Java, скористалася можливістю переробити значення ==, як ви згадали в кінці.
Андрес Ф.

@AndresF. І моя відповідь не копала ваш коментар, просто кажучи, що різні мови підходять до цих питань по-різному. :-) Мені справді подобається, як VB поводиться з цим ... пауза для шипінь і бусів від ненависників VB ... "=" завжди порівнює значення (для системно визначених типів), примітивне чи об'єктне. "Є" порівнює ручки двох об'єктів. Це здається мені інтуїтивнішим.
Джей

Звичайно. Але Scala набагато ближче до Java, ніж Visual Basic. Мені подобається думати, що дизайнери Scala зрозуміли, що використання Java ==було схильним до помилок.
Андрес Ф.

0

Це дійсно питання для Strings, а не тільки для рядків, але і для інших незмінних об'єктів , що представляють деякі «цінність», наприклад Double, BigIntegerі навіть InetAddress.

Для того, щоб зробити ==оператора придатним до використання Strings та інших класів цінностей, я бачу три варіанти:

  • Повідомте компілятору про всі ці класи цінності та спосіб порівняння їх вмісту. Якби це була лише декілька занять з java.langпакету, я б вважав це, але це не охоплює випадки, такі як InetAddress.

  • Дозволити оператору перевантажувати, щоб клас визначав його ==поведінку порівняння.

  • Видаліть загальнодоступні конструктори та використовуйте статичні методи, що повертають екземпляри з пулу, завжди повертаючи той самий екземпляр для того ж значення. Щоб уникнути витоку пам'яті, вам потрібно щось подібне до SoftReferences в пулі, якого не було в Java 1.0. А тепер, щоб зберегти сумісність, String()конструктори більше не можна видаляти.

Єдине, що можна було б зробити сьогодні, - це запровадити перевантаження оператора, і особисто мені не хотілося б, щоб Java йшла цим маршрутом.

Для мене найважливіша читабельність коду, і Java-програміст знає, що оператори мають фіксований сенс, визначений у мовній специфікації, тоді як методи визначаються деяким кодом, і їх значення слід шукати в Javadoc методу. Я хотів би залишитися з цією відмінністю, навіть якщо це означає, що порівняння String не зможе використовувати ==оператор.

Існує лише один аспект порівняння Java, який мене дратує: ефект автоматичного боксу та «unboxing». Він приховує відмінність між примітивним і типом обгортки. Але коли порівнювати їх ==, вони ДУЖЕ відрізняються.

    int i=123456;
    Integer j=123456;
    Integer k=123456;
    System.out.println(i==j);  // true or false? Do you know without reading the specs?
    System.out.println(j==k);  // true or false? Do you know without reading the specs?
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.