Por que o Java não oferece sobrecarga de operadores?

Vindo de C ++ para Java, a pergunta óbvia não respondida é por que o Java não inclui sobrecarga de operadores?

Não é Complex a, b, c; a = b + c; Complex a, b, c; a = b + c; muito mais simples que o Complex a, b, c; a = b.add(c); Complex a, b, c; a = b.add(c); ?

Existe uma razão conhecida para isso, argumentos válidos para não permitir a sobrecarga do operador? A razão é arbitrária ou perdida no tempo?

   

Supondo que você queira sobrescrever o valor anterior do object referido por a , então uma function de membro teria que ser invocada.

 Complex a, b, c; // ... a = b.add(c); 

Em C ++, essa expressão diz ao compilador para criar três (3) objects na pilha, executar adição e copiar o valor resultante do object temporário no object 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 chamado Complex , atribuição significa copiar uma referência para um valor existente.

Considere, em vez disso:

 b.set(1, 0); // initialize to real number '1' a = b; b.set(2, 0); assert( !a.equals(b) ); 

Em C ++, isso copia o valor, portanto, a comparação resultará diferente. Em Java, operator= executa uma cópia de referência, portanto b estão se referindo ao mesmo valor. Como resultado, a comparação produzirá ‘igual’, já que o object será comparado a si mesmo.

A diferença entre cópias e referências apenas aumenta a confusão da sobrecarga do operador. Como mencionado por @Sebastian, Java e C # têm que lidar com valor e referenciar a igualdade separadamente – o operator+ provavelmente lida com valores e objects, mas operator= já está implementado para lidar com referências.

Em C ++, você deve estar lidando apenas com um tipo de comparação de cada vez, então pode ser menos confuso. Por exemplo, em Complex , operator= e operator== ambos estão trabalhando em valores – copiando valores e comparando valores respectivamente.

Há muitos posts reclamando sobre a sobrecarga do operador.

Senti que precisava esclarecer os conceitos de “sobrecarga de operadores”, oferecendo um ponto de vista alternativo sobre esse conceito.

Código ofuscante?

Este argumento é uma falácia.

Ofuscação é possível em todas as línguas …

É tão fácil ofuscar o código em C ou Java através de funções / methods quanto em C ++ através de sobrecargas de operadores:

 // C++ T operator + (const T & a, const T & b) // add ? { T c ; c.value = a.value - b.value ; // subtract !!! return c ; } // Java static T add (T a, T b) // add ? { T c = new T() ; c.value = a.value - b.value ; // subtract !!! return c ; } /* C */ T add (T a, T b) /* add ? */ { T c ; c.value = a.value - b.value ; /* subtract !!! */ return c ; } 

… Mesmo nas interfaces padrão do Java

Para outro exemplo, vamos ver a interface Cloneable em Java:

Você deve clonar o object que implementa essa interface. Mas você poderia mentir. E crie um object diferente. Na verdade, essa interface é tão fraca que você poderia retornar um outro tipo de object, apenas por diversão:

 class MySincereHandShake implements Cloneable { public Object clone() { return new MyVengefulKickInYourHead() ; } } 

Como a interface Cloneable pode ser abusada / ofuscada, deveria ser banida pelos mesmos motivos que a sobrecarga do operador C ++ deveria ser?

Nós poderíamos sobrecarregar o toString() de uma class MyComplexNumber para que ela retornasse a hora do dia. A sobrecarga toString() também deveria ser banida? Poderíamos sabotar MyComplexNumber.equals para retornar um valor random, modificar os operandos … etc. etc. etc.

Em Java, como em C ++, ou qualquer linguagem, o programador deve respeitar um mínimo de semântica ao escrever código. Isso significa implementar uma function add que adiciona e Cloneable método de implementação que clona e um operador ++ que incrementa.

O que é ofuscante de qualquer maneira?

Agora que sabemos que o código pode ser sabotado até mesmo através dos methods Java prístinos, podemos nos perguntar sobre o uso real da sobrecarga de operadores em C ++?

Notação clara e natural: methods versus sobrecarga de operadores?

Vamos comparar abaixo, para casos diferentes, o código “mesmo” em Java e C ++, para ter uma ideia de qual tipo de estilo de codificação é mais claro.

