Перевірте два аргументи в Java, або обидва не нульові, або обидва нульові елегантно


161

Я використовував весняне завантаження, щоб розробити проект оболонки, який використовується для надсилання електронної пошти, наприклад

sendmail -from foo@bar.com -password  foobar -subject "hello world"  -to aaa@bbb.com

Якщо аргументи fromта passwordаргументи відсутні, я використовую відправника та пароль за замовчуванням, наприклад, noreply@bar.comта 123456.

Отже, якщо користувач передає fromаргумент, він також повинен передавати passwordаргумент і навпаки. Тобто або обидва є ненульовими, або обидва - недійсними.

Як перевірити це елегантно?

Тепер мій шлях

if ((from != null && password == null) || (from == null && password != null)) {
    throw new RuntimeException("from and password either both exist or both not exist");
}

14
Окрім того, зауважте, як обережно використання пробілів робить код набагато простішим для читання - просто додавання пробілів між операторами у вашому поточному коді суттєво додасть IMO для читання.
Джон Скіт

8
Будь ласка, визначте "елегантність".
Рено

Вам потрібен окремий набір аргументів для облікових даних для автентифікації SMTP та для електронної адреси відправника конверту. Адреса Fromелектронної пошти - це не завжди ім'я автентифікації SMTP.
Каз

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

3
Побічна примітка: якщо це сценарій оболонки, чи не будуть збережені паролі в історії оболонки?
торкніться мого тіла

Відповіді:


331

Існує спосіб використання оператора ^( XOR ):

if (from == null ^ password == null) {
    // Use RuntimeException if you need to
    throw new IllegalArgumentException("message");
}

ifУмова буде вірним , якщо тільки одна змінна дорівнює нулю.

Але я думаю, що зазвичай краще використовувати дві ifумови з різними повідомленнями про виключення. Ви не можете визначити, що пішло не так, використовуючи одну умову.

if ((from == null) && (password != null)) {
    throw new IllegalArgumentException("If from is null, password must be null");
}
if ((from != null) && (password == null)) {
    throw new IllegalArgumentException("If from is not null, password must not be null");
}

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


152
Чи є причина, чому xor на два bool є кращим, ніж !=на два bools?
Ерік Ліпперт

1
Нічого собі, я не усвідомлював, що XOR на булеві - це те саме, що !=. Mindblown. І це дуже велика кількість відгуків у коментарі. І, щоб додати якість до цього коментаря, так, я також вважаю, що подавати різні повідомлення про помилку для різних випадків помилки краще, оскільки користувач буде краще знати, як виправити помилку.
половина

1
Для 2 елементів це чудово. Як це зробити з> 2 елементами?
Anushree Acharjee

Я хочу показати повідомлення про помилку, якщо будь-який з елементів дорівнює> 0. За замовчуванням всі встановлені на 0. Якщо всі> 0, це дійсний сценарій. Як це зробити?
Anushree Acharjee

Це призвело б до витонченого питання про інтерв'ю. Цікаво, що% програмістів це знають. Але я не згоден з тим, що це недостатньо зрозуміло і вимагає розділення на 2 ifпункти - повідомлення у винятку документує це чудово (я відредагував це питання, але воно не з’явиться, поки не буде прийнято)
Адам,

286

Ну, це здається, що ви намагаєтеся перевірити, чи однакова умова обох цих двох чи ні. Ви можете використовувати:

if ((from == null) != (password == null))
{
    ...
}

Або зробіть це більш явним із допоміжними змінними:

boolean gotFrom = from != null;
boolean gotPassword = password != null;
if (gotFrom != gotPassword)
{
    ...
}

18
Ти одна з моїх героїв SO @Kaz, але нічого не говорить "або те, і інше, але не те і інше", як ^це. :-)
Девід Баллок

6
@DavidBullock: Коли мова заходить про булеви, нічого не говорить "або те, і інше, але не те і інше", як ... "не обидва те саме"; XOR - лише інша назва функції "не дорівнює" над булевими.
Каз

5
@Kaz Просто так ^написано "Ей, мої операнди булеві" таким чином, що !=це не так. (Хоча цей ефект, на жаль, зменшується необхідністю перевіряти "мої операнди можуть бути числовими, і я, можливо, не є реляційним оператором в даний момент часу", що я надаю, є недоліком). Ваш статус героя незахищений, незважаючи на незгоду зі мною :-)
Девід Баллок

