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; } }
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 [] = {{}}; }
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:
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
;
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 4) D
class Test
// Doesn't compile Test
execute((Serializable) (() -> {}));
execute((Runnable & Serializable) (() -> {}));
GO TO FULL VERSION