Vindo de C ++ para Java, a pergunta sem resposta óbvia é por que o Java não incluiu sobrecarga de operador?
Não é Complex a, b, c; a = b + c;
muito mais simples do que Complex a, b, c; a = b.add(c);
?
Existe uma razão conhecida para isso, argumentos válidos para não permitir sobrecarga do operador? A razão é arbitrária ou perdida no tempo?
java
operator-overloading
rengolin
fonte
fonte
Respostas:
Supondo que você desejasse substituir o valor anterior do objeto referido por
a
, uma função de membro teria que ser chamada.No C ++, essa expressão diz ao compilador para criar três (3) objetos na pilha, executar adição e copiar o valor resultante do objeto temporário no objeto existente
a
.No entanto, em Java,
operator=
não executa cópia de valor para tipos de referência, e os usuários podem criar apenas novos tipos de referência, não tipos de valor. Portanto, para um tipo definido pelo usuário chamadoComplex
, atribuição significa copiar uma referência para um valor existente.Considere em vez disso:
Em C ++, isso copia o valor, portanto a comparação resultará diferente. Em Java,
operator=
executa a cópia de referência, portanto,a
eb
agora está se referindo ao mesmo valor. Como resultado, a comparação produzirá 'igual', pois o objeto será comparado igual a si mesmo.A diferença entre cópias e referências apenas aumenta a confusão de sobrecarga do operador. Como o @Sebastian mencionou, Java e C # precisam lidar com igualdade de valor e referência separadamente -
operator+
provavelmente lidariam com valores e objetos, masoperator=
já estão implementados para lidar com referências.No C ++, você deve lidar apenas com um tipo de comparação por vez, para que seja menos confuso. Por exemplo, em
Complex
,operator=
eoperator==
são ambos trabalhando em valores - copiando valores e comparando os valores respectivamente.fonte
Existem muitos posts reclamando sobre a sobrecarga do operador.
Eu senti que tinha que esclarecer os conceitos de "sobrecarga do operador", oferecendo um ponto de vista alternativo sobre esse conceito.
Código ofuscante?
Este argumento é uma falácia.
A ofuscação é possível em todos os idiomas ...
É tão fácil ofuscar código em C ou Java por meio de funções / métodos quanto em C ++ por sobrecargas do operador:
... Mesmo nas interfaces padrão do Java
Para outro exemplo, vamos ver a
Cloneable
interface em Java:Você deveria clonar o objeto que implementa essa interface. Mas você pode mentir. E crie um objeto diferente. De fato, essa interface é tão fraca que você pode retornar outro tipo de objeto, apenas por diversão:
Como a
Cloneable
interface pode ser abusada / ofuscada, ela deve ser banida pelos mesmos motivos que a sobrecarga do operador C ++ deve ser?Poderíamos sobrecarregar o
toString()
método de umaMyComplexNumber
classe para que ele retornasse a hora estrita do dia. AtoString()
sobrecarga também deve ser proibida? Poderíamos sabotarMyComplexNumber.equals
para que ele retornasse um valor aleatório, modificasse os operandos ... etc. etc. etc etc.Em Java, como em C ++, ou qualquer outra linguagem, o programador deve respeitar um mínimo de semântica ao escrever código. Isso significa implementar uma
add
função que adiciona e umCloneable
método de implementação que clona e um++
operador que incrementa.O que é ofuscante, afinal?
Agora que sabemos que o código pode ser sabotado mesmo por meio dos métodos Java, podemos nos perguntar sobre o uso real da sobrecarga de operadores em C ++?
Notação clara e natural: métodos versus sobrecarga do operador?
A seguir, compararemos, para casos diferentes, o "mesmo" código em Java e C ++, para ter uma idéia de que tipo de estilo de codificação é mais claro.
Comparações naturais:
Observe que A e B podem ser de qualquer tipo em C ++, desde que as sobrecargas do operador sejam fornecidas. Em Java, quando A e B não são primitivos, o código pode se tornar muito confuso, mesmo para objetos primitivos (BigInteger, etc.) ...
Acessores e subscrição de matriz / contêiner naturais:
Em Java, vemos que para cada contêiner fazer a mesma coisa (acessar seu conteúdo por meio de um índice ou identificador), temos uma maneira diferente de fazê-lo, o que é confuso.
No C ++, cada contêiner usa a mesma maneira de acessar seu conteúdo, graças à sobrecarga do operador.
Manipulação de tipos avançados naturais
Os exemplos abaixo usam um
Matrix
objeto, encontrado usando os primeiros links encontrados no Google para " objeto Java Matrix " e " objeto C ++ Matrix ":E isso não se limita a matrizes. As classes
BigInteger
eBigDecimal
de Java sofrem da mesma verbosidade confusa, enquanto seus equivalentes em C ++ são tão claros quanto os tipos internos.Iteradores naturais:
Funções naturais:
Concatenação de texto:
Ok, em Java você também pode usar
MyString = "Hello " + 25 + " World" ;
... Mas, espere um segundo: isso está sobrecarregando o operador, não é? Não é trapaça ???:-D
Código genérico?
Os mesmos operandos modificadores de código genérico devem ser utilizáveis tanto para embutidos / primitivos (que não têm interfaces em Java), objetos padrão (que não podem ter a interface correta) e objetos definidos pelo usuário.
Por exemplo, calculando o valor médio de dois valores de tipos arbitrários:
Discutindo sobrecarga do operador
Agora que vimos comparações justas entre código C ++ usando sobrecarga de operador e o mesmo código em Java, agora podemos discutir "sobrecarga de operador" como um conceito.
A sobrecarga do operador existia desde antes dos computadores
Mesmo fora da ciência da computação, há sobrecarga de operadores: Por exemplo, em matemática, os operadores gosto
+
,-
,*
, etc. estão sobrecarregadas.Na verdade, o significado de
+
,-
,*
, etc. alterações, dependendo dos tipos dos operandos (numerics, vetores, funções de onda quântica, matrizes, etc.).Muitos de nós, como parte de nossos cursos de ciências, aprendemos várias significados para os operadores, dependendo do tipo de operando. Nós os achamos confusos?
A sobrecarga do operador depende de seus operandos
Essa é a parte mais importante da sobrecarga de operadores: como na matemática ou na física, a operação depende dos tipos de operandos.
Portanto, saiba o tipo do operando e você saberá o efeito da operação.
Mesmo C e Java possuem sobrecarga de operador (codificada)
Em C, o comportamento real de um operador mudará de acordo com seus operandos. Por exemplo, adicionar dois números inteiros é diferente de adicionar dois duplos, ou mesmo um inteiro e um duplo. Existe até todo o domínio aritmético do ponteiro (sem conversão, você pode adicionar a um ponteiro um número inteiro, mas não pode adicionar dois ponteiros ...).
Em Java, não há aritmética de ponteiro, mas alguém ainda achava que a concatenação de strings sem o
+
operador seria ridícula o suficiente para justificar uma exceção no credo "sobrecarregar o operador é mau".Só que você, como codificador C (por motivos históricos) ou Java (por motivos pessoais , veja abaixo), não pode fornecer o seu próprio.
Em C ++, a sobrecarga do operador não é opcional ...
No C ++, a sobrecarga do operador para tipos internos não é possível (e isso é uma coisa boa), mas os tipos definidos pelo usuário podem ter sobrecargas de operador definidas pelo usuário .
Como já foi dito anteriormente, em C ++, e ao contrário de Java, os tipos de usuário não são considerados cidadãos de segunda classe da linguagem, quando comparados aos tipos internos. Portanto, se os tipos internos têm operadores, os tipos de usuários também devem poder tê-los.
A verdade é que, como a
toString()
,clone()
,equals()
os métodos são para Java ( ou seja, quase-padrão semelhante ), C ++ sobrecarga de operador é tanto parte de C ++ que se torna tão natural como os operadores C originais, ou a antes métodos Java mencionados.Combinado com a programação de modelos, a sobrecarga do operador se torna um padrão de design bem conhecido. De fato, você não pode ir muito longe no STL sem usar operadores sobrecarregados e sobrecarregar operadores para sua própria classe.
... mas não deve ser abusado
A sobrecarga do operador deve se esforçar para respeitar a semântica do operador. Não subtraia em um
+
operador (como em "não subtraia em umaadd
função" ou "retorne porcaria em umclone
método").A sobrecarga de elenco pode ser muito perigosa, pois pode levar a ambiguidades. Portanto, eles devem ser realmente reservados para casos bem definidos. Quanto
&&
e||
, nunca sobrecarregá-los a menos que você realmente saiba o que está fazendo, como você vai perder a avaliação do curto-circuito que os operadores nativos&&
e||
desfrutar.Então ... Ok ... Então por que não é possível em Java?
Porque James Gosling disse isso:
Por favor, compare o texto de Gosling acima com o de Stroustrup abaixo:
A sobrecarga do operador beneficiaria o Java?
Alguns objetos se beneficiariam muito da sobrecarga do operador (tipos concretos ou numéricos, como BigDecimal, números complexos, matrizes, contêineres, iteradores, comparadores, analisadores, etc.).
No C ++, você pode aproveitar esse benefício devido à humildade do Stroustrup. Em Java, você está simplesmente ferrado por causa da escolha pessoal de Gosling .
Poderia ser adicionado ao Java?
Os motivos para não adicionar sobrecarga de operador agora em Java podem ser uma mistura de políticas internas, alergia ao recurso, desconfiança dos desenvolvedores (você sabe, os sabotadores que parecem assombrar equipes Java ...), compatibilidade com as JVMs anteriores, hora de escrever uma especificação correta, etc.
Portanto, não prenda a respiração esperando por esse recurso ...
Mas eles fazem isso em c # !!!
Sim...
Embora isso esteja longe de ser a única diferença entre os dois idiomas, este nunca deixa de me divertir.
Aparentemente, os C # pessoas, com sua "cada primitiva é um
struct
, e umstruct
deriva de objeto" , deu certo na primeira tentativa.E eles fazem isso em outras línguas !!!
Apesar de todo o FUD contra a sobrecarga de operador definida usada, os seguintes idiomas são compatíveis: Scala , Dart , Python , F # , C # , D , Algol 68 , Smalltalk , Groovy , Perl 6 , C ++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...
Tantas línguas, com tantas filosofias diferentes (e às vezes opostas), e ainda assim todas elas concordam com esse ponto.
Alimento para o pensamento...
fonte
James Gosling comparou o design do Java ao seguinte:
Você pode ler o contexto da citação aqui
Basicamente, a sobrecarga do operador é excelente para uma classe que modela algum tipo de ponto, moeda ou número complexo. Mas depois disso você começa a ficar sem exemplos rapidamente.
Outro fator foi o abuso do recurso em C ++ por desenvolvedores sobrecarregando operadores como '&&', '||', os operadores de elenco e, é claro, 'novos'. A complexidade resultante da combinação disso com a passagem por valor e exceções é bem abordada no livro Excepcional em C ++ .
fonte
Many C++ design decisions have their roots in my dislike for forcing people to do things in some particular way [...] Often, I was tempted to outlaw a feature I personally disliked, I refrained from doing so because I did not think I had the right to force my views on others. (B. Stroustrup)
.I'd like them to go have a look at some C++ code out there that is hideously put together with weird hacks and "exceptional" features of the language
: Programadores ruins escreverão códigos incorretos, independentemente do idioma. Apenas tente emular uma "passagem por referência" para que os parâmetros de função em Java tenham uma idéia. Eu vi o código e ri tanto que doeu. Esse é o tipo de coisa que Gosling não usou, portanto, precisava de hackers horríveis para ter em Java, ainda assim, existe nativamente, a custo zero, tanto em C # quanto em C ++.Confira Boost.Units: link text
Ele fornece análise dimensional com zero de sobrecarga por sobrecarga do operador. Quanto mais claro isso pode ficar?
realmente produziria "Energia = 4 J", o que está correto.
fonte
Os designers de Java decidiram que a sobrecarga do operador era mais problemática do que valia a pena. Simples assim.
Em uma linguagem em que toda variável de objeto é realmente uma referência, a sobrecarga do operador tem o risco adicional de ser completamente ilógica - para um programador de C ++, pelo menos. Compare a situação com a sobrecarga do operador de C # == e
Object.Equals
eObject.ReferenceEquals
(ou como quer que seja chamado).fonte
O Groovy tem sobrecarga de operador e é executado na JVM. Se você não se importa com o desempenho atingido (que diminui todos os dias). É automático com base nos nomes dos métodos. por exemplo, '+' chama o método 'mais (argumento)'.
fonte
where ...
torna-se.Where(i => ...
). Se eles tivessem feito o mesmo com operadores aritméticos, muitas coisas seriam mais simples e mais poderosas. Java tem a vantagem de uma lista limpa e pode fazer isso direito (embora por razões religiosas, provavelmente nunca será).Eu acho que essa pode ter sido uma escolha consciente do design para forçar os desenvolvedores a criar funções cujos nomes claramente comunicam suas intenções. Em C ++, os desenvolvedores sobrecarregavam os operadores com funcionalidades que geralmente não tinham relação com a natureza comumente aceita de um determinado operador, tornando quase impossível determinar o que um pedaço de código faz sem observar a definição do operador.
fonte
In C++ developers would overload operators with functionality that would often have no relation to the commonly accepted nature of the given operator
: Esta é uma afirmação gratuita. Sou desenvolvedor profissional de C ++ há 12 anos e raramente encontrei esse problema. Na verdade, a maioria dos bugs e erros de projeto que eu vi em C ++ foram no código de estilo C (void *
, moldes, etc.)add
função pode realmente ser mal utilizada (como fazer uma multiplicação ou adquirir um mutex) ... O abuso mencionado pelo user14128 não se limita aos operadores, mas existe algum tipo de medo patológico sobre a sobrecarga de operadores que, acredito, vem dos dias anteriores do C vs. C ++, um medo que não foi modificado diretamente para Java, mas, felizmente, não foi usado em C # ... No final, respeitando a semântica e escrever funções / operadores claros é o trabalho do desenvolvedor. Não é o idioma.cout << f() || g();
os parênteses não o tornam mais claro, eles o corrigem. E se os operadores de troca de bits não estivessem sendo abusados, não seriam necessários. Por que écout << (5&3) << endl;
melhor quecout.fmt(5&3)(endl);
? Usar o operador de chamada de função em uma variável de membro functor seria um design infinitamente melhor para fluxos do que redirecionar operadores bit a bit apenas porque o glifo parece bom. Mas isso está longe de ser a única coisa errada nos fluxos.Bem, você pode realmente dar um tiro no próprio pé com sobrecarga do operador. É como nos ponteiros as pessoas cometem erros estúpidos com eles e, por isso, foi decidido tirar a tesoura.
Pelo menos eu acho que é esse o motivo. Estou do seu lado de qualquer maneira. :)
fonte
Algumas pessoas dizem que a sobrecarga do operador em Java levaria à ofuscação. Essas pessoas já pararam para ver algum código Java fazendo algumas contas básicas, como aumentar um valor financeiro em uma porcentagem usando o BigDecimal? ... a verbosidade de tal exercício se torna sua própria demonstração de ofuscação. Ironicamente, adicionar sobrecarga de operador ao Java nos permitiria criar nossa própria classe Currency, o que tornaria esse código matemático elegante e simples (menos ofuscado).
fonte
Dizer que a sobrecarga do operador leva a erros lógicos do tipo que o operador não corresponde à lógica da operação, é como não dizer nada. O mesmo tipo de erro ocorrerá se o nome da função for inadequado para a lógica de operação - então qual é a solução: abandone a capacidade de uso da função !? Esta é uma resposta cômica - "Inadequado para a lógica de operação", todo nome de parâmetro, toda classe, função ou qualquer outra coisa que possa ser lógica e inadequada. Eu acho que essa opção deve estar disponível em uma linguagem de programação respeitável, e aqueles que pensam que é inseguro - ei, nenhum deles diz que você precisa usá-la. Vamos pegar o c #. Eles caíram nos ponteiros, mas ei - há uma declaração de 'código inseguro' - programa como você quiser por seu próprio risco.
fonte
Tecnicamente, há sobrecarga do operador em todas as linguagens de programação que podem lidar com diferentes tipos de números, por exemplo, números inteiros e reais. Explicação: O termo sobrecarga significa que existem simplesmente várias implementações para uma função. Na maioria das linguagens de programação, diferentes implementações são fornecidas para o operador +, uma para números inteiros e outra para reais, isso é chamado de sobrecarga do operador.
Agora, muitas pessoas acham estranho que Java tenha sobrecarga de operador para o operador + para adicionar seqüências de caracteres juntas, e do ponto de vista matemático isso seria realmente estranho, mas visto do ponto de vista de um desenvolvedor de linguagem de programação, não há nada errado em adicionar sobrecarga de operador integrada para o operador + para outras classes, por exemplo, String. No entanto, a maioria das pessoas concorda que depois que você adiciona sobrecarga interna para + para String, geralmente é uma boa ideia fornecer essa funcionalidade também para o desenvolvedor.
Discordo completamente da falácia de que a sobrecarga do operador ofusca o código, pois isso é deixado para o desenvolvedor decidir. É ingênuo pensar e, para ser sincero, está ficando velho.
+1 para adicionar sobrecarga de operador no Java 8.
fonte
+
para concatenar qualquer coisa string-ish é IMHO bastante hediondo, assim como a sobrecarga de/
C e FORTRAN para divisão inteira e fracionária. Em muitas versões do Pascal, o uso de operadores aritméticos em qualquer tipo numérico produzirá resultados numericamente equivalentes à conversão dos operandosReal
, embora os resultados que podem não ser números inteiros devam ser alimentados atravésTrunc
ouRound
antes que possam ser atribuídos a números inteiros.Supondo que Java como a linguagem de implementação, a, bec sejam referências ao tipo Complex com valores iniciais de null. Supondo também que o Complex seja imutável como o BigInteger mencionado e o BigDecimal imutável semelhante , acho que você quis dizer o seguinte, ao atribuir a referência ao Complex retornada pela adição de bec, e não comparando essa referência a a.
fonte
Às vezes, seria bom ter sobrecarga de operador, classes de amigos e herança múltipla.
No entanto, ainda acho que foi uma boa decisão. Se Java tivesse sobrecarregado o operador, nunca poderíamos ter certeza do significado do operador sem examinar o código-fonte. No momento, isso não é necessário. E acho que seu exemplo de uso de métodos em vez de sobrecarga de operador também é bastante legível. Se você quiser deixar as coisas mais claras, pode sempre adicionar um comentário acima das declarações cabeludas.
fonte
Este não é um bom motivo para desaprová-lo, mas é prático:
As pessoas nem sempre o usam com responsabilidade. Veja este exemplo da biblioteca Python scapy:
Aqui está a explicação:
fonte
Alternativas ao suporte nativo à sobrecarga do operador Java
Como o Java não possui sobrecarga de operador, aqui estão algumas alternativas que você pode procurar:
Se alguém tiver conhecimento de outras pessoas, comente e eu adicionarei essa lista.
fonte
Embora a linguagem Java não suporte diretamente a sobrecarga do operador, você pode usar o plug-in do compilador Manifold em qualquer projeto Java para ativá-lo. Ele suporta Java 8 - 13 (a versão atual do Java) e é totalmente suportado no IntelliJ IDEA.
fonte