3
Прочитавши вимоги проблеми, тоді ваше рішення, код має сенс і є читабельним. Але як розробник 4-х років (ще в школі), читаючи цей код, потім намагаючись зрозуміти, яка «вимога бізнесу» є, це буде головним болем. З цього коду я не відразу розумію, " fromі passwordобидва повинні бути нульовими або обидва не бути нульовими". Можливо, це лише я та моя недосвідченість, але я віддаю перевагу більш читаному рішення, як у відповіді @ stig-hemmer, навіть якщо це коштує ще 3 рядки коду. Я думаю, я просто не одразу отримую bool != bool - це не інтуїтивно зрозуміло.
Chris Cirefice

2
@DavidBullock ^Оператор є побітовим оператором; це насправді не означає, що його операнди булеві. Булевий оператор xor є xor.
Brilliand

222

Особисто я вважаю за краще читати елегантне.

if (from != null && password == null) {
    throw new RuntimeException("-from given without -password");
}
if (from == null && password != null) {
    throw new RuntimeException("-password given without -from");
}

52
+1 для кращих повідомлень. Це є важливим, ніхто не любить handwaving «що - щось пішло не так» -помилки повідомлення, так що ніхто не повинен викликати такі повідомлення. Однак на практиці слід віддавати перевагу більш конкретному винятку (зокрема, IllegalArgumentExceptionзамість голого RuntimeException)
Marco13

6
@matt, схоже, ваша критика пов'язана не з кодом, а з текстом винятків. Але я думаю, що гідність цієї відповіді полягає в структурі тверджень if, а не в змісті повідомлень про помилки. Це найкраща відповідь, оскільки це покращує функціональність; OP може легко замінити будь-які рядки, які їм подобаються, для текстів виключень.
Ден Хендерсон

4
@matt Перевага полягає в тому, що стає можливим розрізнити, який з двох недійсних станів відбувся, і налаштувати повідомлення про помилку. Тож замість того, щоб сказати «ви зробили одну з цих двох речей неправильно», ви можете сказати «ви зробили це» або «ви зробили це» (а потім перейдіть до надання кроків для вирішення, якщо потрібно). Резолюція може бути однаковою в обох випадках, але ніколи не гарно говорити, що "ви зробили одну з цих дій неправильно". Джерело: в умовах, коли мені не вдалося забезпечити цю функціональність, я поставив багато запитань користувачам, які задовольнили першу умову мого тексту про помилку.
Ден Хендерсон

4
@DanHenderson> Ніколи не годиться говорити "ти зробив одну з цих речей не так". Я не погоджуюсь. Пароль / ім’я користувача безпечніше просто сказати, що ім’я користувача та пароль не відповідають.
мат

2
@DanHenderson З точки зору безпеки, може бути краще не розрізняти випадок, коли ім'я користувача є в каталозі, або ні, оскільки в іншому випадку зловмисник може дізнатися дійсні імена користувачів. Однак змішування null / not null (в даному випадку) завжди є помилкою використання, а показ більш детального повідомлення про помилку не просочує більше інформації, ніж користувач, наданий в першу чергу.
siegi

16

Поставте цю функціональність у метод 2 аргументів із підписом:

void assertBothNullOrBothNotNull(Object a, Object b) throws RuntimeException

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


14
Ніякого простору не економити порівняно ((from == null) != (password == null)), це теж дуже просто зрозуміти. У не корисних методах щось не так.
edc65

3
Існує один рядок для оператора if, другий рядок для заяви кидка і третій рядок для закриваючої дужки: Усі замінюються одним рядком. Ще один рядок збережено, якщо ви надаєте дужки закриття новій лінії!
Traubenfuchs

10
Читаючи код, ви маєте одне ім'я методу, яке вам потрібно зрозуміти, проти якого жонглювати.
Traubenfuchs

1
безумовно, +1, завжди віддайте перевагу абстрагуванню деталей та операторів реальної реалізації та оперативних описів із описовими методами та назвами змінних, які роблять зрозумілі INTENTION. логіка містить помилки. коментарі ще гірші. Назва методу показує наміри та виділяє логіку, тому простіше знайти та виправити помилки. це рішення не вимагає від читача взагалі знати або думати про якийсь синтаксис або логіку, воно дозволяє зосередитись на ВІДПОВІДНИХ ВИМОГАХ (замість чого саме це має значення)
sara

1
У вас виникнуть непотрібні проблеми, якби ви хотіли, щоб тут було якесь описове повідомлення про виключення.
Хонза Брабек

11

Рішенням Java 8 було б користуватися Objects.isNull(Object), передбачаючи статичний імпорт:

if (isNull(from) != isNull(password)) {
    throw ...;
}

Для Java <8 (або якщо ви не любите використовувати Objects.isNull()), ви можете легко написати власний isNull()метод.


6
Не подобається. from == null != password == nullтримає все на одному кадрі стека, але використовуючи Objects.isNull(Object)непотрібно штовхає та вискакує два кадри. Object.isNull (Object) є, тому що "Цей метод існує, щоб використовуватись як предикат" (тобто в потоках).
Девід Буллок

5
Простий такий метод, як правило, буде швидко накреслений JIT, тому вплив на продуктивність, швидше за все, незначний. Ми дійсно можемо обговорити використання Objects.isNull()- ви можете написати своє, якщо хочете - але, що стосується читабельності, я думаю, що використання isNull()краще. Крім того вам потрібні додаткові дужки , щоб зробити просте вираження компіляції: from == null != (password == null).
Didier L

2
Я погоджуюсь про JIT'ing (і порядок операцій ... я був ледачий). Тим не менш, (val == null)настільки багато можливостей, передбачених для порівняння з нульовим, мені важко перемогти два великих виклику методу жиру, що дивляться мені в обличчя, навіть якщо метод досить функціональний, безперебійний і добре, названий. Це все-таки я. Нещодавно я вирішив, що я м'яко дивний.
Девід Баллок

4
чесно, хто дбає про один кадр стека? стек - це проблема, коли ви маєте справу з (можливо, нескінченною) рекурсією або якщо у вас є 1000-ярусна програма з графіком об'єкта розміром пустелі Сахари.
сара

9

Ось загальне рішення для будь-якої кількості нульових перевірок

public static int nulls(Object... objs)
{
    int n = 0;
    for(Object obj : objs) if(obj == null) n++;
    return n;
}

public static void main (String[] args) throws java.lang.Exception
{
    String a = null;
    String b = "";
    String c = "Test";

    System.out.println (" "+nulls(a,b,c));
}

Використання

// equivalent to (a==null & !(b==null|c==null) | .. | c==null & !(a==null|b==null))
if (nulls(a,b,c) == 1) { .. }

// equivalent to (a==null | b==null | c==null)
if (nulls(a,b,c) >= 1) { .. }

// equivalent to (a!=null | b!=null | c!=null)
if (nulls(a,b,c) < 3) { .. }

// equivalent to (a==null & b==null & c==null)
if (nulls(a,b,c) == 3) { .. }

// equivalent to (a!=null & b!=null & c!=null)
if (nulls(a,b,c) == 0) { .. }

2
Хороший підхід, але ваш перший "еквівалентний" коментар жахливо помиляється (поза орфографічною помилкою, яка існує у всіх коментарях)
Бен Войгт

@BenVoigt Дякую за повідомлення, виправлене зараз
Khaled.K

9

Оскільки ви хочете зробити щось особливе (використовувати за замовчуванням), коли відправник і пароль відсутні, обробіть це спочатку.
Після цього у вас повинен бути як відправник, так і пароль для відправлення електронного листа; киньте виняток, якщо жодного немає.

// use defaults if neither is provided
if ((from == null) && (password == null)) {
    from = DEFAULT_SENDER;
    password = DEFAULT_PASSWORD;
}

// we should have a sender and a password now
if (from == null) {
    throw new MissingSenderException();
}
if (password == null) {
    throw new MissingPasswordException();
}

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


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


1
Не заперечуючи, тому що я зробив випадковий вибір для вибірки однієї з ваших відповідей і не можу визначити обіцяне посилання xkcd! Ганьба тобі!
dhein

