Почему именно Java не допускает числовые условия, такие как if (5) {…}, если C делает?

33

У меня есть эти две маленькие программы:

С

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

    return 0;
}

Джава

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 в логическое значение без ошибок. Java более строго типизирована и дает сбой, потому что неявный разговор не допускается.

Поэтому мой вопрос: где я неправильно понял вещи?

Что я не ищу

Мой вопрос не связан с плохим стилем кодирования. Я знаю, что это плохо, но меня интересует, почему C это позволяет, а Java нет. Поэтому меня интересует система типов языка, особенно его сильные стороны.

toogley
источник
22
@toogley: Что вы хотите знать о системе типов в Java? Система типов не позволяет этого, потому что спецификация языка запрещает это. Причины, по которым С это позволяет, а 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 .

ifне был просто NOPв C в течение почти 30 лет. Он напрямую воздействовал на числовые значения. Словоблудие в стандарте C ( ссылка PDF ), даже спустя более десяти лет после введения bools в C, по-прежнему определяет поведение if(p 148) и ?:(p 100), используя термины «неравный 0» и «равный 0». ", а не булевы термины" истина "или" ложь "или что-то подобное.

Удобно, ...

3. ... числа просто соответствуют инструкциям процессора.

JZи JNZваши основные инструкции по сборке x86 для условного ветвления. Сокращения , являются " J UMP , если Z эро" и " J UMP , если N OT Z ERO". Эквиваленты для PDP-11, где С возникла, являются BEQ( « Б ранчо , если EQ UAL„) и BNE(“ Б ранчо , если N OT E каче»).

Эти инструкции проверяют, привела ли предыдущая операция к нулю или нет, и переходят (или нет) соответственно.

4. Java уделяет гораздо больше внимания безопасности, чем C когда-либо делал 2

И, учитывая безопасность, они решили, что ограничение ifна booleans стоит затрат (как на внедрение такого ограничения, так и в результате альтернативных издержек).


1. У B вообще нет типов. Языки ассемблера, как правило, тоже нет. Тем не менее, языки B и ассемблера прекрасно справляются с ветвлением.

2. По словам Денниса Ричи, описывая запланированные модификации B, которые стали C (выделено мной):

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

8bittree
источник
13
Хороший момент о булевых выражениях в C, отображаемых непосредственно в инструкции процессора. Я не думал об этом раньше.
Роберт Харви
17
Я думаю, что пункт 4 вводит в заблуждение, поскольку, похоже, подразумевает, что у С есть некоторый пробел в области безопасности ... С позволит вам делать в основном все, что вы хотите: С предполагает, что вы знаете, что делаете, часто с потрясающе катастрофическими результатами.
TemporalWolf
13
@TemporalWolf Вы утверждаете, что если C позволяет вам делать то, что вы хотите, было плохо. Разница, на мой взгляд, в том, что Си был написан для программистов, и ожидается наличие базовой компетенции. Java написана для кода обезьян, и если вы можете печатать, вы можете программировать на нем. Часто эффектно плохо, но кого это волнует? Я никогда не забуду, когда учитель в начальном классе Java, которого я был вынужден принять в качестве части моего несовершеннолетнего CS, отметил, что использование рекурсии для вычисления чисел Фибоначчи может быть полезным, потому что его «легко писать». Вот почему у нас есть программное обеспечение.
DRF
25
@DRF Один из моих профессоров по сетевому классу C сказал: «C похож на коробку с ручными гранатами со всеми вытащенными булавками». У тебя много силы, но по сути гарантированно взорвется тебе в лицо. В большинстве случаев это не стоит хлопот, и вы будете намного продуктивнее с языком более высокого уровня. Транскодировал ли я Python в хаки-биты, чтобы получить увеличение скорости на 6 порядков? Да да у меня есть Но это исключение, а не правило. Я могу достичь за день в Python то, что заняло бы у меня две недели в Си ... и я приличный программист Си.
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 :Оператор 
        if ( Expression )

    IfThenElseStatement :
        if ( Expression ) StatementNoShortIf else Заявление

    IfThenElseStatementNoShortIf :
        if ( Выражение ) StatementNoShortIf else StatementNoShortIf
