Por que exatamente o Java não permite condicionais numéricos como se (5) {…} se C permite?

33

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.

toogley
fonte
22
@toogley: O que há no sistema de tipos em Java que você deseja conhecer especificamente? O sistema de tipos não permite isso porque a especificação da linguagem o proíbe. As razões pelas quais o C permite e o Java não têm tudo a ver com o que é considerado aceitável nas duas linguagens. O comportamento resultante dos sistemas de tipos é o efeito disso, não a causa.
Robert Harvey
8
@toogley: por que você acha que não entendeu algo? Lendo o texto novamente, não entendo qual é a sua pergunta.
Doc Brown
26
Na verdade, este não é um exemplo de digitação fraca em C. No passado (C89), C nem sequer tinha um tipo booleano; portanto, todas as operações "booleanas" realmente operavam com intvalores. Um exemplo mais apropriado seria if (pointer).
Rufflewind
5
Eu diminuí a votação porque não parece que muita pesquisa foi feita tentando entender isso.
Jpmc26
11
Parece que a pergunta original é um pouco diferente da versão editada (é por isso que alguns dos comentários e respostas parecem estar respondendo a uma pergunta diferente). Em sua forma atual, não parece haver uma pergunta aqui. Que mal-entendido? Java está se comportando exatamente como você pensa que deveria.
Jamesdlin #

Respostas:

134

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 intparabool

Como pôde isso? C nem sequer tinha um booltipo verdadeiro para converter até 1999 . O C foi criado no início da década de 1970 e iffazia parte dele antes mesmo do C, quando era apenas uma série de modificações no B 1 .

