Pergunta

Vindo do C++ para o Java, a pergunta óbvia sem resposta é por que o Java não incluiu a sobrecarga do 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 permitindo sobrecarga do operador?A razão é arbitrária ou perdida no tempo?

Foi útil?

Solução

Supondo que você queira sobrescrever o valor anterior do objeto referido por a, então uma função membro teria que ser invocada.

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

Em C++, esta expressão diz ao compilador para criar três (3) objetos na pilha, realizar a adição e cópia de 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 só podem criar 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) ); // this assertion will fail

Em C++, isso copia o valor, portanto a comparação resultará diferente.Em Java, operator= executa cópia de referência, então a e b agora estão se referindo ao mesmo valor.Como resultado, a comparação produzirá 'igual', pois o objeto comparará igual a si mesmo.

A diferença entre cópias e referências apenas aumenta a confusão da sobrecarga do operador.Como @Sebastian mencionou, Java e C# precisam lidar com valor e igualdade de referência separadamente - operator+ provavelmente lidaria com valores e objetos, mas operator= já está implementado para lidar com referências.

Em C++, você deve lidar apenas com um tipo de comparação por vez, para que seja menos confuso.Por exemplo, em Complex, operator= e operator== ambos estão trabalhando em valores - copiando valores e comparando valores, respectivamente.

Outras dicas

Há muitos posts reclamando da sobrecarga do operador.

Achei que deveria esclarecer os conceitos de "sobrecarga do operador", oferecendo uma visão alternativa sobre este conceito.

Ofuscação de código?

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 meio de sobrecargas de operador:

// 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 o Cloneable interface em Java:

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

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

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

Poderíamos sobrecarregar o toString() método de um MyComplexNumber class para que ele retorne a hora do dia restrita.Deveria o toString() a sobrecarga também será proibida?Poderíamos sabotar MyComplexNumber.equals para que ele retorne um valor aleatório, modifique os operandos...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.Isto significa implementar um add função que adiciona, e Cloneable método de implementação que clona e um ++ operador do que incrementos.

O que é ofuscante, afinal?

Agora que sabemos que o código pode ser sabotado mesmo através dos métodos Java puros, podemos nos perguntar sobre o uso real da sobrecarga de operadores em C++.

Notação clara e natural:métodos vs.sobrecarga do operador?

Compararemos a seguir, para casos diferentes, o "mesmo" código 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 sejam fornecidas sobrecargas de operador.Em Java, quando A e B não são primitivos, o código pode se tornar muito confuso, mesmo para objetos do tipo primitivo (BigInteger, etc.)...

Acessadores e assinaturas naturais de array/contêiner:

// 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 container fazer a mesma coisa (acessar seu conteúdo através de um índice ou identificador), temos uma maneira diferente de fazer isso, o que é confuso.

Em C++, cada contêiner utiliza a mesma forma de acessar seu conteúdo, graças à sobrecarga do operador.

Manipulação natural de tipos avançados

Os exemplos abaixo usam um Matrix objeto, encontrado usando os primeiros links encontrados no Google para "Objeto Matriz Java" e "objeto matriz 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 se limita a matrizes.O BigInteger e BigDecimal classes de Java sofrem da mesma verbosidade confusa, enquanto seus equivalentes em C++ são tão claros quanto os tipos integrados.

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<E> "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

Funtores 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 é traição???

:-D

Código genérico?

Os mesmos operandos de modificação de código genérico devem ser utilizáveis ​​tanto para embutidos/primitivos (que não possuem 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:

// C++ primitive/advanced types
template<typename T>
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 operador e o mesmo código em Java, podemos discutir a "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 do operador:Por exemplo, em matemática, operadores como +, -, *, etc.estão sobrecarregados.

Na verdade, 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 operadores, dependendo dos tipos de operandos.Nós os achamos confusos?

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 dos seus operandos.

Portanto, conheça o tipo do operando e saberá o efeito da operação.

Até 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, somar dois inteiros é diferente de somar 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 um número inteiro a um ponteiro, mas não pode adicionar dois ponteiros...).

Em Java, não há aritmética de ponteiro, mas alguém ainda encontrou concatenação de strings sem o + operador seria ridículo o suficiente para justificar uma exceção no credo "a sobrecarga do operador é má".

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

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

Em C++, a sobrecarga de operadores para tipos integrados não é possível (e isso é uma coisa boa), mas usuário definido tipos podem ter usuário definido sobrecargas do operador.

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

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

Combinada com a programação de modelos, a sobrecarga do operador torna-se um padrão de projeto bem conhecido.Na verdade, você não pode ir muito longe em 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 um add função" ou "devolver porcaria em um clone método").

A sobrecarga de elenco pode ser muito perigosa porque pode levar a ambigüidades.Portanto, eles deveriam ser reservados para casos bem definidos.Quanto a && e ||, nunca 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 || aproveitar.

Então...OK...Então por que isso não é possível em Java?

Porque James Gosling disse isso:

Deixei de fora a sobrecarga do operador como um escolha bastante pessoal porque eu tinha visto muitas pessoas abusarem dele em C++.

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

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

Muitas decisões de design em C++ têm suas raízes em minha aversão por forçar as pessoas a fazerem as coisas de uma maneira específica [...] Muitas vezes, fiquei tentado a proibir um recurso que pessoalmente não gostava, abstive-me de fazê-lo porque Eu não achava que tinha o direito de impor meus pontos de vista aos outros.

Bjarne Stroustrup.Fonte:O Design e a Evolução do C++ (1.3 Contexto Geral)

A sobrecarga do operador beneficiaria o Java?

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

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

Poderia ser adicionado ao Java?

As razões para não adicionar sobrecarga de operadores 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, 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 esta esteja longe de ser a única diferença entre as duas línguas, esta nunca deixa de me divertir.

Aparentemente, o pessoal do C#, com seus “Todo primitivo é um struct, e um struct deriva de Objeto", acertou na primeira tentativa.

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

Apesar de todo o FUD contra a sobrecarga de operador definida usada, as seguintes linguagens o suportam: escala, Dardo, Pitão, Fá#, C#, D, Algol 68, Conversa fiada, Legal, Perl 6, C++, Rubi, Haskell, MATLAB, Eiffel, Lua, Clojure, Fortran 90, Rápido, Ada, Delfos 2005...

Tantas línguas, com tantas filosofias diferentes (e às vezes opostas), e ainda assim todas concordam nesse ponto.

Alimento para o pensamento...

James Gosling comparou o projeto Java ao seguinte:

“Existe um princípio sobre mudança, quando você muda de um apartamento para outro.Uma experiência interessante é arrumar seu apartamento e colocar tudo em caixas, depois passar para o próximo apartamento e não desempacotar nada até precisar.Então você está fazendo sua primeira refeição e tirando algo de uma caixa.Então, depois de mais ou menos um mês, você usou isso para descobrir quais coisas na sua vida você realmente precisa, e então você pega o resto das coisas - esquece 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 questões de design:não faça as coisas só porque são legais ou apenas porque são interessantes."

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

Basicamente, a sobrecarga do operador é ótima 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 conversão e, claro, 'new'.A complexidade resultante da combinação disso com passagem por valor e exceções é bem abordada no C++ excepcional livro.

Confira Boost.Units: Texto do link

Ele fornece análise dimensional sem sobrecarga por meio de sobrecarga do operador.Quão mais claro isso pode ficar?

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

na verdade, produziria "Energia = 4 J", o que está correto.

Os projetistas Java decidiram que a sobrecarga do operador era mais problemática do que compensava.Simples assim.

Em uma linguagem onde cada variável de objeto é na verdade uma referência, a sobrecarga do operador apresenta o risco adicional de ser bastante ilógica - pelo menos para um programador C++.Compare a situação com a sobrecarga do operador == do C# e Object.Equals e Object.ReferenceEquals (ou como é chamado).

Legal tem sobrecarga de operador e é executado na JVM.Se você não se importa com o impacto no desempenho (que fica menor a cada dia).É automático com base nos nomes dos métodos.por exemplo, '+' chama o método 'plus(argument)'.

Acho que esta 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 de um determinado operador, tornando quase impossível determinar o que um trecho de código faz sem examinar a definição do operador.

Bem, você pode realmente dar um tiro no próprio pé com a sobrecarga do operador.É como se com os ponteiros as pessoas cometessem erros estúpidos com eles e por isso foi decidido tirar a tesoura.