Выражение должно иметь тип booleanили Boolean, иначе происходит ошибка времени компиляции.

Java, OTOH, в частности, требует , чтобы управляющее выражение в ifоператоре имело логический тип.

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

редактировать

Что касается того, почему языки отличаются в этом конкретном отношении, несколько моментов:

  1. C был получен из B, который был "типизированным" языком - в основном, все было 32- или 36-битным словом (в зависимости от аппаратного обеспечения), и все арифметические операции были целочисленными операциями. Система типов C была добавлена ​​немного, так что ...

  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. Языки разные, потому что это разные языки.
Джон Боде
Да, «дело не в слабой и сильной типизации». Просто посмотрите на автобокс и автоматическую распаковку. Если бы C имел такие, он не мог бы также использовать любой скалярный тип.
дедупликатор
5

Кажется, что многие ответы нацелены на встроенное выражение присваивания, которое находится внутри условного выражения. (Хотя это известная потенциальная ловушка, в данном случае она не является источником сообщения об ошибке Java.)

Возможно, это связано с тем, что OP не опубликовал фактическое сообщение об ошибке, и ^каретка указывает непосредственно на =оператора присваивания.

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

Он жалуется на тестирование не булевого значения со следующей ошибкой:

ошибка: несовместимые типы: int нельзя преобразовать в логическое значение

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

Это также относится к указателям тестирования C на null / non-null через if (p) ...и if (!p) ..., которые Java аналогично не позволяет вместо этого требовать явного оператора сравнения для получения требуемого логического значения.

Эрик Эйдт
источник
1
C делает имеет логический тип. Но это новее, чем ifутверждение.
MSalters
3
Bool @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для примитивов нет четкого определения ). Это быстро превратилось бы в кошмар.

прогноз

Ну, и, в конце концов, может быть, это просто предпочтение языковых дизайнеров. Кажется, каждый язык крутится по-своему ...

Например, в Ruby нет примитивных типов. Все, буквально все, является объектом. Им очень легко убедиться, что у каждого объекта есть определенный метод.

Ruby ищет правдивость на всех типах объектов, которые вы можете бросить в него. Интересно, что у него все еще нет booleanтипа (потому что у него нет примитивов), и у него тоже нет Booleanкласса. Если вы спросите, какой класс trueимеет значение (доступно с помощью true.class), вы получите TrueClass. Этот класс на самом деле имеет методы, а именно 4 оператора для booleans ( | & ^ ==). Здесь, ifсчитает его значение фальси тогда и только тогда, когда оно равно falseили nil(the nullRuby). Все остальное правда. Так что, 0или ""оба правда.

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

Что может удивить программиста на Perl, который привык $value <> 0 || length($value)>0 || defined($value)быть правдивым. И так далее.

Введите SQL с его соглашением, что nullвнутри любого выражения автоматически делает его ложным, несмотря ни на что. Так (null==null) = false. В рубине (nil==nil) = true. Счастливые времена.

Anoe
источник
На самом деле, ((int)3) * ((float)2.5)это довольно четко определено в Java (это так 7.5f).
Пало Эберманн
Вы правы, @ PaŭloEbermann, я удалил этот пример.
AnoE
Вау там ... был бы признателен за комментарии от downvoters и тот, кто проголосовал за фактическое удаление ответа.
AnoE
На самом деле преобразование из intв floatтакже теряет информацию в целом. Java также запрещает такое неявное приведение?
Руслан
@ Руслан нет (то же самое для long → double) - я думаю, идея заключается в том, что потенциальная потеря информации существует только в наименее значимых местах и ​​происходит только в случаях, которые считаются не столь важными (довольно большие целочисленные значения).
Паŭло Эберманн
1

