Por que Java não permite que definições de funções estejam presentes fora da classe?

22

Ao contrário do C ++, em Java, não podemos ter apenas declarações de função na classe e definições fora da classe. Por que é tão?

É para enfatizar que um único arquivo em Java deve conter apenas uma classe e nada mais?

mosquito
fonte
1
Por definições , você quer dizer propriedades ou assinaturas de métodos, como arquivos de cabeçalho?
Deco
Uma boa resposta satírica à sua pergunta: steve-yegge.blogspot.com/2006/03/…
Brandon

Respostas:

33

A diferença entre C ++ e Java está no que as linguagens consideram sua menor unidade de ligação.

Como C foi projetado para coexistir com a montagem, essa unidade é a sub-rotina chamada por um endereço. (Isso vale para outros idiomas que são compilados em arquivos de objetos nativos, como FORTRAN.) Em outras palavras, um arquivo de objeto que contém uma função foo()terá um símbolo chamado _fooque será resolvido em nada além de um endereço como0xdeadbeefdurante a vinculação. É tudo o que existe. Se a função aceitar argumentos, cabe ao chamador garantir que tudo o que a função espera esteja em ordem antes de chamar seu endereço. Normalmente, isso é feito empilhando coisas na pilha, e o compilador cuida do trabalho pesado e certificando-se de que os protótipos correspondam. Não há verificação disso entre arquivos de objetos; se você aumentar a ligação, a chamada não será executada conforme o planejado e você não receberá um aviso sobre isso. Apesar do perigo, isso possibilita que arquivos de objetos compilados de vários idiomas (incluindo assembly) sejam vinculados em um programa que funcione sem muito barulho.

C ++, apesar de toda a sua fantasia adicional, funciona da mesma maneira. O compilador calçadeira namespaces, classes e métodos / members / etc. nesta convenção, achatando o conteúdo das classes em nomes únicos que são mutilados de maneira a torná-los únicos. Por exemplo, um método como Foo::bar(int baz)pode ser modificado _ZN4Foo4barEiquando colocado em um arquivo de objeto e um endereço como 0xBADCAFEem tempo de execução. Isso depende inteiramente do compilador; portanto, se você tentar vincular dois objetos com esquemas diferentes de manipulação, ficará sem sorte. Por mais feio que isso seja, significa que você pode usar um extern "C"bloco para desativar a manipulação, tornando possível tornar o código C ++ facilmente acessível para outros idiomas. O C ++ herdou a noção de funções flutuantes do C, principalmente porque o formato do objeto nativo permite.

Java é um animal diferente que vive em um mundo isolado com seu próprio formato de arquivo de objeto, o .classarquivo. Os arquivos de classe contêm uma grande quantidade de informações sobre seu conteúdo, permitindo que o ambiente faça coisas com as classes em tempo de execução com as quais o mecanismo de ligação nativo nem sonhava. Essa informação deve começar em algum lugar, e esse ponto de partida é oclass. As informações disponíveis permitem que o código compilado se descreva sem a necessidade de arquivos separados contendo uma descrição no código-fonte, como você faria em C, C ++ ou em outros idiomas. Isso oferece todos os idiomas de benefícios de segurança de tipo que usam a falta de ligação nativa, mesmo em tempo de execução, e é o que permite que você pesque uma classe arbitrária de um arquivo usando reflexão e use-a com uma falha garantida se algo não corresponder .

Se você ainda não descobriu, toda essa segurança traz uma desvantagem: tudo o que você vincula a um programa Java deve ser Java. (Por "link", quero dizer sempre que algo em um arquivo de classe se refere a algo em outro.) Você pode vincular (no sentido nativo) ao código nativo usando JNI , mas há um contrato implícito que diz que se você quebrar o lado nativo , você possui as duas peças.

O Java era grande e não era particularmente rápido no hardware disponível quando foi introduzido pela primeira vez, assim como Ada havia sido na década anterior. Apenas Jim Gosling pode dizer com certeza quais eram suas motivações para tornar a menor unidade de ligação da classe Java, mas eu teria que adivinhar que a complexidade extra que a adição de free floaters acrescentaria ao tempo de execução poderia ter sido um grande negócio.

Blrfl
fonte
link quebrado, obter link do arquivo da web
noɥʇʎԀʎzɐɹƆ 16/10
14