Pelo menos acho que é esse o motivo.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 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:abandonar a capacidade de uso da função!?Esta é uma resposta cômica - "Inadequado para lógica de operação", todo nome de parâmetro, toda classe, função ou o que quer que seja pode ser logicamente inapropriado.Eu acho que esta opção deveria estar disponível em uma linguagem de programação respeitável, e aqueles que pensam que não é segura - ei, não, ambos dizem que você tem que usá-la.Vamos pegar o C#.Eles abaixaram os ponteiros, mas ei - existe uma declaração de 'código inseguro' - programe como quiser por sua própria conta e risco.

Algumas pessoas dizem que a sobrecarga do operador em Java levaria à ofuscação.Essas pessoas já pararam para olhar algum código Java fazendo algumas contas básicas, como aumentar um valor financeiro em uma porcentagem usando BigDecimal?....a verbosidade de tal exercício torna-se a 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 obscuro).

Tecnicamente, há sobrecarga de operadores em todas as linguagens de programação que podem lidar com diferentes tipos de números, por ex.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 inteiros e outra para reais, isso é chamado de sobrecarga de operador.

Agora, muitas pessoas acham estranho que Java tenha sobrecarga de operador para o operador + para adicionar strings, e do ponto de vista matemático isso seria realmente estranho, mas visto do ponto de vista do desenvolvedor de uma linguagem de programação, não há nada de errado em adicionar sobrecarga de operador integrada para o operador + para outras classes, por ex.Corda.No entanto, a maioria das pessoas concorda que, depois de adicionar sobrecarga interna para + para String, geralmente é uma boa ideia fornecer essa funcionalidade também para o desenvolvedor.

Discordo totalmente da falácia de que a sobrecarga do operador ofusca o código, pois isso cabe ao desenvolvedor decidir.É ingênuo pensar isso e, para ser sincero, está envelhecendo.

+1 para adicionar sobrecarga de operador em Java 8.

Assumindo Java como a linguagem de implementação, então a, b e c seriam todos referências ao tipo Complex com valores iniciais nulos.Supondo também que Complex é imutável como o mencionado BigInteger e semelhante imutável GrandeDecimal, acho que você quer dizer o seguinte, já que está atribuindo a referência ao Complex retornado pela adição de b e c, e não comparando essa referência a a.

Não é :

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

muito mais simples que:

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

Às vezes seria bom ter sobrecarga de operadores, classes amigas e herança múltipla.

No entanto, ainda acho que foi uma boa decisão.Se Java tivesse sobrecarga de operadores, nunca poderíamos ter certeza do significado dos operadores sem examinar o código-fonte.Atualmente 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, você pode sempre adicionar um comentário acima das declarações complicadas.

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

Este não é um bom motivo para proibi-lo, mas é prático:

As pessoas nem sempre o usam de forma responsável.Veja este exemplo da biblioteca Python scapy:

>>> IP()
<IP |>
>>> IP()/TCP()
<IP frag=0 proto=TCP |<TCP |>>
>>> Ether()/IP()/TCP()
<Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>>
>>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n"
<IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>>
>>> Ether()/IP()/IP()/UDP()
<Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>>
>>> IP(proto=55)/TCP()
<IP frag=0 proto=55 |<TCP |>>

Aqui está a explicação:

O operador foi usado como operador de composição entre duas camadas.Ao fazer isso, a camada inferior pode ter um ou mais de seus campos de padrões sobrecarregados de acordo com a camada superior.(Você ainda pode dar o valor que deseja).Uma string pode ser usada como camada bruta.

Alternativas ao suporte nativo de sobrecarga do operador Java

Como Java não tem sobrecarga de operadores, aqui estão algumas alternativas que você pode procurar:

  1. Use outro idioma.Ambos Legal e escala têm sobrecarga de operador e são baseados em Java.
  2. Usar java-oo, um plugin que permite a sobrecarga do operador em Java.Observe que NÃO é independente de plataforma.Além disso, tem muitos problemas e não é compatível com as versões mais recentes do Java (ou seja,Java 10).(Fonte original do StackOverflow)
  3. Usar JNI, Java Native Interface ou alternativas.Isso permite que você escreva métodos C ou C++ (talvez outros?) Para uso em Java.É claro que isso também NÃO é independente de plataforma.

Se alguém souber de outros, por favor, comente e irei adicioná-lo a esta lista.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top