¿Por qué exactamente Java no permite condicionales numéricos como if (5) {...} si C lo hace?

33

Tengo estos dos pequeños programas:

do

#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");
       }
   }
}

que informa el mensaje de error:

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

Mi entendimiento

Hasta ahora, entendí este ejemplo como una demostración de los diferentes sistemas de tipos. C tiene un tipo más débil y permite la conversión de int a boolean sin errores. Java está más fuertemente tipado y falla, porque no se permite ninguna conversación implícita.

Por lo tanto, mi pregunta: ¿dónde entendí mal las cosas?

Lo que no estoy buscando

Mi pregunta no está relacionada con un mal estilo de codificación. Sé que es malo, pero me interesa saber por qué C lo permite y Java no. Por lo tanto, estoy interesado en el sistema de tipos de lenguaje, específicamente su fortaleza.

toogley
fuente
22
@toogley: ¿Qué tiene que ver específicamente con el sistema de tipos en Java? El sistema de tipos no permite esto porque la especificación del lenguaje lo prohíbe. Las razones por las cuales C lo permite y Java no tiene todo que ver con lo que se considera aceptable en ambos lenguajes. El comportamiento resultante de los sistemas tipo es el efecto de eso, no la causa.
Robert Harvey
8
@toogley: ¿por qué crees que malinterpretaste algo? Leyendo el texto nuevamente, no entiendo cuál es tu pregunta.
Doc Brown
26
En realidad, este no es un ejemplo de tipeo débil en C. En el pasado (C89), C ni siquiera tenía un tipo booleano, por lo que todas las operaciones "booleanas" realmente operaban en intvalores. Un ejemplo más apropiado sería if (pointer).
Rufflewind
55
Voté en contra porque no parece que se haya hecho mucha investigación tratando de entender esto.
jpmc26
11
Parece que la pregunta original es un poco diferente a la versión editada (por lo que algunos de los comentarios y respuestas parecen responder a una pregunta diferente). En su forma actual, no parece haber una pregunta aquí. ¿Qué malentendido? Java se comporta exactamente como crees que debería ser.
jamesdlin

Respuestas:

134

1. C y Java son lenguajes diferentes

El hecho de que se comporten de manera diferente no debería ser terriblemente sorprendente.

2. C no está haciendo ninguna conversión de intabool

Como podria C ni siquiera tenía un booltipo verdadero para convertir hasta 1999 . C fue creado a principios de la década de 1970, y iffue parte de él incluso antes de que fuera C, cuando era solo una serie de modificaciones a B 1 .

ifno fue simplemente un NOPen C por casi 30 años. Actuó directamente sobre valores numéricos. El verborrea en el estándar C ( enlace PDF ), incluso más de una década después de la introducción de bools a C, todavía especifica el comportamiento de if(p 148) y ?:(p 100) usando los términos "desigual a 0" e "igual a 0 "en lugar de los términos booleanos" verdadero "o" falso "o algo similar.

Convenientemente ...

3. ... los números simplemente son lo que operan las instrucciones del procesador.

JZy JNZson sus instrucciones básicas de ensamblaje x86 para la ramificación condicional. Las abreviaturas son " J UMP si Z ero" y " J UMP si N ot Z ero". Los equivalentes para el PDP-11, donde se originó C, son BEQ(" B ranch si EQ ual") y BNE(" B ranch si N ot E qual").

Estas instrucciones verifican si la operación anterior resultó en cero o no y saltan (o no) en consecuencia.

4. Java tiene mucho más énfasis en la seguridad que C 2

Y, con la seguridad en mente, decidieron que restringir ifa booleans valía la pena el costo (tanto de implementar tal restricción como de los costos de oportunidad resultantes).


1. B ni siquiera tiene tipos en absoluto. Los lenguajes de ensamblaje generalmente tampoco. Sin embargo, B y los lenguajes de ensamblaje logran manejar la ramificación muy bien.

2) En palabras de Dennis Ritchie al describir las modificaciones planificadas a B que se convirtieron en C (énfasis mío):

... parecía que era necesario un esquema de tipeo para hacer frente a los caracteres y el direccionamiento de bytes, y para prepararse para el próximo hardware de punto flotante. Otros problemas, particularmente la seguridad de tipo y la verificación de la interfaz, no parecían tan importantes como lo fueron más tarde.

8bittree
fuente
13
Un buen punto sobre las expresiones booleanas en la asignación de C directamente a las instrucciones del procesador. No había pensado en eso antes.
Robert Harvey
17
Creo que el punto 4 es engañoso, ya que parece implicar que C tiene una pizca de enfoque de seguridad ... C te permitirá hacer básicamente lo que quieras: C asume que sabes lo que estás haciendo, a menudo con resultados espectacularmente desastrosos.
TemporalWolf
13
@TemporalWolf Usted declara que si C le permitiera hacer lo que quiere es malo. La diferencia en mi opinión es que C fue escrito para programadores y se espera una competencia básica. Java está escrito para monos de código y, si puede escribir, puede programarlo. A menudo espectacularmente mal pero a quién le importa ¿verdad? Nunca olvidaré cuando un maestro en la clase introductoria de Java que me vi obligado a tomar como parte de mi CS menor, señaló que usar la recursividad para calcular los números de Fibonnaci podría ser útil porque era "fácil de escribir". Por eso tenemos el software que tenemos.
DRF
25
@DRF Uno de mis profesores para una clase de redes C dijo: "C es como una caja de granadas de mano con todos los alfileres extraídos". Tienes mucho poder, pero esencialmente está garantizado que te explotará en la cara. En la gran mayoría de los casos, no vale la pena, y será mucho más productivo con un lenguaje de nivel superior. ¿He transcodificado Python en hacky C bit-twiddling para obtener un aumento de velocidad de 6 órdenes de magnitud? Si, si tengo. Pero esa es la excepción, no la regla. Puedo lograr en un día en Python lo que me llevaría dos semanas en C ... y soy un programador decente en C.
TemporalWolf
11
@DRF Dada la prevalencia de las vulnerabilidades de desbordamiento del búfer en el software convencional desarrollado por compañías de renombre, aparentemente no se puede confiar en que incluso los programadores con competencia básica no salgan disparados.
Restablece a Monica
14

C 2011 Borrador en línea

6.8.4.1 La ifinstrucción

Restricciones

1 La expresión controladora de una ifdeclaración tendrá un tipo escalar.

Semántica

2 En ambas formas, la primera subestación se ejecuta si la expresión se compara desigual a 0. En la elseforma, la segunda subestación se ejecuta si la expresión se compara igual a 0. Si la primera subestación se alcanza mediante una etiqueta, la segunda subestación es sin ejecutar.

3 Un elseestá asociado con el precedente léxico más cercano si la sintaxis lo permite.

Tenga en cuenta que esta cláusula especifica solo que la expresión de control tendrá un tipo escalar ( char/ short/int / long/ etc.), no específicamente un tipo booleano. Se ejecuta una rama si la expresión de control tiene un valor distinto de cero.

Compara eso con

Especificación del lenguaje Java SE 8

14.9 La ifdeclaración

La ifdeclaración permite la ejecución condicional de una declaración o la elección condicional de dos declaraciones, ejecutando una u otra pero no ambas.
    IfThenStatement :Declaración 
        if ( expresión )

    IfThenElseStatement :
        si ( Expresión ) StatementNoShortIf otra declaración

    IfThenElseStatementNoShortIf :
        if ( Expresión ) StatementNoShortIf else StatementNoShortIf
La expresión debe tener tipo booleano Boolean, o se produce un error en tiempo de compilación.

Java, OTOH, requiere específicamente que la expresión de control en una ifinstrucción tenga un tipo booleano.

Por lo tanto, no se trata de escribir débil versus fuerte, se trata de lo que cada definición de lenguaje respectiva especifica como una expresión de control válida.

Editar

En cuanto a por qué los idiomas son diferentes en este aspecto particular, varios puntos:

  1. C se derivó de B, que era un lenguaje "sin tipo", básicamente, todo era una palabra de 32 a 36 bits (dependiendo del hardware), y todas las operaciones aritméticas eran operaciones enteras. El sistema de tipos de C se atornillaba poco a poco, de modo que ...

  2. C no tenía un tipo booleano distinto hasta la versión de 1999 del lenguaje. C simplemente siguió la convención B de usar cero para representar falsey no cero para representar true.

  3. Java data de C un par de décadas después y fue diseñado específicamente para abordar algunas de las deficiencias de C y C ++. Sin duda, ajustar la restricción sobre lo que puede ser una expresión de control en una ifdeclaración fue parte de eso.

  4. No hay razón para esperar ningún dos lenguajes de programación que se pueden hacer las cosas de la misma manera. Incluso los lenguajes tan estrechamente relacionados como C y C ++ divergen de algunas maneras interesantes, de modo que puede tener programas legales de C que no son programas legales de C ++, o son programas legales de C ++ pero con semántica diferente, etc.

John Bode
fuente
Demasiado triste, no puedo marcar dos respuestas como "aceptado" ..
toogley
1
Sí, esta es una buena reformulación de la pregunta. Pero la pregunta preguntaba por qué hay esta diferencia entre los dos idiomas. No has abordado esto en absoluto.
Dawood dice que reinstalar a Monica
@DawoodibnKareem: La pregunta era por qué C permitió la conversión de inta boolean, mientras que Java no lo hizo. La respuesta es que no hay tal conversión en C. Los idiomas son diferentes porque son idiomas diferentes.
John Bode
Sí, "no se trata de escribir débil versus fuerte". Solo mira el auto-boxing y auto-unboxing. Si C tuviera esos, tampoco podría permitir ningún tipo escalar.
Deduplicador
5

Muchas de las respuestas parecen estar dirigidas a la expresión de asignación incrustada que está dentro de la expresión condicional. (Si bien ese es un tipo conocido de trampa potencial, no es la fuente del mensaje de error de Java en este caso).

Posiblemente esto se deba a que el OP no publicó el mensaje de error real y el ^cursor apunta directamente al= operador de asignación.

Sin embargo, el compilador apunta a = porque es el operador el que produce el valor final (y, por lo tanto, el tipo final) de la expresión que ve el condicional.

Se queja de probar un valor no booleano, con el siguiente tipo de error:

error: tipos incompatibles: int no se puede convertir a booleano

La prueba de enteros, aunque a veces es conveniente, se considera una trampa potencial que los diseñadores de Java eligen evitar. Después de todo, Java tiene un verdadero tipo de datos booleanos , que C no tiene (no tiene ningún tipo booleano) .

Esto también se aplica a los punteros de prueba de C para nulo / no nulo a través de if (p) ...y if (!p) ..., que Java de manera similar no permite, en cambio, requiere un operador de comparación explícito para obtener el booleano requerido.

Erik Eidt
fuente
1
C hace tener un tipo booleano. Pero eso es más nuevo que la ifdeclaración.
MSalters
3
El bool de @MSalters C sigue siendo un entero oculto, por lo tanto, puedes hacerlo bool b = ...; int v = 5 + b;. Esto es diferente a los idiomas con un tipo booleano completo que no se puede usar en aritmética.
Julio
El booleano de C es solo un entero muy pequeño. "verdadero" y "falso" podrían definirse fácilmente con macros o lo que sea.
Oskar Skog
44
@Jules: Solo estás señalando que C tiene una seguridad de tipografía débil. El hecho de que un tipo booleano se convierta en un valor entero no significa que sea un tipo entero. Según su lógica, los tipos enteros serían tipos de punto flotante ya que int v = 5; float f = 2.0 + v;es legal en C.
MSalters
1
@MSalters en realidad _Booles uno de los tipos enteros sin signo estándar definidos por el Estándar (ver 6.2.5 / 6).
Ruslan
2

tipos incompatibles: int no se puede convertir a booleano

Estoy interesado en por qué C lo permite y Java no. Por lo tanto, estoy interesado en el sistema de tipos de lenguaje, específicamente su fortaleza.

Hay dos partes en su pregunta:

¿Por qué Java no convertir inta boolean?

Esto se reduce a que Java pretende ser lo más explícito posible. Es muy estático, muy "en tu cara" con su sistema de tipos. Las cosas que se convierten automáticamente en otros idiomas no lo son, en Java. Tienes que escribir int a=(int)0.5también. La conversión floata intperdería información; lo mismo que convertir intaboolean y por lo tanto sería propenso a errores. Además, habrían tenido que especificar muchas combinaciones. Claro, estas cosas parecen ser obvias, pero pretendían errar por precaución.