Comparações naturais:

 // C++ comparison for built-ins and user-defined types bool isEqual = A == B ; bool isNotEqual = A != B ; bool isLesser = A < B ; bool isLesserOrEqual = A <= B ; // Java comparison for user-defined types boolean isEqual = A.equals(B) ; boolean isNotEqual = ! A.equals(B) ; boolean isLesser = A.comparesTo(B) < 0 ; boolean isLesserOrEqual = A.comparesTo(B) <= 0 ; 

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 objects primitivos (BigInteger, etc.) ...

Contratadores de matriz / contêineres naturais e assinatura:

 // C++ container accessors, more natural value = myArray[25] ; // subscript operator value = myVector[25] ; // subscript operator value = myString[25] ; // subscript operator value = myMap["25"] ; // subscript operator myArray[25] = value ; // subscript operator myVector[25] = value ; // subscript operator myString[25] = value ; // subscript operator myMap["25"] = value ; // subscript operator // Java container accessors, each one has its special notation value = myArray[25] ; // subscript operator value = myVector.get(25) ; // method get value = myString.charAt(25) ; // method charAt value = myMap.get("25") ; // method get myArray[25] = value ; // subscript operator myVector.set(25, value) ; // method set myMap.put("25", value) ; // method put 

Em Java, vemos que para cada contêiner fazer a mesma coisa (acessar seu conteúdo através de um índice ou identificador), temos uma maneira diferente de fazê-lo, o que é confuso.

Em C ++, cada contêiner usa o mesmo caminho para acessar seu conteúdo, graças à sobrecarga do operador.

Manipulação de tipos avançados naturais

Os exemplos abaixo usam um object Matrix , encontrado usando os primeiros links encontrados no Google para " object Java Matrix " e " object Matrix c ++ ":

 // C++ YMatrix matrix implementation on CodeProject // http://www.codeproject.com/KB/architecture/ymatrix.aspx // A, B, C, D, E, F are Matrix objects; E = A * (B / 2) ; E += (A - B) * (C + D) ; F = E ; // deep copy of the matrix // Java JAMA matrix implementation (seriously...) // http://math.nist.gov/javanumerics/jama/doc/ // A, B, C, D, E, F are Matrix objects; E = A.times(B.times(0.5)) ; E.plusEquals(A.minus(B).times(C.plus(D))) ; F = E.copy() ; // deep copy of the matrix 

E isso não está limitado a matrizes. As classs BigInteger e BigDecimal de Java sofrem da mesma verbosidade confusa, enquanto seus equivalentes em C ++ são tão claros quanto os tipos internos.

Iteradores naturais:

 // C++ Random Access iterators ++it ; // move to the next item --it ; // move to the previous item it += 5 ; // move to the next 5th item (random access) value = *it ; // gets the value of the current item *it = 3.1415 ; // sets the value 3.1415 to the current item (*it).foo() ; // call method foo() of the current item // Java ListIterator "bi-directional" iterators value = it.next() ; // move to the next item & return the value value = it.previous() ; // move to the previous item & return the value it.set(3.1415) ; // sets the value 3.1415 to the current item 

Functores naturais:

 // C++ Functors myFunctorObject("Hello World", 42) ; // Java Functors ??? myFunctorObject.execute("Hello World", 42) ; 

Concatenação de texto:

 // C++ stream handling (with the < < operator) stringStream << "Hello " << 25 << " World" ; fileStream << "Hello " << 25 << " World" ; outputStream << "Hello " << 25 << " World" ; networkStream << "Hello " << 25 << " World" ; anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ; // Java concatenation myStringBuffer.append("Hello ").append(25).append(" World") ; 

Ok, em Java você pode usar MyString = "Hello " + 25 + " World" ; também ... Mas espere um segundo: isso é sobrecarga do operador, não é? Não é trapaça ???

😀

Código genérico?

Os mesmos operandos de modificação de código genérico devem ser utilizáveis ​​tanto para built-ins / primitivos (que não possuem interfaces em Java), objects padrão (que não podem ter a interface correta) e objects definidos pelo usuário.