@Zaibis На мій захист, це просто хобі, а не робота на повний робочий день. Але я побачу, чи зможу знайти її ...
SQB

8

Я хотів би запропонувати іншу альтернативу, яка полягає в тому, як я б насправді написав цей фрагмент коду:

if( from != null )
{
    if( password == null )
        error( "password required for " + from );
}
else
{
    if( password != null )
        warn( "the given password will not be used" );
}

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

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


Я коментую дещо розмовно, тож я можу слідувати за тим, що з "// у нас є нуль від". Стислість прикладу робить це справді незначним. Мені подобається чіткість логіки та скорочення тестів, які це представляє.
Нейт

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

Що стосується елегантності, це, мабуть, один з найменш елегантних способів зробити це.
dramzy

7

Я думаю, що правильним способом впоратися з цим є врахування трьох ситуацій: і "від", і "пароль" не надаються, не передбачені, а суміш цих двох надається.

if(from != null && password != null){
    //use the provided values
} else if(from == null && password == null){
    //both values are null use the default values
} else{
   //throw an exception because the input is not correct.
}

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

throw new IllegalArgumentException("form of " + form + 
    " cannot be used with a "
    + (password==null?"null":"not null") +  
    " password. Either provide a value for both, or no value for both"
);

2
Цей код помилковий не тому, що він не працює, а тому, що його дуже важко зрозуміти. Налагодження такого коду, написаного кимось іншим, - це просто кошмар.
NO_NAME

2
@NO_NAME Я не бачу, чому це важко зрозуміти. В ОП було передбачено три випадки: коли надаються і форма, і пароль, коли жоден не надається, і змішаний випадок, який повинен бути винятком. Ви посилаєтесь на середню умовну, щоб перевірити, чи обидва вони є нульовими?
мат

2
Я погоджуюся з @NO_NAME, що середній випадок займає занадто сильний аналіз, щоб переглянути його. Якщо ви не проаналізуєте верхню лінію, ви не отримаєте, що нуль не має нічого спільного з серединою. Рівність і з паролем у цьому випадку насправді є лише побічним ефектом того, що він не є нульовим.
jimm101

1
@ jimm101 Так, я бачив, що це проблема. Перевага від продуктивності буде мінімальною / не виявляється для більш детального рішення. Я не впевнений, що це питання. Неоновити його.
мат

2
@matt Перевага від продуктивності може не бути - незрозуміло, що робитиме компілятор і що чіп витягне з пам'яті в цьому випадку. Багато оптимізаційних циклів можна записати на оптимізаціях, які насправді нічого не дають.
jimm101

6

Ось відносно прямолінійний спосіб, який не передбачає жодного тривалого Xor og. Однак це вимагає, щоб ви були трохи докладнішими, але вгору ви можете використовувати спеціальні Винятки, які я запропонував, щоб отримати більш змістовне повідомлення про помилку.

private void validatePasswordExists(Parameters params) {
   if (!params.hasKey("password")){
      throw new PasswordMissingException("Password missing");
   }
}

private void validateFromExists(Parameters params) {
   if (!params.hasKey("from")){
      throw new FromEmailMissingException("From-email missing");
   }
}

private void validateParams(Parameters params) {

  if (params.hasKey("from") || params.hasKey("password")){
     validateFromExists(params);
     validatePasswordExists(params);
  }
}

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

1
@anphu Ні. Просто ні. Використання винятків збільшує читабельність коду, оскільки він позбавляється від клавіш if-else та робить потік коду зрозумілішим. docs.oracle.com/javase/tutorial/essential/exceptions/…
Арнаб Датта

1
Я думаю, що ви плутаєте щось, що є справді винятковим проти того, що відбувається звичайно і може вважатися частиною звичайного виконання. Java doc використовує приклади, які є винятковими, тобто не мають пам'яті під час читання файлу; Зустріч з недійсними парамами не є винятковою. "Не використовуйте винятки для нормального потоку управління." - blogs.msdn.com/b/kcwalina/archive/2005/03/16/396787.aspx
An Phu

