JavaRush /Blog Java /Random-ES /10 cosas que no sabías sobre Java
minuteman
Nivel 32

10 cosas que no sabías sobre Java

Publicado en el grupo Random-ES
Entonces, ¿has empezado a trabajar recientemente con Java? ¿Recuerda los días en que se llamaba "Oak", cuando la orientación a objetos todavía era un tema candente, cuando la gente de C++ pensaba que Java no tenía ninguna posibilidad y cuando nadie había oído hablar siquiera de los applets? Puedo asumir que ni siquiera sabes la mitad de las siguientes cosas. Comencemos la semana con algunas sorpresas interesantes sobre el funcionamiento interno de Java. 10 cosas que no sabías sobre Java - 11. No existen las excepciones marcadas. ¡Así es! La JVM no tiene idea de tal cosa, sólo el lenguaje Java la tiene. Hoy todo el mundo está de acuerdo en que las excepciones comprobadas fueron un error. Como dijo Bruce Eckel en su charla final en GeeCON en Praga, ningún otro lenguaje desde Java usa excepciones marcadas, incluso Java 8 ya no las cubre en la nueva API Streams (lo que puede ser un poco molesto cuando sus lambdas usan IO o JDBC). ). ¿Quiere pruebas de que la JVM no sabe tal cosa? Pruebe el siguiente código: esto no solo se compilará, sino que también generará una SQLException; ni siquiera necesita usar @SneakyThrows de Lombok para esto. 2. Puede tener métodos sobrecargados que solo difieren en sus tipos de devolución.public class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void doThrow(Exception e) { Test. doThrow0(e); } @SuppressWarnings("unchecked") static void doThrow0(Exception e) throws E { throw (E) e; } } Esto no se compilará, ¿verdad? class Test { Object x() { return "abc"; } String x() { return "123"; } } Bien. El lenguaje Java no permite anular dos métodos de manera equivalente dentro de la misma clase al mismo tiempo, independientemente de sus diferencias en los tipos de lanzamiento o retorno. Pero espera un minuto. Consulte la documentación nuevamente para Class.getMethod(String, Class…). Dice: Tenga en cuenta que es posible que haya más de un método correspondiente en una clase, porque mientras el lenguaje Java prohíbe múltiples métodos con la misma firma pero diferentes tipos de retorno, la Máquina Virtual Java no lo hace. Esta flexibilidad en una máquina virtual se puede utilizar para implementar varias funciones del lenguaje. Por ejemplo, los rendimientos covariantes se pueden realizar con métodos puente; El método puente y el método anulado tendrían la misma firma pero tipos de retorno diferentes. Vaya, eso tiene sentido. En realidad, suceden muchas cosas cuando escribes lo siguiente: Mira el código de bytes generado: Entonces, en realidad es un objeto en código de bytes. Esto se entiende bien. En realidad, el compilador genera el método de puente sintético porque se puede esperar el tipo de retorno de Parent.x() en ciertas partes de las llamadas. Ya no será posible agregar genéricos sin dichos métodos puente en la representación binaria. Entonces, cambiar la JVM para permitir dicha característica produjo menos dolor (lo que también permite la anulación del método covariante como efecto secundario...) Inteligente, ¿verdad? 3. Todos los siguientes son arreglos bidimensionales. Esto es realmente cierto. Incluso si su analizador mental no puede comprender inmediatamente el tipo de retorno de los métodos descritos anteriormente, ¡todos son iguales! Como el siguiente fragmento de código. ¿Crees que esto es una locura? ¡La cantidad de oportunidades para escribir también simplemente hace explotar la imaginación! Escriba la anotación. Un dispositivo cuyo misterio es superado sólo por su potencia. O en otras palabras: cuando hago mi último compromiso justo antes de mis vacaciones de 4 semanas. Te doy permiso para usarlo como quieras. 4. No obtendrás un condicional Entonces, ¿pensaste que ya sabías todo sobre los condicionales cuando empezaste a usarlos? Déjame decepcionarte: estabas equivocado. La mayoría de vosotros pensaréis que los siguientes dos ejemplos son equivalentes: ¿ equivalentes a este? No. Usemos una prueba rápida. El programa generará lo siguiente: ¡ Sí! El operador condicional realizará conversiones de tipos si es necesario. ¿Porque de lo contrario esperaría que el programa arrojara una NullPointerException? 5. Tampoco obtendrá un operador de asignación compuesta. ¿Es suficiente el ingenio? Veamos los siguientes dos fragmentos de código: abstract class Parent { abstract T x(); } class Child extends Parent { @Override String x() { return "abc"; } } // Method descriptor #15 ()Ljava/lang/String; // Stack: 1, Locals: 1 java.lang.String x(); 0 ldc [16] 2 areturn Line numbers: [pc: 0, line: 7] Local variable table: [pc: 0, pc: 3] local: this index: 0 type: Child // Method descriptor #18 ()Ljava/lang/Object; // Stack: 1, Locals: 1 bridge synthetic java.lang.Object x(); 0 aload_0 [this] 1 invokevirtual Child.x() : java.lang.String [19] 4 areturn Line numbers: [pc: 0, line: 1] class Test { int[][] a() { return new int[0][]; } int[] b() [] { return new int[0][]; } int c() [][] { return new int[0][]; } } class Test { int[][] a = {{}}; int[] b[] = {{}}; int c[][] = {{}}; } @Target(ElementType.TYPE_USE) @interface Crazy {} class Test { @Crazy int[][] a1 = {{}}; int @Crazy [][] a2 = {{}}; int[] @Crazy [] a3 = {{}}; @Crazy int[] b1[] = {{}}; int @Crazy [] b2[] = {{}}; int[] b3 @Crazy [] = {{}}; @Crazy int c1[][] = {{}}; int c2 @Crazy [][] = {{}}; int c3[] @Crazy [] = {{}}; } 10 cosas que no sabías sobre Java - 2 Object o1 = true ? new Integer(1) : new Double(2.0); Object o2; if (true) o2 = new Integer(1); else o2 = new Double(2.0); System.out.println(o1); System.out.println(o2); 1.0 1 Integer i = new Integer(1); if (i.equals(1)) i = null; Double d = new Double(2.0); Object o = true ? i : d; // NullPointerException! System.out.println(o); i += j; i = i + j; Intuitivamente, deberían ser iguales, ¿verdad? Pero ¿sabes qué? Son diferentes. La especificación JLS dice: Una expresión compuesta de tipo E1 op = E2 es equivalente a E1 = (T) ((E1) op (E2)), donde T es el tipo de E1, excepto que E1 se evalúa solo una vez. Un buen ejemplo es usar *= o /= : byte b = 10; b *= 5.7; System.out.println(b); // prints 57 o: o byte b = 100; b /= 2.5; System.out.println(b); // prints 40 : char ch = '0'; ch *= 1.1; System.out.println(ch); // prints '4' o: char ch = 'A'; ch *= 1.5; System.out.println(ch); // prints 'a' Entonces, ¿sigue siendo una herramienta útil? 6. Enteros aleatorios Ahora viene una tarea más difícil. No leas la solución. Vea si puede encontrar la respuesta usted mismo. Cuando ejecuto el siguiente programa: for (int i = 0; i < 10; i++) { System.out.println((Integer) i); } A veces obtengo el siguiente resultado: ¿ 92 221 45 48 236 183 39 193 33 84 Pero cómo es esto posible? Ok, la respuesta está en anular el caché de enteros del JDK mediante la reflexión y luego usar el autoboxing y auto-unboxing. ¡No hagas esto sin el permiso de un adulto! O en otras palabras: 10 cosas que no sabías sobre Java - 3 7. GOTO Uno de mis favoritos. ¡Java tiene GOTO! Escribe esto: int goto = 1; y obtendrás esto: Eso es porque goto es una palabra reservada no utilizada, por si acaso... Pero esa no es la parte emocionante. Lo bueno es que puedes incluir goto emparejado con break, continue y bloques marcados: Saltar hacia adelante En código de bytes: Saltar hacia atrás En código de bytes: 8. Java tiene alias de tipo En otros lenguajes (como Ceilán), podemos definir el tipo alias muy fácil: la clase People aquí está construida de tal manera que se puede intercambiar con un Set Test.java:44: error: expected int goto = 1; ^ label: { // do stuff if (check) break label; // do more stuff } 2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 .. label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true); 2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 .. interface People => Set ; : En Java, no podemos simplemente definir un alias en el nivel superior. Pero podemos hacer esto según las necesidades de una clase o método. Supongamos que no estamos contentos con nombres como Integer, Long, etc. y queremos nombres más cortos: I y L. Fácil: en el ejemplo anterior, Integer se convierte a I para la visibilidad de la clase Test, mientras que Long se convierte a L para las necesidades del método x(). Ahora podemos llamar a este método así: Por supuesto, esta técnica no debe tomarse en serio. En este caso, Integer y Long son tipos finales, lo que significa que I y L son conversiones eficientes (casi, la conversión solo es en una dirección). Si decidiéramos utilizar tipos no finales (por ejemplo, Objeto), entonces podríamos arreglárnoslas con genéricos normales. Jugamos un poco y eso fue suficiente. Pasemos a algo realmente interesante. 9. ¡Algunos tipos de relaciones son indecidibles! Bien, esto se va a poner realmente interesante, así que toma una taza de café concentrado y veamos los dos tipos siguientes: People? p1 = null; Set ? p2 = p1; People? p3 = p2; class Test { void x(I i, L l) { System.out.println( i.intValue() + ", " + l.longValue() ); } } new Test().x(1, 2L);// A helper type. You could also just use List interface Type {} class C implements Type > {} class D