В дополнение к другим хорошим ответам я хотел бы поговорить о согласованности между языками.

Когда мы думаем о математически чистом операторе if, мы понимаем, что условие может быть либо истинным, либо ложным, а не другим значением. Каждый основной язык программирования уважает этот математический идеал; если вы дадите логическое значение true / false для оператора 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; но рассматривается как истинный в Ruby.
  • if ([]) трактуется как ложное в Python, но верно в JavaScript.

У каждого языка есть свои веские причины относиться к выражениям тем или иным образом. (Например, единственными ложными значениями в Ruby являются falseи nil, следовательно, они 0являются правдивыми.)

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

Nayuki
источник
1
Вы знаете, что x != 0это так же, как x != 0 && !isNaN(x)? Кроме того, это обычно s != nullдля указателей, но что-то еще для не указателей.
дедупликатор
@ Дедупликатор на каком языке это то же самое?
Пауло Эберманн
1
Используя IEEE754, NaN ≠ 0. NaN ≠ вообще ничего. Так что, если (x) будет выполняться, и если (! X) также будет выполняться ...
gnasher729
0

Каждый скалярный тип в C, указатель, логическое значение (начиная с C99), число (с плавающей запятой или нет) и перечисление (из-за прямого сопоставления с числами) имеют «естественное» значение Фолси, так что этого достаточно для любого условного выражения. ,

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

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

Любой современный компилятор C или C ++ справляется с этим легко, выдавая предупреждение или ошибку для таких сомнительных конструкций, если их спросят.
Для тех случаев, когда это именно то, что было задумано, добавление скобок помогает.

Подводя итог, можно сказать, что ограничение на логическое (и коробочный эквивалент в Java) выглядит как неудачная попытка создать класс опечаток, вызванных выбором =ошибок компиляции для присваивания.

Deduplicator
источник
Использование ==с булевыми операндами, первый из которых является переменной (которую затем можно было бы опечатать =) внутри, 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, приведенной выше, недопустимо то, как вы пытаетесь его использовать.

Кроме того, вывод о том, что любое ненулевое значение = true и любое нулевое значение = false не обязательно является переносимым (да, верите или нет, некоторые системы рассматривают 0 как true и 1 как false ), поэтому в рамках цели № 2 выше не допускается неявно , Вы все еще можете явно разыграть.

Джон Ву
источник
4
Если это не было добавлено в Java 8, не существует явного приведения между числовыми типами и логическими значениями. Чтобы преобразовать числовое значение в логическое, вы должны использовать оператор сравнения; чтобы преобразовать логическое значение в числовое, вы обычно используете троичный оператор.
Питер Тейлор
Вы могли бы обосновать запрет использования присвоения для его значения из-за этих опечаток. Но учитывая то, как часто вы видите == true и == false в Java, очевидно, что ограничение управляющего выражения булевым типом (необязательно в рамке) не очень помогает.
дедупликатор
Java тривиально гарантирует переносимость по отношению к «некоторые системы рассматривают 0 как истину и 1 как ложь», поскольку это ноль Java и ложь Java. Это действительно не имеет значения, что об этом думает ОС.
maaartinus
-1

В качестве примера того, что делают другие языки: Swift требует выражения типа, поддерживающего протокол «BooleanType», что означает, что он должен иметь метод «boolValue». Тип "bool", очевидно, поддерживает этот протокол, и вы можете создавать свои собственные типы, поддерживающие его. Целочисленные типы не поддерживают этот протокол.

В более старых версиях языка дополнительные типы поддерживали «BooleanType», поэтому вы могли написать «если x» вместо «if x! = Nil». Что делает использование "Optional Bool" очень запутанным. Необязательный bool имеет значения nil, false или true и «if b» не будет выполнено, если b равно nil, и выполнено, если b равно true или false. Это больше не разрешено.

И, кажется, есть один парень, которому очень не нравится открывать свои горизонты ...

gnasher729
источник