ifnão era simplesmente um NOPem 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 de bools em C, ainda especifica o comportamento de if(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.

JZe JNZsã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ão BEQ(" fazenda B se EQ ual") e BNE(" 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 ifaboolean 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):

... parecia que era necessário um esquema de digitação para lidar com caracteres e endereçamento de bytes e para se preparar para o hardware de ponto flutuante que estava por vir. Outros problemas, principalmente a segurança de tipo e a verificação de interface, não pareciam tão importantes quanto se tornaram mais tarde.

8bittree
fonte
13
Ponto interessante sobre expressões booleanas no mapeamento C diretamente para as instruções do processador. Eu não tinha pensado nisso antes.
Robert Harvey
17
Acho que o ponto 4 é enganador, pois parece implicar que C tenha um pouco de foco na segurança ... C permitirá que você faça basicamente o que quiser: C pressupõe que você sabe o que está fazendo, geralmente com resultados espetacularmente desastrosos.
TemporalWolf
13
@TemporalWolf Você afirma que como se C deixasse você fazer o que você queria era ruim. A diferença na minha opinião é que C foi escrito para programadores e é esperada uma competência básica. Java é escrito para macacos de código e, se você pode digitar, pode programar nele. Muitas vezes espetacularmente ruim, mas quem se importa, certo? Nunca esquecerei quando um professor da aula introdutória de Java que fui forçado a fazer parte do meu menor de CS, apontou que o uso da recursão para calcular os números de Fibonnaci pode ser útil porque era "fácil de escrever". É por isso que temos o software que temos.
DRF
25
@DRF Um dos meus professores de uma turma de redes C disse: "C é como uma caixa de granadas de mão com todos os pinos arrancados". Você tem muito poder, mas é garantido que vai explodir na sua cara. Na maioria dos casos, não vale a pena, e você será muito mais produtivo com um idioma de nível superior. Transcodifiquei o Python para o truque de bits C hacky para obter um aumento de 6 ordem de magnitude na velocidade? Sim, sim eu tenho. Mas essa é a exceção, não a regra. Eu posso realizar em um dia em Python o que levaria duas semanas em C ... e eu sou um programador decente em C.
TemporalWolf
11
@DRF Dada a prevalência de explorações de buffer overflow nos principais softwares desenvolvidos por empresas de primeira linha, aparentemente nem mesmo os programadores com competência básica podem confiar em não se perder.
Reinstale Monica
14

C 2011 Rascunho Online

6.8.4.1 if

Restrições da declaração

1 A expressão de controle de uma ifdeclaração deve ter um tipo escalar.

Semântica

2 Em ambas as formas, a primeira subestação é executada se a expressão for comparada a 0. Na elseforma, a segunda subestação é executada se a expressão for comparada a 0. Se a primeira subestação for alcançada através de um rótulo, a segunda subestação será Não executado.

3 Anelse é associado ao lexicamente mais próximo anterior, se for permitido pela sintaxe.

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

14.9 O if declaração

A ifdeclaração permite a execução condicional de uma declaração ou a escolha condicional de duas declarações, executando uma ou a outra, mas não as duas.
    IfThenStatement :
        if ( ExpressãoInstrução )

    IfThenElseStatement :
        if ( Expression ) StatementNoShortIf else declaração

    IfThenElseStatementNoShortIf :
        if ( Expression ) StatementNoShortIf else StatementNoShortIf
A expressão deve ter o tipo booleanouBoolean , ou ocorre um erro em tempo de compilação.

Java, OTOH, exige especificamente que a expressão de controle em umif 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:

  1. 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 ...

  2. 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 falsee diferente de zero para representar true.

  3. 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 ifdeclaração fazia parte disso.

  4. 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.

John Bode
fonte
Triste demais, não consigo marcar duas respostas como "aceitas" ..
toogley
1
Sim, esta é uma boa reformulação da pergunta. Mas a pergunta foi feita por que há essa diferença entre os dois idiomas. Você ainda não abordou isso.
Dawood diz que restabelece Monica
@DawoodibnKareem: A questão era por que C permitiu a conversão de intpara booleanenquanto 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.
John Bode
Sim, "não se trata de digitação fraca versus forte". Basta olhar para o boxe automático e o unboxing automático. Se C os tivesse, também não poderia permitir nenhum tipo escalar.
Deduplicator
5

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:

erro: tipos incompatíveis: int não pode ser convertido em booleano

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) ...e if (!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.

Erik Eidt
fonte
1
C faz ter um tipo booleano. Mas isso é mais novo que a ifafirmação.
MSalters
3
O bool do @MSalters C ainda é um número inteiro coberto, portanto você pode fazer bool b = ...; int v = 5 + b;. Isso é diferente dos idiomas com um tipo booleano completo que não pode ser usado em aritmética.
Jules
O booleano de C é apenas um número inteiro realmente pequeno. "true" e "false" podem ser facilmente definidos com macros ou qualquer outra coisa.
Oskar Skog
4
@ Jules: Você está apenas apontando que C tem uma fraca segurança de tipo. Só porque um tipo booleano se converte em um valor inteiro não significa que é um tipo inteiro. Por sua lógica, tipos inteiros seria tipos de ponto flutuante desde int v = 5; float f = 2.0 + v;é legal em C.
MSalters
1
@MSalters, na verdade, _Boolé um dos tipos de número inteiro não assinado padrão, conforme definido pelo Padrão (consulte 6.2.5 / 6).
Ruslan
2

tipos incompatíveis: int não pode ser convertido em booleano

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.

Há duas partes na sua pergunta:

Por que o Java não se converte intemboolean ?

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.5também. A conversão floatpara intperderia informações; o mesmo que converter intparaboolean 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 ifnão aceita outros tipos além de boolean?

ifpoderia perfeitamente ser definido de forma a permitir outros tipos que não boolean. Poderia ter uma definição que diz o seguinte é equivalente:

  • true
  • int != 0
  • String com .length>0
  • Qualquer outra referência de objeto que não seja null(e não seja Booleancom valor false).
  • Ou ainda: qualquer outra referência de objeto que não seja nulle cujo método Object.check_if(inventado por mim apenas para esta ocasião) retorne true.

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 nullpara 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 booleantipo (porque não tem primitivos) e também não tem Booleanclasse. Se você perguntar com que classe o valor truetem (disponível com facilidade true.class), você recebe TrueClass. Essa classe realmente tem métodos, os 4 operadores para booleanos ( | & ^ ==). Aqui, ifconsidera seu valor falsey se e somente se é um falseou nil(o nullde Ruby). Tudo o resto é verdade. Então, 0ou ""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 nulldentro 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.

AnoE
fonte
Na verdade, ((int)3) * ((float)2.5)é bastante bem definido em Java (é 7.5f).
Paŭlo Ebermann
Você está certo, @ PaŭloEbermann, eu apaguei esse exemplo.
AnoE
Woah lá ... gostaria de receber comentários dos que recusaram e quem votou na verdade para excluir a resposta.
AnoE
Na verdade, a conversão de intpara floattambém perde informações em geral. O Java também proíbe essa conversão implícita?
Ruslan #
@Ruslan no (mesmo por muito tempo → duplo) - Eu acho que a idéia é que a perda potencial de informações exista apenas nos lugares menos significativos, e só acontece nos casos que não são considerados tão importantes (valores inteiros muito grandes).
Pa Elo Ebermann 14/05
1

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:

  • Suponha que nseja um número inteiro. Agora defina if (n)como abreviação para if (n != 0).
  • Suponha que xseja um número de ponto flutuante. Agora defina if (x)como abreviação para if (x != 0 && !isNaN(x)).
  • Suponha que pseja um tipo de ponteiro. Agora defina if (p)como abreviação para if (p != null).
  • Suponha que sé um tipo de sequência. Agora defina if (s)para serif (s != null && s != "") .
  • Suponha que aé um tipo de matriz. Agora defina if (a)para ser if (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 falsee nil, portanto, 0sã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.

Nayuki
fonte
1
Você sabe que x != 0é o mesmo que x != 0 && !isNaN(x)? Além disso, normalmente é s != nullpara ponteiros, mas algo mais para não-ponteiros.
Deduplicator
@Duplicador em que idioma é o mesmo?
Paŭlo Ebermann 14/05
1
Usando IEEE754, NaN = 0. NaN = qualquer coisa. Então, se (x) seria executado, e se iria executar bem ... (x!)
gnasher729
0

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.

Desduplicador
fonte
O uso ==com operandos booleanos, dos quais o primeiro é uma variável (que então poderia ser digitada =) dentro de um ifacontece com muito menos frequência do que com outros tipos, eu diria, por isso é apenas uma tentativa semi-falhada.
Pa Elo Ebermann 14/05
Ter 0.0 -> false e qualquer valor diferente de 0.0 -> true não me parece "natural".
Gshow007
@ PaŭloEbermann: O resultado parcial com efeitos colaterais infelizes, embora exista uma maneira fácil de atingir a meta sem efeitos colaterais, é uma falha clara no meu livro.
Deduplicator
Você está perdendo os outros motivos. E sobre o valor truthy de "", (Object) "", 0.0, -0.0, NaN, matrizes vazias, e Boolean.FALSE? Especialmente o último é engraçado, pois é um ponteiro não nulo (verdadeiro) que desmarca para falso. Eu também odeio escrever if (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.
Maaartinus 14/05
@maaartinus: Como eu disse, a introdução do auto-unboxing no Java 5.0 significava que se eles atribuíssem um valor de verdade correspondente ao nulo aos ponteiros, eles teriam um grande problema. Mas isso é um problema com o auto- (des-) boxe, e mais nada. Um idioma sem auto- (des-) boxe como C está felizmente livre disso.
Deduplicator
-1

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.

Dois dos objetivos de design do Java eram:

  1. 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.

  2. 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.

John Wu
fonte
4
A menos que tenha sido adicionado no Java 8, não há conversão explícita entre tipos numéricos e booleanos. Para converter um valor numérico em um booleano, você deve usar um operador de comparação; Para converter um valor booleano em um numérico, você normalmente usa o operador ternário.
Peter Taylor
Você poderia defender a proibição do uso de uma atribuição pelo seu valor devido a esses erros de digitação. Porém, considerando a frequência com que você vê == true e == false em Java, obviamente limitar a expressão de controle ao tipo booleano (opcionalmente em caixa) não ajuda muito.
Deduplicator
O Java garantiria trivialmente a portabilidade escrita para "alguns sistemas tratam 0 como verdadeiro e 1 como falso", como é o zero do Java e o falso do Java. Realmente não importa o que o sistema operacional pense sobre isso.
Maaartinus 14/05
-1

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 ...

gnasher729
fonte