Acredito que a resposta é, de acordo com a Wikipedia, que o Java foi projetado para ser simples e orientado a objetos. As funções são destinadas a operar nas classes em que são definidas. Com essa linha de pensamento, ter funções fora de uma classe não faz sentido. Vou chegar à conclusão de que o Java não permite porque não se encaixa no OOP puro.

Uma rápida pesquisa no Google para mim não resultou muito nas motivações de design da linguagem Java.

mortalapeman
fonte
6
A existência de tipos primitivos não exclui o Java de ser "OOP puro"?
Radu Murzea
5
@SoboLAN: Sim, e adicionar funções tornaria ainda menos "pura OOP".
Giorgio
8
@SoboLAN que depende da sua definição de "OOP puro". Em algum momento tudo é uma combinação de bits na memória do computador depois de tudo ...
jwenting
3
@ jwenting: Bem, o objetivo da abstração nas linguagens de programação é ocultar os bits subjacentes o máximo possível. Quanto mais você pode ver esses bits em seu programa, mais você deve começar a pensar que sua linguagem de programação oferece abstrações com vazamentos (a menos que seja construída perto do metal de propósito). Portanto, sim, tudo se resume à manipulação de bits, mas linguagens diferentes oferecem diferentes níveis de abstração; caso contrário, você não seria capaz de distinguir assembly de uma linguagem de nível superior.
Giorgio
2
Depende da sua definição de "OOP puro": todo valor de dados é um objeto e toda operação de dados é a chamada do método de resultado obtida através da passagem de mensagens. Até onde eu sei, não há mais nada.
Giorgio
11

A verdadeira questão é: qual seria o mérito de continuar fazendo as coisas da maneira C ++ e qual era o objetivo original do arquivo de cabeçalho? A resposta curta é que o estilo do arquivo de cabeçalho permitiu tempos de compilação mais rápidos em projetos grandes nos quais muitas classes poderiam fazer referência ao mesmo tipo. Isso não é necessário em JAVA e .NET devido à natureza dos compiladores.

Veja esta resposta aqui: Os arquivos de cabeçalho são realmente bons?

Jonathan Henson
fonte
1
+1, embora eu realmente tenha ouvido pessoas dizerem que gostam da separação entre interface pública e implementação privada fornecida pelos arquivos de cabeçalho. Essas pessoas estão erradas, é claro. ;)
vaughandroid
6
@ Casaco Em Java, você pode conseguir isso com interfacee class:) Não há necessidade de cabeçalhos!
Andrés F.
1
Eu nunca entenderei a conveniência de ter que procurar mais de 3 arquivos para entender como uma instância simples de classe funciona.
precisa
@ErikReppen normalmente, a interface (ou arquivo de cabeçalho em C) é a coisa para a qual os clientes entram no formato legível pelo usuário para escrever suas soluções, o restante sendo fornecido apenas em formato binário (é claro em Java, não é necessário fornecer as fontes das interfaces, classfiles e javadoc).
Jwenting
@jwenting Eu não tinha pensado nesse caso. Estou acostumado a pensar no próximo pobre bastardo que tem que manter essa base de código boba, porque muitas vezes sou eu no próximo trabalho esfaqueando meus olhos depois de passar horas olhando para algum tipo de interface bizarra e alegre sub / superclasse arquitetura completa.
Erik Reppen
3

Um arquivo Java representa uma classe. Se você tivesse um procedimento fora da classe, qual seria o escopo? Seria global? Ou pertenceria à classe que o arquivo Java representa?

Presumivelmente, você o coloca nesse arquivo Java em vez de outro arquivo por um motivo - porque ele acompanha essa classe mais do que qualquer outra classe. Se um procedimento fora de uma classe foi realmente associado a essa classe, por que não forçá-lo a entrar na classe a que pertence? Java lida com isso como um método estático dentro da classe.

Se um procedimento de classe externa fosse permitido, presumivelmente não teria acesso especial à classe em que o arquivo foi declarado, limitando-a a uma função de utilitário que não altera nenhum dado.

A única desvantagem possível dessa limitação Java é que, se você realmente possui procedimentos globais que não estão associados a nenhuma classe, acaba criando uma classe MyGlobals para mantê-los e importa essa classe em todos os outros arquivos que usam esses procedimentos. .