implements Type >>> {} Entonces, ¿qué significan C y D? En cierto sentido, son recursivos, similares a la recursividad en java.lang.Enum. Considere: dadas las especificaciones anteriores, la implementación real de enum es simplemente azúcar sintáctico: con eso en mente, volvamos a nuestros dos tipos. ¿Se compilará el siguiente código? ¿Una pregunta difícil... y en realidad no se está resolviendo? ¿Es C un subtipo de Type ? Intente compilar esto en su Eclipse o Idea y ellos le dirán lo que piensan. Tíralo por el desagüe... ¡ Algunas relaciones de tipos en Java son indecidibles! 10. Intersección de tipos Java tiene una característica muy interesante llamada intersección de tipos. Puede declarar un tipo (genérico) que en realidad sea la intersección de dos tipos. Por ejemplo: el parámetro de tipo personalizado T que asocia con instancias de la clase Test debe incluir las interfaces serializable y clonable. Por ejemplo, String no se puede restringir, pero Date sí: esta característica tiene múltiples usos en Java8, donde se pueden convertir tipos. ¿Cómo ayuda esto? Casi nada, pero si desea convertir su expresión lambda en el tipo que necesita, entonces no hay otra manera. Digamos que tienes una limitación tan loca en tu método: quieres Runnable que al mismo tiempo es Serializable solo si quieres ejecutarlo en otro lugar y enviar el resultado a través de la red. Lambda y la serialización añaden un poco de ironía. Puede serializar su expresión lambda si su tipo de destino y sus argumentos son serializables. Pero incluso si esto fuera cierto, no habilitan automáticamente la interfaz serializable. Debes traerlos a este tipo tú mismo. Pero cuando lanzas solo a Serializable: entonces la lambda ya no será Runnable, así que transfiérela a ambos tipos: Y en conclusión: public abstract class Enum > { ... } // This enum MyEnum {} // Is really just sugar for this class MyEnum extends Enum { ... } class Test { Type c = new C(); Type> d = new D (); } Step 0) C Step 1) Type > >? Step 0) D > Step 1) Type >>> > Step 2) D >> Step 3) List >> > Step 4) D > >> Step . . . (expand forever) class Test { } // Doesn't compile Test s = null; // Compiles Test d = null; void execute(T t) {} execute((Serializable) (() -> {}));execute((Runnable & Serializable) (() -> {}));

Java es tan poderoso como misterioso.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION