Чому саме Java не дозволяє числові умовні умови, такі як if (5) {…}, якщо C?


33

У мене є ці дві маленькі програми:

С

#include <stdio.h>
int main()
{
    if (5) {
        printf("true\n");
    }
    else {
        printf("false\n");
    }

    return 0;
}

Java

class type_system {
   public static void main(String args[]) {
       if (5) {
           System.out.println("true");
       }
       else {
           System.out.println("false");
       }
   }
}

яке повідомляє про помилку:

type_system.java:4: error: incompatible types: int cannot be converted to boolean
       if (5) {
           ^
1 error

Моє розуміння

Поки що я зрозумів цей приклад як демонстрацію систем різних типів. C набирається слабкіше і дозволяє переходити від int до boolean без помилок. Java сильніше набирається і не працює, оскільки не допускається неявна розмова.

Тому моє запитання: Де я неправильно зрозумів речі?

Чого я не шукаю

Моє запитання не пов'язане з поганим стилем кодування. Я знаю, що це погано, але мене цікавить, чому C це дозволяє, а Java - ні. Тому мене цікавить система типу мови, зокрема її міцність.


22
@toogley: Про що типово хочеш знати систему типів на Java? Система типів цього не дозволяє, оскільки специфікація мови забороняє це. Причини, через які C це дозволяє, а Java не має нічого спільного з тим, що вважається прийнятним для обох мов. Внаслідок цього поведінка систем типу є наслідком цього, а не причиною.
Роберт Харві

8
@toogley: чому ти думаєш, що ти щось неправильно зрозумів? Читаючи текст ще раз, я не розумію, у чому ваше запитання.
Док Браун

26
Насправді, це не є прикладом слабкого набору тексту в C. Раніше (C89) C навіть не мав булевого типу, тому всі "булеві" операції насправді діяли на intзначеннях. Більш правильним прикладом може бути if (pointer).
Rufflewind

5
Я прихильнився тому, що, схоже, не було багато досліджень, намагаючись зрозуміти це.
jpmc26

11
Здається, що оригінальне запитане запитання дещо відрізняється від редагованої версії (саме тому деякі коментарі та відповіді, здається, відповідають на інше питання). У його нинішньому вигляді, схоже, тут не виникає питання. Яке непорозуміння? Java поводиться саме так, як ви вважаєте, що має бути.
Джеймсдлін

Відповіді:


134

1. C і Java - різні мови

Те, що вони поводяться по-різному, не повинно бути дивовижним.

2. C не здійснює перетворення з intнаbool

Як це могло? У C навіть не було справжнього boolтипу, до якого слід перетворитись до 1999 року . C був створений на початку 1970-х років, і ifбув його частиною до того, як він був навіть C, ще тоді, коли це була лише серія модифікацій B 1 .

ifNOPмайже 30 років не був просто ін. Він безпосередньо діяв на числових значеннях. Багатослівність у стандарті C ( PDF-посилання ), навіть через десятиліття після введення bools до C, все ще вказує на поведінку if(p 148) та ?:(p 100), використовуючи терміни "нерівні 0" та "рівне 0" ", а не булеві терміни" правда "чи" помилка "чи щось подібне.

Зручно, ...

3. ... цифри просто стають те, на що працюють інструкції процесора.

JZі JNZє вашою базовою інструкцією по збірці x86 для умовного розгалуження. Абревіатури - " J ump, якщо Z ero" та " J ump, якщо N ot Z ero". Еквівалентами для PDP-11, звідки походить C, є BEQ(" B ranch, якщо EQ ual") та BNE(" B ranch, якщо N ot E qual").

Ці інструкції перевіряють, чи призвела попередня операція до нуля чи ні, і стрибають (чи ні) відповідно.

4. У Java набагато більший акцент на безпеці, ніж у С коли-небудь 2

І, маючи на увазі безпеку, вони вирішили, що обмеження ifна booleans коштує витрат (як впровадження такого обмеження, так і пов'язані з цим можливості).


1. У B взагалі немає типів. Мови складання, як правило, ні. І все-таки B та мови монтажу вдається впоратися з розгалуженням просто чудово.

2. Слова Денніса Річі, коли описував заплановані модифікації B, які стали C (наголос мій):

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


13
Приємний момент щодо булевих виразів у зіставленні C безпосередньо на інструкції процесора. Я раніше про це не думав.
Роберт Харві

17
Я думаю, що пункт 4 вводить в оману, оскільки, мабуть, мається на увазі, що C має певний зміст фокусу безпеки ... C дозволить вам робити все, що завгодно: C передбачає, що ви знаєте, що робите, часто до вражаючих результатів.
TemporalWolf

13
@TemporalWolf Ви заявляєте, що ніби C дозволяє вам робити те, що ви хочете, було погано. На мій погляд, різниця полягає в тому, що C написано для програмістів і очікується основна компетентність. Java написана для мавп з кодом, і якщо ви можете ввести, ви можете запрограмувати в ній. Часто вражаюче погано, але кого це турбує правильно? Я ніколи не забуду, коли вчитель у вступному класі ява був змушений взяти участь у складі мого неповнолітнього, зазначив, що використання рекурсії для обчислення цифр Фібонначі може бути корисним, оскільки це було "легко написати". Ось чому у нас є програмне забезпечення, яке ми маємо.
ДРФ

25
@DRF Один з моїх професорів для мережевого класу C сказав: "C - це як коробка ручних гранат із усіма витягнутими шпильками". У вас багато сил, але це, по суті, гарантовано підірве вам в обличчя. У переважній більшості випадків це не варте клопоту, і ви будете набагато продуктивнішими з мовою вищого рівня. Чи я перекодував Python у покірливий C біт-подвій, щоб отримати швидкість збільшення на 6 порядку? Так, так, маю. Але це виняток, а не правило. Я можу за день в Python досягти того, що зайняло б два тижні в C ... і я гідний програміст на С.
TemporalWolf

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

14

C 2011 Інтернет Проект

6.8.4.1 У ifзаяві

Обмеження

1 Контролююче вираз ifзаяви має скалярний тип.

Семантика

2 В обох формах перший підзаголовок виконується, якщо вираз порівнюється з неоднаковим до 0. У elseформі другий підзаголовок виконується, якщо вираз порівнюється з рівним 0. Якщо перший підзаголовок буде досягнуто за допомогою мітки, другий підзаголовок є не виконується.

3 An elseпов'язується з лексично найближчим попереднім, якщо це дозволено синтаксисом.

Зауважте, що цей пункт вказує лише на те, що керуючий вираз повинен мати скалярний тип ( char/ short/ int/ long/ тощо), а не конкретно булевий тип. Гілка виконується, якщо керуючий вираз має ненульове значення.

Порівняйте це з

Специфікація мови Java SE 8

14.9 ifЗаява заяву дозволяє умовне виконання оператора або умовний вибір двох операторів, виконуючи один або інший , але не обидва.

if
    IfThenStatement :
        якщо ( вираз ) заяву

    IfThenElseStatement :
        якщо ( Expression ) StatementNoShortIf інше заяву

    IfThenElseStatementNoShortIf :
        if ( Вираз ) ЗаявуNoShortIf else StatementNoShortIf
Вираз повинен мати тип booleanабо Boolean, інакше відбувається помилка часу компіляції.

Java, OTOH, спеціально вимагає, щоб керуючий вираз у ifоператорі мав булевий тип.

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

Редагувати

Щодо того, чому мови відрізняються в цьому конкретному відношенні, кілька моментів:

  1. C був похідним від B, що був "безтиповою" мовою - в основному все було від 32 до 36 біт слова (залежно від обладнання), і всі арифметичні операції були цілими операціями. Система типу С була зафіксована трохи за раз, так що ...

  2. C не мав чіткого булевого типу до мовної версії 1999 року. C просто дотримувався конвенції B про використання нуля для подання falseта не нуля для подання true.

  3. Java поставила дату C на добру пару десятиліть і була розроблена спеціально для усунення деяких недоліків C та C ++. Безперечно, посилення обмеження на те, що може бути контрольним виразом у ifвиписці, було частиною цього.

  4. Немає підстав сподіватися, що будь-які дві мови програмування будуть робити те саме. Навіть мови, настільки тісно пов'язані, як C та C ++, розходяться цікавими способами, наприклад, у вас можуть бути легальні програми C, які не є законними програмами C ++, або є легальними програмами C ++, але з різною семантикою тощо.


Дуже сумно, я не можу позначити дві відповіді як "прийняті" ..
toogley

1
Так, це вдале переосмислення питання. Але питання запитало, чому існує така різниця між двома мовами. Ви взагалі не зверталися до цього.
Давуд каже, що поверніть Моніку

@DawoodibnKareem: Питання полягало в тому, чому C дозволив переходити intдо, booleanпоки Java не робила. Відповідь полягає в тому, що такої конверсії немає в C. Мови різні, оскільки вони різні.
Джон Боде

Так, "мова не йде про слабку та сильну машинопис". Просто подивіться на автоматичний бокс та авторозпакування. Якщо у Ц були такі, він не міг дозволити жодного скалярного типу.
Дедуплікатор

5

Багато відповідей, схоже, націлені на вираження вбудованого призначення, яке знаходиться в умовному виразі. (Хоча це відомий вид потенційного підводного шару, в цьому випадку він не є джерелом повідомлення про помилку Java.)

Можливо, це пов'язано з тим, що ОП не опублікувало фактичне повідомлення про помилку, а ^карета вказує безпосередньо на =оператора призначення.

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

Він скаржиться на тестування не булевого значення із таким видом помилок:

помилка: несумісні типи: int не може бути перетворений в булевий

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

Це також стосується тестових покажчиків C на null / non-null через if (p) ...і if (!p) ..., що Java аналогічно не дозволяє, замість цього вимагає явного оператора порівняння для отримання необхідного булевого.


1
C робить має логічний тип. Але це новіше, ніж ifтвердження.
MSalters

3
Буль @MSalters C все ще є цілим числом під обкладинками, отже, ви можете це зробити bool b = ...; int v = 5 + b;. Це відрізняється від мов з повним булевим типом, які не можна використовувати в арифметиці.
Жуль

Булева кількість C - це просто дійсно мале ціле число. "true" і "false" легко можна визначити за допомогою макросів або будь-якого іншого.
Оскар скаго

4
@Jules: Ви просто вказуєте, що C має слабку безпеку типу. Тільки тому, що булевий тип перетворюється на ціле число, не означає, що це цілий тип. За вашою логікою, цілі типи будуть типами з плаваючою комою, оскільки int v = 5; float f = 2.0 + v;це законно в C.
MSalters

1
@MSalters насправді _Boolє одним із стандартних цілих цілочисленних типів, як визначено Стандартом (див. 6.2.5 / 6).
Руслан

2

несумісні типи: int не може бути перетворений у логічний

Мене цікавить, чому C це дозволяє, а Java ні. Тому мене цікавить система типу мови, зокрема її міцність.

До вашого питання є дві частини:

Чому Java не конвертувати intв boolean?

Це зводиться до того, що Java має бути максимально чітким. Це дуже статично, дуже "в обличчя" зі своєю системою типів. Речі, які автоматично передаються на іншій мові, на Java не такі. Ви також повинні писати int a=(int)0.5. Перетворення floatна intвтрачає інформацію; такий же, як перетворення intв booleanі, таким чином, буде схильним до помилок. Крім того, їм довелося б вказати багато комбінацій. Звичайно, такі речі здаються очевидними, але вони мали намір помилитися на стороні обережності.

О, і порівняно з іншими мовами, Java була надзвичайно точною у своїй специфікації, оскільки байт-код був не просто внутрішньою деталлю реалізації. Вони повинні були б точно вказати будь-які взаємодії. Величезне починання.

Чому ifне приймає інші типи, ніж boolean?

ifцілком можна було б визначити, щоб дозволити інші типи, ніж boolean. Він може мати визначення, яке говорить про те, що таке рівнозначне:

  • true
  • int != 0
  • String з .length>0
  • Будь-яка інша посилання на об'єкт, яка не є null(а не має Booleanзначення false).
  • Або навіть: будь-яка інша посилання на об'єкт, яка не є nullі чий метод Object.check_if(придуманий мною саме для цього випадку) повертає true.

Вони не зробили; реальної потреби в цьому не було, і вони хотіли, щоб це було якомога міцнішим, статичним, прозорим, легким для читання тощо. Без неявних ознак. Крім того, реалізація була б досить складною, я впевнений, що потрібно перевіряти кожне значення для всіх можливих випадків, тому продуктивність, можливо, також відігравала невеликий фактор (Java раніше була слоухау на комп’ютерах того дня; пам’ятайте, що там не було компіляторів JIT з першими випусками, принаймні, не на комп’ютерах, якими я користувався тоді).

Більш глибока причина

Більш глибокою причиною може бути той факт, що у Java є свої примітивні типи, отже, її система типів розбита між об'єктами та примітивами. Можливо, якби вони цього уникали, то все вийшло б інакше. З правилами, наведеними в попередньому розділі, вони повинні були б чітко визначити правдивість кожного примітиву (оскільки примітиви не мають суперкласу, і nullдля примітивів немає чітко визначеного ). Це швидко перетвориться на кошмар.

Прогноз

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

Наприклад, у Рубі немає примітивних типів. Все, буквально все, є об’єктом. Вони дуже просто переконуються, що кожен об'єкт має певний метод.

Рубі все-таки шукає правдивість на всіх видах предметів, на які можна кинути. Цікаво, що він досі не має booleanтипу (бо не має примітивів), іBoolean класу він також не має . Якщо ви запитаєте, який клас trueмає значення (зручно доступне true.class), ви отримуєте TrueClass. Цей клас насправді має методи, а саме 4 оператори для booleans ( | & ^ ==). Тут ifвраховується його значення фальси, якщо і тільки якщо воно є falseабо nil( nullРубі). Все інше правда. Отже, 0або ""обидва вірні.

Для них було б тривіально створити метод, Object#truthy?який можна було б реалізувати для будь-якого класу та повернути індивідуальну правдивість. Наприклад, String#truthy?могло б бути реалізовано так, що це стосується не порожніх рядків чи чогось іншого. Вони цього не зробили, навіть незважаючи на те, що Рубі - це антитеза Java у більшості департаментів (динамічне введення качок з міксином, повторне відкриття класів та все таке).

Що може дивувати програміста Perl, який звик $value <> 0 || length($value)>0 || defined($value)бути правдивим. І так далі.

Введіть SQL з його умовою, що nullвсередині будь-якого виразу автоматично робиться помилковим, незважаючи ні на що. Отже (null==null) = false. В Ruby (nil==nil) = true. Щасливі часи.


Насправді ((int)3) * ((float)2.5)це досить чітко визначено на Java (це так 7.5f).
Paŭlo Ebermann

Ви маєте рацію, @ PaŭloEbermann, я видалив цей приклад.
AnoE

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

Фактично перетворення з intна floatв цілому втрачає інформацію. Чи забороняє Java також такі неявні ролі?
Руслан

@Ruslan ні (те ж саме для довгих → подвійних) - Я думаю, ідея полягає в тому, що потенційна втрата інформації існує лише в найменш значущих місцях і відбувається лише у випадках, які вважаються не такими важливими (досить великі цілі значення).
Paŭlo Ebermann

1

Окрім інших тонких відповідей, я хотів би поговорити про послідовність між мовами.

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

Все йде нормально. Це те, що реалізує Java, і лише те, що реалізує Java.

Інші мови намагаються наблизити зручності для не булевих значень. Наприклад:

  • Припустимо n, ціле число. Тепер визначте if (n)стенограму для if (n != 0).
  • Припустимо, xце число з плаваючою комою. Тепер визначте if (x)стенограму для if (x != 0 && !isNaN(x)).
  • Припустимо p, це тип вказівника. Тепер визначте if (p)стенограму для if (p != null).
  • Припустимо s, це рядковий тип. Тепер визначте, if (s)що буде if (s != null && s != "").
  • Припустимо a, це тип масиву. Тепер визначте, if (a)що буде if (a != null && a.length > 0).

Ця ідея надання коротких стендів if-тестів здається гарною на поверхні ... поки ви не зіткнетеся з відмінностями в дизайні та думках:

  • if (0)трактується як помилкове в C, Python, JavaScript; але трактували як Рубі.
  • if ([]) трактується як помилковий в Python, але правда в JavaScript.

У кожної мови є свої вагомі причини, що стосуються виразів так чи інакше. (Так , наприклад, тільки falsy значення в Ruby , є falseі nil, отже , 0є truthy.)

Java прийняла чіткий дизайн, щоб змусити вас надавати булеве значення if-оператору. Якщо ви поспішно перевели код з C / Ruby / Python на Java, ви не можете залишити будь-який розслаблений if-тест незмінним; потрібно виписати умову явно на Java. Виділення хвилини для паузи та думки може врятувати вас від неохайних помилок.


1
Чи знаєте ви, що x != 0це те саме, що x != 0 && !isNaN(x)? Крім того, це звичайно s != nullдля покажчиків, але щось інше для не вказівників.
Дедуплікатор

@ Дедуплікатор, якою мовою це те саме?
Paŭlo Ebermann

1
Використовуючи IEEE754, NaN ≠ 0. NaN ≠ що-небудь взагалі. Отже, якщо (x) виконає, і якщо (! X) виконає також ...
gnasher729

0

Ну, кожен скалярний тип у С, вказівник, булевий (з С99), число (з плаваючою комою чи ні) та перерахування (за рахунок прямого відображення на числа) має "природне" значення фальси, тому це досить добре для будь-якого умовного вираження .

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

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

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

Підводячи підсумок, обмеження на булеві (і коробковий еквівалент у Java) виглядає як невдала спроба скласти клас помилок, спричинених вибором =для компіляції помилок компіляції.


Я б сказав, що використання ==булевих операндів, з яких перший - це змінна (яка потім може бути набрана =) всередині a, ifтрапляється набагато рідше, ніж у інших типів, я б сказав, тому це лише спроба напівпровалу.
Paŭlo Ebermann

Маючи 0,0 -> false та будь-яке значення, крім 0,0 -> true, для мене зовсім не виглядає "природним".
gnasher729

@ PaŭloEbermann: Частковий результат при нещасних побічних ефектах, хоча існує простий спосіб досягти мети без побічних ефектів, - явна невдача в моїй книзі.
Дедуплікатор

Ви пропускаєте інші причини. Що можна сказати про значення truthy з "", (Object) "", 0.0, -0.0, NaN, порожні масиви, а Boolean.FALSE? Особливо останній смішний, оскільки ненульовий вказівник (true) розблоковує значення false. +++ Я також ненавиджу писати if (o != null), але заощаджувати мені кілька символів щодня і дозволяти мені витратити пів дня на відлагодження мого "розумного" вираження - це не гарна справа. З цього приводу я хотів би побачити якийсь середній шлях для Java: Більш щедрі правила, але взагалі не залишаючи двозначності.
maaartinus

@maaartinus: Як я вже сказав, введення автоматичного розпакування в Java 5.0 означало, що якщо вони присвоїли покажчикам значення правди, що відповідає нулю, то вони мали б досить проблему. Але це проблема в автоматичному (не) боксі, не в чому іншому. Мова без автоматичного (не) боксу, як C, щасливо вільна від цього.
Дедуплікатор

-1

Моє запитання не пов'язане з поганим стилем кодування. Я знаю його погано, але я втручався в те, чому C це дозволяє, а java ні.

Двома з цілей дизайну Java були:

  1. Дозвольте розробнику зосередитись на бізнес-проблемі. Роблять простіші і менш схильні до помилок, наприклад, збирання сміття, щоб розробникам не потрібно було зосереджуватися на витоках пам'яті.

  2. Переносний між платформами, наприклад, працювати на будь-якому комп'ютері з будь-яким процесором.

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

Крім того, висновок про те, що будь-яке ненулеве значення = істинне і будь-яке нульове значення = хибне, не обов’язково є портативним (так, вірите чи ні, деякі системи трактують 0 як істинне, а 1 - як хибне ), тому під ціллю №2 вище не допускається неявно . Зрозуміло, ви можете відігравати явну роль.


4
Якщо це не додано в Java 8, не існує явного переліку між числовими типами та булевими. Для перетворення числового значення в булеве значення потрібно використовувати оператор порівняння; для перетворення булевого значення в числове ви зазвичай використовуєте потрійний оператор.
Пітер Тейлор

Ви можете звернутися із заявою про заборону використання завдання для його значення через ці помилки. Але враховуючи, як часто ви бачите == true і == false на Java, очевидно обмеження керуючого виразу (необов’язково в коробці) булевого типу не дуже допомагає.
Дедуплікатор

Java тривіально забезпечить переносимість wrt на "деякі системи трактують 0 як істинне, а 1 - як хибне", оскільки це нуль Java, а Java - помилково. Це насправді не має значення, що про це думає ОС.
maaartinus

-1

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

У старих версіях мови додаткові типи підтримували "BooleanType", щоб ви могли написати "if x" замість "if x! = Nil". Що зроблено за допомогою "необов'язкового bool" дуже заплутано. Необов'язковий bool має значення nil, false або true, і "якщо b" не виконується, якщо b було нульовим, а виконується, якщо b є true або false. Це більше не дозволено.

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

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