Home » .NET

Por qué Java no y .NET sí? – Sobrecarga de operadores

22. April 2009 by rene_pacios 0 Comments

image Siguiendo con esta línea de post, comparativos entre Java y .NET, casi objetiva, xD, Tal vez es porque las prácticas de la facultad me caen en Java, y cuando me pongo a ver un ejercicio o algo, siempre termino en Google, preguntándome/le porque el NetBeans no me colorea la palabra Operator y la respuestas que me he encontrado son que java no soporta la sobrecarga de operadores , aunque según he leído si lo utiliza internamente, esto es fácilmente deducible ya que para concatenar 2 cadenas de caracteres se utiliza el signo + al igual que para sumar 2 enteros.

No pienso enrollarme mucho en este post, existen multitud de tutoriales donde explican como funciona la sobrecarga de operadores, tan solo os voy a plasmar el código de una práctica de la facultad en una y otra plataforma, y dejaré que seáis vosotros los que opinéis. La practica requería implementar una clase para trabajar con fracciones matemáticas, también llamados quebrados, a muchos al igual que a mi seguro se os ocurriría que es el típico ejercicio para utilizar la sobrecarga de operadores, al igual que si os comentan el problema de hallar el factorial de un número, se os puede venir a la mente que es el típico ejercicio de recursividad, pero como ya comenté, java no los soporta.

Este sería el código de clase en Java ( a ver si se colorea bien diciendo que es C#)

   1:  
   2: class QuebradoException extends Exception{
   3:     public QuebradoException() {}
   4:     public QuebradoException(String mensaje) {
   5:     super(mensaje);
   6:     }
   7: }
   8:  
   9: public class Quebrado implements Cloneable 
  10: {
  11:     private int n;
  12:     private int d;
  13:  
  14:     /*---------- Constructores ----------- */
  15:     public Quebrado(int n, int d) {
  16:         this.set(n,d);
  17:         }
  18:     Quebrado(int n) {
  19:         this(n,1);
  20:     }
  21:     
  22:     /*---------- Métodos de consulta ----------*/
  23:     public int num() {  
  24:             return(this.n);
  25:         }
  26:     public int den() {
  27:         return(this.d);
  28:     }
  29:         
  30:     public Quebrado por(Quebrado q) {
  31:         return(new Quebrado( this.num()*q.num(), this.den()*q.den() ));
  32:     }
  33:     
  34:     public Quebrado sum(Quebrado q) {
  35:         return(new Quebrado( this.num()*q.den()+q.num()*this.den(), this.den()*q.den() ) );
  36:     }
  37:     
  38:     public Quebrado res(Quebrado q) {
  39:         return(sum(q.por(new Quebrado(-1))));
  40:     }
  41:     
  42:     public Quebrado div(Quebrado q) throws QuebradoException {
  43:         if(q.num()==0) {
  44:         throw (new QuebradoException());
  45:         }
  46:         
  47:         return(new Quebrado( this.num()*q.den(), this.den()*q.num() ));
  48:     
  49:     }
  50:     public boolean equals(Quebrado q) {
  51:         
  52:         // Dos numeros son iguales si al dividirlos obtenemos 1 o, en el caso de los quebrados, 
  53:         // si el numerador y denominador de la división son iguales
  54:         return( this.num()*q.den()==this.den()*q.num() );
  55:     }
  56:     
  57:     public static int mcd(int N, int D) {
  58:         int a; // dividendo
  59:         int b; // divisor
  60:         int r; // resto
  61:     
  62:         // algoritmo de Euclides
  63:         a = Math.abs(N);
  64:         b = Math.abs(D);
  65:         
  66:         
  67:         while (b!=0) {
  68:             r = a % b;
  69:                 a = b;
  70:                 b = r;
  71:         }    
  72:         return(a);
  73:     } 
  74:     
  75:     public void set(int n, int d) {
  76:             
  77:         if(n*d<0)
  78:             { n=-1*Math.abs(n); }
  79:         else 
  80:             { n=Math.abs(n); }
  81:         d=Math.abs(d);
  82:         
  83:         this.n=n;
  84:         this.d=d;
  85:     }
  86:     
  87:     public void simplifica() {
  88:         
  89:         int mimcd=0;
  90:         int nn=Math.abs(this.num());
  91:         int dd=Math.abs(this.den());
  92:         
  93:         if(nn>=dd)
  94:             { mimcd=mcd(nn, dd); }
  95:         else
  96:             { mimcd=mcd(dd, nn); }
  97:         
  98:         this.set(this.n/mimcd, this.d/mimcd);
  99:     }
 100:         
 101:     public String toString() {
 102:         return(this.num()+"/"+this.den());
 103:     }
 104:         
 105:     protected Object clone() throws CloneNotSupportedException{
 106:         // se puede hacer un clonado "simple" al no ser un objeto compuesto
 107:         return(super.clone());
 108:     }
 109:  
 110: } // Class Quebrado

Como podéis apreciar es un código bastante sencillo, en donde podemos realizar las operaciones básicas (sumar, restar, simplificar,etc.) con las fracciones, si eres programador en .NET o en C++ o algún otro lenguaje que soporte sobrecarga de operadores, seguramente se te habría ocurrido algo similar a esto:

   1:  
   2: Public Class QuebradoException
   3:     Inherits System.Exception
   4:     Public Sub New()
   5:         MyBase.New()
   6:     End Sub
   7:  
   8:     Public Sub New(ByVal message As String)
   9:         MyBase.New(message)
  10:     End Sub
  11:  
  12:     Public Sub New(ByVal message As String, ByVal inner As Exception)
  13:         MyBase.New(message, inner)
  14:     End Sub
  15:  
  16:     Public Sub New( _
  17:         ByVal info As System.Runtime.Serialization.SerializationInfo, _
  18:         ByVal context As System.Runtime.Serialization.StreamingContext)
  19:         MyBase.New(info, context)
  20:     End Sub
  21: End Class
  22:  
  23: Public Class Quebrado
  24:     Implements ICloneable
  25:  
  26: #Region "Atributos de clase"
  27:     Private _n As Integer
  28:     Private _d As Integer
  29: #End Region
  30:  
  31: #Region "Contructores"
  32:     Public Sub New(ByVal n As Integer, ByVal d As Integer)
  33:         _n = n
  34:         _d = d
  35:     End Sub
  36:     Public Sub New(ByVal n As Integer)
  37:         Me.New(n, 1)
  38:     End Sub
  39: #End Region
  40:  
  41: #Region "Propiedades"
  42:     'al hacer las propiedades de lectura/escritora, 
  43:     'no veo necesario implementar la funcion set (Vease clase Java)
  44:     Public Property Num() As Integer
  45:         Get
  46:             Return _n
  47:         End Get
  48:         Set(ByVal value As Integer)
  49:             _n = value
  50:         End Set
  51:     End Property
  52:  
  53:     Public Property Den() As Integer
  54:         Get
  55:             Return _d
  56:         End Get
  57:         Set(ByVal value As Integer)
  58:             _d = value
  59:         End Set
  60:     End Property
  61:  
  62: #End Region
  63:  
  64: #Region "Operaciones"
  65:     Public Shared Operator *(ByVal q1 As Quebrado, ByVal q2 As Quebrado) As Quebrado
  66:         Return New Quebrado(q1.Num() * q2.Num(), q1.Den() * q2.Den())
  67:     End Operator
  68:  
  69:     Public Shared Operator +(ByVal q1 As Quebrado, ByVal q2 As Quebrado) As Quebrado
  70:         Return New Quebrado(q1.Num() * q2.Den() + q2.Num() * q1.Den(), q1.Den() * q2.Den())
  71:     End Operator
  72:  
  73:     Public Shared Operator -(ByVal q1 As Quebrado, ByVal q2 As Quebrado) As Quebrado
  74:         Return q1 + (q2 * New Quebrado(-1))
  75:     End Operator
  76:  
  77:     Public Shared Operator /(ByVal q1 As Quebrado, ByVal q2 As Quebrado) As Quebrado
  78:         If q2.Num = 0 Then Throw New QuebradoException
  79:         Return New Quebrado(q1.Num() * q2.Den(), q1.Den() * q2.Num())
  80:     End Operator
  81: #End Region
  82:  
  83: #Region "Funciones"
  84:     Public Overrides Function Equals(ByVal obj As Object) As Boolean
  85:         ' Dos numeros son iguales si al dividirlos obtenemos 1 o, en el caso de los quebrados, 
  86:         ' si el numerador y denominador de la división son iguales
  87:         Return (Me.Num() * CType(obj, Quebrado).Den()) = (Me.Den() * CType(obj, Quebrado).Num())
  88:     End Function
  89:  
  90:     Public Overrides Function ToString() As String
  91:         Return Num() & "/" & Den()
  92:     End Function
  93:  
  94:     Public Shared Function mcd(ByVal N As Integer, ByVal D As Integer) As Integer
  95:         Dim a, b, r As Integer 'dividendo, divisor, resto respectivamente
  96:         'usamos el algoritmo de Euclides
  97:         a = Math.Abs(N)
  98:         b = Math.Abs(D)
  99:  
 100:         While b <> 0
 101:             r = a Mod b
 102:             a = b
 103:             b = r
 104:         End While
 105:         Return a
 106:     End Function
 107:  
 108:     Public Sub simplifica()
 109:         Dim mimcd As Integer = 0
 110:         Dim nn, dd As Integer
 111:         nn = Math.Abs(Me.Num())
 112:         dd = Math.Abs(Me.Den())
 113:  
 114:         'otra forma de poner las operaciones condicionales, que terminan con una simple asignacion
 115:         mimcd = IIf(nn >= dd, mcd(nn, dd), mcd(dd, nn))
 116:         If (nn >= dd) Then
 117:             mimcd = mcd(nn, dd)
 118:         Else
 119:             mimcd = mcd(dd, nn)
 120:         End If
 121:  
 122:         Me.Num /= mimcd
 123:         Me.Den /= mimcd
 124:     End Sub
 125:  
 126:     Public Function Clone() As Object Implements System.ICloneable.Clone
 127:         ' Se puede hacer una copia superficial del objeto al este no ser compuesto
 128:         Return Me.MemberwiseClone
 129:     End Function
 130:  
 131: #End Region

 

Bueno, pues aquí está el código de la misma clase para VB.NET, no me voy a meter a fondo a comparar los códigos, pero si señalar un par de detalles. En primer lugar como habréis visto, en esta clase sobrecargado los operadores +,-,*,/ he intentado mantener la misma estructura que marcaba el ejercicio en Java para poder hacer la comparación. Alguien podría pensar que porque no he sobrecargado el operador = para comparar 2 quebrados, bueno creo que  es una buena práctica  sobrescribir los métodos heredados de la clase object y también he querido aproximar mi implementación a la propuesta por el ejercicio en Java.

Escribiendo esto me estoy acordando de algo que puede ser interesante y que ahora paso a comentar. Existen algunos métodos, que por el funcionamiento de .NET, deberían implementarse aunque no le tengamos en un principio darles un uso directo, supongo que ya te habrás dado cuenta de a que me refiero. Por ejemplo, si desease comparar dos quebrados para saber cual es el mayor o cual es el menor, podría hacer una función que los comparase simplificados y sobrecargar los operadores >,<,>=,<= para obtener el mayor o el menor,  creo que la forma más optima de hacer esto sería implementado la interfaz IComparable, y redefiniendo la función compareTo(No es necesario que tenga este nombre, pero si que implemente dicho interfaz) , este método, independientemente de que es el que utilizaría para los métodos de los operadores anteriormente descritos, es el que hace posible poder ordenar un colección de objetos, realizar búsquedas más efectivas, etc…, en este caso una colección de Quebrados, sin apenas esfuerzos para .NET.

Cambiando un poco de tema, el uso de “Regiones de código”, seguramente os puede parecer algo trivial, pero una vez que te acostumbras a trabajar con ellas, y a estructurar el código de alguna forma (gusto del consumidor)  es algo que se hecha en falta, os imagináis programar en .NET sin Intellisense con la cantidad de namespaces, clases que hay ? Evidentemente, el uso de regiones en el código no sirve de nada si no es soportado por el IDE de desarrollo. Como anecdota, tengo la teoría de que fue algo que Microsoft se sacó de la manga con VS 2002, ya que en VB 6 y anteriores (supongo, no soy tan mayor, xD) cuando se creaba un formulario con Visual Studio 6, el IDE creaba el código del diseño del mismo (Posiciones de los controles, tamaños, etc…), pero ese código no era editable, era el propio IDE quien lo ocultaba, si querías modificarlo a “pedal”, debías abrir los archivos con algún otro editor de texto. Con la llegada de .NET y el nuevo VS, en la clase de un formulario, aparecía una Reguion llamada “Codigo del diseñador de VS”, o algo así. En esta región es donde el IDE genera el código de posiciones, instancias de controles, etc.. que nosotros modificamos desde el diseñador. Ahora no encuentro el enlace, pero os puedo prometer que, hace ya algún tiempo, he leído que había personas que no les agradaba que apareciese el código del diseñador dentro de la clase, la verdad personalmente no le veía mucho sentido a esta queja, y fue uno de los comentarios que también recuerdo haber leído con la aparición de las clases parciales

Uso de los métodos tipo propiedades: como podéis apreciar la forma de acceder a los atributos denominador y numerador de la clase se realiza mediante al menos un método de por cada atributo accesible, esto lo define el principio de ocultación de la información de las POO, en java esto es realizado por un método normal y corriente, y separando la acción de GET y la de SET en 2 métodos, en .NET, aunque también es posible hacerlo de la misma forma que en Java, existen unos métodos especiales denominados propertys que encapsulan la funcionalidad del Set y Get de los atributos en un sólo método, pudiendo definir el comportamiento del método tanto al asignar un valor como al recuperarlo. Puede suceder que no interese en un determinado problema tener un método Set, en ese caso puede definirse una propiedad como ReadOnly, en donde solo definimos la parte Set del método o análogamente también podemos definirla como WriteOnly. Y que pasa si quiero que todo el dominio de mi aplicación pueda leer un dato y solo puedan escribir el dato las clases que hereden, evidentemente en java para obtener este comportamiento, definimos el método get como public y el el método set como protected. En .NET también se puede hacer por medio de las property , es cierto que no siempre se pudo, si queríamos hacer cosas por el estilo en versiones anteriores al la v 2.0, tendríamos que optar por métodos normales como hace Java. Pero a partir de la versión v2.0 de .NET inclusive, se podría dar distinta accesibilidad a cada “submétodo” de las propertys”.

Otro punto que me parece interesante de las propiedades es que son fácilmente distinguibles del resto de métodos, cosa que en Java no. Vale ponemos un comentario, o pueden ser métodos tan sencillos que también son diferenciables, pero esta diferencia de métodos vs propiedades nos sirve para mapear campos o columnas de controles de datos para mostrar en la capa de presentación, o diferenciarlos cuando usamos reflexion. Como obtendrías dinámicamente todos los datos (atributos) de una clase y el nombre de los mismos desde Java?

De acuerdo, no estoy cumpliendo lo prometido, dije que no me iba a enrollar, pero empiezas a escribir… y bueno ya sabéis lo que pasa, creo que no me queda nada más que comentar, y a decir verdad, si me queda, lo dejaré para otro día. Nada más dejaros un par de códigos de ejemplo para probar las clases creadas y podáis observar  lo sencillo que se hace utilizar la clase hecha con operadores, y lo sencillo que utilizando este método. Y al final os dejo un ejemplo de al vida real donde podréis ver las ventajas de utilizar operadores los cuales Java no soporta (esto en negrilla es para que os animéis a comentar)

Código Main en JAVA:

   1: public class Main {
   2:  
   3:     public static void main(String[] args) {
   4:       
   5:         Quebrado q1=new Quebrado(2,5);
   6:         Quebrado q2=new Quebrado(1,10);
   7:         Quebrado q3=new Quebrado(10,25);
   8:         
   9:         pinta(q1,q2,'+',q1.sum(q2));
  10:         pinta(q1,q2,'-',q1.res(q2));
  11:         pinta(q1,q2,'*',q1.por(q2));
  12:         pinta(q1,q2,'/',q1.div(q2));
  13:         
  14:         if (q1.equals(q3))
  15:              pinta(q1,"Es igual que",q3);                    
  16:         else
  17:             pinta(q1,"NO es igual que",q3);            
  18:         
  19:         if (q1.equals(q2))
  20:              pinta(q1,"Es igual que",q2);                    
  21:         else
  22:             pinta(q1,"NO es igual que",q2);            
  23:         
  24:         Quebrado simple;
  25:         simple=(Quebrado)q3.clone();
  26:         simple.simplifica();
  27:         
  28:         pinta(simple,"Es simplificado de ",q3);
  29:         
  30:     }
  31:       private static void pinta(Quebrado q1,String oper,Quebrado q)
  32:     {
  33:         System.out.println(q1.toString()+" "+oper+" "+ q.toString());
  34:     }
  35:     
  36:     private static void pinta(Quebrado q1,Quebrado q2,char oper,Quebrado q)
  37:     {
  38:         System.out.println(q1.toString()+" "+oper+" "+q2.toString()+" = "+ q.toString());
  39:     }
  40:  
  41: }

Código Main en VB.NET: (Si lo hubiese puesto en C# creo que apenas tendría que cambiar nada xD)

   1: Module Module1
   2:     Sub Main()
   3:         Dim q1, q2, q3 As Quebrado
   4:  
   5:         q1 = New Quebrado(2, 5)
   6:         q2 = New Quebrado(1, 10)
   7:         q3 = New Quebrado(10, 25)
   8:  
   9:         pinta(q1, q2, "+", q1 + q2)
  10:         pinta(q1, q2, "-", q1 - q2)
  11:         pinta(q1, q2, "*", q1 * q2)
  12:         pinta(q1, q2, "/", q1 / q2)
  13:  
  14:         If (q1.equals(q3)) Then
  15:             pinta(q1, "Es igual que", q3)
  16:         Else
  17:             pinta(q1, "NO es igual que", q3)
  18:         End If
  19:  
  20:         If (q1.Equals(q2)) Then
  21:             pinta(q1, "Es igual que", q2)
  22:         Else
  23:             pinta(q1, "NO es igual que", q2)
  24:         End If
  25:  
  26:         Dim simple As Quebrado
  27:         simple = q3.Clone()
  28:         simple.simplifica()
  29:  
  30:         pinta(simple, "Es simplificado de ", q3)
  31:         Console.Read()
  32:  
  33:     End Sub
  34:  
  35:     Private Sub pinta(ByVal q1 As Quebrado, ByVal oper As String, ByVal q2 As Quebrado)
  36:         Console.WriteLine(q1.ToString() + " " + oper + " " + q2.ToString())
  37:     End Sub
  38:     
  39:     Private Sub pinta(ByVal q1 As Quebrado, ByVal q2 As Quebrado, ByVal oper As Char, ByVal q As Quebrado)
  40:         Console.WriteLine(q1.ToString() + " " + oper + " " + q2.ToString() + " = " + q.ToString())
  41:     End Sub
  42:  
  43: End Module

 

Viendo la utilidad: podríamos tener un programa que funcionase con valores Decimales (Double) con lo cual perderíamos precisión, y lo fácil que sería migrar dicho código al trabajo con fracciones, las formulas no deberíamos cambiar nada, ya que si tenemos a+b*c/d+a y contamos con que todos los valores son decimales, y alguno puede ser 3,333333…. podríamos cambiar la definición de las variables, y no tendríamos que cambiar nada en el código ya que nuestra clase utiliza los mismos operadores, y cambiaríamos nuestro valor por 10/3 por ejemplo. Sin embargo, en Java, a parte de tener que cambiar la definición de la variable, tendríamos que variar las fórmulas de mi programa, tal que la expresión anterior: a+b*c/d+a debería cambiarlo por algo como: (a.mas(b.por(c))).div((d.mas)) teniendo evidentemente que tener mucho cuidado con los paréntesis, las operaciones, etc …

Espero que os haya gustado, este fue la práctica que me animó a escribir esta serie de post y que espero poder continuar.

Saludos: René Pacios 

Currently rated 2.0 by 1 people

  • Currently 2/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Comments are closed