Sobreescritura y Sobrecarga de Operadores

Definiciones

Esto es lo que dice la wikipedia sobre Method overriding:

Method overriding, in object oriented programming, is a language feature that allows a subclass to provide a specific implementation of a method that is already provided by one of its superclasses. The implementation in the subclass overrides (replaces) the implementation in the superclass.

A subclass can give its own definition of methods which also happen to have the same signature as the method in its superclass. This means that the subclass's method has the same name and parameter list as the superclass's overridden method. Constraints on the similarity of return type vary from language to language, as some languages support covariance on return types.

Method overriding is an important feature that facilitates polymorphism2.1 in the design of object-oriented programs.

Some languages allow the programmer to prevent a method from being overridden, or disallow method overriding in certain core classes. This may or may not involve an inability to subclass from a given class.

In many cases, abstract classes are designed — i.e. classes that exist only in order to have specialized subclasses derived from them. Such abstract classes have methods that do not perform any useful operations and are meant to be overridden by specific implementations in the subclasses. Thus, the abstract superclass defines a common interface which all the subclasses inherit.

En el caso de los operadores, un término mas convencional que redefinición o sobreescritura (overriding) es overloading o sobrecarga de operadores [1], [2]. La diferencia es que sobrecarga sugiere que hay múltiples instancias de un método (y por tanto del correspondiente operador asociado) que sólo difieren en el tipo de sus parámetros.

Operadores que pueden Sobrecargarse

Citemos la documentación de groovy en http://groovy.codehaus.org/Operator+Overloading:

Groovy supports operator overloading which makes working with Numbers, Collections, Maps and various other data structures easier to use.

Various operators in Groovy are mapped onto regular Java method calls on objects. This allows you the developer to provide your own Java or Groovy objects which can take advantage of operator overloading. The following table describes the operators supported in Groovy and the methods they map to.

Operator                Method
a + b                    a.plus(b)
a - b                    a.minus(b)
a * b                    a.multiply(b)
a ** b                   a.power(b)
a / b                    a.div(b)
a % b                    a.mod(b)
a | b                    a.or(b)
a & b                    a.and(b)
a ^ b                    a.xor(b)
a++ or ++a               a.next()
a-- or --a               a.previous()
a[b]                     a.getAt(b)
a[b] = c                 a.putAt(b, c)
a << b                   a.leftShift(b)
a >> b                   a.rightShift(b)
switch(a) { case(b) : }  b.isCase(a)
~a                       a.bitwiseNegate()
-a                       a.negative()
+a                       a.positive()

Operadores de Comparación y Manejo de Valores Nulos

Note that all the following comparison operators handle nulls gracefully avoiding the throwing of java.lang.NullPointerException
Operator        Method
a == b          a.equals(b) or a.compareTo(b) == 0 **
a != b          ! a.equals(b)
a <=> b         a.compareTo(b)
a > b           a.compareTo(b) > 0
a >= b          a.compareTo(b) >= 0
a < b           a.compareTo(b) < 0
a <= b          a.compareTo(b) <= 0

Also in Groovy comparison operators handle nulls gracefully. So that a == b will never throw a NullPointerException whether a or b or both are null.

groovy:000> a = null
===> null
groovy:000> b = "foo"
===> foo
groovy:000> a != b
===> true
groovy:000> b != a
===> true
groovy:000> a == null
===> true

Coerción y Operadores de Comparación

In addition when comparing numbers of different types the type coercion rules apply to convert numbers to the largest numeric type before the comparison. So the following is valid in Groovy

~/src/groovy/overloading$ cat -n Numberscohercioncomparison.groovy 
 1  #!/usr/bin/env groovy
 2  Byte a = 12
 3  Double b = 256
 4  
 5  println  "a($a) instanceof Byte: ${a instanceof Byte}"
 6  println  "b($b) instanceof Double: ${b instanceof Double}"
 7  
 8  if (b > a) {
 9    println "(b > a): Cohercion working. $b is greater than $a" 
10    println  "($b > $a) instanceof Boolean: ${(b > a) instanceof Boolean}"
11  }
12  
13  if (a > (Byte) b) {
14    println "Byte $a is greater than (byte) Double $b" 
15    println  "($a > (Byte) $b) instanceof Boolean: ${(a > (Byte) b) instanceof Boolean}"
16  }
Al ejecutar este ejemplo obtenemos la salida:
~/src/groovy/overloading$ ./Numberscohercioncomparison.groovy 
a(12) instanceof Byte: true
b(256.0) instanceof Double: true
(b > a): Cohercion working. 256.0 is greater than 12
(256.0 > 12) instanceof Boolean: true
Byte 12 is greater than (byte) Double 256.0
(12 > (Byte) 256.0) instanceof Boolean: true

Sobreescritura de la asignación

En Java:
The equals() method of java.lang.Object acts the same as the == operator; that is, it tests for object identity rather than object equality. The implicit contract2.2 of the equals() method, however, is that it tests for equality rather than identity. Thus most classes will override equals() with a version that does field by field comparisons before deciding whether to return true or false.

En Groovy:

The == operator doesn't always exactly match the .equals() method. You can think of them as equivalent in most situations. In situations where two objects might be thought equal via normal Groovy coercion mechanisms, the == operator will report them as equal; the .equals() method will not do so if doing so would break the normal rules Java has around the equals method. Expect further improvements to Groovy over time to provide clearer, more powerful and more consistent behavior in this area.

Sobrecarga del Operador in via isCase

Sobreescribir isCase altera la conducta del operador in. El siguiente programa:

generaciondecodigos@nereida:~/Lgroovy/overloading$ cat -n isCase1.groovy 
 1  class A{
 2    boolean isCase(Object o){
 3      if(o == 'A') return true
 4      else return false
 5    }
 6  }
 7  
 8  def a= new A()
 9  
10  println "a.isCase('A')  = "+a.isCase('A')
11  println "('A' in a)     = "+('A' in a)         //more common, shortcut syntax for isCase()
12  
13  println "(a.isCase('Z') = "+ (a.isCase('Z'))
14  println "('Z' in a)     = "+ ('Z' in a) //more common, shortcut syntax for isCase()

Produce la salida:

generaciondecodigos@nereida:~/Lgroovy/overloading$ groovy isCase1.groovy 
a.isCase('A')  = true
('A' in a)     = true
(a.isCase('Z') = false
('Z' in a)     = false

He aqui otro ejemplo:

generaciondecodigos@nereida:~/Lgroovy/overloading$ cat -n isCase2.groovy 
 1  class MyList extends ArrayList {
 2      boolean isCase(Object val) {
 3        return val == 66
 4      }
 5  }
 6  
 7  def myList = new MyList()
 8  myList << 55
 9  println (55 in myList) // return false but myList.contains(55) returns true
10  println (66 in myList) // returns true but myList.contains(55) returns false

Sobrecarga del Switch via isCase

The switch statement inspects an expression and resumes execution from the first matching case-expression, ie, regex matched, list or set or range contained in, class an instance of, or object equal to.

Veamos un ejemplo:

generaciondecodigos@nereida:~/Lgroovy/overloading$ cat -n Switch1.groovy 
     1  def values= [
     2      'abc': 'abc',
     3      'xyz': 'list',
     4         18: 'range',
     5         31: BigInteger,
     6    'dream': 'something beginning with dr',
     7       1.23: 'none',
     8  ]
     9  values.each{
    10    def result
    11    switch( it.key ){
    12      case 'abc': //if switched expression matches case-expression, execute all
    13                  //statements until 'break'
    14        result= 'abc'
    15        break
    16      case [4, 5, 6, 'xyz']:
    17        result= 'list'
    18        break
    19      case 'xyz': //this case is never chosen because 'xyz' is matched by
    20                  //previous case, then 'break' executed
    21        result= 'xyz'
    22        break
    23      case 12..30:
    24        result= 'range'
    25        break
    26      case Integer:
    27        result= Integer //because this case doesn't have a 'break', result
    28                        //overwritten by BigInteger in next line
    29      case BigInteger:
    30        result= BigInteger
    31        break
    32      case ~/dr.*/:
    33        result= 'something beginning with dr'
    34        break
    35      case {it instanceof Integer && it>30}: //use Closure
    36        result= 'result is > 30'
    37        break
    38      default:
    39        result= 'none'
    40    }
    41    println "it = (${it.key}, ${it.value}) => result == $result"
    42  }
Este es el resultado de la ejecución:
generaciondecodigos@nereida:~/Lgroovy/overloading$ groovy Switch1.groovy 
it = (abc, abc) => result == abc
it = (xyz, list) => result == list
it = (18, range) => result == range
it = (31, class java.math.BigInteger) => result == class java.math.BigInteger
it = (dream, something beginning with dr) => result == something beginning with dr
it = (1.23, none) => result == none

When we supply our own values in the case-expression, the isCase method is invoked to determine whether or not the switch-expression is matched. If there's no isCase method, the equals method is used to test for equality:

generaciondecodigos@nereida:~/Lgroovy/overloading$ cat -n isCase3.groovy 
     1  class A{
     2    boolean isCase(Object switchValue){ //'isCase' method used for case-expression
     3      if(switchValue == 'Hi') return true
     4      else return false
     5    }
     6  }
     7  switch( 'Hi' ){
     8    case new A():
     9      println "case A"
    10      break
    11    default:
    12      println "A was false"
    13  }
    14  
    15  class B{
    16    boolean equals(Object switchValue){ //'equals' method used for case-expression
    17      this.class == switchValue.getClass()
    18    }
    19  }
    20  switch( new B() ){
    21    case new B():
    22      println "case B" 
    23      break
    24    default:
    25      println "B was false"
    26  }
El resultado de la ejecución del programa anterior es:
generaciondecodigos@nereida:~/Lgroovy/overloading$ groovy isCase3.groovy 
case A
case B

"because a Java switch/case does not work like a Groovy switch/case. In Java a case can take only int compatible constants, in Groovy it can take expressions. In Java all cases share a scope, in Groovy each case has its own scope. In Groovy we call the isCase method, in Java it has to be a number we switch with. If we for example use a closure as case, then this might cause side effects. There are cases where we can let them behave the same and usually when using the java version you won't see a difference in Groovy besides the placement and logic of default."

So, while in Java the default can be placed anywhere in the switch/case, the default in Groovy is used more as an else than assigning a default case.

Ejemplo: Operaciones con Divisas

El siguiente ejemplo muestra implementaciones de los operadores de igualdad equals y suma plus para una clase Money.

generaciondecodigos@nereida:~/src/groovy/overloading$ cat -n Operator_overrride.groovy 
 1  #!/usr/bin/env groovy
 2  class Money {
 3    private int amount
 4    private String currency
 5  
 6    Money(amountValue, currencyValue) {
 7      amount = amountValue
 8      currency = currencyValue
 9    }
10    
11    boolean equals (Object other) {
12      if (null == other)               return false
13      if (!(other instanceof Money))   return false
14      if (currency !=  other.currency) return false
15      if (amount != other.amount)      return false
16  
17      return true
18    }
19  
20    int hashCode() {
21      amount.hashCode()+currency.hashCode()
22    }
23    
24    Money plus(Money other) {
25      if (other == null)               return null
26      if (other.currency != currency)
27        throw new IllegalArgumentException(
28          "cannot add $other.currency to $currency"
29        )
30      return new Money(amount+other.amount, currency)
31    }
32  
33    String toString() {
34      "$amount $currency"
35    }
36  }
37  
38  
39  def buck = new Money(1, '$')
40  println buck+buck
41  
42  def euro = new Money(1, '€')
43  println euro+euro
44  
45  assert(buck == new Money(1, '$'))

Véase También



Subsecciones
Casiano Rodríguez León
2010-04-30