|
Operadores |
Anterior | Siguiente |
Los operadores de Java son muy parecidos en
estilo y funcionamiento a los de C. Tanto C, como C++, como Java, proporcionan un conjunto
de operadores para poder realizar acciones sobre uno o dos operandos. Un operador que
actúa sobre un solo operando es un operador unario, y un operador que actúa sobre
dos operandos es un operador binario. Algunos operadores pueden funcionar como unarios y como binarios, el ejemplo más claro es el operador - (signo menos). Como operador binario, el signo menos hace que el operando de la derecha sea sustraido al operando de la izquierda; como operador unario hace que el signo algebraico del operando que se encuentre a su derecha sea cambiado. En la siguiente tabla aparecen los operadores que se utilizan en Java, por orden de precedencia: . [] () ++ -- ! ~ instanceof * / % + - << >> >>> < > <= >= == != & ^ | && || ? : = op= (*= /= %= += -= etc.) , Los operadores numéricos se comportan como esperamos: int + int = int Los operadores relacionales devuelven un valor booleano. Para las cadenas, se pueden utilizar los operadores relacionales para comparaciones además de + y += para la concatenación: String nombre = "nombre" + "Apellido"; El operador = siempre hace copias de objetos, marcando los antiguos para borrarlos, y ya se encargará el garbage collector de devolver al sistema la memoria ocupada por el objeto eliminado. Java, a diferencia de C++, no soporta la sobrecarga de operadores. Esto significa que no es posible redefinir el entorno en el que actúa un operador con respecto a los objetos de un nuevo tipo que el programador haya definido como propios. Un caso interesante, que se sale de la afirmación anterior, es el operador + (signo más), que se puede utilizar para realizar una suma aritmética o para concatenar cadenas (¡Java lo sobrecarga internamente!). Cuando el signo más se utiliza en esta última forma, el operando de la derecha se convierte automáticamente en una cadena de caracteres antes de ser concatenada con el operando que se encuentra a la izquierda del operador +. Esto asume que el compilador sabe que el operando de la derecha es capaz de soportar la conversión. Y ese conocimiento lo tiene porque comprueba todos los tipos primitivos y muchos de los tipos internos que se utilizan. Obviamente, el compilador no sabe absolutamente nada de los tipos que haya definido el programador. A continuación se detallan algunos de los operadores que admite Java y que se suelen agrupar en operadores aritméticos, relacionales y condicionales, operadores lógicos y que actúan sobre bits y, finalmente, operadores de asignación. Java soporta varios operadores aritméticos que actúan sobre números enteros y números en coma flotante. Los operadores binarios soportados por Java son:
Como se ha indicado anteriormente, el operador más (+), se puede utilizar para concatenar cadenas, como se observa en el ejemplo siguiente: "miVariable tiene el valor " + miVariable + " en este programa" Esta operación hace que se tome la representación como cadena del valor de miVariable para su uso exclusivo en la expresión. De ninguna forma se altera el valor que contiene la variable. En C++ no se soporta la concatenación de cadenas con el operador más (+); hay que utilizar la función strcat() para poder concatenar dos cadenas y el uso de esta función puede resultar peligroso porque no hay protección contra el traspaso de los límites del array que se utiliza como destino de la cadena concatenada. El operador módulo (%), que devuelve el resto de una división, a diferencia de C++, en Java funciona con tipos en coma flotante además de con tipos enteros. Cuando se ejecuta el programa que se muestra en el ejemplo java401.java. La salida que se obtiene por pantalla tras la ejecución del ejemplo es la que se reproduce a continuación: %java java401 x mod 10 = 3 y mod 10 = 3.299999999999997 Los operadores unarios que soporta Java son:
En los operadores de incremento (++) y decremento (--), en la versión prefijo, el operando aparece a la derecha del operador, ++x; mientras que en la versión sufijo, el operando aparece a la izquierda del operador, x++. La diferencia entre estas versiones es el momento en el tiempo en que se realiza la operación representada por el operador si éste y su operando aparecen en una expresión larga. Con la versión prefijo, la variable se incrementa (o decrementa) antes de que sea utilizada para evaluar la expresión en que se encuentre, mientras que en la versión sufijo, se utiliza la variable para realizar la evaluación de la expresión y luego se incrementa (o decrementa) en una unidad su valor. Operadores Relacionales y Condicionales Aunque Java y C++ soportan los mismos operadores relacionales, devuelven valores diferentes. Los operadores relacionales en Java devuelven un tipo booleano, true o false; mientras que en C++ devuelven un valor entero, en donde se puede considerar al valor cero como false y a un valor no-cero como true.
Los operadores relacionales combinados con los operadores condicionales (o lógicos en C++), se utilizan para obtener expresiones más complejas. Los operadores condicionales que soporta Java son:
Los programadores C++ se encontrarán con que Java restringe el uso de estos operadores, porque en C++ la representación de true y false es muy diferente a Java. En Java, los operandos de estos operadores deben ser tipos booleanos, o expresiones que devuelvan un tipo booleano, mientras que en C++ puede ser cualquier tipo entero o expresión que devuelva un tipo entero, sin restricciones. Una característica importante del funcionamiento de los operadores && y ||, tanto en Java como en C++ (y que es pasado a veces por alto, incluso por programadores experimentados) es que las expresiones se evalúan de izquierda a derecha y que la evaluación de la expresión finaliza tan pronto como se pueda determinar el valor de la expresión. Por ejemplo, en la expresión siguiente: ( a < b ) || ( c < d ) si la variable a es menor que la variable b, no hay necesidad de evaluar el operando izquierdo del operador || para determinar el valor de la expresión entera. En casos de complicadas, complejas y largas expresiones, el orden en que se realizan estas comprobaciones puede ser fundamental, y cualquier error en la colocación de los operandos puede dar al traste con la evaluación que se desea realizar y, estos errores son harto difíciles de detectar, ya que se debe estudiar concienzudamente el resultado de las expresiones en tiempo de ejecución para poder detectar el problema. Java y C, C++ comparten un conjunto de operadores que realizan operaciones sobre un solo bit cada vez. Java también soporta el operador >>>, que no existe en C o C++ y, además, el entorno en el que se realizan algunas de las operaciones no siempre es el mismo en Java que en los otros lenguajes. Los operadores de bits que soporta Java son los que aparecen en la siguiente tablita:
En Java, el operador de desplazamiento hacia la derecha sin signo, rellena los bits que pueden quedar vacíos con ceros. Los bits que son desplazados fuera del entorno se pierden. En el desplazamiento a la izquierda, hay que ser precavidos cuando se trata de desplazar enteros positivos pequeños, porque el desplazamiento a la izquierda tiene el efecto de multiplicar por 2 para cada posición de bit que se desplace; y esto es peligroso porque si se desplaza un bit 1 a la posición más alta, el bit 31 o el 63, el valor se convertirá en negativo. El operador = es un operador binario de asignación de valores. El valor almacenado en la memoria y representado por el operando situado a la derecha del operador es copiado en la memoria indicada por el operando de la izquierda. Al contrario que en C++, el operador de asignación (ni ningún otro) no se puede sobrecargar en Java. Java soporta toda la panoplia de operadores de asignación que se componen con otros operadores para realizar la operación que indique ese operador y luego asignar el valor obtenido al operando situado a la izquierda del operador de asignación. De este modo se pueden realizar dos operaciones con un solo operador. += -= *= /= %= &= |= ^= <<= >>= >>>= Por ejemplo, las dos sentencias que siguen realizan la misma función: x += y; x = x + y; Y las otras comprobaciones siguen el mismo patrón. C++ no soporta el operador >>>= porque tampoco soporta el operador a nivel de bits de desplazamiento sin signo (>>>). Operadores Ternario if-then-else Java, lo mismo que C y C++, soporta este operador ternario. No obstante, la construcción utilizada por este operador es lago confusa, aunque se puede llegar a entender perfectamente cuando uno lee en el pensamiento lo que está escrito en el código. La forma general del operador es: expresion ? sentencia1 : sentencia2 en donde expresion puede ser cualquier expresión de la que se obtenga como resultado un valor booleano, porque en Java todas las expresiones condicionales se evalúan a booleano; y si es true entonces se ejecuta la sentencia1 y en caso contrario se ejecuta la sentencia2. La limitación que impone el operador es que sentencia1 y sentencia2 deben devolver el mismo tipo, y éste no puede ser void. Puede resultar útil para evaluar algún valor que seleccione una expresión a utilizar, como en la siguiente sentencia: cociente = denominador == 0 ? 0 : numerador / denominador Cuando Java evalúa la asignación, primero mira la expresión que está a la izquierda del interrogante. Si denominador es cero, entonces evalúa la expresión que está entre el interrogante y los dos puntos, y se utiliza como valor de la expresión completa. Si denominador no es cero, entonces evalúa la expresión que está después de los dos puntos y se utiliza el resultado como valor de la expresión completa, que se asigna a la variable que está a la izquierda del operador de asignación, cociente. En el ejemplo java402.java se utiliza este operador para comprobar que el denominador no es cero antes de evaluar la expresión que divide por él, devolviendo un cero en caso contrario. Hay dos expresiones, una que tiene un denominados cero y otra que no. class java402 { public static void main( String args[] ) { int a = 28; int b = 4; int c = 45; int d = 0; // Utilizamos el operador ternario para asignar valores a // las dos variables e y f, que son resultado de la // evaluación realizada por el operador int e = (b == 0) ? 0 : (a / b); int f = (d == 0) ? 0 : (c / d); // int f = c / d; System.out.println( "a = " + a ); System.out.println( "b = " + b ); System.out.println( "c = " + c ); System.out.println( "d = " + d ); System.out.println(); System.out.println( "a / b = " + e ); System.out.println( "c / d = " + f ); } } El programa se ejecuta sin errores, y la salida que genera por pantalla es la que se reproduce: %java java402 a = 28 b = 4 c = 45 d = 0 a / b = 7 c / d = 0 Si ahora cambiamos la línea que asigna un valor a la variable f, y quitamos este operador, dejándola como: int f = c / d; se producirá una excepción en tiempo de ejecución de división por cero, deteniéndose la ejecución del programa en esa línea. %java java402 java.lang.ArithmeticException: / by zero at java402.main(java402.java:40) Errores comunes en el uso de Operadores Uno de los errores más comunes que se cometen cuando se utilizan operadores es el uso incorrecto, o no uso, de los paréntesis cuando la expresión que se va a evaluar es compleja. Y esto sigue siendo cierto en Java. Cuando se programa en C/C++, el error más común es el siguiente: while( x = y ) { // . . . } EL programador intenta realmente hacer una equivalencia (==) en vez de una asignación. En C/C++ el resultado de esta asignación siempre será true si y no es cero, y lo más seguro es que esto se convierta en un bucle infinito. En Java, el resultado de esta expresión no es un booleano, y el compilador espera un booleano, que no puede conseguir a partir de un entero, así que aparecerá un error en tiempo de compilación que evitará que el programa se caiga al ejecutarlo. Es decir, este error tan común, para que se dé en Java tienen que ser x e y booleanos, lo cual no es lo más usual. Otro error muy difícil de detectar en C++ es cuando se utiliza el operador unario & cuando la lógica requiere un operador &&; o utilizar el operador unario |, en lugar del operador ||. Este es otro problema difícil de detectar porque el resultado es casi siempre el mismo, aunque sólo por accidente. En Java, no ocurre esto y se puede utilizar perfectamente el operador unario & como sinónimo del operador &&, o el operador | como sinónimo de ||; pero si se hace así, la evaluación de la expresión no concluirá hasta que todos los operandos hayan sido evaluados, perdiendo de esta forma las posibles ventajas que podría reportar la evaluación de izquierda a derecha. Es lo que se conoce como casting, refiriéndose a "colocar un molde". Java automáticamente cambia el tipo de un dato a otro cuando es pertinente. Por ejemplo, si se asigna un valor entero a una variable declarada como flotante, el compilador convierte automáticamente el int a float. El casting, o moldeo, permite hacer esto explícitamente, o forzarlo cuando normalmente no se haría. Para realizar un moldeo, se coloca el tipo de dato que se desea (incluyendo todos los modificadores) dentro de paréntesis a la izquierda del valor. Por ejemplo: void moldeos() { int i = 100; long l = (long)i; long l2 = (long)200; } Como se puede ver, es posible realizar el moldeo de un valor numérico del mismo modo que el de una variable. No obstante, en el ejemplo el moldeo es superfluo porque el compilador ya promociona los enteros a flotantes cuando es necesario automáticamente, sin necesidad de que se le indique. Aunque hay otras ocasiones en que es imprescindible colocar el moldeo para que el código compile. En C/C++ el moldeo puede causar auténticos quebraderos de cabeza. En Java, el moldeo es seguro, con la excepción de cuando se realiza una conversión estrecha, es decir, cuando se quiere pasar de un dato que contiene mucha información a otro que no puede contener tanta, en donde se corre el riesgo de perder información. Aquí es donde el compilador fuerza a que se coloque un moldeo expreso, indicando al programador que "esto puede ser algo peligroso, así que si quieres que lo haga, dímelo expresamente". Cuando se trata de una conversión ancha, no es necesario el moldeo explícito, ya que el nuevo tipo podrá contener más información que la que contiene el tipo original, no ocasionándose ninguna pérdida de información, así que el compilador puede realizar el moldeo explícitamente, sin la intervención del programador para confirmar la acción. Java permite el moldeo de cualquier tipo primitivo en otro tipo primitivo, excepto en el caso de los booleanos, en que no se permite moldeo alguno. Tampoco se permite el moldeo de clases. Para convertir una clase en otra hay que utilizar métodos específicos. La clase String es un caso especial que se verá más adelante. |
|