De fato, o mecanismo de importação Java precisa dessa restrição para funcionar. Com todas as APIs disponíveis, o compilador java precisa saber exatamente o que compilar e o que compilar, portanto, as instruções de importação explícitas na parte superior do arquivo. Sem ter que agrupar suas globais em uma classe artificial, como você diria ao compilador Java para compilar suas globais e não todas e todas as globais em seu caminho de classe? E a colisão de namespace em que você tem um doStuff () e outra pessoa tem um doStuff ()? Isso não funcionaria. Forçar você a especificar MyClass.doStuff () e YourClass.doStuff () corrige esses problemas. Forçar seus procedimentos a entrar no MyClass em vez de fora, apenas esclarece essa restrição e não impõe restrições adicionais ao seu código.

Java errou várias coisas - a serialização tem tantas pequenas verrugas que é quase difícil demais ser útil (pense em SerialVersionUID). Também pode ser usado para quebrar singletons e outros padrões de design comuns. O método clone () no Object deve ser dividido em deepClone () e shallowClone () e deve ser seguro quanto ao tipo. Todas as classes da API poderiam ter sido imutáveis ​​por padrão (do jeito que estão no Scala). Mas a restrição de que todos os procedimentos devem pertencer a uma classe é boa. Serve principalmente para simplificar e esclarecer o idioma e seu código sem impor restrições onerosas.

GlenPeterson
fonte
3

Eu acho que a maioria das pessoas que responderam e seus eleitores entenderam mal a pergunta. Isso reflete que eles não conhecem C ++.

"Definição" e "Declaração" são palavras com um significado muito específico em C ++.

O OP não significa alterar a maneira como o Java funciona. Esta é uma pergunta puramente sobre sintaxe. Eu acho que é uma pergunta válida.

No C ++, existem duas maneiras de definir uma função de membro.

A primeira maneira é a maneira Java. Basta colocar todo o código dentro do aparelho:

class Box {
public:
    // definition of member function
    void change(int newInt) { 
        this._m = newInt;
    }
private:
    int _m
}

Segunda maneira:

class Box {
public:  
    // declaration of member function
    void change(int newInt); 
private:
    int _m
}

// definition of member function
// this can be in the same file as the declaration
void Box::change(int newInt) {
    this._m = newInt;
}

Ambos os programas são iguais. A função changeainda é uma função membro: ela não existe fora da classe. Além disso, a definição de classe DEVE incluir os nomes e tipos de TODAS as funções e variáveis ​​membro, assim como em Java.

Jonathan Henson está certo de que este é um artefato da maneira como os cabeçalhos funcionam em C ++: permite que você coloque declarações no arquivo de cabeçalho e implementações em um arquivo .cpp separado para que seu programa não viole o ODR (regra de definição única). Mas tem méritos fora disso: permite que você veja a interface de uma classe grande rapidamente.

Em Java, você pode aproximar esse efeito com classes ou interfaces abstratas, mas essas não podem ter o mesmo nome que a classe de implementação, o que a torna bastante desajeitada.

Erik van Velzen
fonte
e isso mostra que vocês dois não entendem pessoas nem Java. Você pode usar o mesmo nome para interface e implementação, desde que não estejam no mesmo pacote.
jwenting
Considero que o pacote (ou namespace ou classe externa) faz parte do nome.
Erik van Velzen
2

Eu acho que é um artefato do mecanismo de carregamento de classe. Cada arquivo de classe é um contêiner para um objeto carregável. Não há lugar "fora" dos arquivos de classe.

ddyer
fonte
1
Não vejo por que organizar o código-fonte em unidades separadas impediria a manutenção do formato do arquivo de classe.
Mat
existe uma correspondência 1: 1 entre arquivos de classe e arquivos de origem, que é uma das melhores decisões de design no sistema geral.
ddyer
@ddyer Você nunca viu Foo $ 2 $ 1.class então? (consulte nomes de arquivos de classe de classe interna Java )
Isso tornaria um mapeamento "para"? de qualquer forma, cada classe é gerada a partir da compilação de exatamente um arquivo de origem, o que é uma boa decisão de design.
ddyer
0

O C #, que é muito semelhante ao Java, possui esse tipo de recurso através do uso de métodos parciais, exceto que os métodos parciais são exclusivamente privados.

Métodos parciais: http://msdn.microsoft.com/en-us/library/6b0scde8.aspx

Classes e métodos parciais: http://msdn.microsoft.com/en-us/library/wa80x488.aspx

Não vejo nenhuma razão para o Java não poder fazer o mesmo, mas provavelmente se trata apenas de saber se há uma necessidade percebida da base de usuários para adicionar esse recurso à linguagem.