Por exemplo, calculando o valor médio de dois valores de tipos arbitrários:

 // C++ primitive/advanced types template T getAverage(const T & p_lhs, const T & p_rhs) { return (p_lhs + p_rhs) / 2 ; } int intValue = getAverage(25, 42) ; double doubleValue = getAverage(25.25, 42.42) ; complex complexValue = getAverage(cA, cB) ; // cA, cB are complex Matrix matrixValue = getAverage(mA, mB) ; // mA, mB are Matrix // Java primitive/advanced types // It won't really work in Java, even with generics. Sorry. 

Discutindo a sobrecarga do operador

Agora que vimos comparações justas entre o código C ++ usando sobrecarga de operadores e o mesmo código em Java, podemos agora discutir "sobrecarga de operadores" como um conceito.

Sobrecarga de operador existia desde antes dos computadores

Mesmo fora da ciência da computação, há sobrecarga do operador: por exemplo, na matemática, operadores como + , - , * , etc. estão sobrecarregados.

De fato, o significado de + , - , * , etc. muda dependendo dos tipos de operandos (numéricos, vetores, funções de onda quântica, matrizes, etc.).

A maioria de nós, como parte de nossos cursos de ciências, aprendeu vários significados para os operadores, dependendo dos tipos de operandos. Nós achamos eles confusos, eles?

A sobrecarga do operador depende de seus operandos

Esta é a parte mais importante da sobrecarga do operador: como na matemática ou na física, a operação depende dos tipos de seus operandos.

Portanto, saiba o tipo do operando e você saberá o efeito da operação.

Mesmo C e Java têm sobrecarga de operador (codificada)

Em C, o comportamento real de um operador mudará de acordo com seus operandos. Por exemplo, adicionar dois inteiros é diferente de adicionar dois duplos, ou um inteiro e um duplo. Existe até mesmo todo o domínio aritmético do ponteiro (sem conversão, você pode adicionar um ponteiro a um inteiro, mas não pode adicionar dois pointers ...).

Em Java, não há aritmética de pointers, mas alguém ainda encontra concatenação de strings sem o operador + seria ridículo o suficiente para justificar uma exceção na crença "sobrecarga de operador é má".

É só que você, como um codificador C (por razões históricas) ou Java (por razões pessoais , veja abaixo), você não pode fornecer o seu próprio.

Em C ++, a sobrecarga do operador não é opcional ...

Em C ++, a sobrecarga do operador para tipos internos não é possível (e isso é bom), mas os tipos definidos pelo usuário podem ter sobrecargas do operador definidas pelo usuário .

Como já foi dito anteriormente, em C ++, e ao contrário do Java, os tipos de usuários não são considerados cidadãos de segunda class da linguagem, quando comparados aos tipos internos. Portanto, se tipos internos tiverem operadores, os tipos de usuários também poderão tê-los.

A verdade é que, como os methods toString() , clone() , equals() são para Java ( ou seja, quase-padrão ), a sobrecarga do operador C ++ é tão parte do C ++ que se torna tão natural quanto os operadores C originais. ou os methods Java mencionados anteriormente.

Combinada com a programação de modelos, a sobrecarga do operador se torna um padrão de design bem conhecido. Na verdade, você não pode ir muito longe no STL sem usar operadores sobrecarregados e sobrecarregar os operadores para sua própria class.

... 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 uma function add " ou "retorne uma porcaria em um método clone ").

A sobrecarga do casting pode ser muito perigosa porque pode levar a ambiguidades. Então eles deveriam ser reservados para casos bem definidos. Quanto a && e || , não os sobrecarregue a menos que você realmente saiba o que está fazendo, pois perderá a avaliação de curto-circuito que os operadores nativos && e || apreciar.

Então ... Ok ... Então porque não é possível em Java?

Porque James Gosling disse isso:

Eu deixei de fora a sobrecarga do operador como uma escolha bastante pessoal, porque eu tinha visto muitas pessoas abusarem dela em C ++.

James Gosling. Fonte: http://www.gotw.ca/publications/c_family_interview.htm

Por favor, compare o texto de Gosling acima com Stroustrup abaixo:

Muitas decisões de design em C ++ têm suas raízes em minha antipatia por forçar as pessoas a fazerem as coisas de uma forma particular [...] Muitas vezes, eu fiquei tentada a proibir um recurso que eu pessoalmente não gostava, abstive-me de fazê-lo porque eu não o direito de forçar minhas opiniões sobre os outros .

Bjarne Stroustrup. Fonte: O Desing e Evolution of C ++ (1.3 Antecedentes Gerais)

