Eu tenho esses dois pequenos programas:
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");
}
}
}
que relata a mensagem de erro:
type_system.java:4: error: incompatible types: int cannot be converted to boolean
if (5) {
^
1 error
Meu entendimento
Até agora, entendi esse exemplo como uma demonstração dos diferentes sistemas de tipos. C é digitado de maneira mais fraca e permite a conversão de int para booleano sem erros. O Java é mais fortemente digitado e falha, porque nenhuma conversa implícita é permitida.
Portanto, minha pergunta: onde eu entendi errado as coisas?
O que não estou procurando
Minha pergunta não está relacionada ao estilo de codificação ruim. Eu sei que é ruim, mas estou interessado em saber por que C permite e Java não. Portanto, estou interessado no sistema de tipos da linguagem, especificamente em sua força.
java
c
type-systems
toogley
fonte
fonte
int
valores. Um exemplo mais apropriado seriaif (pointer)
.Respostas:
1. C e Java são linguagens diferentes
O fato de eles se comportarem de maneira diferente não deve ser terrivelmente surpreendente.
2. C não está fazendo nenhuma conversão de
int
parabool
Como pôde isso? C nem sequer tinha um
bool
tipo verdadeiro para converter até 1999 . O C foi criado no início da década de 1970 eif
fazia parte dele antes mesmo do C, quando era apenas uma série de modificações no B 1 .if
não era simplesmente umNOP
em C por quase 30 anos. Ele atuou diretamente em valores numéricos. O palavreado no padrão C ( link em PDF ), mesmo mais de uma década após a introdução debool
s em C, ainda especifica o comportamento deif
(p 148) e?:
(p 100) usando os termos "desigual a 0" e "igual a 0 "em vez dos termos booleanos" verdadeiro "ou" falso "ou algo semelhante.Convenientemente, ...
3. ... os números são exatamente o que as instruções do processador operam.
JZ
eJNZ
são suas instruções básicas de montagem x86 para ramificação condicional. As abreviaturas são " J ump se Z ero" e " J ump se N ot Z ero". Os equivalentes para o PDP-11, onde C se originou, sãoBEQ
(" fazenda B se EQ ual") eBNE
(" fazenda B se N ot E qual").Estas instruções verificam se a operação anterior resultou em zero ou não e pulam (ou não) de acordo.
4. Java tem uma ênfase muito maior na segurança do que C 2
E, com a segurança em mente, eles decidiram que restringir
if
aboolean
s valia o custo (tanto para implementar essa restrição quanto os custos de oportunidade resultantes).1 B nem sequer tem tipos. As linguagens assembly geralmente também não. No entanto, as linguagens B e assembly conseguem lidar bem com as ramificações.
2. Nas palavras de Dennis Ritchie ao descrever as modificações planejadas em B que se tornaram C (grifo meu):
fonte
C 2011 Rascunho Online
Observe que esta cláusula especifica apenas que a expressão de controle deve ter um tipo escalar (
char
/short
/int
/long
/ etc.), Não especificamente um tipo booleano. Uma ramificação é executada se a expressão de controle tiver um valor diferente de zero.Compare isso com
Especificação da linguagem Java SE 8
Java, OTOH, exige especificamente que a expressão de controle em um
if
instrução tenha um tipo booleano.Portanto, não se trata de digitação fraca versus forte, é sobre o que cada respectiva definição de idioma especifica como uma expressão de controle válida.
Editar
Quanto ao motivo pelo qual as línguas são diferentes nesse aspecto específico, vários pontos:
C foi derivado de B, que era uma linguagem "sem tipo" - basicamente, tudo era uma palavra de 32 a 36 bits (dependendo do hardware), e todas as operações aritméticas eram operações inteiras. O sistema do tipo C foi parafusado um pouco de cada vez, de modo que ...
C não tinha um tipo booleano distinto até a versão de 1999 da linguagem. C simplesmente seguiu a convenção B de usar zero para representar
false
e diferente de zero para representartrue
.O Java pós-C por um bom par de décadas e foi projetado especificamente para resolver algumas das deficiências de C e C ++. Sem dúvida, restringir a restrição sobre o que pode ser uma expressão de controle em uma
if
declaração fazia parte disso.Não há nenhuma razão para esperar quaisquer duas linguagens de programação para fazer as coisas da mesma maneira. Mesmo linguagens tão estreitamente relacionadas como C e C ++ divergem de algumas maneiras interessantes, de modo que você pode ter programas C legais que não são programas C ++ legais ou são programas C ++ legais, mas com semântica diferente etc.
fonte
int
paraboolean
enquanto o Java não. A resposta é que não existe essa conversão em C. Os idiomas são diferentes porque são idiomas diferentes.Muitas das respostas parecem estar direcionadas à expressão de atribuição incorporada que está dentro da expressão condicional. (Embora esse seja um tipo conhecido de armadilha potencial, não é a fonte da mensagem de erro do Java nesse caso.)
Possivelmente, isso ocorre porque o OP não publicou a mensagem de erro real e o
^
sinal de intercalação aponta diretamente para o=
do operador de atribuição.No entanto, o compilador está apontando para o
=
porque é o operador que produz o valor final (e, portanto, o tipo final) da expressão que o condicional vê.Está reclamando sobre o teste de um valor não booleano, com o seguinte tipo de erro:
Testar números inteiros, embora às vezes conveniente, é considerado uma armadilha potencial que os designers de Java optam por evitar. Afinal, Java tem um verdadeiro tipo de dados boolean , que C faz não (tem nenhum tipo boolean) .
Isso também se aplica aos ponteiros de teste de C para via nula / não nula via
if (p) ...
eif (!p) ...
, que Java da mesma forma não permite, em vez disso, requer que um operador de comparação explícito obtenha o booleano necessário.fonte
if
afirmação.bool b = ...; int v = 5 + b;
. Isso é diferente dos idiomas com um tipo booleano completo que não pode ser usado em aritmética.int v = 5; float f = 2.0 + v;
é legal em C._Bool
é um dos tipos de número inteiro não assinado padrão, conforme definido pelo Padrão (consulte 6.2.5 / 6).Há duas partes na sua pergunta:
Por que o Java não se converte
int
emboolean
?Isso se resume ao Java, que deve ser o mais explícito possível. É muito estático, muito "na sua cara" com seu sistema de tipos. Coisas que são convertidas automaticamente em outras linguagens não são assim, em Java. Você tem que escrever
int a=(int)0.5
também. A conversãofloat
paraint
perderia informações; o mesmo que converterint
paraboolean
e seria, portanto, propenso a erros. Além disso, eles teriam que especificar muitas combinações. Claro, essas coisas parecem óbvias, mas pretendiam errar com cautela.Ah, e comparado a outras linguagens, o Java era imensamente exato em sua especificação, pois o bytecode não era apenas um detalhe interno da implementação. Eles teriam que especificar toda e qualquer interação, com precisão. Enorme empresa.
Por que
if
não aceita outros tipos além deboolean
?if
poderia perfeitamente ser definido de forma a permitir outros tipos que nãoboolean
. Poderia ter uma definição que diz o seguinte é equivalente:true
int != 0
String
com.length>0
null
(e não sejaBoolean
com valorfalse
).null
e cujo métodoObject.check_if
(inventado por mim apenas para esta ocasião) retornetrue
.Eles não fizeram; não havia necessidade real e eles queriam que fosse o mais robusto, estático, transparente, fácil de ler etc. Sem recursos implícitos. Além disso, a implementação seria bem complexa, tenho certeza, tendo que testar cada valor para todos os casos possíveis, portanto o desempenho também pode ter desempenhado um pequeno fator (o Java costumava ser sloooow nos computadores daquele dia; lembre-se de que não havia compiladores JIT com os primeiros lançamentos, pelo menos não nos computadores que usei na época).
Razão mais profunda
Uma razão mais profunda pode muito bem ser o fato de o Java ter seus tipos primitivos, portanto, seu sistema de tipos é dividido entre objetos e primitivos. Talvez, se os tivessem evitado, as coisas tivessem acontecido de outra maneira. Com as regras fornecidas na seção anterior, eles teriam que definir a veracidade de cada primitivo explicitamente (uma vez que os primitivos não compartilham uma superclasse e não há um conjunto bem definido
null
para os primitivos). Isso se tornaria um pesadelo, rapidamente.Outlook
Bem, e no final, talvez seja apenas uma preferência dos designers de linguagem. Cada idioma parece girar seu próprio caminho até lá ...
Por exemplo, Ruby não tem tipos primitivos. Tudo, literalmente tudo, é um objeto. Eles têm um tempo muito fácil para garantir que cada objeto tenha um determinado método.
Ruby procura veracidade em todos os tipos de objetos que você pode jogar nele. Curiosamente, ele ainda não tem
boolean
tipo (porque não tem primitivos) e também não temBoolean
classe. Se você perguntar com que classe o valortrue
tem (disponível com facilidadetrue.class
), você recebeTrueClass
. Essa classe realmente tem métodos, os 4 operadores para booleanos (| & ^ ==
). Aqui,if
considera seu valor falsey se e somente se é umfalse
ounil
(onull
de Ruby). Tudo o resto é verdade. Então,0
ou""
são verdadeiras.Teria sido trivial para eles criar um método
Object#truthy?
que pudesse ser implementado para qualquer classe e retornar uma veracidade individual. Por exemplo,String#truthy?
poderia ter sido implementado para ser verdadeiro para cadeias não vazias, ou outros enfeites. Eles não o fizeram, embora Ruby seja a antítese do Java na maioria dos departamentos (digitação dinâmica com mixin, reabertura de classes e tudo mais).O que pode ser surpreendente para um programador Perl que está acostumado a
$value <> 0 || length($value)>0 || defined($value)
ser verdadeiro. E assim por diante.Digite SQL com a convenção de que
null
dentro de qualquer expressão a torna automaticamente falsa, não importa o quê. Então(null==null) = false
. Em Ruby(nil==nil) = true
,. Tempos felizes.fonte
((int)3) * ((float)2.5)
é bastante bem definido em Java (é7.5f
).int
parafloat
também perde informações em geral. O Java também proíbe essa conversão implícita?Além das outras boas respostas, gostaria de falar sobre a consistência entre os idiomas.
Quando pensamos em uma declaração if matematicamente pura, entendemos que a condição pode ser verdadeira ou falsa, sem outro valor. Toda linguagem de programação importante respeita esse ideal matemático; se você atribuir um valor booleano verdadeiro / falso a uma instrução if, poderá esperar ver um comportamento consistente e intuitivo o tempo todo.
Por enquanto, tudo bem. É isso que o Java implementa e apenas o que o Java implementa.
Outros idiomas tentam trazer conveniências para valores não-booleanos. Por exemplo:
n
seja um número inteiro. Agora definaif (n)
como abreviação paraif (n != 0)
.x
seja um número de ponto flutuante. Agora definaif (x)
como abreviação paraif (x != 0 && !isNaN(x))
.p
seja um tipo de ponteiro. Agora definaif (p)
como abreviação paraif (p != null)
.s
é um tipo de sequência. Agora definaif (s)
para serif (s != null && s != "")
.a
é um tipo de matriz. Agora definaif (a)
para serif (a != null && a.length > 0)
.Essa ideia de fornecer testes se de taquigrafia parece boa na superfície ... até você encontrar diferenças de design e opinião:
if (0)
é tratado como falso em C, Python, JavaScript; mas tratado como verdadeiro em Ruby.if ([])
é tratado como falso em Python, mas verdadeiro em JavaScript.Cada idioma tem suas próprias boas razões para tratar expressões de uma maneira ou de outra. (Por exemplo, os únicos valores falsos em Ruby são
false
enil
, portanto,0
são verdadeiros.)Java adotou um design explícito para forçar você a fornecer um valor booleano a uma instrução if. Se você traduziu rapidamente o código de C / Ruby / Python para Java, não poderá deixar nenhum teste if fraco; você precisa escrever a condição explicitamente em Java. Tomar um momento para fazer uma pausa e pensar pode salvá-lo de erros desleixados.
fonte
x != 0
é o mesmo quex != 0 && !isNaN(x)
? Além disso, normalmente és != null
para ponteiros, mas algo mais para não-ponteiros.Bem, todo tipo escalar em C, ponteiro, booleano (desde C99), número (ponto flutuante ou não) e enumeração (devido ao mapeamento direto para números) tem um valor de falsey "natural", portanto é bom o suficiente para qualquer expressão condicional .
O Java também os possui (mesmo que os ponteiros do Java sejam chamados de referências e sejam fortemente restritos), mas introduziu o boxe automático no Java 5.0, que atrapalha as águas inaceitavelmente. Além disso, os programadores de Java exortam o valor intrínseco de digitar mais.
O único erro que gerou o debate se restringindo expressões condicionais de tipo boolean é o erro de digitação de escrever um trabalho onde uma comparação foi destinado, que é não dirigida, restringindo o tipo de expressões condicionais em tudo , mas, não permitindo o uso de uma atribuição-expressão nua pelo seu valor.
Qualquer compilador C ou C ++ moderno lida com isso com facilidade, emitindo um aviso ou erro para essas construções questionáveis, se solicitado.
Nos casos em que é exatamente o que se pretendia, adicionar parênteses ajuda.
Resumindo, restringir a booleano (e o equivalente em caixa em Java) parece uma tentativa fracassada de fazer uma classe de erros de digitação causados pela escolha
=
de erros de compilação de atribuição.fonte
==
com operandos booleanos, dos quais o primeiro é uma variável (que então poderia ser digitada=
) dentro de umif
acontece com muito menos frequência do que com outros tipos, eu diria, por isso é apenas uma tentativa semi-falhada.""
,(Object) ""
,0.0
,-0.0
,NaN
, matrizes vazias, eBoolean.FALSE
? Especialmente o último é engraçado, pois é um ponteiro não nulo (verdadeiro) que desmarca para falso. Eu também odeio escreverif (o != null)
, mas poupar alguns caracteres todos os dias e me permitir passar meio dia depurando minha expressão "inteligente" não é um bom negócio. Dito isso, eu adoraria ver um caminho intermediário para Java: regras mais generosas, mas sem ambiguidade.Dois dos objetivos de design do Java eram:
Permita que o desenvolvedor se concentre no problema de negócios. Torne as coisas mais simples e menos propensas a erros, por exemplo, coleta de lixo para que os desenvolvedores não precisem se concentrar em vazamentos de memória.
Portátil entre plataformas, por exemplo, executado em qualquer computador com qualquer CPU.
O uso da atribuição como expressão era conhecido por causar muitos erros devido a erros de digitação; portanto, no objetivo nº 1 acima, não é permitido o modo como você está tentando usá-lo.
Além disso, inferindo que qualquer valor diferente de zero = true e qualquer valor zero = false não é necessariamente portátil (sim, acredite ou não, alguns sistemas tratam 0 como verdadeiro e 1 como falso ), portanto, no objetivo # 2 acima, não é permitido implicitamente . Você ainda pode transmitir explicitamente, é claro.
fonte
Como exemplo, o que outras linguagens fazem: Swift requer uma expressão de um tipo que suporte o protocolo "BooleanType", o que significa que deve ter um método "boolValue". O tipo "bool" obviamente suporta esse protocolo e você pode criar seus próprios tipos de suporte. Tipos inteiros não suportam este protocolo.
Nas versões anteriores do idioma, os tipos opcionais eram compatíveis com "BooleanType", para que você pudesse escrever "se x" em vez de "se x! = Nil". O que tornava muito confuso o uso de um "bool opcional". Um bool opcional tem valores nulos, falso ou verdadeiro e "se b" não seria executado se b fosse nulo e executado se b fosse verdadeiro ou falso. Isso não é mais permitido.
E parece haver um cara que realmente não gosta de abrir seus horizontes ...
fonte