A maioria das ferramentas de geração de código para C # gera classes parciais para que o desenvolvedor possa adicionar facilmente código escrito manualmente à classe em um arquivo separado, se desejado.

Aprendiz do Dr. Wily
fonte
0

O C ++ exige que todo o texto da classe seja compilado como parte de cada unidade de compilação que use qualquer membro ou produza instâncias. A única maneira de manter os tempos de compilação sãos é fazer com que o texto da própria classe contenha - na medida do possível - apenas as coisas que são realmente necessárias para seus consumidores. O fato de os métodos C ++ serem frequentemente escritos fora das classes que os contêm é um truque vil realmente desagradável, motivado pelo fato de exigir que o compilador processe o texto de cada método de classe uma vez para cada unidade de compilação em que a classe é usada levaria a totalmente tempos de construção insanos.

Em Java, os arquivos de classe compilados contêm, entre outras coisas, informações que são amplamente equivalentes a um arquivo .h em C ++. Os consumidores da classe podem extrair todas as informações necessárias desse arquivo, sem precisar que o compilador processe o arquivo .java. Ao contrário do C ++, onde o arquivo .h contém informações disponibilizadas para as implementações e clientes das classes contidas nele, o fluxo em Java é revertido: o arquivo que os clientes usam não é um arquivo de origem usado na compilação do código de classe, mas é produzido pelo compilador usando informações do arquivo de código de classe. Como não há necessidade de dividir o código de classe entre um arquivo que contém informações que os clientes precisam e um arquivo que contém a implementação, o Java não permite essa divisão.

supercat
fonte
-1

Eu acho que parte disso é que o Java é uma linguagem protecionista que se preocupa com o uso em grandes equipes. As classes não podem ser substituídas ou redefinidas. Você tem 4 níveis de modificadores de acesso que definem muito especificamente como os métodos podem e não podem ser usados. Tudo é forte / estaticamente tipado para proteger os desenvolvedores do hijinx de incompatibilidade de tipo causado por outras pessoas ou por eles mesmos. Ter classes e funções como unidades menores facilita muito a reinvenção do paradigma de como arquitetar um aplicativo.

Compare com o JavaScript, onde funções de primeira classe de substantivo / verbo de propósito duplo caem e são repassadas como doces de uma pinata aberta, o construtor de funções equivalentes à classe pode ter seu protótipo alterado, adicionando novas propriedades ou alterando as antigas para casos que já foram criados a qualquer momento, absolutamente nada impede que você substitua qualquer construção por sua própria versão, existem 5 tipos e eles se convertem e avaliam automaticamente em diferentes circunstâncias e você pode anexar novas propriedades a qualquer coisa:

function nounAndVerb(){
}
nounAndVerb.newProperty = 'egads!';

Em última análise, trata-se de nichos e mercados. O JavaScript é tão apropriado e desastroso nas mãos de 100 (muitos dos quais provavelmente serão medíocres) devs quanto o Java já esteve nas mãos de pequenos grupos de desenvolvedores que tentam escrever uma interface da Web com ele. Quando você trabalha com 100 desenvolvedores, a última coisa que você quer é um cara reinventando o paradigma condenado. Quando você trabalha com interface do usuário ou o desenvolvimento rápido é uma preocupação mais crítica, a última coisa que você deseja é impedir que você faça coisas relativamente simples rapidamente, simplesmente porque seria fácil fazê-las de maneira estúpida se você não tomar cuidado.

No final do dia, porém, ambas são linguagens populares de uso geral, então há um pouco de argumento filosófico lá. Minha maior discussão pessoal com Java e C # é que nunca vi ou trabalhei com uma base de código herdada em que a maioria dos desenvolvedores parecia ter entendido o valor da OOP básica. Quando você entra no jogo tendo que agrupar tudo em uma classe, talvez seja mais fácil presumir que você está fazendo OOP em vez de usar espaguete disfarçado de cadeias maciças de 3-5 classes de linha.

Dito isto, não há nada mais terrível do que o JavaScript escrito por alguém que sabe apenas o suficiente para ser perigoso e não tem medo de exibi-lo. E acho que é essa a ideia geral. Esse cara supostamente precisa seguir as mesmas regras em Java. Eu argumentaria que o bastardo sempre encontrará um caminho.

Erik Reppen
fonte