Perché esattamente Java non consente condizionali numerici come if (5) {...} se C fa?

32

Ho questi due piccoli programmi:

C

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

che riporta il messaggio di errore:

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

Le mie conoscenze

Finora, ho capito questo esempio come una dimostrazione dei diversi sistemi di tipi. C è più debolmente digitato e consente la conversione da int a booleano senza errori. Java è più tipizzato e fallisce, perché non è permessa alcuna conversazione implicita.

Pertanto, la mia domanda: dove ho frainteso le cose?

Cosa non sto cercando

La mia domanda non è correlata a uno stile di codifica errato. So che è male, ma mi interessa sapere perché C lo consente e Java no. Pertanto, mi interessa il sistema dei tipi di linguaggio, in particolare la sua forza.

    
posta toogley 11.05.2017 - 22:05
fonte

8 risposte

134

1. C e Java sono lingue diverse

Il fatto che si comportino diversamente non dovrebbe essere terribilmente sorprendente.

2. C non sta facendo alcuna conversione da int a bool

Come potrebbe? C non ha nemmeno un vero tipo bool da convertire in fino a quando 1999 . C è stato creato nei primi anni '70 e if ne faceva parte prima ancora di C, quando era solo una serie di modifiche a B 1 .

if non era semplicemente un NOP in C per quasi 30 anni. Ha agito direttamente su valori numerici. La verbosità nello standard C ( link PDF ), anche su un decennio dopo l'introduzione di bool s in C, specifica ancora il comportamento di if (p 148) e ?: (p 100) usando i termini "non uguale a 0" e "uguale a 0" anziché il Booleano termini "vero" o "falso" o qualcosa di simile.

Convenientemente, ...

3. ... i numeri sono esattamente come funzionano le istruzioni del processore.

JZ e JNZ sono le istruzioni di assemblaggio x86 di base per la ramificazione condizionale. Le abbreviazioni sono " J ump se Z ero" e " J ump se N ot Z ero". Gli equivalenti per il PDP-11, in cui C ha origine, sono BEQ (" B ranch se EQ ual") e BNE (" B ranch se N ot E qual ").

Queste istruzioni controllano se l'operazione precedente ha dato come risultato uno zero o meno e il salto (o meno) di conseguenza.

4. Java ha una maggiore enfasi sulla sicurezza di quanto C abbia mai fatto 2

E, tenendo presente la sicurezza, hanno deciso che limitare il valore di if a boolean s valeva il costo (sia di implementare tale restrizione che i costi di opportunità risultanti).

1. B non ha nemmeno i tipi. Anche le lingue di assemblaggio non lo sono. Eppure i linguaggi di assemblaggio e B riescono a gestire perfettamente la ramificazione.

2. Nelle parole di Dennis Ritchie quando descrivono le modifiche pianificate a B che diventa C (enfasi mia):

...it seemed that a typing scheme was necessary to cope with characters and byte addressing, and to prepare for the coming floating-point hardware. Other issues, particularly type safety and interface checking, did not seem as important then as they became later.

    
risposta data 11.05.2017 - 23:16
fonte
13

Bozza online C 2011

6.8.4.1 The if statement

Constraints

1    The controlling expression of an if statement shall have scalar type.

Semantics

2    In both forms, the first substatement is executed if the expression compares unequal to 0. In the else form, the second substatement is executed if the expression compares equal to 0. If the first substatement is reached via a label, the second substatement is not executed.

3    An else is associated with the lexically nearest preceding if that is allowed by the syntax.

Si noti che questa clausola specifica solo che l'espressione di controllo deve avere un tipo scalare ( char / short / int / long / etc.), non specificamente un tipo booleano. Un ramo viene eseguito se l'espressione di controllo ha un valore diverso da zero.

Confrontalo con

Specifica lingua Java SE 8

14.9 The if Statement

The if statement allows conditional execution of a statement or a conditional choice of two statements, executing one or the other but not both.
    IfThenStatement:
        if ( Expression ) Statement

    IfThenElseStatement:
        if ( Expression ) StatementNoShortIf else Statement

    IfThenElseStatementNoShortIf:
        if ( Expression ) StatementNoShortIf else StatementNoShortIf
The Expression must have type boolean or Boolean, or a compile-time error occurs.

Java, OTOH, richiede specificamente che l'espressione di controllo in un'istruzione if abbia un tipo booleano.

Quindi, non si tratta di digitazione debole o strong, ma di ciò che ciascuna definizione di lingua specifica specifica come espressione di controllo valida.

Modifica

Per quanto riguarda perché le lingue sono diverse sotto questo aspetto, diversi punti:

  1. C era derivato da B, che era un linguaggio "senza tipo" - in pratica, tutto era una parola da 32 a 36 bit (a seconda dell'hardware) e tutte le operazioni aritmetiche erano operazioni con interi. Il sistema di tipo C è stato imbullonato un po 'alla volta, in modo che ...

  2. C non aveva un tipo booleano distinto fino alla versione 1999 della lingua. C ha semplicemente seguito la convenzione B dell'uso dello zero per rappresentare false e non zero per rappresentare true .

  3. Java post-date C di un paio di decenni, ed è stato progettato specificamente per risolvere alcune delle carenze di C e di C ++. Indubbiamente, stringere la restrizione su ciò che potrebbe essere un'espressione di controllo in una dichiarazione if era parte di quello.

  4. Non c'è motivo di aspettarsi che qualsiasi due linguaggi di programmazione facciano le cose allo stesso modo. Anche i linguaggi strettamente correlati come C e C ++ divergono in alcuni modi interessanti, in modo che tu possa avere programmi C legali che non sono programmi C ++ legali, o sono programmi C ++ legali ma con semantica diversa, ecc.

risposta data 12.05.2017 - 18:07
fonte
5

Molte delle risposte sembrano mirare all'espressione di assegnazione incorporata che si trova all'interno dell'espressione condizionale. (Anche se questo è un tipo noto di potenziale trappola, in questo caso non è la fonte del messaggio di errore Java.)

Forse perché l'OP non ha pubblicato il messaggio di errore effettivo e, il ^ caret punta direttamente al = dell'operatore di assegnazione.

Tuttavia il compilatore punta a = perché è l'operatore che produce il valore finale (e quindi il tipo finale) dell'espressione che vede il condizionale.

Si lamenta di testare un valore non booleano, con il seguente tipo di errore:

error: incompatible types: int cannot be converted to boolean

Il test degli interi, sebbene a volte conveniente, è considerato una potenziale trappola che i progettisti Java hanno scelto di evitare. Dopotutto, Java ha un vero tipo di dati booleani , che C non (non ha tipo booleano) .

Questo vale anche per i puntatori di test di C per null / non null tramite if (p) ... e if (!p) ... , che Java non consente, analogamente, di richiedere un operatore di confronto esplicito per ottenere il valore booleano richiesto.

    
risposta data 11.05.2017 - 23:08
fonte
2

incompatible types: int cannot be converted to boolean

I'm interested in why C does allow it and java not. Therefore, I'm interested in the language's type system, specifically its strongness.

Ci sono due parti alla tua domanda:

Perché Java non converte int in boolean ?

Questo si riduce a Java per essere il più esplicito possibile. È molto statico, molto "in faccia" con il suo sistema di tipo. Le cose che vengono digitate automaticamente in altre lingue non sono così, in Java. Devi scrivere int a=(int)0.5 pure. Convertire float in int perderebbe informazioni; come convertire int in boolean e sarebbe quindi soggetto a errori. Inoltre, avrebbero dovuto specificare molte combinazioni. Certo, queste cose sembrano essere ovvie, ma intendevano sbagliare sul lato della cautela.

Oh, e rispetto ad altri linguaggi, Java era estremamente esatto nelle sue specifiche, in quanto il bytecode non era solo un dettaglio di implementazione interno. Dovrebbero specificare tutte le interazioni, precisamente. Enorme impresa.

Perché if non accetta altri tipi di boolean ?

if potrebbe perfettamente essere definito in modo da consentire altri tipi oltre boolean . Potrebbe avere una definizione che dice che i seguenti sono equivalenti:

  • true
  • int != 0
  • String con .length>0
  • Qualsiasi altro riferimento a un oggetto che non è null (e non a Boolean con valore false ).
  • O anche: qualsiasi altro riferimento all'oggetto che non è null e il cui metodo Object.check_if (inventato da me solo per questa occasione) restituisce true .

Non l'hanno fatto; non ce n'era bisogno reale e volevano che fosse robusto, statico, trasparente, facile da leggere ecc. possibile. Nessuna caratteristica implicita. Inoltre, l'implementazione sarebbe piuttosto complessa, ne sono sicuro, dovendo testare ogni valore per tutti i possibili casi, quindi anche le prestazioni potrebbero aver giocato un piccolo fattore (Java era solito essere sloooow sui computer di quel giorno; non c'erano compilatori JIT con le prime versioni, almeno non sui computer che usavo allora).

Motivo più approfondito

Una ragione più profonda potrebbe essere il fatto che Java ha i suoi tipi primitivi, quindi il suo sistema di tipi è diviso tra oggetti e primitive. Forse, se avessero evitato quelli, le cose sarebbero andate diversamente. Con le regole fornite nella sezione precedente, dovrebbero definire esplicitamente la veridicità di ogni singolo primitivo (poiché i primitivi non condividono una super classe e non esiste un null ben definito per primitivi). Questo si trasformerebbe in un incubo, rapidamente.

Outlook

Bene, e alla fine, forse è solo una preferenza dei progettisti di linguaggi. Ogni lingua sembra girare proprio lì ...

Ad esempio, Ruby non ha tipi primitivi. Tutto, letteralmente tutto, è un oggetto. Hanno un tempo molto semplice per assicurarsi che ogni oggetto abbia un determinato metodo.

Ruby cerca la verità su tutti i tipi di oggetti che puoi lanciare. È interessante notare che non ha ancora il tipo boolean (perché non ha primitive), e non ha nemmeno Boolean di classe. Se chiedi quale classe ha il valore true (facilmente disponibile con true.class ), ottieni TrueClass . Quella classe ha effettivamente metodi, vale a dire i 4 operatori per i booleani ( | & ^ == ). Qui, if considera il suo valore falsey se e solo se è false o nil (il null di Ruby). Tutto il resto è vero. Quindi, 0 o "" sono entrambi veri.

Sarebbe stato banale per loro creare un metodo Object#truthy? che potesse essere implementato per qualsiasi classe e restituire una verità individuale. Ad esempio, String#truthy? avrebbe potuto essere implementato per essere vero per stringhe non vuote o quant'altro. Non lo fecero, anche se Ruby è l'antitesi di Java nella maggior parte dei dipartimenti (digitazione dinamica con mixin, classi di riapertura e tutto il resto).

Il che potrebbe sorprendere un programmatore Perl abituato a $value <> 0 || length($value)>0 || defined($value) a essere sincero. E così via.

Inserisci SQL con la sua convenzione che null all'interno di qualsiasi espressione lo rende automaticamente falso, a prescindere da cosa. Quindi (null==null) = false . In Ruby, (nil==nil) = true . Momenti felici.

    
risposta data 13.05.2017 - 01:29
fonte
1

Oltre alle altre belle risposte, mi piacerebbe parlare della coerenza tra le lingue.

Quando pensiamo a un'istruzione if matematicamente pura, capiamo che la condizione può essere vera o falsa, nessun altro valore. Tutti i principali linguaggi di programmazione rispettano questo ideale matematico; se dai un valore booleano vero / falso a un'istruzione if, allora puoi aspettarti di vedere un comportamento coerente e intuitivo tutto il tempo.

Fin qui tutto bene. Questo è ciò che Java implementa e solo ciò che Java implementa.

Altre lingue cercano di offrire comodità per valori non booleani. Ad esempio:

  • Supponi che n sia un numero intero. Definisci ora if (n) come abbreviazione di if (n != 0) .
  • Supponi che x sia un numero in virgola mobile. Definisci ora if (x) come abbreviazione di if (x != 0 && !isNaN(x)) .
  • Supponi che p sia un tipo di puntatore. Definisci ora if (p) come abbreviazione di if (p != null) .
  • Supponi che s sia un tipo di stringa. Definisci ora if (s) come if (s != null && s != "") .
  • Supponi che a sia un tipo di matrice. Definisci ora if (a) come if (a != null && a.length > 0) .

Questa idea di fornire stenografi in caso di prove sembra buona in superficie ... fino a quando non si incontrano differenze nei disegni e nelle opinioni:

  • if (0) è trattato come falso in C, Python, JavaScript; ma trattato come vero in Ruby.
  • if ([]) è trattato come falso in Python ma vero in JavaScript.

Ogni lingua ha le sue buone ragioni per trattare le espressioni in un modo o nell'altro. (Ad esempio, gli unici valori di falsy in Ruby sono false e nil , quindi 0 è truey.)

Java ha adottato un design esplicito per forzare l'utente a fornire un valore booleano a un'istruzione if. Se traducete frettolosamente il codice da C / Ruby / Python a Java, non potete lasciare invariati gli eventuali if test lassisti; è necessario scrivere la condizione esplicitamente in Java. Prendersi un momento per mettere in pausa e pensare può salvarti da errori sbagliati.

    
risposta data 13.05.2017 - 19:17
fonte
0

Bene, ogni tipo scalare in C, puntatore, booleano (dal C99), numero (in virgola mobile o meno) ed enumerazione (a causa della mappatura diretta ai numeri) ha un valore di falsa "naturale", quindi è abbastanza buono per qualsiasi espressione condizionale.

Java ha anche quelli (anche se i puntatori Java sono chiamati riferimenti e sono strongmente limitati), ma ha introdotto il boxing automatico in Java 5.0 che confonde le acque in modo inaccettabile. Inoltre, i programmatori Java esortano il valore intrinseco di digitare di più.

L'unico errore che ha generato il dibattito se limitare le espressioni condizionali al tipo booleano è l'errore di scrittura di un compito in cui era previsto un confronto, che è non indirizzato limitando il tipo di espressioni condizionali affatto , ma non consentendo l'uso di un'espressione di assegnazione nuda per il suo valore.

Qualsiasi compilatore C o C ++ moderno lo gestisce facilmente, fornendo un avvertimento o un errore per tali costrutti discutibili se richiesto.
Per quei casi in cui è esattamente ciò che è stato inteso, l'aggiunta di parentesi aiuta.

Per riassumere, restringere a boolean (e all'equivalente in box in Java) sembra un tentativo fallito di creare una classe di errori di battitura causata scegliendo = per gli errori di compilazione delle assegnazioni.

    
risposta data 14.05.2017 - 10:57
fonte
-1

My question is not related to bad coding style. I know its bad, but i'm intersted in why C does allow it and java not.

Due degli obiettivi di progettazione di Java erano:

  1. Consenti allo sviluppatore di concentrarsi sui problemi di business. Rendi le cose più semplici e meno soggette a errori, ad es. garbage collection in modo che gli sviluppatori non debbano concentrarsi sulle perdite di memoria.

  2. Portatile tra piattaforme, ad es. eseguire su qualsiasi computer con qualsiasi CPU.

L'uso del compito come espressione era noto per causare un sacco di errori dovuti a errori di battitura, quindi sotto l'obiettivo # 1 sopra, non è consentito il modo in cui stai tentando di usarlo.

Inoltre, deducendo che qualsiasi valore diverso da zero = vero e qualsiasi valore zero = falso non è necessariamente portatile (sì, che ci crediate o no, alcuni sistemi considerano 0 come vero e 1 come falso ), quindi sotto l'obiettivo # 2 sopra, non è consentito implicitamente. Puoi comunque trasmettere espressamente, ovviamente.

    
risposta data 11.05.2017 - 22:44
fonte
-1

Ad esempio, quali altri linguaggi fanno: Swift richiede un'espressione di un tipo che supporti il protocollo "BooleanType", il che significa che deve avere un metodo "boolValue". Il tipo "bool" supporta ovviamente questo protocollo ed è possibile creare i propri tipi supportandolo. I tipi interi non supportano questo protocollo.

Nelle versioni precedenti del linguaggio i tipi facoltativi supportavano "BooleanType" in modo da poter scrivere "se x" invece di "if x! = nil". Che ha reso l'uso di un "bool opzionale" molto confuso. Un bool opzionale ha valori nil, false o true e "if b" non verrebbe eseguito se b fosse nil ed eseguito se b fosse vero o falso. Questo non è più permesso.

E sembra esserci un ragazzo che non ama aprire i tuoi orizzonti ...

    
risposta data 13.05.2017 - 18:07
fonte

Leggi altre domande sui tag