A sobrecarga do operador beneficiaria o Java?

Alguns objects se beneficiariam muito com a sobrecarga do operador (tipos concretos ou numéricos, como BigDecimal, números complexos, matrizes, containers, iteradores, comparadores, analisadores etc.).

Em C ++, você pode lucrar com esse benefício por causa da humildade do Stroustrup. Em Java, você está simplesmente ferrado por causa da escolha pessoal de Gosling.

Poderia ser adicionado ao Java?

As razões para não adicionar a sobrecarga do operador agora em Java podem ser uma mistura de política interna, alergia ao recurso, desconfiança dos desenvolvedores (você sabe, os sabotadores que parecem assombrar as equipes Java ...), compatibilidade com as JVMs anteriores, tempo para escrever uma especificação correta, etc.

Então não prenda a respiração esperando por esse recurso ...

Mas eles fazem isso em C # !!!

Sim...

Enquanto isso está longe de ser a única diferença entre os dois idiomas, este nunca falha em me divertir.

Aparentemente, o C # folks, com o seu "todo primitivo é uma struct , e uma struct deriva de Object" , acertou na primeira tentativa.

E eles fazem isso em outras línguas !!!

Apesar de todos os FUD contra o uso da sobrecarga de operadores definidos, os seguintes idiomas suportam: 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 linguagens, com tantas filosofias diferentes (e às vezes opostas), e ainda assim todas concordam com esse ponto.

Alimento para o pensamento...

James Gosling comparou o design de Java ao seguinte:

“Há esse princípio de se mudar, quando você se muda de um apartamento para outro. Um experimento interessante é arrumar o seu apartamento e colocar tudo em checkboxs, então mudar para o próximo apartamento e não descompactar nada até que você precise. Então você Você está fazendo sua primeira refeição, e você está tirando algo de uma checkbox Então, depois de um mês ou mais, você usou isso para entender as coisas na sua vida que você realmente precisa, e então você pega o resto da comida. coisas – esqueça o quanto você gosta ou o quão legal é – e você simplesmente joga fora. É incrível como isso simplifica sua vida, e você pode usar esse princípio em todos os tipos de problemas de design: não fazer coisas só porque eles é legal ou só porque eles são interessantes. ”

Você pode ler o contexto da citação aqui

Basicamente, a sobrecarga do operador é ótima para uma class 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 casting e, claro, ‘novo’. A complexidade resultante de combinar isso com passar por valor e exceções é bem abordada no livro Exceptional C ++ .

Confira Boost.Units: link text

Ele fornece análise dimensional de sobrecarga zero através da sobrecarga do operador. Quanto mais claro isso pode ficar?

 quantity F = 2.0*newton; quantity dx = 2.0*meter; quantity E = F * dx; std::cout < < "Energy = " << E << endl; 

Na verdade, seria a saída "Energia = 4 J", que está correto.

Os projetistas de Java decidiram que a sobrecarga de operadores era mais problemática do que valia a pena. Simples assim.

Em uma linguagem em que cada variável de object é, na verdade, uma referência, a sobrecarga de operadores tem o risco adicional de ser bastante ilógica – para um programador de C ++, pelo menos. Compare a situação com a sobrecarga do operador C # ‘s e Object.Equals e Object.ReferenceEquals (ou seja lá como for chamado).

O Groovy tem sobrecarga do operador e é executado na JVM. Se você não se importa com o desempenho (que fica menor todos os dias). É automático com base nos nomes dos methods. por exemplo, ‘+’ chama o método ‘mais (argumento)’.

Acho que isso pode ter sido uma escolha consciente de design para forçar os desenvolvedores a criar funções cujos nomes comunicam claramente suas intenções. Em C ++, os desenvolvedores sobrecarregariam os operadores com funcionalidades que, muitas vezes, não teriam relação com a natureza comumente aceita do operador em questão, tornando quase impossível determinar o que uma parte do código faz sem observar a definição do operador.

Bem, você pode realmente atirar no pé com a sobrecarga do operador. É como com os pointers que as pessoas cometem erros estúpidos com eles e por isso foi decidido tirar a tesoura.

Pelo menos acho que essa é a razão. Eu estou do seu lado de qualquer maneira. 🙂

