Java 7: Hacia dónde va y por qué no me gusta

Para aquellos que no hayan visto nada de Java 7, el “Project Coin” ha aprobado cinco (realmente siete) nuevas características. Voy a comentar algunas de ellas y, como habréis notado por el título del post, a explicar por qué no me gustan.

Strings en switchs

Como todos sabéis, en Java no está permitido el uso de strings en los switchs. Básicamente un switch sólo permite la evaluación de aquellos tipos que puedan tener un cast a int (int, byte, short, char, enum, Character, Byte, Short e Integer). String no es ahormable a int y por tanto no se acepta. Ahora bien, en la práctica, es útil hacer switchs con strings, así que con esta propuesta el siguiente código será válido:

        String valor = "algo";
        switch (valor) {
            case "otra cosa":
                break;
            case "algo":
                //este código será ejecutado
                break;
        }

No me cabe ninguna duda de que es mucho más legible que soluciones actuales, como la siguiente:

        String valor = "algo";
        switch (valor.hashCode()) {
            case -1850569338:
                break;
            case 2996819:
                //este código será ejecutado
                break;
        }

Sin embargo, aunque veo su utilidad, estoy en contra de esta modificación. ¿Por qué? Porque el comportamiento de String se convierte en una casa de putas.

El funcionamiento teórico del switch es simple, una variable será comparada con N alternativas, y si alguna de ellas coincide, se ejecutará esa porción de código. Todos tenemos claro que un int de valor 5 es igual a otro int de valor 5, pero un String con valor «hola» no es igual a otro String de valor «hola»… simplemente la clase String tiene un comportamiento contra-intuitivo. Veamos un ejemplo:

    boolean siempreFalso() {
        return new String("hola") == new String("hola");
    }

Mucha guitarra, pero siempre los mismos acordes…

En contra de lo que uno podría pensar, ese método siempre retornará false. El operador de igualdad no compara el valor de las cadenas, sino sus referencias en memoria, y dado que ambos «hola» no son el mismo objeto, la comparación se probará falsa. Esto no ocurriría en el caso de un switch, ya que se compararía el valor y no la referencia.

[Edición: Javier Collado ha caido en la cuenta de que el ejemplo era un poco artificioso, leed los dos primeros comentarios para tener una explicación de por qué]

Desarrolladores de Java, centren la pelota. O hacemos que String no mire el valor sino la referencia, por ortogonalidad aunque sea contra-intuitivo, o hacemos que mire el valor y que le den a la ortogonalidad. Pero hacer una clase String que sea ortogonal para luego decir que eso es muy molesto y modificar otras partes del lenguaje para que String se comporte de forma no ortogonal, me parece que es hacer el idiota. Con todo el cariño y respeto del mundo.

Mejora de la inferencia de tipos en la creación de instancias genéricas

La verdad es que el título en inglés suena bastante mejor, «Improved Type Inference for Generic Instance Creation«. Como dirían en mi pueblo, mucha mecha para tan poca dinamita. Tras este nombre tan rimbombante se oculta una característica que busca reducir la verbosidad de los tipos genéricos. Atención a la jugada, primero pongo la linea actual, y luego como quedaría en Java 7:

//Actual
ArrayList<String> miListaDeStrings = new ArrayList<String>();

//Java 7
ArrayList<String> miListaDeStrings = new ArrayList<>();

Acojonante, siete versiones para esto ¬¬. Lo más gracioso del tema es que en la propia propuesta se dice que otras alternativas serían mejores, pero se elige esta porque, o bien no se necesitan otras, o bien tendrían demasiadas desventajas.

¿Alguien se pregunta por qué está ahí el <>? Pues porque si por ejemplo tienes un objeto Map sin parametrizar, se asume que son datos crudos, no que estás infiriendo el tipo. Así que para mantener la compatibilidad hacia atrás, se ponen los <>.

Si no he entendido nada mal, la compatibilidad a nivel de bytecode no se ve comprometida, simplemente las aplicaciones obsoletas que no tengan en cuenta estos cambios, deberían compilarse contra el JDK para el que fueron diseñadas. Amén de que este problema me parece una cosa menor, me hace gracia que piensen que esta pijada de inferencia es significante. Daos cuenta que esto no funciona si queremos inferir un lvalue… no se ha implementado ningún tipo de tipo de inferencia como «auto» en C++ o «var» en C#, no. Esto sólo sirve para las creaciones. No es que sea un juguete… es que es un juguete.

Aparentemente a los desarrolladores de Java no les ha parecido interesante poder hacer cosas como esta:

//Oh, sin lvalues es imposible hacer esto
auto mejorCliente = metodoQueDevuelveElMejorCliente();

//así inferiríamos el tipo de la colección. Java 7 FAIL
for(auto cliente : metodoQueDevuelveUnaColeccionDeClientes()) {
}

Identificadores exóticos

Los identificadores exóticos están aquí por la proposición JSR292, que trata principalmente de hacer la JVM accesible a lenguajes dinámicos. Un identificador exótico nos permitiría llamar a una variable «mira que nombre tan mono». Lo siguiente sería correcto:

int #"mira que nombre" = 22;
System.out.println(#"mira que nombre"); // muestra 22

Vamos a ver, por mi perfecto, pero ¿no había más caracteres en el universo? ¿les pagan por popularizar #? Ojo al dato:

int x = 22;
int #"x+100" = 100;
System.out.println(#"x+100"); // Muestra 100;
System.out.println(#()x+100); // Muestra 122;

Es un ejemplo y puede ser rebuscado, sí. Pero ahora decidme que nunca habéis puesto donde no era unas comillas y unos paréntesis… sobre todo con la manía que tienen los IDEs de decidir por tí donde poner dobles comillas o cerrar los paréntesis.

¿Por qué es válida la segunda línea? Por la inclusión de los cierres lambda. El compilador no detectará ningún error porque no lo entenderá como un identificador exótico ni como una expresión inválida, sino como un cierre lambda.

Personalmente hubiera preferido que el indicador de una expresión lambda fuera «->» o «<-«.

Conclusión

Podría seguir con otras características, pero voy a parar aquí porque esto está quedando muy largo. Sin duda muchas de las características implementadas son útiles y resuelven problemas reales. Mi crítica es más bien que ese trabajo es bueno, pero corto. Con la impresión que me quedo después de haber leído las propuestas es que el excesivo celo por la compatibilidad, la peculiar forma que tienen de entender la ortogonalidad y la falta de innovación parece llevarles a un «quiero y no puedo».

¿Qué opináis vosotros?

Por Carballude

Me llamo Pablo Carballude González, soy graduado en computación con master en HCI y Seguridad Informática. Actualmente trabajo para Amazon en Seattle como Software Developer Engineer. Soy de esas personas que no saben si los textos autobiográficos deben ser en primera o tercera persona. Lo intenté en segunda, pero no le entendí nada :P

31 comentarios

  1. El tema de las cadenas… ya no es muy normal y para ello yo añadiría más al ejemplo que pones:


    String a = "hola";
    String b = "hola";
    System.out.println((a==b)); // imprime true
    System.out.println((a=="hola")); // imprime true
    System.out.println(("hola"=="hola")); // imprime true
    System.out.println((new String("hola")==new String("hola"))); // imprime false

    Así que lo del switch no se sale de lo que ya hace en la actualidad.

  2. Muy bueno, pero me temo que no es correcto.

    System.out.println((a==b)); // imprime true
    System.out.println((a=="hola")); // imprime true
    System.out.println(("hola"=="hola")); // imprime true

    Efectivamente imprimen true, pero siguen comparando referencias y no contenido. Cuando escribimos una cadena directamente (como «hola»), el compilador sabe que debe crear un objeto String con ese valor. Es decir, sabe que teclear «hola»==»hola» es lo mismo que new String(«hola»)==new String(«hola»). Al saber que ambos objetos son idénticos, no crea dos… sino uno, y hace que ambas variables apunten a él. Es decir, «hola»==»hola» no está comparando el valor de dos Strings, sino que está comparando dos referencias al mismo objeto. Es más, dado que el resultado de todas esas operaciones es conocido en tiempo de compilación, es probable que el código que realmente se esté ejecutando sea este:

    System.out.println(true); // imprime true
    System.out.println(true); // imprime true
    System.out.println(true); // imprime true
    System.out.println((new String("hola")==new String("hola"))); // imprime false

    La creación del objeto a y b es evitable porque no hay operaciones que los usen, ya que las que parecían usarlo tienen solución conocida en compilación.

    Escribiendo el new String(«hola») == new String(«hola»), evitas que el compilador haga la optimización (porque no es muy listo), pero cualquier forma de evitarlo sirve. Una muy clara es hacer que un «hola» esté codificado y el otro se obtenga por teclado:

            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            String algo = br.readLine();
            String hola = "hola";
            System.out.println(algo==hola); //imprime false aunque teclees hola

    En este caso el compilador no puede saber que lo que vas a teclear sea igual a «hola» así que no puede hacer la optimización y por tanto la comparación es falsa ;)

    Como dije en el artículo, el comportamiento es contra-intuitivo, pero coherente.

  3. Vamo allá :)
    Así vista, la reforma no parece tan, tan mala. Simplemente… insustancial. Pero por lo que he visto parece que te has dejado lo importante en el tintero… Las lambdas!! Eso es lo verdaderamente polémico, y podría argumentarte a favor o en contra… Pero pa’ otro día ;-)

    Respecto a la ortogonalidad de String, estamos ante un caso perdido. Java nunca ha sido ortogonal, y a veces lo han intentado y mejor hubiera sido no tocarlo. Por ejemplo, ¿por qué no está permitida la sobrecarga de operadores, y sin embargo, desde el principio han sobrecargado el método ‘+’ de String? (fomentando programación tremendamente ineficiente, todo sea dicho). O ¿por qué los arrays no forman parte de Collections? Y ya que nos ponemos, ¿por qué tardamos tantos años en ver el autoboxing de los tipos simples en un lenguaje supuestamente OO puro?
    Pero aún así, en el caso del switch… si piensas en que en el switch se usase para la comparación el ‘equals’ en vez de ‘==’ -cosa que problablemente no se haga sino que esté trampeada, pero aún así- todo cuadraría perfectamente.
    En el caso de los tipos simples, un case 7 vendría a ser de forma complicada un

    if (new Integer(parametro_del_switch).equals(new Integer(7)) {
    //...
    }

    Que se use un método distinto para el switch al ‘==’ tampoco está tan fuera de lo común, pasa en muchos lenguajes, y permite hacer florituras muy chulas con un poco de imaginación. Al fin y al cabo el caso del switch es simplemente reacomodar la forma de pensar. Y por cierto, ya iba siendo puta hora de que permitiesen usar Strings en el switch :-)

    Los identificadores exóticos sí que me parecen una chorrada monumental. Y el azúcar sintáctico para la inferencia de tipos, pues bueno. Si contamos que de mano, la genericidad en Java es un chiste malísimo, pues casi es el mal menor… :-P

  4. Coincido contigo. En el artículo pongo las cosas que no me gustan, lo que no quiere decir que no tengan su lado bueno… pero no son el motivo de que me parezca que Java 7 no avanza hacia donde debe. El problema no son las características implementadas, sino todas las que se han quedado fuera.Es un avance lento… y aun más si lo comparamos con otros lenguajes de proposito general que le hacen la competencia.

    En cuanto a lo del switch… pues sí quizá… pero supuestamente, si a.equals(b) es cierto, a.hashCode() == b.hashCode() debe serlo, y nadie garantiza que dos objetos distintos tengan distinto hash. Es decir, podemos usar equals, pero habría que trampear muchas cosas…

    Respecto a lo de no meterme con lambdas, sólo he hablado de Project Coin, las lambda tienen su propio proyecto :P Todo es cosa de meterse con ello ;)

  5. El compiler de java hace varias cosas al inicializar, una de ellas es capturar todos los java.lang.String que tiene en el codigo y meterlos en un mapa para optimizar y utilizar menos memoria.

    Ahora podriamos comparar por referencia 2 nuevos string de la siguiente formar

    String a = getValueFromAnotherSide(«parametro»); // supongamos que este valor tiene «hola»
    String b = getValueFromAnotherSide(«parametro_2»); // este valor tambien tiene «hola»

    System.out.println(a == b); // imprime false

    ahora si hacemos

    a.intern();
    b.intern();

    System.out.println(a == b); // imprime true

    my two cents

  6. Esto es bastante absurdo. No digo que sean mentiras pero tampoco son juicios razonables si no que son casos rebuscados para justificar tus prejuicios.

  7. aalku, sinceramente no creo que nada de lo que digo sea rebuscado.

    La «Mejora de la inferencia de tipos en la creación de instancias genéricas» de rebuscado no tiene nada y el uso de strings en switch tampoco. Quizá el ejemplo de los operadores esotéricos esté un poco forzado… pero el ejemplo es lo de menos, es su naturaleza lo que no me gusta.

    Que no son mentiras está claro porque los datos están ahí para el que quiera comprobarlo, pero cada cosa que digo la argumento y pongo ejemplos de ello. Me parece genial que tu valoración sea distinta y creas que Java está siguiendo un camino adecuado… pero no creo que puedas decir que mi opinión es un simple prejuicio.

  8. Te quejás mucho de los Strings en switchs, si el operador + está soportado para Strings, ¿porque no también el switch? Si has programado alguna vez en java deberías saber que la clase String «es una clase especial» simplemente por que es la «clase más utilizada», es por eso que tu queja no tiene buen fundamento.

  9. En los años que llevo programando en Java, nunca se me ha ocurrido comparar el contenido de dos Strings usando «==», como en BigDecimal, hay que usar .equals() para comparar contenidos o sobreescribirlo si prefieres otro comportamiento. Coincido contigo en que la implementación de genéricos (si es que la hay) en Java es una máscara y poco más (bueno sí, una mierda…), que como mucho sirve para evitar errores en tiempo de ejecución, ya que internamente se añade el tipo «a pelo» y que desde luego no puedes hacer las virguerías que te permite C++ o Delphi o C#. Pero piensa también que cambiar un lenguaje de tipificación estricta, multi-plataforma, multi-dispositivo y que lleva tantos años usándose como Java es muy, pero que muy complicado, piensa en la cantidad de fabricantes que lo usan en sus aparatos y en cómo afectaría un cambio sin retro-compatibilidad a nivel industrial. Por eso es un lenguaje tan extendido y por eso sus limitaciones en cuanto a cambios.

    Si tus excusas para no programar en Java son los switches de Srings y los genéricos…

  10. Andrés:
    El simple hecho de que des por asumido que es «una clase especial» es una muestra de por qué eso está mal.
    Sinceramente, decir que como ya hay cosas raras con el operador +, qué más da meter más cosas raras en el switch, me parece una justificación que roza lo absurdo.
    Java no es ortogonal, si algo se debería hacer es intentar que lo sea, no pervertirlo aun más…

    JooJooMan:
    Java no permite la sobrecarga de operadores, aun así por defecto sobrecarga + para strings, eso ya chirría.
    La retro-compatibilidad tiene límites, personalmente creo que su precio nunca debe ser el progreso… pero seguro que ahí entran en juego factores que nada tienen que ver con detalles técnicos y sí con cifras de venta.

    No hice esto como «excusa para no programar en Java». De hecho, uso Java habitualmente por diversos motivos y obviamente tiene cosas buenas y puede ser lo ideal para ciertos escenarios. Simplemente intento señalar algunas cosas que, a mi modo de ver, están mal.

  11. Que fuerte, que lindezas… entiendo q java tenga tanto uso debido a la multiplataforma y que la evolucion sea muuuy complicada, pero a nivel de lenguaje os pregunto: ¿Habeis usado c#? ¿Que os parece? Para mi es una maravilla.. y no quiero hablar ya de IDE’s de desarrollo, de verdad he trabajado 5 años con java y 5 en c# y señores, no hay comparación, para cuando los eclipses y compañia sean la mitad de lo que es Visual Studio 2010 ahora.. las máquinas programaran solas y ya no nos hará falta IDE.

  12. No digo que tu opinión sea un prejuicio, creo que no te ha gustado y las causas del desagrado las has buscado a posteriori, y eso se nota en el resultado del artículo.

    new String(loquesea) != new String(loquesea) porque cualquier new debe devolver, por fuerza, un objeto nuevo. Eso no se puede cambiar, está en las bases de Java. ¿Por eso nos tenemos que perder la posibilidad de usar strings en switches? Me parece absurdo.

    El tema operador ‘diamante’ es pura economía. Muchas veces hay un montón de texto en la declaración de variables con genéricos, List<Map<List<Set>,Map<? extends List,List<Z>>>>, y en la instanciación hay que escribir normalmente el mismo texto, lo que es un esfuerzo inútil (aunque se pueda copiar y pegar) y a veces si se escribe mal puede que sea indetectable para el compilador. Son pequeños problemas pero son problemas, y nos ofrecen una solución, no veo qué tieme de malo. Cualquier cambio en Java tiene un impacto económico en cientos de empresas y pueden ahorrar millones de euros a nivel mundial.

  13. Bah… lo que deberian hacer es olvidar java y aprender python…

    La vida seria mas bonita si todo el mundo programase en python.

  14. Hola soy Senior engineer at oracle y mi opinion de este blogs es que no tiene sustento alguno, solo se he esta buscando la quinta pata al gato y no demostrar las mejoras que se han hecho al codigo ni a la Jvm en si.

  15. yo no entendi ni madres XDDDDDDDDDDDDDDDDDDDDD
    asi que mejor sigo usando mi viejo y confiable vb 6.0 XDDDDDDDDDDDDDDDDDDDDDD

  16. Opino totalmente lo mismo que Antonio.
    Con respecto a ignatious todo lo contrario, ‘la vida seria mas bonita si todo el mundo NO programase en python’.

  17. No entiendo ni un reverendo orto de lo que significan esas lineas de codigo ni de que coños significa lo que esta en este blog

  18. No hay que buscar 3 pies al gato como creo que estas haciendo con lo del switch. ¿Acaso no lo vas a usar?

    El uso de String en switch es una mejora bastante buena y que se usará un huevo.

    Mejora de la inferencia de tipos en la creación de instancias genéricas – Pues algo mas comodo ahora, aunque no me parece que aporte mucho

    Lo Identificadores exóticos… pues como que no lo veo, y no creo que los use nadie, exceptuando el tipico que va de gurú…

  19. Java es como una estrella masiva = AGUJERO NEGRO.
    Los lenguajes para cafeteras y lavadoras usados para crear aplicaciones corporativas es un engaño de las empresas de servicio para vender recursos humanos.
    Tengo 25 ños de programador y no he visto una aplicación corporativa escrita en Java que no sea un mamotreto de librerias repetidas, espagueti code, clases sobrecargadas y control del negocio via ErrorException o como se escriba.

    Quisiera ver 10 aplicaciones Java rodando en la misma máquina alguna vez. Ni de coña, se viene abajo el servidor.

    Es un buen lenguaje pero no para lo que se está usando. Hasta que los javivis no sepan XML como dios mandan seguirán tratando de construir el HTML en el servidor, manda huevos. La W3C tratando de separar la presentación de los datos y los de Java empeñado en lo contrario.

    Suerte a todos los javivi, serán como los programadores de basic, los habrá a patadas y a fuerza bruta conseguirar sacar adelante este lenguaje.

  20. Lo que odio en Java es que hay que escribir demasiado código para cosas tan sencillas como abrir un archivo plano y leerlo. Será que me he acostumbrado a python. Y no solo es el diseño de las clases estándar y sus jerarquias que hicieron los de Sun en su momento, sino en el propio diseño del lenguaje. Por ejemplo, nunca entenderé el uso de «new» en Java para crear clases cuando sintácticamente se lo pudieron ahorrar.
    Clase A = new Clase();
    si con:
    A = Clase();
    bastaba. Y así, unas pocas, que parece poco pero cuando escribes mucho código se nota bastante.

  21. Если класс чертеж, объект -деталь, то из этого примераpublic class RasterSB extends SeekBar { Paint bmpPaint=new Paint () как понять что чертеж внутри себя содержит деталь?bmpPaint это же объект, т.е. деталь,а RasterSB шаблон-чертеж для создания других объектов.

  22. Y si para usar los String en un switch, no te creas una clase que los embeba y sobrecargas hashcode()?
    Puedes incluso poner constantes con nombres adecuados, y el switch es 1: 2: 3: …o
    MiString.CASO_UNO:
    MiString.CASO_DOS:

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *