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:
// 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 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:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
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 uma MyComplexNumber
classe para que ele retornasse a hora estrita do dia. A toString()
sobrecarga também deve ser proibida? Poderíamos sabotar MyComplexNumber.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 um Cloneable
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:
// 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.) ...
Acessores e subscrição de matriz / contêiner naturais:
// 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 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 ":
// 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. 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
Funções 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ê 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:
// 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 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 uma add
função" ou "retorne porcaria em um clone
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:
Eu deixei de fora a sobrecarga do operador como uma opção 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 o de Stroustrup abaixo:
Muitas decisões de projeto em C ++ têm suas raízes na minha aversão por forçar as pessoas a fazer as coisas de alguma maneira específica [...] Muitas vezes, eu fui tentado a proibir um recurso que eu pessoalmente não gostei, me abstive de fazê-lo porque não achava que tinha. o direito de forçar meus pontos de vista sobre os outros .
Bjarne Stroustrup. Fonte: O Design e Evolução do C ++ (1.3 Contexto Geral)
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 um struct
deriva de objeto" , deu certo na primeira tentativa.
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...