it-swarm-pt.tech

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?

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

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

375
rengolin

Supondo que você queria sobrescrever o valor anterior do objeto referido por a, uma função 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) 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 denominado 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 uma 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', já que o objeto 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 o valor e referenciar a igualdade separadamente - operator+ provavelmente lidaria com valores e objetos, 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== estão trabalhando em valores - copiando valores e comparando valores respectivamente.

17
Aaron

Há muitos posts reclamando da 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 código em C ou Java através de funções/métodos 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 objeto que implementa essa interface. Mas você poderia mentir. E crie um objeto diferente. De fato, 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() ;
    }
}

Como a interface Cloneable pode ser abusada/ofuscada, deve ser banida da mesma forma que a sobrecarga do operador C++ deve ser?

Nós poderíamos sobrecarregar o método toString() de uma classe MyComplexNumber para que ele retornasse a hora stringificada do dia. A sobrecarga toString() também deveria ser banida? Poderíamos Sabotar MyComplexNumber.equals para que retorne um valor aleatório, modifique 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 função add que adiciona e um método de implementação Cloneable que é clone 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 métodos Java prístinos, podemos nos perguntar sobre o uso real da sobrecarga de operadores em C++?

Notação clara e natural: métodos 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 objetos primitivos (BigInteger, etc.) ...

Contratores 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 objeto Matrix, encontrado usando os primeiros links encontrados no Google para " objeto Java Matrix " e " c ++ Matrix object ":

// 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 classes 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<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

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

:-D

Código genérico?

Os mesmos operandos de modificação de código genérico devem ser utilizáveis ​​para built-ins/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 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, a significação 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 do que adicionar dois duplos, ou mesmo 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 ponteiros ...).

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

É só que você, como C (por razões históricas) ou Java (por razões pessoais, veja abaixo) codificador, 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 é uma coisa boa), mas usuário definido tipos podem ter usuário definido sobrecargas do operador.

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 classe da linguagem, quando comparados aos tipos internos. Portanto, se os tipos internos tiverem operadores, os tipos de usuários também poderão tê-los.

A verdade é que, como os métodos toString(), clone(), equals() são para Java (ou seja, quase-padrão-likeA sobrecarga do operador C++ faz parte do C++ e torna-se 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 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 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 uma função add", ou "retorne uma porcaria em um método clone").

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

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

Porque James Gosling disse isso:

Eu deixei de sobrecarregar o operador como um escolha bastante pessoal porque eu já vi muitas pessoas abusarem disso 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 projeto em C++ têm suas raízes em minha antipatia por forçar as pessoas a fazerem as coisas de um modo particular [...] Muitas vezes, eu fiquei tentada a proibir um recurso que eu pessoalmente não gostava, eu me abstive de fazê-lo porque Eu não achava que tinha 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 objetos 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 do Gosling escolha pessoal.

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 pessoal do C #, com seus "cada primitivo é um struct e um struct deriva do objeto", acertou na primeira tentativa.

E eles fazem isso em outras linguagens !!!

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

738
paercebal

James Gosling comparou o design de Java ao seguinte:

"Existe 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 caixas, depois passar 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 caixa Então, depois de um mês ou mais, você usou isso para descobrir as coisas de 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 a 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 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, 'new'. A complexidade resultante de combinar isso com passar por valor e exceções é bem abordada no C++ Exceptional book.

39
Garth Gilmour

Confira Boost.Units: link text

Ele fornece análise dimensional com sobrecarga zero através da sobrecarga do operador. Quanto 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;

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

20
user15793

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 objeto é, na verdade, uma referência, a sobrecarga de operadores adquire o risco adicional de ser bastante ilógica - para um programador C++, pelo menos. Compare a situação com a sobrecarga do operador do C # e o Object.Equals e o Object.ReferenceEquals (ou o que quer que seja chamado).

11
Sebastian Redl

Groovy tem sobrecarga de 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 métodos. Por exemplo, '+' chama o método 'mais (argumento)'.

8
noah

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 uma funcionalidade que muitas vezes não teria 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.

6
user14128

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

Pelo menos eu acho que essa é a razão. Eu estou do seu lado de qualquer jeito. :)

5
Sarien

Tecnicamente, existe sobrecarga do operador em todas as linguagens de programação que podem lidar com diferentes tipos de números, por ex. números inteiros e reais. Explanation: O termo sobrecarga significa que existem simplesmente várias implementações para uma função. 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 operador para o operador + para adicionar strings juntos 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 para o operador + para outras classes, por exemplo Corda. No entanto, a maioria das pessoas concorda que, depois de adicionar 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. É ingênuo pensar e, para ser sincero, está ficando velho.

+1 para adicionar sobrecarga do operador no Java 8.

4
Olai

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 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: descartar a capacidade de uso da função !? Esta é uma resposta cômica - "Inapropriado para lógica de operação", todo nome de parâmetro, toda classe, função 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 - ei não diz que você tem que usá-lo. Vamos pegar o C #. Eles abaixaram os ponteiros, mas ei - há um programa de 'código inseguro' - programa como você gosta por sua própria conta e risco.

4
Kvant

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. Ironicamente, adicionar a sobrecarga do operador ao Java nos permitiria criar nossa própria classe Currency, que tornaria esse código matemático elegante e simples (menos obscurecido).

3
Volksman

Assumindo que Java seja a linguagem de implementação, então a, b e c seriam todas referências ao tipo Complex com valores iniciais de null. Também assumindo que Complex é imutável como o BigInteger e imutável semelhante BigDecimal , eu acho que você quer dizer o seguinte, como você está atribuindo a referência para o Complexo retornado da adição b e c, e não comparando esta referência a a.

Não é:

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

much mais simples que:

Complex a, b, c; a = b.add(c);
2
David Schlosnagle

À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 o Java tivesse sobrecarregado o operador, nunca poderíamos ter certeza dos significados do operador sem consultar o código-fonte. No momento isso não é necessário. E acho que seu exemplo de usar métodos em vez de sobrecarga de operadores também é bastante legível. Se você quiser deixar as coisas mais claras, você sempre pode adicionar um comentário acima de frases cabeludas.

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

Alternativas para o suporte nativo de sobrecarga do operador Java

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

  1. Use outro idioma. Ambos Groovy e Scala possuem sobrecarga do operador e são baseados em Java.
  2. Use Java-oo , um plugin que permite a sobrecarga do operador em Java. Note que não é independente de plataforma. Além disso, ele possui muitos problemas e não é compatível com os últimos lançamentos do Java (por exemplo, Java 10). ( Original StackOverflow Source )
  3. Use 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 tiver conhecimento dos outros, por favor comente, e eu vou adicioná-lo a esta lista.

0
gagarwa

Esta não é uma boa razão para rejeitá-la, mas uma prática:

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

>>> 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 um 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 você quer). Uma string pode ser usada como uma camada bruta.

0
Sarien