Ну, по-перше: якщо відсутні / належні аргументи не є винятковими, чому тоді IllegalArgumentException навіть існує? По-друге: якщо ви справді вважаєте, що недійсні аргументи не є винятковими, то і їх не повинно бути достовірними. Іншими словами, просто спробуйте зробити дію, і коли щось піде не так, тоді киньте виняток. Такий підхід є просто чудовим, але покарання за виконання, про яке ви говорите, виникає тут, а не в тому підході, який я запропонував. Винятки Java не повільні під час кидка; це стек-трек, який потребує часу для відновлення.
Арнаб Датта

Контекст є ключовим. У контексті ОП (програма sendmail) звичайне та очікуване забуття або імені користувача, і пароля. У альго-ракетному керівництві параметр нульової координати повинен містити виняток. Я не сказав не перевіряти парами. У контексті ОП я сказав, що не використовуйте виняток для керування логікою програми. Розмотування сліду тракту - це перф-хіт, про який я говорю. Використовуючи свій підхід, врешті-решт ви або захопите PasswordMissingException / FromEmailMissingException, тобто розмотаєте, або ще гірше, відпускаєте його без обробки. .
пн

6

Здається, ніхто не згадував про термального оператора :

if (a==null? b!=null:b==null)

Добре працює для перевірки цього конкретного стану, але не узагальнює добре минулі дві змінні.


1
Виглядає добре, але важче зрозуміти , ніж ^, !=з двома BOOLS або два ifз
coolguy

@coolguy Моя здогадка полягає в тому, що потрійний оператор набагато частіше зустрічається, ніж оператор XOR (не кажучи вже про "розумовий сюрприз" щоразу, коли ви бачите XOR у коді, який не робить бітових операцій), і ця формулювання має тенденцію уникати скорочення і вставте помилки, які викликають подвійний випадок, якщо.
gbronner

Не рекомендовано. Це не буде відрізняти (a! = Null && b == null) і (a == null && b! = Null). Якщо ви збираєтеся скористатися a == null ? (b == null? "both null" : "a null while b is not") : (b ==null? "b null while a is not")
термінальним

5

Як я бачу ваші наміри, не потрібно завжди перевіряти обидва виняткові недоліки, але перевіряти, чи passwordнемає, якщо і лише якщо fromне є нульовим. Ви можете ігнорувати даний passwordаргумент і використовувати свій власний типовий параметр, якщо він fromє нульовим.

Написане псевдо має бути таким:

if (from == null) { // form is null, ignore given password here
    // use your own defaults
} else if (password == null) { // form is given but password is not
    // throw exception
} else { // both arguments are given
    // use given arguments
}

4
Єдина проблема з цим полягає в тому, що коли користувач постачає passwordбез доставки from, вони, можливо, мали намір перекрити пароль, але зберегти обліковий запис за замовчуванням. Якщо користувач це робить, це недійсна інструкція, і їм слід так сказати. Програма не повинна діяти так, як якщо б введення було дійсним, і продовжувати намагатися використовувати обліковий запис за замовчуванням із паролем, який користувач не вказав. Я клянусь у таких програмах.
Девід Баллок

4

Я здивований, що ніхто не згадав про просте рішення створення fromта passwordполя класу та передачі посилання на екземпляр цього класу:

class Account {
    final String name, password;
    Account(String name, String password) {
        this.name = Objects.requireNonNull(name, "name");
        this.password = Objects.requireNonNull(password, "password");
    }
}

// the code that requires an account
Account from;
// do stuff

Тут fromможе бути null або non-null, і якщо це не null, обидва його поля мають ненульові значення.

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

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


Він не працює так, як очікувалося, оскільки new Account(null, null)викине NPE, навіть якщо обліковий запис з нульовим ім'ям та паролем все ще діє.
HieuHT

Ідея полягає в тому, щоб пропустити null, якщо ім’я та пароль є недійсними, незалежно від того, в реальному світі ви б сказали, що обліковий запис існує.
Моніку

Що я отримую від OP - це означає, що або обидва є ненульовими, або обидва - недійсними. Мене просто цікавило, як можна створити об’єкт облікового запису з іменем і паролем, нульовим?
HieuHT

@HieuHT Вибачте, мій останній коментар був незрозумілим. Якщо ім’я та пароль недійсні, ви б використовували nullзамість Account. Ви б не перейшли nullдо Accountконструктора.
Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.