Dizer que a sobrecarga do operador leva a erros lógicos do tipo que o operador não corresponde à lógica de operação, é como não dizer nada. O mesmo tipo de erro ocorrerá se o nome da function for inadequado para a lógica de operação – então qual é a solução: descartar a capacidade de uso da function !? Esta é uma resposta cômica – “Inapropriado para a lógica de operação”, todo nome de parâmetro, toda class, function ou qualquer outra coisa pode ser logicamente inadequada. Eu acho que esta opção deve estar disponível em linguagem de programação respeitável, e aqueles que pensam que é inseguro – hey não diz que você tem que usá-lo. Vamos pegar o C #. Eles inclinaram os pointers, mas ei – não há programa de ‘código inseguro’ – programa como você gosta em seu próprio risco.

Tecnicamente, há sobrecarga de operador em toda linguagem de programação que pode lidar com diferentes tipos de números, por exemplo, números inteiros e reais. Explanation: O termo sobrecarga significa que existem simplesmente várias implementações para uma function. Na maioria das linguagens de programação, implementações diferentes são fornecidas para o operador +, uma para inteiros, uma para reais, isso é chamado sobrecarga de operador.

Agora, muitas pessoas acham estranho que Java tenha sobrecarga de operadores para o operador + para adicionar strings juntos e, do ponto de vista matemático, isso seria 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 para o operador + para outras classs, por exemplo, String. No entanto, a maioria das pessoas concorda que, depois de adicionar a sobrecarga incorporada para + de String, geralmente é uma boa ideia fornecer essa funcionalidade também para o desenvolvedor.

Um discordo completamente com a falácia de que a sobrecarga do operador ofusca o código, pois isso é deixado para o desenvolvedor decidir. Isso é ingênuo de pensar e, para ser sincero, está ficando velho.

+1 para adicionar sobrecarga do operador no Java 8.

Algumas pessoas dizem que a sobrecarga de operadores em Java levaria à obscurecimento. Essas pessoas já pararam para ver alguns códigos Java fazendo algumas contas básicas, como aumentar um valor financeiro em uma porcentagem usando BigDecimal? …. a verbosidade de tal exercício torna-se sua própria demonstração de obscurecimento. Ironically, adding operator overloading to Java would allow us to create our own Currency class which would make such mathematical code elegant and simple (less obsfuscated).

Assuming Java as the implementation language then a, b, and c would all be references to type Complex with initial values of null. Also assuming that Complex is immutable as the mentioned BigInteger and similar immutable BigDecimal , I’d I think you mean the following, as you’re assigning the reference to the Complex returned from adding b and c, and not comparing this reference to a.

Isn’t :

 Complex a, b, c; a = b + c; 

much simpler than:

 Complex a, b, c; a = b.add(c); 

Sometimes it would be nice to have operator overloading, friend classs and multiple inheritance.

However I still think it was a good decision. If Java would have had operator overloading then we could never be sure of operator meanings without looking through source code. At present that’s not necessary. And I think your example of using methods instead of operator overloading is also quite readable. If you want to make things more clear you could always add a comment above hairy statements.

 // a = b + c Complex a, b, c; a = b.add(c); 

This is not a good reason to disallow it but a practical one:

People do not always use it responsibly. Look at this example from the Python library scapy:

 >>> IP()  >>> IP()/TCP() > >>> Ether()/IP()/TCP() >> >>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n" >> >>> Ether()/IP()/IP()/UDP() >>> >>> IP(proto=55)/TCP() > 

Aqui está a explicação:

The / operator has been used as a composition operator between two layers. When doing so, the lower layer can have one or more of its defaults fields overloaded according to the upper layer. (You still can give the value you want). A string can be used as a raw layer.

Alternatives to Native Support of Java Operator Overloading

Since Java doesn’t have operator overloading, here are some alternatives you can look into:

  1. Use another language. Both Groovy and Scala have operator overloading, and are based on Java.
  2. Use java-oo , a plugin that enables operator overloading in Java. Note that it is NOT platform independent. Also, it has many issues, and is not compatible with the latest releases of Java (ie Java 10). ( Original StackOverflow Source )
  3. Use JNI , Java Native Interface, or alternatives. This allows you to write C or C++ (maybe others?) methods for use in Java. Of course this is also NOT platform independent.

If anyone is aware of others, please comment, and I will add it to this list.