Ah, y en comparación con otros lenguajes, Java fue enormemente exacto en su especificación, ya que el código de bytes no era solo un detalle de implementación interna. Tendrían que especificar cualquiera y todas las interacciones, precisamente. Enorme empresa.

¿Por qué ifno acepta otros tipos queboolean ?

ifperfectamente podría definirse como para permitir otros tipos que boolean. Podría tener una definición que diga que los siguientes son equivalentes:

  • true
  • int != 0
  • String con .length>0
  • Cualquier otra referencia de objeto que no sea null(y no un Booleanvalor false).
  • O incluso: cualquier otra referencia de objeto que no sea nully cuyo método Object.check_if(inventado por mí solo para esta ocasión) regrese true.

No lo hicieron; no era realmente necesario, y querían tenerlo lo más robusto, estático, transparente, fácil de leer, etc. posible. Sin características implícitas. Además, la implementación sería bastante compleja, estoy seguro, tener que probar cada valor para todos los casos posibles, por lo que el rendimiento también puede haber jugado un pequeño factor también (Java solía ser lento en las computadoras de ese día; recuerde allí no había compiladores JIT con los primeros lanzamientos, al menos no en las computadoras que usé entonces).

Razón más profunda

Una razón más profunda podría ser el hecho de que Java tiene sus tipos primitivos, por lo tanto, su sistema de tipos está dividido entre objetos y primitivos. Tal vez, si hubieran evitado eso, las cosas habrían resultado de otra manera. Con las reglas dadas en la sección anterior, tendrían que definir la veracidad de cada primitiva explícitamente (ya que las primitivas no comparten una superclase, y no hay una bien definida nullpara las primitivas). Esto se convertiría en una pesadilla, rápidamente.

panorama

Bueno, y al final, tal vez sea solo una preferencia de los diseñadores de idiomas. Cada idioma parece girar a su manera allí ...

Por ejemplo, Ruby no tiene tipos primitivos. Todo, literalmente todo, es un objeto. Les resulta muy fácil asegurarse de que cada objeto tenga un método determinado.

Ruby busca la veracidad en todos los tipos de objetos que puedes arrojarle. Curiosamente, todavía no tiene booleantipo (porque no tiene primitivas), y tampoco tiene Booleanclase. Si pregunta qué clase tiene el valor true(fácilmente disponible true.class), obtiene TrueClass. Esa clase realmente tiene métodos, a saber, los 4 operadores para booleans ( | & ^ ==). Aquí, ifconsidera su valor falsey si y solo si es falseo nil(el nullde Ruby). Todo lo demás es verdad. Entonces, 0o ""ambos son ciertos.

Hubiera sido trivial para ellos crear un método Object#truthy?que podría implementarse para cualquier clase y devolver una verdad individual. Por ejemplo, String#truthy?podría haberse implementado para ser verdadero para cadenas no vacías, o cualquier otra cosa. No lo hicieron, a pesar de que Ruby es la antítesis de Java en la mayoría de los departamentos (tipeo de pato dinámico con mixin, reapertura de clases y todo eso).

Lo que puede sorprender a un programador de Perl que está acostumbrado a $value <> 0 || length($value)>0 || defined($value)ser veraz. Y así.

Ingrese SQL con su convención que nulldentro de cualquier expresión automáticamente lo hace falso, pase lo que pase. Por lo tanto (null==null) = false. En Ruby, (nil==nil) = true. Tiempos felices.

AnoE
fuente
En realidad, ((int)3) * ((float)2.5)está bastante bien definido en Java (lo es 7.5f).
Paŭlo Ebermann
Tienes razón, @ PaŭloEbermann, he eliminado ese ejemplo.
AnoE
Woah allí ... agradecería los comentarios de los votantes negativos y el que votó para eliminar realmente la respuesta.
AnoE
En realidad, la conversión de inta floattambién pierde información en general. ¿Java también prohíbe tal conversión implícita?
Ruslan
@Ruslan no (igual para largo → doble): supongo que la idea es que la pérdida potencial de información solo existe en los lugares menos significativos, y solo ocurre en casos que no se consideran tan importantes (valores enteros bastante grandes).
Paŭlo Ebermann
1

Además de las otras buenas respuestas, me gustaría hablar sobre la consistencia entre los idiomas.

Cuando pensamos en una declaración si matemáticamente pura, entendemos que la condición puede ser verdadera o falsa, no otro valor. Cada lenguaje de programación importante respeta este ideal matemático; si le das un valor booleano verdadero / falso a una declaración if, entonces puedes esperar ver un comportamiento consistente e intuitivo todo el tiempo.

Hasta aquí todo bien. Esto es lo que implementa Java, y solo lo que implementa Java.

Otros idiomas intentan brindar conveniencias para valores no booleanos. Por ejemplo:

  • Supongamos que nes un entero. Ahora defina if (n)ser taquigrafía para if (n != 0).
  • Supongamos que xes un número de coma flotante. Ahora defina if (x)ser taquigrafía para if (x != 0 && !isNaN(x)).
  • Supongamos que pes un tipo de puntero. Ahora defina if (p)ser taquigrafía para if (p != null).
  • Supongamos que ses un tipo de cadena. Ahora defina if (s)ser if (s != null && s != "").
  • Supongamos que aes un tipo de matriz. Ahora defina if (a)ser if (a != null && a.length > 0).

Esta idea de proporcionar pruebas if abreviadas parece buena en la superficie ... hasta que encuentre diferencias en diseños y opiniones:

  • if (0)se trata como falso en C, Python, JavaScript; pero tratado como verdadero en Ruby.
  • if ([]) se trata como falso en Python pero cierto en JavaScript.

Cada idioma tiene sus propias buenas razones para tratar expresiones de una forma u otra. (Por ejemplo, los únicos valores falsos en Ruby son falsey nil, por 0lo tanto, son verdaderos).

Java adoptó un diseño explícito para obligarlo a proporcionar un valor booleano a una instrucción if. Si tradujo apresuradamente el código de C / Ruby / Python a Java, no puede dejar las pruebas if lax sin cambios; necesita escribir la condición explícitamente en Java. Tomar un momento para detenerse y pensar puede salvarlo de errores descuidados.

Nayuki
fuente
1
¿Sabes que x != 0es lo mismo que x != 0 && !isNaN(x)? Además, normalmente es s != nullpara punteros, pero algo más para no punteros.
Deduplicador
@Deduplicator ¿en qué idioma es el mismo?
Paŭlo Ebermann
1
Usando IEEE754, NaN ≠ 0. NaN ≠ cualquier cosa. Entonces, si (x) se ejecutaría, y si (! X) también se ejecutaría ...
gnasher729
0

Bueno, cada tipo escalar en C, puntero, booleano (desde C99), número (punto flotante o no) y enumeración (debido a la asignación directa a números) tiene un valor de Falsey "natural", por lo que es lo suficientemente bueno para cualquier expresión condicional .

Java también los tiene (incluso si los punteros de Java se denominan referencias y están muy restringidos), pero introdujo el auto-boxing en Java 5.0 que confunde las aguas de manera inaceptable. Además, los programadores de Java exhortan el valor intrínseco de escribir más.

El único error que generó el debate acerca de si la restricción de las expresiones condicionales de tipo booleano es el error tipográfico de escribir una misión en la que se pretende una comparación, que se no se dirigió al restringir el tipo de expresiones condicionales en absoluto , pero al no permitir el uso de una asignación de expresión desnuda por su valor

Cualquier compilador moderno de C o C ++ lo maneja fácilmente, dando una advertencia o error para tales construcciones cuestionables si se le pregunta.
Para aquellos casos en los que es exactamente lo que se pretendía, resulta útil agregar paréntesis.

En resumen, restringir a booleano (y el equivalente en recuadro en Java) parece un intento fallido de hacer una clase de errores tipográficos causados ​​por la elección =de errores de compilación de asignación.

Deduplicador
fuente
El uso ==con operandos booleanos, de los cuales el primero es una variable (que luego podría escribirse =) dentro, ifocurre con mucha menos frecuencia que con otros tipos, diría, por lo que es solo un intento semi-fallido.
Paŭlo Ebermann
Tener 0.0 -> falso y cualquier valor que no sea 0.0 -> verdadero no me parece "natural" en absoluto.
gnasher729
@ PaŭloEbermann: El resultado parcial con efectos secundarios desafortunados, aunque hay una manera fácil de lograr el objetivo sin efectos secundarios, es un claro fracaso en mi libro.
Deduplicador
Te estás perdiendo las otras razones. ¿Qué pasa con el valor de Truthy "", (Object) "", 0.0, -0.0, NaN, las matrices vacías, y Boolean.FALSE? Especialmente el último es divertido, ya que es un puntero no nulo (verdadero) que desempaqueta a falso. +++ También odio escribir if (o != null), pero ahorrarme algunos caracteres todos los días y dejarme pasar medio día depurando mi expresión "inteligente" no es un buen negocio. Dicho esto, me encantaría ver un camino intermedio para Java: reglas más generosas pero sin dejar ninguna ambigüedad.
maaartinus
@maaartinus: Como dije, la introducción del auto-unboxing en Java 5.0 significaba que si asignaban un valor de verdad correspondiente a la nulidad a los punteros, tendrían un gran problema. Pero eso es un problema con el auto- (un-) boxeo, nada más. Un lenguaje sin auto- (un-) boxeo como C está felizmente libre de eso.
Deduplicador
-1

Mi pregunta no está relacionada con un mal estilo de codificación. Sé que es malo, pero estoy interesado en por qué C lo permite y Java no.

Dos de los objetivos de diseño de Java fueron:

  1. Permita que el desarrollador se concentre en el problema comercial. Haga las cosas más simples y menos propensas a errores, por ejemplo, recolección de basura para que los desarrolladores no necesiten centrarse en las pérdidas de memoria.

  2. Portátil entre plataformas, por ejemplo, ejecutar en cualquier computadora con cualquier CPU.

Se sabía que el uso de la asignación como expresión causaba muchos errores debido a errores tipográficos, por lo que, según el objetivo n. ° 1 anterior, no está permitido la forma en que está tratando de usarlo.

Además, inferir que cualquier valor distinto de cero = verdadero y cualquier valor cero = falso no es necesariamente portátil (sí, lo creas o no, algunos sistemas tratan 0 como verdadero y 1 como falso ), por lo que bajo el objetivo # 2 anterior, no está permitido implícitamente . Todavía puedes emitir explícitamente, por supuesto.

John Wu
fuente
44
A menos que se haya agregado en Java 8, no hay conversión explícita entre tipos numéricos y booleanos. Para convertir un valor numérico a booleano, debe usar un operador de comparación; para convertir un valor booleano a uno numérico, normalmente se usa el operador ternario.
Peter Taylor
Podría presentar un caso para no permitir el uso de una asignación por su valor debido a esos errores tipográficos. Pero teniendo en cuenta la frecuencia con la que ve == verdadero y == falso en Java, obviamente limitar la expresión de control al tipo booleano (opcionalmente en caja) no ayuda mucho.
Deduplicador
Java garantizaría trivialmente la portabilidad wrt a "algunos sistemas tratan 0 como verdadero y 1 como falso", ya que es cero de Java y falso de Java. Realmente no importa lo que el SO piense al respecto.
maaartinus
-1

Como ejemplo de lo que hacen otros idiomas: Swift requiere una expresión de un tipo que admita el protocolo "BooleanType", lo que significa que debe tener un método "boolValue". El tipo "bool" obviamente es compatible con este protocolo y usted puede crear sus propios tipos. Los tipos enteros no admiten este protocolo.

En versiones anteriores del idioma, los tipos opcionales admitían "BooleanType" para que pudiera escribir "if x" en lugar de "if x! = Nil". Lo que hizo que usar un "Bool opcional" fuera muy confuso. Un bool opcional tiene valores nulo, falso o verdadero y "si b" no se ejecutaría si b fuera nulo y si b fuera verdadero o falso. Esto ya no está permitido.

Y parece que hay un tipo al que no le gusta abrir tus horizontes ...

gnasher729
fuente