J'ai ces deux petits programmes:
#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");
}
}
}
qui signale le message d'erreur:
type_system.Java:4: error: incompatible types: int cannot be converted to boolean
if (5) {
^
1 error
Jusqu'à présent, j'ai compris cet exemple comme une démonstration des différents systèmes de types. C est plus faiblement typé et permet la conversion de int en booléen sans erreur. Java est plus fortement typé et échoue, car aucune conversation implicite n'est autorisée.
Par conséquent, ma question: où ai-je mal compris les choses?
Ma question n'est pas liée au mauvais style de codage. Je sais que c'est mauvais, mais je suis intéressé par pourquoi C le permet et Java pas. Par conséquent, je suis intéressé par le système de type du langage, en particulier sa force.
Le fait qu'ils se comportent différemment ne devrait pas être très surprenant.
int
vers bool
Comment est-ce possible? C n'avait même pas de vrai type bool
à convertir en jusqu'en 1999 . C a été créé au début des années 1970 et if
en faisait partie avant même C, à l'époque où il ne s'agissait que d'une série de modifications de B 1.
if
n'était pas simplement un NOP
en C depuis près de 30 ans. Il a agi directement sur les valeurs numériques. Le verbiage dans la norme C ( lien PDF ), même plus d'une décennie après l'introduction de bool
s dans C, spécifie toujours le comportement de if
(p 148) et ?:
(p 100) en utilisant les termes "différent de 0" et "égal à 0" plutôt que les termes booléens "vrai" ou "faux" ou quelque chose de similaire.
Idéalement, ...
JZ
et JNZ
sont vos instructions d'assemblage x86 de base pour le branchement conditionnel. Les abréviations sont " [~ # ~] j [~ # ~] ump if [~ # ~] z [~ # ~] ero "et" [~ # ~] j [~ # ~] ump if [~ # ~] n [~ # ~] ot [~ # ~] z [~ # ~] ero ". Les équivalents pour le PDP-11, d'où provient C, sont BEQ
(" [~ # ~] b [~ # ~] ranch if [~ # ~] eq [~ # ~] ual ") et BNE
(" [~ # ~] b [~ # ~] ranch if [~ # ~] n [~ # ~] ot [~ # ~] e [~ # ~] qual ").
Ces instructions vérifient si l'opération précédente a abouti à un zéro ou non et sautent (ou non) en conséquence.
Et, dans un souci de sécurité, ils ont décidé que la restriction de if
à boolean
s en valait le coût (à la fois la mise en œuvre d'une telle restriction et les coûts d'opportunité qui en résultent).
1. B n'a même pas de types du tout. Les langages d'assemblage ne le sont généralement pas non plus. Pourtant, les langages B et d'assemblage parviennent à gérer correctement les branchements.
2. Dans les mots de Dennis Ritchie lors de la description des modifications prévues de B qui sont devenues C (c'est moi qui souligne):
... il semblait qu'un schéma de typage était nécessaire pour faire face aux caractères et à l'adressage d'octets, et pour préparer le matériel à virgule flottante à venir. D'autres problèmes, en particulier la sécurité des types et la vérification des interfaces, ne semblaient pas aussi importants alors comme ils sont devenus plus tard.
6.8.4.1 L'instructionif
Contraintes
1 L'expression de contrôle d'une instructionif
doit être de type scalaire.
Sémantique
2 Dans les deux formes, la première sous-déclaration est exécutée si l'expression est différente de 0. Dans la formeelse
, la deuxième sous-déclaration est exécutée si l'expression se compare à 0. Si la première sous-déclaration est atteint via une étiquette, la deuxième sous-instruction n'est pas exécutée.
3 Unelse
est associé au précédent lexicalement le plus proche si cela est autorisé par la syntaxe.
Notez que cette clause spécifie uniquement que l'expression de contrôle doit avoir un type scalaire (char
/short
/int
/long
/etc.), Pas spécifiquement un type booléen. Une branche est exécutée si l'expression de contrôle a une valeur non nulle.
Comparez cela avec
Spécification du langage Java SE 8
14.9 L'instructionif
L'instructionif
permet l'exécution conditionnelle d'une instruction ou le choix conditionnel de deux instructions, en exécutant l'une ou l'autre mais pas les deux.IfThenStatement: If ( Expression) Statement IfThenElseStatement: If ( Expression) StatementNoShortIf else Statement IfThenElseStatementNoShortIf: If ( Expression) StatementNoShortIf else StatementNoShortIfLe Expression doit avoir le typeboolean
ouBoolean
, sinon une erreur de compilation se produit.
Java, OTOH, requiert spécifiquement que l'expression de contrôle dans une instruction if
ait un type booléen.
Il ne s'agit donc pas d'un typage faible ou fort, mais de ce que chaque définition de langage respective spécifie comme expression de contrôle valide.
Modifier
Quant à pourquoi les langues sont différentes à cet égard, plusieurs points:
C était dérivé de B, qui était un langage "sans type" - en gros, tout était un mot de 32 à 36 bits (selon le matériel), et toutes les opérations arithmétiques étaient des opérations entières. Le système de type C a été boulonné un peu à la fois, de sorte que ...
C n'avait pas de type booléen distinct jusqu'à la version 1999 du langage. C a simplement suivi la convention B d'utiliser zéro pour représenter false
et non nul pour représenter true
.
Java post-date C de quelques bonnes décennies et a été spécialement conçu pour remédier à certaines des lacunes de C et C++. Il ne fait aucun doute que le resserrement de la restriction sur ce qui peut être une expression de contrôle dans une instruction if
en faisait partie.
Il n'y a aucune raison de s'attendre à any deux langages de programmation à faire les choses de la même manière. Même les langages aussi étroitement liés que C et C++ divergent de certaines manières intéressantes, de sorte que vous pouvez avoir des programmes C légaux qui ne sont pas des programmes C++ légaux, ou qui sont des programmes C++ légaux mais avec une sémantique différente, etc.
De nombreuses réponses semblent viser l'expression d'affectation intégrée qui se trouve dans l'expression conditionnelle. (Bien que ce soit un piège potentiel connu, ce n'est pas la source du message d'erreur Java dans ce cas.)
Cela est peut-être dû au fait que l'OP n'a pas publié le message d'erreur réel et que le signe d'insertion ^
Pointe directement vers le =
De l'opérateur d'affectation.
Cependant, le compilateur pointe vers le =
Car c'est l'opérateur qui produit la valeur finale (et donc le type final) de l'expression que le conditionnel voit.
Il se plaint de tester une valeur non booléenne, avec le type d'erreur suivant:
erreur: types incompatibles: int ne peut pas être converti en booléen
Tester des entiers, bien que parfois pratique, est considéré comme un piège potentiel que les concepteurs Java choisissent d'éviter. Après tout, Java a un true boolean type de données, ce que C fait pas (il n'a pas de type booléen) .
Cela s'applique également aux pointeurs de test de C pour null/non-null via if (p) ...
et if (!p) ...
, ce qui Java de même ne permet pas à la place d'exiger une comparaison explicite pour obtenir le booléen requis.
types incompatibles: int ne peut pas être converti en booléen
Je suis intéressé par la raison pour laquelle C le permet et Java non. Par conséquent, je suis intéressé par le système de type du langage, en particulier sa force.
Votre question comporte deux parties:
int
en boolean
?Cela revient à Java étant destiné à être aussi explicite que possible. Il est très statique, très "en face" avec son système de type. Les choses qui sont automatiquement transtypées dans d'autres langages sont ce n'est pas le cas, en Java. Vous devez également écrire int a=(int)0.5
. La conversion de float
en int
entraînerait la perte d'informations, tout comme la conversion de int
en boolean
et seraient donc sujettes aux erreurs. De plus, ils auraient dû spécifier beaucoup de combinaisons. Bien sûr, ces choses semblent évidentes, mais elles avaient l'intention de faire preuve de prudence.
Oh, et par rapport à d'autres langages, Java était énormément exact dans sa spécification, car le bytecode n'était pas seulement un détail d'implémentation interne. Ils devaient spécifier tout et toutes les interactions, précisément. Énorme entreprise.
if
n'accepte-t-il pas d'autres types que boolean
?if
pourrait parfaitement être défini de manière à autoriser d'autres types que boolean
. Il pourrait avoir une définition qui dit que les éléments suivants sont équivalents:
true
int != 0
String
avec .length>0
null
(et non un Boolean
avec la valeur false
).null
et dont la méthode Object.check_if
(Inventée par moi juste pour cette occasion) renvoie true
.Ils ne l'ont pas fait; il n'y en avait pas vraiment besoin, et ils voulaient que ce soit aussi robuste, statique, transparent, facile à lire, etc. que possible. Pas de fonctionnalités implicites. De plus, l'implémentation serait assez complexe, j'en suis sûr, devant tester chaque valeur pour tous les cas possibles, donc les performances ont peut-être également joué un petit facteur (Java était autrefois lent sur les ordinateurs de ce jour; rappelez-vous là était pas de compilateurs JIT avec les premières versions, du moins pas sur les ordinateurs que j'utilisais alors).
Une raison plus profonde pourrait bien être le fait que Java a ses types primitifs, d'où son système de types est partagé entre les objets et les primitives. Peut-être que s'ils avaient évité ces éléments, les choses auraient tourné autrement) Avec les règles données dans la section précédente, ils devraient définir explicitement la véracité de chaque primitive explicitement (puisque les primitives ne partagent pas une super classe, et il n'y a pas de null
pour les primitifs). Cela se transformerait rapidement en cauchemar.
Eh bien, et à la fin, c'est peut-être juste une préférence des concepteurs de langage. Chaque langue semble y faire son chemin ...
Par exemple, Ruby n'a pas de types primitifs. Tout, littéralement tout, est un objet. Il est très facile de s'assurer que chaque objet a une certaine méthode.
Ruby recherche la véracité de tous les types d'objets que vous pouvez lui lancer. Chose intéressante, il n'a toujours pas de type boolean
(car il n'a pas de primitives), et il n'a pas non plus de classe Boolean
. Si vous demandez quelle classe a la valeur true
(disponible avec true.class
), Vous obtenez TrueClass
. Cette classe a en fait des méthodes, à savoir les 4 opérateurs pour les booléens (| & ^ ==
). Ici, if
considère sa valeur falsey si et seulement si c'est false
ou nil
(le null
de Ruby). Tout le reste est vrai. Ainsi, 0
Ou ""
Sont tous les deux vrais.
Il aurait été trivial pour eux de créer une méthode Object#truthy?
Qui pourrait être implémentée pour n'importe quelle classe et retourner une véracité individuelle. Par exemple, String#truthy?
Aurait pu être implémenté pour être vrai pour les chaînes non vides, etc. Ils ne l'ont pas fait, même si Ruby est l'antithèse de Java dans la plupart des départements (typage dynamique de canard avec mixin, classes de réouverture et tout ça) .
Ce qui pourrait surprendre un programmeur Perl habitué à $value <> 0 || length($value)>0 || defined($value)
étant la vérité. Etc.
Entrez SQL avec sa convention que null
à l'intérieur d'une expression la rend automatiquement fausse, quoi qu'il arrive. Donc (null==null) = false
. En Ruby, (nil==nil) = true
. Moments heureux.
En plus des autres bonnes réponses, je voudrais parler de la cohérence entre les langues.
Lorsque nous pensons à une instruction if mathématiquement pure, nous comprenons que la condition peut être vraie ou fausse, aucune autre valeur. Chaque langage de programmation majeur respecte cet idéal mathématique; si vous donnez une valeur booléenne vrai/faux à une instruction if, alors vous pouvez vous attendre à voir un comportement cohérent et intuitif tout le temps.
Jusqu'ici tout va bien. C'est ce que Java implémente, et seulement ce que Java implémente.
D'autres langues tentent d'apporter des commodités pour les valeurs non booléennes. Par exemple:
n
est un entier. Définissez maintenant if (n)
comme raccourci pour if (n != 0)
.x
est un nombre à virgule flottante. Définissez maintenant if (x)
comme raccourci pour if (x != 0 && !isNaN(x))
.p
est un type de pointeur. Définissez maintenant if (p)
comme raccourci pour if (p != null)
.s
est un type de chaîne. Définissez maintenant if (s)
comme if (s != null && s != "")
.a
est un type de tableau. Définissez maintenant if (a)
comme if (a != null && a.length > 0)
.Cette idée de fournir des tests if raccourcis semble bonne en surface ... jusqu'à ce que vous rencontriez des différences de conception et d'opinions:
if (0)
est traité comme faux en C, Python, JavaScript; mais traité comme vrai en Ruby.if ([])
est traité comme faux dans Python mais vrai dans JavaScript.Chaque langue a ses propres bonnes raisons de traiter les expressions d'une manière ou d'une autre. (Par exemple, les seules valeurs de falsification dans Ruby sont false
et nil
, donc 0
est véridique.)
Java a adopté une conception explicite pour vous forcer à fournir une valeur booléenne à une instruction if. Si vous avez traduit à la hâte du code de C/Ruby/Python vers Java, vous ne pouvez pas laisser les if-tests laxistes inchangés; vous devez écrire la condition explicitement en Java. Prendre un moment pour faire une pause et réfléchir peut vous éviter des erreurs bâclées.
Eh bien, chaque type scalaire en C, pointeur, booléen (depuis C99), nombre (virgule flottante ou non) et énumération (en raison de la correspondance directe avec les nombres) a une valeur de falsey "naturelle", donc c'est assez bon pour toute expression conditionnelle .
Java en a aussi (même si Java sont appelés références et sont fortement restreints), mais il a introduit la boxe automatique dans Java 5.0 qui embrouille les eaux de manière inacceptable) De plus, les programmeurs Java exhortent la valeur intrinsèque de taper davantage.
La seule erreur qui a déclenché le débat sur la restriction des expressions conditionnelles au type booléen est la faute de frappe d'écrire une affectation où une comparaison était prévue, ce qui est pas résolu en restreignant le type d'expressions conditionnelles du tout, mais en interdisant l'utilisation d'une expression d'affectation nue pour sa valeur.
Tout compilateur C ou C++ moderne gère cela facilement, donnant un avertissement ou une erreur pour ces constructions douteuses si demandé.
Pour les cas où c'est exactement ce qui était prévu, l'ajout de parenthèses est utile.
Pour résumer, restreindre à booléen (et l'équivalent encadré en Java) ressemble à une tentative infructueuse de création d'une classe de fautes de frappe provoquée par le choix de =
pour les erreurs de compilation d'affectation.